/*
|
* 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-2010 Sun Microsystems, Inc.
|
* Portions Copyright 2010-2016 ForgeRock AS.
|
*/
|
package org.opends.server.schema;
|
|
import java.text.SimpleDateFormat;
|
import java.util.ArrayList;
|
import java.util.Calendar;
|
import java.util.Collection;
|
import java.util.GregorianCalendar;
|
import java.util.List;
|
import java.util.Set;
|
import java.util.TimeZone;
|
|
import org.forgerock.opendj.ldap.Assertion;
|
import org.forgerock.opendj.ldap.AttributeDescription;
|
import org.forgerock.opendj.ldap.ByteString;
|
import org.forgerock.opendj.ldap.ConditionResult;
|
import org.forgerock.opendj.ldap.DecodeException;
|
import org.forgerock.opendj.ldap.schema.AttributeType;
|
import org.forgerock.opendj.ldap.schema.MatchingRule;
|
import org.opends.server.TestCaseUtils;
|
import org.opends.server.core.DirectoryServer;
|
import org.opends.server.types.Attribute;
|
import org.opends.server.types.DN;
|
import org.opends.server.types.Entry;
|
import org.opends.server.types.FilterType;
|
import org.opends.server.types.SearchFilter;
|
import org.opends.server.util.TimeThread;
|
import org.testng.annotations.BeforeClass;
|
import org.testng.annotations.DataProvider;
|
import org.testng.annotations.Test;
|
|
import static org.assertj.core.api.Assertions.*;
|
import static org.opends.server.schema.GeneralizedTimeSyntax.*;
|
import static org.opends.server.schema.SchemaConstants.*;
|
import static org.testng.Assert.*;
|
|
/** This class tests various time-based matching rules. */
|
@SuppressWarnings("javadoc")
|
public final class TimeBasedMatchingRuleTest
|
extends SchemaTestCase
|
{
|
/** User DNs to be used in tests. */
|
private DN user1;
|
private DN user2;
|
private DN user3;
|
private DN user4;
|
private DN user5;
|
|
private static final String TIME_ATTR = "test-time";
|
private static final String DATE_ATTR = "test-date";
|
|
|
/**
|
* Ensures that the Directory Server is running before executing the
|
* testcases.
|
*
|
* @throws Exception If an unexpected problem occurs.
|
*/
|
@BeforeClass
|
public void startServer()
|
throws Exception
|
{
|
user1 = DN.valueOf("cn=user1,dc=example,dc=com");
|
user2 = DN.valueOf("cn=user2,dc=example,dc=com");
|
user3 = DN.valueOf("cn=user3,dc=example,dc=com");
|
user4 = DN.valueOf("cn=user4,dc=example,dc=com");
|
user5 = DN.valueOf("cn=user5,dc=example,dc=com");
|
|
/*
|
Extend the schema and add an attribute which is based on
|
generalizedTimeSyntax. Since all the existing attributes based
|
on that syntax are read-only, let us create a new attribute and
|
add it.*/
|
int resultCode = TestCaseUtils.applyModifications(true,
|
"dn: cn=schema",
|
"changetype: modify",
|
"add: attributeTypes",
|
"attributeTypes: ( test-time-oid NAME 'test-time' DESC 'Test time attribute' EQUALITY " +
|
"generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE )",
|
"attributeTypes: ( test-date-oid NAME 'test-date' DESC 'Test date attribute' EQUALITY " +
|
"generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE )",
|
"-",
|
"add: objectclasses",
|
"objectclasses: ( testoc-oid NAME 'testOC' SUP top AUXILIARY MUST test-time)",
|
"objectclasses: ( testoc2-oid NAME 'testOC2' SUP top AUXILIARY MUST test-date)"
|
);
|
assertEquals(0, resultCode);
|
}
|
|
@DataProvider
|
public Object[][] relativeTime()
|
{
|
return new Object[][] {
|
// relativeTime less than expired events
|
{ TIME_ATTR + ":" + EXT_OMR_RELATIVE_TIME_LT_OID + ":=-60m", new DN[] { user1, user2, } },
|
// relativeTime less than future events
|
{ TIME_ATTR + ":" + EXT_OMR_RELATIVE_TIME_LT_OID + ":=1d", new DN[] { user1, user2, user3, user5, } },
|
// relativeTime greater than expired events
|
{ TIME_ATTR + ":" + EXT_OMR_RELATIVE_TIME_GT_OID + ":=-1h", new DN[] { user3, user4, user5, } },
|
// relativeTime greater than future events
|
{ TIME_ATTR + ":" + EXT_OMR_RELATIVE_TIME_GT_OID + ":=0s", new DN[] { user3, user4, } },
|
};
|
}
|
|
@Test(dataProvider = "relativeTime")
|
public void testRelativeTimeUsingAssertion(String filterString, DN[] expectedDNs) throws Exception
|
{
|
SearchFilter filter = SearchFilter.createFilterFromString(filterString);
|
assertThat(getMatchingEntryDNs(filter)).containsOnly(expectedDNs);
|
}
|
|
private Collection<DN> getMatchingEntryDNs(SearchFilter filter) throws Exception
|
{
|
AttributeType attrType = filter.getAttributeType();
|
MatchingRule rule = DirectoryServer.getMatchingRule(filter.getMatchingRuleID());
|
Assertion assertion = rule.getAssertion(filter.getAssertionValue());
|
|
Collection<DN> results = new ArrayList<>();
|
for (Entry entry : makeEntries())
|
{
|
Attribute attribute = entry.getExactAttribute(AttributeDescription.create(attrType));
|
if (attribute != null)
|
{
|
ByteString attrValue = rule.normalizeAttributeValue(attribute.iterator().next());
|
if (assertion.matches(attrValue).toBoolean())
|
{
|
results.add(entry.getName());
|
}
|
}
|
}
|
return results;
|
}
|
|
/** Test to search using the relative time matching rule with index. */
|
@Test(dataProvider = "relativeTime")
|
public void testRelativeTimeWithIndex(String filterString, DN[] expectedDNs) throws Exception
|
{
|
FakeEntryIndex index = new FakeEntryIndex(TIME_ATTR);
|
index.addAll(makeEntries());
|
Collection<Entry> entries = index.evaluateFilter(filterString);
|
assertThat(toNames(entries)).containsOnly(expectedDNs);
|
}
|
|
private List<DN> toNames(Collection<? extends Entry> entries)
|
{
|
List<DN> results = new ArrayList<>();
|
for (Entry entry : entries)
|
{
|
results.add(entry.getName());
|
}
|
return results;
|
}
|
|
/**
|
* Test to match the attribute and the assertion values using a partial date and time
|
* matching rule.
|
*/
|
@Test(dataProvider="partialDateTimeValues")
|
public void testPartialDateNTimeMatch(long timeInMillis, String generalizedTime, String assertionValue)
|
throws Exception
|
{
|
MatchingRule partialTimeRule = DirectoryServer.getMatchingRule(
|
EXT_PARTIAL_DATE_TIME_NAME.toLowerCase());
|
Assertion assertion = partialTimeRule.getAssertion(ByteString.valueOfUtf8(assertionValue));
|
assertEquals(assertion.matches(ByteString.valueOfLong(timeInMillis)), ConditionResult.TRUE);
|
}
|
|
@Test(dataProvider="partialDateTimeValues")
|
public void testPartialDateNTimeMatchViaIndex(long timeInMillis, String generalizedTime, String assertionValue)
|
throws Exception
|
{
|
ByteString attrValue = ByteString.valueOfUtf8(generalizedTime);
|
ByteString assertValue = ByteString.valueOfUtf8(assertionValue);
|
|
FakeByteStringIndex fakeIndex = new FakeByteStringIndex(EXT_PARTIAL_DATE_TIME_NAME);
|
fakeIndex.add(attrValue);
|
Set<ByteString> attrValues = fakeIndex.evaluateAssertionValue(assertValue, FilterType.EXTENSIBLE_MATCH);
|
assertThat(attrValues).containsOnly(attrValue);
|
}
|
|
|
|
/** Tests the assertion syntax of the relative time matching rules. */
|
@Test(dataProvider= "relativeTimeValues")
|
public void testRelativeTimeMatchingRuleAssertionSyntax(String assertion,boolean isValid)
|
{
|
MatchingRule relativeTimeLTRule =
|
DirectoryServer.getMatchingRule(EXT_OMR_RELATIVE_TIME_LT_ALT_NAME.toLowerCase());
|
try
|
{
|
relativeTimeLTRule.getAssertion(ByteString.valueOfUtf8(assertion));
|
// An invalid value can't get away without throwing exception.
|
assertTrue(isValid);
|
}
|
catch (DecodeException e)
|
{
|
//invalid values will throw an exception.
|
assertFalse(isValid);
|
}
|
}
|
|
|
|
/** Tests the assertion syntax of the partial date and time matching rules. */
|
@Test(dataProvider= "partialDateTimeSyntaxes")
|
public void testPartialDateTimeMatchingRuleAssertionSyntax(String assertion,boolean isValid)
|
{
|
MatchingRule partialDTRule =
|
DirectoryServer.getMatchingRule(EXT_PARTIAL_DATE_TIME_OID);
|
try
|
{
|
partialDTRule.getAssertion(ByteString.valueOfUtf8(assertion));
|
assertTrue(isValid);
|
}
|
catch (DecodeException e)
|
{
|
//invalid values will throw an exception.
|
assertFalse(isValid);
|
}
|
}
|
|
|
|
/** Generates data for testing relative time matching rule assertion syntax. */
|
@DataProvider
|
public Object[][] relativeTimeValues()
|
{
|
return new Object[][] {
|
{"1s",true},
|
{"1s0d",false},
|
{"-1d",true},
|
{"2h",true},
|
{"+2w",true},
|
{"0",true},
|
{"0s",true},
|
{"0d",true},
|
{"xyz",false},
|
{"12w-2d",false},
|
{"1s2s",false},
|
{"1d4s5d",false}
|
};
|
}
|
|
|
/** Generates the data for testing partial time date and time values. */
|
@DataProvider
|
public Object[][] partialDateTimeValues()
|
{
|
SimpleDateFormat sdf = new SimpleDateFormat("YYYYMMddHHmmssZ");
|
GregorianCalendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
c.setLenient(false);
|
c.clear();
|
sdf.setCalendar(c);
|
c.set(Calendar.HOUR_OF_DAY,23);
|
c.set(Calendar.MINUTE,0);
|
c.set(Calendar.SECOND,0);
|
long time1 = c.getTimeInMillis();
|
String format1 = sdf.format(c.getTime());
|
c.set(Calendar.HOUR_OF_DAY,00);
|
c.set(Calendar.MINUTE,59);
|
c.set(Calendar.SECOND,59);
|
long time2 = c.getTimeInMillis();
|
String format2 = sdf.format(c.getTime());
|
|
return new Object[][] {
|
{ time1, format1, "0s" },
|
{ time1, format1, "0m" },
|
{ time1, format1, "23h" },
|
{ time2, format2, "59m59s" },
|
{ time2, format2, "0h59m59s" },
|
{ time2, format2, "01D01M" },
|
};
|
}
|
|
|
|
/** Generates data for testing partial date and time assertion syntax. */
|
@DataProvider
|
public Object[][] partialDateTimeSyntaxes()
|
{
|
//Get the current time.
|
GregorianCalendar cal =
|
new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
cal.setLenient(false);
|
|
//Get the date today.
|
int second = cal.get(Calendar.SECOND);
|
int minute = cal.get(Calendar.MINUTE);
|
int hour = cal.get(Calendar.HOUR);
|
int date = cal.get(Calendar.DATE);
|
int month = cal.get(Calendar.MONTH) + 1;
|
int year = cal.get(Calendar.YEAR);
|
|
return new Object[][] {
|
{"20MM30DD1978YY",false},
|
{"02MM29DD2009YY",false},
|
{"02MM31DD2010YY",false},
|
{"-1s",false},
|
{"02M29D2008Y",true},
|
{"DDYY",false},
|
{"02D",true},
|
{"12M",true},
|
{"1978Y",true},
|
{"0MM",false},
|
{"20MM03DD10MM",false},
|
{"00s12m13h",true},
|
{"00s12m14h1M3D1978Y",true},
|
{"1s",true},
|
{"12m",true},
|
{"23h",true},
|
{"61s",false},
|
{"60m",false},
|
{"24h",false},
|
{second+"s",true},
|
{minute+"m",true},
|
{hour+"h",true},
|
{date+"D",true},
|
{month+"M",true},
|
{year+"Y",true},
|
{month+"M"+date+"D",true},
|
{year+"Y"+date+"D",true},
|
{month+"M"+year+"Y"+date+"D",true}
|
};
|
}
|
|
private List<Entry> makeEntries() throws Exception
|
{
|
// Get the current time from the TimeThread. Using the current time from new
|
// calendar may fail if the time thread using a stale time.
|
long currentTime = TimeThread.getTime();
|
|
return TestCaseUtils.makeEntries(
|
"dn: cn=user1,dc=example,dc=com",
|
"objectclass: person",
|
"objectclass: testoc",
|
"cn: user1",
|
"sn: user1",
|
TIME_ATTR + ": "+ format(currentTime-4000*1000), //more than 1 hour old.
|
"",
|
"dn: cn=user2,dc=example,dc=com",
|
"objectclass: person",
|
"objectclass: testoc",
|
"cn: user2",
|
"sn: user2",
|
TIME_ATTR + ": " + format(currentTime-25*3600*1000), //more than a day old.
|
"",
|
"dn: cn=user3,dc=example,dc=com",
|
"objectclass: person",
|
"objectclass: testoc",
|
"cn: user3",
|
"sn: user3",
|
TIME_ATTR + ": " + format(currentTime+4000*1000), //more than 1 hour in advance.
|
"",
|
"dn: cn=user4,dc=example,dc=com",
|
"objectclass: person",
|
"objectclass: testoc",
|
"cn: user4",
|
"sn: user4",
|
TIME_ATTR + ": " + format(currentTime+25*3600*1000), // more than 1 day in advance
|
"",
|
"dn: cn=user5,dc=example,dc=com",
|
"objectclass: person",
|
"objectclass: testoc",
|
"cn: user5",
|
"sn: user5",
|
TIME_ATTR + ": " + format(currentTime), // now.
|
"",
|
"dn: cn=user6,dc=example,dc=com",
|
"objectclass: person",
|
"objectclass: testoc2",
|
"cn: user6",
|
"sn: user6",
|
DATE_ATTR + ": 19651101000000Z", // Nov 1st 1965
|
"",
|
"dn: cn=user7,dc=example,dc=com",
|
"objectclass: person",
|
"objectclass: testoc2",
|
"cn: user7",
|
"sn: user7",
|
DATE_ATTR + ": 20101104000000Z", // Nov 4th 2010
|
"",
|
"dn: cn=user8,dc=example,dc=com",
|
"objectclass: person",
|
"objectclass: testoc2",
|
"cn: user8",
|
"sn: user8",
|
DATE_ATTR + ": 20000101000000Z" // Jan 1st 2000
|
);
|
}
|
}
|