From b1ac698e6eb1838e20e62d360192ccde1debfb9c Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Thu, 09 Oct 2014 13:26:55 +0000
Subject: [PATCH] OPENDJ-1592 CR-4782 Migrate time-based matching rules
---
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeLessThanMatchingRuleTest.java | 143 +++++++
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeGreaterThanMatchingRuleTest.java | 142 +++++++
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/PartialDateAndTimeMatchingRuleTestCase.java | 202 ++++++++++
opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties | 47 ++
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/TimeBasedMatchingRulesImpl.java | 654 ++++++++++++++++++++++++++++++++
5 files changed, 1,188 insertions(+), 0 deletions(-)
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/TimeBasedMatchingRulesImpl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/TimeBasedMatchingRulesImpl.java
new file mode 100644
index 0000000..1b85211
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/TimeBasedMatchingRulesImpl.java
@@ -0,0 +1,654 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ * Portions Copyright 2011-2014 ForgeRock AS
+ */
+package org.forgerock.opendj.ldap.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.List;
+import java.util.TimeZone;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizedIllegalArgumentException;
+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.GeneralizedTime;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+import org.forgerock.opendj.ldap.spi.Indexer;
+import org.forgerock.opendj.ldap.spi.IndexingOptions;
+
+import com.forgerock.opendj.util.TimeSource;
+
+import static com.forgerock.opendj.ldap.CoreMessages.*;
+import static com.forgerock.opendj.util.StaticUtils.*;
+
+/**
+ * Implementations of time-based matching rules.
+ */
+public final class TimeBasedMatchingRulesImpl {
+
+ private static final String RELATIVE_TIME_INDEX_ID = "rt";
+ private static final String PARTIAL_DATE_TIME_INDEX_ID = "pdt";
+ private static final String EXTENSIBLE_INDEX_ID = "ext";
+
+ private static final TimeZone TIME_ZONE_UTC = TimeZone.getTimeZone("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';
+
+ private TimeBasedMatchingRulesImpl() {
+ // not instantiable
+ }
+
+ /**
+ * Creates a relative time greater than matching rule.
+ *
+ * @return the matching rule implementation
+ */
+ public static MatchingRuleImpl relativeTimeGTOMatchingRule() {
+ return new RelativeTimeGreaterThanOrderingMatchingRuleImpl();
+ }
+
+ /**
+ * Creates a relative time less than matching rule.
+ *
+ * @return the matching rule implementation
+ */
+ public static MatchingRuleImpl relativeTimeLTOMatchingRule() {
+ return new RelativeTimeLessThanOrderingMatchingRuleImpl();
+ }
+
+ /**
+ * Creates a partial date and time matching rule.
+ *
+ * @return the matching rule implementation
+ */
+ public static MatchingRuleImpl partialDateAndTimeMatchingRule() {
+ return new PartialDateAndTimeMatchingRuleImpl();
+ }
+
+ /**
+ * This class defines a matching rule which is used for time-based searches.
+ */
+ private abstract static class TimeBasedMatchingRuleImpl extends AbstractMatchingRuleImpl {
+
+ /** Unit tests can inject fake timestamps if necessary. */
+ final TimeSource timeSource = TimeSource.DEFAULT;
+
+ /** {@inheritDoc} */
+ @Override
+ public ByteString normalizeAttributeValue(Schema schema, ByteSequence value) throws DecodeException {
+ try {
+ return ByteString.valueOf(GeneralizedTime.valueOf(value.toString()).getTimeInMillis());
+ } catch (final LocalizedIllegalArgumentException e) {
+ throw DecodeException.error(e.getMessageObject());
+ }
+ }
+
+ int multiplyByTenAndAddUnits(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;
+ }
+ }
+
+ /**
+ * Defines the relative time ordering matching rule.
+ */
+ private abstract static class RelativeTimeOrderingMatchingRuleImpl extends TimeBasedMatchingRuleImpl {
+
+ final Indexer indexer = new DefaultIndexer(RELATIVE_TIME_INDEX_ID + "." + EXTENSIBLE_INDEX_ID);
+
+ /** {@inheritDoc} */
+ @Override
+ public Collection<? extends Indexer> getIndexers() {
+ return Collections.singletonList(indexer);
+ }
+
+ /**
+ * Normalize the provided assertion value.
+ * <p>
+ * An assertion value may contain one of the following:
+ * <pre>
+ * s = second
+ * m = minute
+ * h = hour
+ * d = day
+ * w = week
+ * </pre>
+ *
+ * An example assertion is
+ * <pre>
+ * OID:=(-)1d
+ * </pre>
+ *
+ * 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.
+ * <p>
+ * 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.
+ */
+ ByteString normalizeAssertionValue(ByteSequence assertionValue) throws DecodeException {
+ int index = 0;
+ boolean signed = false;
+ byte firstByte = assertionValue.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 < assertionValue.length(); index++) {
+ byte b = assertionValue.byteAt(index);
+ if (isDigit((char) b)) {
+ number = multiplyByTenAndAddUnits(number, b);
+ } else {
+ if (containsTimeUnit) {
+ // We already have time unit found by now.
+ throw DecodeException.error(WARN_ATTR_CONFLICTING_ASSERTION_FORMAT.get(assertionValue));
+ } 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:
+ throw DecodeException.error(
+ WARN_ATTR_INVALID_RELATIVE_TIME_ASSERTION_FORMAT.get(assertionValue, (char) b));
+ }
+ }
+ 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 = timeSource.currentTimeMillis();
+ return ByteString.valueOf(signed ? now - delta : now + delta);
+ }
+
+ }
+
+ /**
+ * Defines the "greater-than" relative time matching rule.
+ */
+ private static final class RelativeTimeGreaterThanOrderingMatchingRuleImpl extends
+ RelativeTimeOrderingMatchingRuleImpl {
+
+ /** {@inheritDoc} */
+ @Override
+ public Assertion getAssertion(final Schema schema, final ByteSequence value) throws DecodeException {
+ final ByteString assertionValue = normalizeAssertionValue(value);
+
+ return new Assertion() {
+ @Override
+ public ConditionResult matches(ByteSequence attributeValue) {
+ return ConditionResult.valueOf(attributeValue.compareTo(assertionValue) > 0);
+ }
+
+ @Override
+ public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+ return factory.createRangeMatchQuery(indexer.getIndexID(), assertionValue, ByteString.empty(),
+ false, false);
+ }
+ };
+ }
+ }
+
+ /**
+ * Defines the "less-than" relative time matching rule.
+ */
+ private static final class RelativeTimeLessThanOrderingMatchingRuleImpl extends
+ RelativeTimeOrderingMatchingRuleImpl {
+
+ /** {@inheritDoc} */
+ @Override
+ public Assertion getAssertion(final Schema schema, final ByteSequence value) throws DecodeException {
+ final ByteString assertionValue = normalizeAssertionValue(value);
+
+ return new Assertion() {
+ @Override
+ public ConditionResult matches(ByteSequence attributeValue) {
+ return ConditionResult.valueOf(attributeValue.compareTo(assertionValue) < 0);
+ }
+
+ @Override
+ public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+ return factory.createRangeMatchQuery(indexer.getIndexID(), ByteString.empty(), assertionValue,
+ false, false);
+ }
+ };
+ }
+ }
+
+ /**
+ * Defines the partial date and time matching rule.
+ */
+ private static final class PartialDateAndTimeMatchingRuleImpl extends TimeBasedMatchingRuleImpl {
+
+ final Indexer indexer = new PartialDateAndTimeIndexer(this);
+
+ /** {@inheritDoc} */
+ @Override
+ public Collection<? extends Indexer> getIndexers() {
+ return Collections.singletonList(indexer);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Assertion getAssertion(final Schema schema, 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 {
+ final ByteBuffer buffer = ByteBuffer.wrap(assertionValue.toByteArray());
+ int assertSecond = buffer.getInt(0);
+ int assertMinute = buffer.getInt(4);
+ int assertHour = buffer.getInt(8);
+ int assertDate = buffer.getInt(12);
+ int assertMonth = buffer.getInt(16);
+ int assertYear = buffer.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.getIndexID(), getKey(assertionValue, type));
+ }
+ };
+ }
+
+ /**
+ * Normalize the provided assertion value.
+ * <p>
+ * An assertion value may contain one or all of the following:
+ * <pre>
+ * D = day
+ * M = month
+ * Y = year
+ * h = hour
+ * m = month
+ * s = second
+ * </pre>
+ *
+ * An example assertion is
+ * <pre>
+ * OID:=04M
+ * </pre>
+ * In this example we are searching for entries corresponding to month
+ * of april.
+ * <p>
+ * The normalized value is actually the format of : smhDMY.
+ */
+ private ByteString normalizeAssertionValue(ByteSequence assertionValue) throws DecodeException {
+ final int initDate = 0;
+ final int initValue = -1;
+ int second = initValue;
+ int minute = initValue;
+ int hour = initValue;
+ int date = initDate;
+ int month = initValue;
+ int year = initDate;
+ int number = 0;
+
+ int length = assertionValue.length();
+ for (int index = 0; index < length; index++) {
+ byte b = assertionValue.byteAt(index);
+ if (isDigit((char) b)) {
+ number = multiplyByTenAndAddUnits(number, b);
+ } else {
+ LocalizableMessage message = null;
+ switch (b) {
+ case 's':
+ if (second != initValue) {
+ message = WARN_ATTR_DUPLICATE_SECOND_ASSERTION_FORMAT.get(assertionValue, date);
+ } else {
+ second = number;
+ }
+ break;
+ case 'm':
+ if (minute != initValue) {
+ message = WARN_ATTR_DUPLICATE_MINUTE_ASSERTION_FORMAT.get(assertionValue, date);
+ } else {
+ minute = number;
+ }
+ break;
+ case 'h':
+ if (hour != initValue) {
+ message = WARN_ATTR_DUPLICATE_HOUR_ASSERTION_FORMAT.get(assertionValue, date);
+ } else {
+ hour = number;
+ }
+ break;
+ case 'D':
+ if (number == 0) {
+ message = WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(assertionValue, number);
+ } else if (date != initDate) {
+ message = WARN_ATTR_DUPLICATE_DATE_ASSERTION_FORMAT.get(assertionValue, date);
+ } else {
+ date = number;
+ }
+ break;
+ case 'M':
+ if (number == 0) {
+ message = WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(assertionValue, number);
+ } else if (month != initValue) {
+ message = WARN_ATTR_DUPLICATE_MONTH_ASSERTION_FORMAT.get(assertionValue, month);
+ } else {
+ month = number;
+ }
+ break;
+ case 'Y':
+ if (number == 0) {
+ message = WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(assertionValue, number);
+ } else if (year != initDate) {
+ message = WARN_ATTR_DUPLICATE_YEAR_ASSERTION_FORMAT.get(assertionValue, year);
+ } else {
+ year = number;
+ }
+ break;
+ default:
+ message = WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT.get(assertionValue, (char) b);
+ }
+ if (message != null) {
+ throw DecodeException.error(message);
+ }
+ number = 0;
+ }
+ }
+
+ month = toCalendarMonth(month, assertionValue);
+
+ // 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.
+ throw DecodeException.error(WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(assertionValue, year));
+ }
+ if (isDateInvalid(date, month, year)) {
+ throw DecodeException.error(WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(assertionValue, date));
+ }
+ if (hour < initValue || hour > 23) {
+ throw DecodeException.error(WARN_ATTR_INVALID_HOUR_ASSERTION_FORMAT.get(assertionValue, hour));
+ }
+ if (minute < initValue || minute > 59) {
+ throw DecodeException.error(WARN_ATTR_INVALID_MINUTE_ASSERTION_FORMAT.get(assertionValue, minute));
+ }
+ // Consider leap seconds.
+ if (second < initValue || second > 60) {
+ throw DecodeException.error(WARN_ATTR_INVALID_SECOND_ASSERTION_FORMAT.get(assertionValue, second));
+ }
+
+ // Since we reached here we have a valid assertion value.
+ // Construct a normalized value in the order: SECOND MINUTE HOUR DATE MONTH YEAR.
+ return new ByteStringBuilder(6 * 4)
+ .append(second).append(minute).append(hour)
+ .append(date).append(month).append(year).toByteString();
+ }
+
+ 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) {
+ return year % 400 == 0 || (year % 100 != 0 && 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:
+ throw DecodeException.error(WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(value, month));
+ }
+ }
+
+ private ConditionResult valuesMatch(ByteSequence attributeValue, ByteSequence assertionValue) {
+ // Build the information from the attribute value.
+ GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC);
+ 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 b = ByteBuffer.wrap(assertionValue.toByteArray());
+ int assertSecond = b.getInt(0);
+ int assertMinute = b.getInt(4);
+ int assertHour = b.getInt(8);
+ int assertDate = b.getInt(12);
+ int assertMonth = b.getInt(16);
+ int assertYear = b.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;
+ }
+
+ /**
+ * 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 timeInMillis = 0L;
+ try {
+ timeInMillis = GeneralizedTime.valueOf(attributeValue.toString()).getTimeInMillis();
+ } catch (IllegalArgumentException e) {
+ return;
+ }
+ GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC);
+ cal.setTimeInMillis(timeInMillis);
+ 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) {
+ return new ByteStringBuilder().append(type).append(value).toByteString();
+ }
+ }
+
+ /**
+ * Indexer for Partial Date and Time Matching rules.
+ */
+ private static final class PartialDateAndTimeIndexer implements Indexer {
+
+ private final PartialDateAndTimeMatchingRuleImpl matchingRule;
+
+ private PartialDateAndTimeIndexer(PartialDateAndTimeMatchingRuleImpl 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_ID + "." + EXTENSIBLE_INDEX_ID;
+ }
+ }
+}
diff --git a/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties b/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
index 4cba3a8..835d19b 100755
--- a/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
+++ b/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -1595,3 +1595,50 @@
character
ERR_ADDRESSMASK_FORMAT_DECODE_ERROR=Cannot decode the provided \
address mask because the it has an invalid format
+WARN_ATTR_CONFLICTING_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because more than \
+ one time units are not allowed
+WARN_ATTR_INVALID_RELATIVE_TIME_ASSERTION_FORMAT=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)
+WARN_ATTR_DUPLICATE_SECOND_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because there is \
+ conflicting value "%d" for s (second) specification
+WARN_ATTR_DUPLICATE_MINUTE_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because there is \
+ conflicting value "%d" for m (minute) specification
+WARN_ATTR_DUPLICATE_HOUR_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because there is \
+ conflicting value "%d" for h (hour) specification
+WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because "%d" is not \
+ a valid date specification
+WARN_ATTR_DUPLICATE_DATE_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because there is \
+ conflicting value "%d" for DD (date) specification
+WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because "%d" is not \
+ a valid month specification
+WARN_ATTR_INVALID_MINUTE_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because "%d" is not \
+ a valid minute specification
+WARN_ATTR_INVALID_HOUR_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because "%d" is not \
+ a valid hour specification
+WARN_ATTR_INVALID_SECOND_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because "%d" is not \
+ a valid second specification
+WARN_ATTR_DUPLICATE_MONTH_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because there is \
+ conflicting value "%d" for MM (month) specification
+WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because "%d" is not \
+ a valid year specification
+WARN_ATTR_DUPLICATE_YEAR_ASSERTION_FORMAT=The provided \
+ value "%s" could not be parsed as a valid assertion value because there is \
+ conflicting value "%d" for YYYY (year) specification
+WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT=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 (date), M (month) and Y (year)
\ No newline at end of file
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/PartialDateAndTimeMatchingRuleTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/PartialDateAndTimeMatchingRuleTestCase.java
new file mode 100644
index 0000000..d24f155
--- /dev/null
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/PartialDateAndTimeMatchingRuleTestCase.java
@@ -0,0 +1,202 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.forgerock.opendj.util.TimeSource;
+
+import static org.fest.assertions.Assertions.*;
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class PartialDateAndTimeMatchingRuleTestCase extends MatchingRuleTest {
+
+ /** {@inheritDoc} */
+ @Override
+ @DataProvider(name = "matchingRuleInvalidAttributeValues")
+ public Object[][] createMatchingRuleInvalidAttributeValues() {
+ return new Object[][] {
+ // too short
+ { "1Z" },
+ // bad year
+ { "201a0630Z" },
+ // bad month
+ { "20141330Z" },
+ // bad day
+ { "20140635Z" },
+ // bad hour
+ { "20140630351010Z" },
+ // bad minute
+ { "20140630108810Z" },
+ // bad second
+ { "20140630101080Z" },
+ };
+ }
+
+ @DataProvider(name = "matchingRuleInvalidAssertionValues")
+ public Object[][] createMatchingRuleInvalidAssertionValues() {
+ return new Object[][] {
+ { " " },
+ { "bla" },
+ // invalid time unit values
+ { "-1Y03M11D12h48m32s" },
+ { "0Y03M11D12h48m32s" },
+ { "2014Y-1M11D12h48m32s" },
+ { "2014Y0M11D12h48m32s" },
+ { "2014Y13M11D12h48m32s" },
+ { "2014Y03M-1D12h48m32s" },
+ { "2014Y03M0D12h48m32s" },
+ { "2014Y13M32D12h48m32s" },
+ { "2014Y03M11D-1h48m32s" },
+ { "2014Y03M11D24h48m32s" },
+ { "2014Y03M11D12h-1m32s" },
+ { "2014Y03M11D12h60m32s" },
+ { "2014Y03M11D12h48m-1s" },
+ { "2014Y03M11D12h48m61s" },
+ // duplicate each time unit
+ { "1Y2014Y03M11D12h" },
+ { "2014Y1M03M11D12h" },
+ { "2014Y03M1D11D12h" },
+ { "2014Y03M11D1h12h" },
+ { "2014Y03M11D12h1m48m" },
+ { "2014Y03M11D12h48m1s32s" },
+ // February and non leap years
+ { "2014Y02M29D" },
+ { "1800Y02M29D" },
+ { "2000Y02M30D" },
+ { "2000Y02M31D" },
+ // 31st of months
+ { "2012Y04M31D" },
+ { "2012Y06M31D" },
+ { "2012Y09M31D" },
+ { "2012Y11M31D" },
+ };
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @DataProvider(name = "matchingrules")
+ public Object[][] createMatchingRuleTest() {
+ final Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(TimeSource.DEFAULT.currentTimeMillis());
+ final Date nowDate = calendar.getTime();
+ calendar.add(Calendar.MONTH, 1);
+ final Date oneMonthAheadDate = calendar.getTime();
+
+ final SimpleDateFormat partialTimeUpToSeconds = new SimpleDateFormat("yyyy'Y'MM'M'dd'D'HH'h'mm'm'ss's'");
+ final SimpleDateFormat generalizedTimeUpToSeconds = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
+ final SimpleDateFormat partialTimeUpToMinutes = new SimpleDateFormat("yyyy'Y'MM'M'dd'D'HH'h'mm'm'");
+ final SimpleDateFormat generalizedTimeUpToMinutes = new SimpleDateFormat("yyyyMMddHHmm'Z'");
+ final SimpleDateFormat partialTimeUpToHours = new SimpleDateFormat("yyyy'Y'MM'M'dd'D'HH'h'");
+ final SimpleDateFormat generalizedTimeUpToHours = new SimpleDateFormat("yyyyMMddHH'Z'");
+
+ return new Object[][] {
+ // expect 3 values : attribute value, assertion value, result
+
+ // use now date and one month ahead dates
+ { generalizedTimeUpToSeconds.format(nowDate), partialTimeUpToSeconds.format(nowDate),
+ ConditionResult.TRUE },
+ { generalizedTimeUpToSeconds.format(oneMonthAheadDate), partialTimeUpToSeconds.format(oneMonthAheadDate),
+ ConditionResult.TRUE },
+ { generalizedTimeUpToMinutes.format(nowDate), partialTimeUpToMinutes.format(nowDate),
+ ConditionResult.TRUE },
+ { generalizedTimeUpToMinutes.format(oneMonthAheadDate), partialTimeUpToMinutes.format(oneMonthAheadDate),
+ ConditionResult.TRUE },
+ { generalizedTimeUpToHours.format(nowDate), partialTimeUpToHours.format(nowDate),
+ ConditionResult.TRUE },
+ { generalizedTimeUpToHours.format(oneMonthAheadDate), partialTimeUpToHours.format(oneMonthAheadDate),
+ ConditionResult.TRUE },
+ // 29th of months and leap years
+ { "20120329120000Z", "2012Y03M29D", ConditionResult.TRUE },
+ { "20120229120000Z", "2012Y02M29D", ConditionResult.TRUE },
+ { "20000229120000Z", "2000Y02M29D", ConditionResult.TRUE },
+ // Generalized time implementation does not allow leap seconds
+ // because Java does not support them. Apparently, it never will support them:
+ // @see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4272347
+ // leap seconds are allowed, even though no formula exists to validate them
+ { "20120630235959Z", "2012Y06M30D23h59m60s", ConditionResult.FALSE },
+ // no match
+ { "20111231235930Z", "2012Y12M31D23h59m30s", ConditionResult.FALSE },
+ { "20121031235930Z", "2012Y12M31D23h59m30s", ConditionResult.FALSE },
+ { "20121230235930Z", "2012Y12M31D23h59m30s", ConditionResult.FALSE },
+ { "20121231225930Z", "2012Y12M31D23h59m30s", ConditionResult.FALSE },
+ { "20121231235830Z", "2012Y12M31D23h59m30s", ConditionResult.FALSE },
+ { "20121231235829Z", "2012Y12M31D23h59m30s", ConditionResult.FALSE },
+ // 30th of months
+ { "19820930120000Z", "1982Y09M30D", ConditionResult.TRUE },
+ // 31st of months
+ { "20120131120000Z", "2012Y01M31D", ConditionResult.TRUE },
+ { "20120331120000Z", "2012Y03M31D", ConditionResult.TRUE },
+ { "20120531120000Z", "2012Y05M31D", ConditionResult.TRUE },
+ { "20120731120000Z", "2012Y07M31D", ConditionResult.TRUE },
+ { "20120831120000Z", "2012Y08M31D", ConditionResult.TRUE },
+ { "20121031120000Z", "2012Y10M31D", ConditionResult.TRUE },
+ { "20121231120000Z", "2012Y12M31D", ConditionResult.TRUE },
+ // Only single time units
+ { "20121231123000Z", "2012Y", ConditionResult.TRUE },
+ { "20121231123000Z", "2012Y12M", ConditionResult.TRUE },
+ { "20121231123000Z", "2012Y31D", ConditionResult.TRUE },
+ { "20121231123000Z", "2012Y12h", ConditionResult.TRUE },
+ { "20121231123000Z", "2012Y30m", ConditionResult.TRUE },
+ { "20121231123000Z", "2012Y0s", ConditionResult.TRUE },
+ };
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected MatchingRule getRule() {
+ // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+ // Only the implementation class and the provided locale are really tested here.
+ String oid = "1.3.6.1.4.1.26027.1.4.7";
+ Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+ buildMatchingRule(oid).
+ syntaxOID(SchemaConstants.SYNTAX_GENERALIZED_TIME_OID).
+ names("partialDateAndTimeMatchingRule").
+ implementation(TimeBasedMatchingRulesImpl.partialDateAndTimeMatchingRule()).
+ addToSchema().
+ toSchema();
+ return schema.getMatchingRule(oid);
+ }
+
+ @Test
+ public void testCreateIndexQuery() throws Exception {
+ Assertion assertion = getRule().getAssertion(ByteString.valueOf("2012Y"));
+
+ String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+ assertThat(indexQuery).matches("intersect\\[exactMatch\\(pdt\\.ext, value=='[0-9A-Z ]*'\\)\\]");
+ }
+}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeGreaterThanMatchingRuleTest.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeGreaterThanMatchingRuleTest.java
new file mode 100644
index 0000000..e4be26c
--- /dev/null
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeGreaterThanMatchingRuleTest.java
@@ -0,0 +1,142 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.GeneralizedTime;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.forgerock.opendj.util.TimeSource;
+
+import static org.fest.assertions.Assertions.*;
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class RelativeTimeGreaterThanMatchingRuleTest extends MatchingRuleTest {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @DataProvider(name = "matchingRuleInvalidAttributeValues")
+ public Object[][] createMatchingRuleInvalidAttributeValues() {
+ return new Object[][] {
+ // too short
+ { "1Z" },
+ // bad year
+ { "201a0630Z" },
+ // bad month
+ { "20141330Z" },
+ // bad day
+ { "20140635Z" },
+ // bad hour
+ { "20140630351010Z" },
+ // bad minute
+ { "20140630108810Z" },
+ // bad second
+ { "20140630101088Z" },
+ };
+ }
+
+ @DataProvider(name = "matchingRuleInvalidAssertionValues")
+ public Object[][] createMatchingRuleInvalidAssertionValues() {
+ return new Object[][] {
+ { " " },
+ { "bla" },
+ { "-30b" },
+ { "-30ms" },
+ };
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @DataProvider(name = "matchingrules")
+ public Object[][] createMatchingRuleTest() {
+ final Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(TimeSource.DEFAULT.currentTimeMillis());
+ final Date nowDate = calendar.getTime();
+ calendar.add(Calendar.MONTH, 1);
+ final Date oneMonthAheadDate = calendar.getTime();
+
+ final String nowGT = GeneralizedTime.valueOf(nowDate).toString();
+ final String oneMonthAheadGT = GeneralizedTime.valueOf(oneMonthAheadDate).toString();
+
+ return new Object[][] {
+ // attribute value, assertion value, result
+ { oneMonthAheadGT, "1", ConditionResult.TRUE },
+ { oneMonthAheadGT, "+1s", ConditionResult.TRUE },
+ { oneMonthAheadGT, "+1h", ConditionResult.TRUE },
+ { oneMonthAheadGT, "+1m", ConditionResult.TRUE },
+ { nowGT, "-30d", ConditionResult.TRUE },
+ { nowGT, "-30w", ConditionResult.TRUE },
+ { nowGT, "-30m", ConditionResult.TRUE },
+ { nowGT, "-30s", ConditionResult.TRUE },
+
+ { oneMonthAheadGT, "6w", ConditionResult.FALSE },
+ { nowGT, "1d", ConditionResult.FALSE },
+ { nowGT, "10s", ConditionResult.FALSE },
+ { nowGT, "+1h", ConditionResult.FALSE },
+ { nowGT, "+1m", ConditionResult.FALSE },
+ { nowGT, "+1w", ConditionResult.FALSE },
+ };
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected MatchingRule getRule() {
+ // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+ // Only the implementation class and the provided locale are really tested here.
+ String oid = "1.3.6.1.4.1.26027.1.4.5";
+ Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+ buildMatchingRule(oid).
+ syntaxOID(SchemaConstants.SYNTAX_GENERALIZED_TIME_OID).
+ names("relativeTimeGTOrderingMatch.gt").
+ implementation(TimeBasedMatchingRulesImpl.relativeTimeGTOMatchingRule()).
+ addToSchema().
+ toSchema();
+ return schema.getMatchingRule(oid);
+ }
+
+ @Test
+ public void testCreateIndexQuery() throws Exception {
+ Assertion assertion = getRule().getAssertion(ByteString.valueOf("+5m"));
+
+ String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+ assertThat(indexQuery).startsWith("rangeMatch(rt.ext, '").endsWith("' < value < '')");
+ }
+}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeLessThanMatchingRuleTest.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeLessThanMatchingRuleTest.java
new file mode 100644
index 0000000..2f39225
--- /dev/null
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeLessThanMatchingRuleTest.java
@@ -0,0 +1,143 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.GeneralizedTime;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.forgerock.opendj.util.TimeSource;
+
+import static org.fest.assertions.Assertions.*;
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class RelativeTimeLessThanMatchingRuleTest extends MatchingRuleTest {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @DataProvider(name = "matchingRuleInvalidAttributeValues")
+ public Object[][] createMatchingRuleInvalidAttributeValues() {
+ return new Object[][] {
+ // too short
+ { "1Z" },
+ // bad year
+ { "201a0630Z" },
+ // bad month
+ { "20141330Z" },
+ // bad day
+ { "20140635Z" },
+ // bad hour
+ { "20140630351010Z" },
+ // bad minute
+ { "20140630108810Z" },
+ // bad second
+ { "20140630101088Z" },
+ };
+ }
+
+ @DataProvider(name = "matchingRuleInvalidAssertionValues")
+ public Object[][] createMatchingRuleInvalidAssertionValues() {
+ return new Object[][] {
+ { " " },
+ { "bla" },
+ { "-30b" },
+ { "-30ms" },
+ };
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @DataProvider(name = "matchingrules")
+ public Object[][] createMatchingRuleTest() {
+ final Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(TimeSource.DEFAULT.currentTimeMillis());
+ final Date nowDate = calendar.getTime();
+ calendar.add(Calendar.MONTH, 1);
+ final Date oneMonthAheadDate = calendar.getTime();
+
+ final String nowGT = GeneralizedTime.valueOf(nowDate).toString();
+ final String oneMonthAheadGT = GeneralizedTime.valueOf(oneMonthAheadDate).toString();
+
+ return new Object[][] {
+ // attribute value, assertion value, result
+ { oneMonthAheadGT, "6w", ConditionResult.TRUE },
+ { oneMonthAheadGT, "2400h", ConditionResult.TRUE },
+ { nowGT, "1d", ConditionResult.TRUE },
+ { nowGT, "10s", ConditionResult.TRUE },
+ { nowGT, "+1h", ConditionResult.TRUE },
+ { nowGT, "+1m", ConditionResult.TRUE },
+ { nowGT, "+1w", ConditionResult.TRUE },
+
+ { oneMonthAheadGT, "1", ConditionResult.FALSE },
+ { oneMonthAheadGT, "+30s", ConditionResult.FALSE },
+ { oneMonthAheadGT, "+2h", ConditionResult.FALSE },
+ { oneMonthAheadGT, "+1m", ConditionResult.FALSE },
+ { nowGT, "-1d", ConditionResult.FALSE },
+ { nowGT, "-2w", ConditionResult.FALSE },
+ { nowGT, "-10m", ConditionResult.FALSE },
+ { nowGT, "-1s", ConditionResult.FALSE },
+ };
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected MatchingRule getRule() {
+ // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+ // Only the implementation class and the provided locale are really tested here.
+ String oid = "1.3.6.1.4.1.26027.1.4.6";
+ Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+ buildMatchingRule(oid).
+ syntaxOID(SchemaConstants.SYNTAX_GENERALIZED_TIME_OID).
+ names("relativeTimeLTOrderingMatch.lt").
+ implementation(TimeBasedMatchingRulesImpl.relativeTimeLTOMatchingRule()).
+ addToSchema().
+ toSchema();
+ return schema.getMatchingRule(oid);
+ }
+
+ @Test
+ public void testCreateIndexQuery() throws Exception {
+ Assertion assertion = getRule().getAssertion(ByteString.valueOf("+5m"));
+
+ String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+ assertThat(indexQuery).startsWith("rangeMatch(rt.ext, '' < value < '");
+ }
+}
--
Gitblit v1.10.0