| opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/TimeBasedMatchingRulesImpl.java | ●●●●● patch | view | raw | blame | history | |
| opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties | ●●●●● patch | view | raw | blame | history | |
| opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/PartialDateAndTimeMatchingRuleTestCase.java | ●●●●● patch | view | raw | blame | history | |
| opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeGreaterThanMatchingRuleTest.java | ●●●●● patch | view | raw | blame | history | |
| opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeLessThanMatchingRuleTest.java | ●●●●● patch | view | raw | blame | history |
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/TimeBasedMatchingRulesImpl.java
New file @@ -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; } } } 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) opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/PartialDateAndTimeMatchingRuleTestCase.java
New file @@ -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 ]*'\\)\\]"); } } opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeGreaterThanMatchingRuleTest.java
New file @@ -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 < '')"); } } opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/RelativeTimeLessThanMatchingRuleTest.java
New file @@ -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 < '"); } }