/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2008 Sun Microsystems, Inc.
*/
package org.opends.server.types;
import org.opends.server.DirectoryServerTestCase;
import org.opends.server.TestCaseUtils;
import org.opends.server.util.StaticUtils;
import org.opends.server.types.DirectoryException;
import org.opends.server.core.DirectoryServer;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeClass;
import org.testng.Assert;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import static java.util.Arrays.asList;
import static org.opends.server.util.StaticUtils.*;
import static org.testng.Assert.*;
/**
* Tests for the org.opends.server.types.SearchFilter class
*
* This class covers the SearchFilter class fairly well. The main gaps are
* with extensible match, attribute options, and there is a lot of code
* that is not reachable because it's in exception handling code that
* is not exercisable externally.
*/
public class SearchFilterTests extends DirectoryServerTestCase {
@BeforeClass
public void setupClass() throws Exception {
TestCaseUtils.startServer();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
//
// createFilterFromString
//
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// -------------------------------------------------------------------------
//
// Test valid filters.
//
// -------------------------------------------------------------------------
// These are valid filters.
@DataProvider(name = "paramsCreateFilterFromStringValidFilters")
public Object[][] paramsCreateFilterFromStringValidFilters() {
return new Object[][]{
{"(&)", "(&)"},
{"(|)", "(|)"},
{"(sn=test)", "(sn=test)"},
{"(sn=*)", "(sn=*)"},
{"(sn=)", "(sn=)"},
{"(sn=*test*)", "(sn=*test*)"},
{"(!(sn=test))", "(!(sn=test))"},
{"(|(sn=test)(sn=test2))", "(|(sn=test)(sn=test2))"},
{"(&(sn=test))", "(&(sn=test))"},
{"(|(sn=test))", "(|(sn=test))"},
};
}
@Test(dataProvider = "paramsCreateFilterFromStringValidFilters")
public void testCreateFilterFromStringValidFilters(
String originalFilter,
String expectedToStringFilter
) throws DirectoryException {
runRecreateFilterTest(originalFilter, expectedToStringFilter);
}
private void runRecreateFilterTest(
String originalFilter,
String expectedToStringFilter
) throws DirectoryException {
String regenerated = SearchFilter.createFilterFromString(originalFilter).toString();
Assert.assertEquals(regenerated, expectedToStringFilter, "original=" + originalFilter + ", expected=" + expectedToStringFilter);
}
// These are valid filters.
@DataProvider(name = "escapeSequenceFilters")
public Object[][] escapeSequenceFilters() {
final char[] CHAR_NIBBLES = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
'A', 'B', 'C', 'D', 'E', 'F'};
final byte[] BYTE_NIBBLES = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
List<String[]> allParameters = new ArrayList<String[]>();
for (int i = 0; i < CHAR_NIBBLES.length; i++) {
char highNibble = CHAR_NIBBLES[i];
byte highByteNibble = BYTE_NIBBLES[i];
for (int j = 0; j < CHAR_NIBBLES.length; j++) {
char lowNibble = CHAR_NIBBLES[j];
byte lowByteNibble = BYTE_NIBBLES[j];
String inputChar = "\\" + highNibble + lowNibble;
byte byteValue = (byte)((((int)highByteNibble) << 4) | lowByteNibble);
String outputChar = getFilterValueForChar(byteValue);
// Exact match
String inputFilter = "(sn=" + inputChar + ")";
String outputFilter = "(sn=" + outputChar + ")";
allParameters.add(new String[]{inputFilter, outputFilter});
// Substring
inputFilter = "(sn=" + inputChar + "*" + inputChar + "*" + inputChar + ")";
outputFilter = "(sn=" + outputChar + "*" + outputChar + "*" + outputChar + ")";
allParameters.add(new String[]{inputFilter, outputFilter});
// <=
inputFilter = "(sn<=" + inputChar + ")";
outputFilter = "(sn<=" + outputChar + ")";
allParameters.add(new String[]{inputFilter, outputFilter});
// >=
inputFilter = "(sn>=" + inputChar + ")";
outputFilter = "(sn>=" + outputChar + ")";
allParameters.add(new String[]{inputFilter, outputFilter});
// =~
inputFilter = "(sn>=" + inputChar + ")";
outputFilter = "(sn>=" + outputChar + ")";
allParameters.add(new String[]{inputFilter, outputFilter});
// =~
inputFilter = "(sn:caseExactMatch:=" + inputChar + ")";
outputFilter = "(sn:caseExactMatch:=" + outputChar + ")";
allParameters.add(new String[]{inputFilter, outputFilter});
}
}
return (Object[][]) allParameters.toArray(new String[][]{});
}
// These are filters with invalid escape sequences.
@DataProvider(name = "invalidEscapeSequenceFilters")
public Object[][] invalidEscapeSequenceFilters() {
final char[] VALID_NIBBLES = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
'A', 'B', 'C', 'D', 'E', 'F'};
final char[] INVALID_NIBBBLES = {'g', 'z', 'G', 'Z', '-', '=', '+', '\00', ')',
'n', 't', '\\'};
List<String> invalidEscapeSequences = new ArrayList<String>();
for (int i = 0; i < VALID_NIBBLES.length; i++) {
char validNibble = VALID_NIBBLES[i];
for (int j = 0; j < INVALID_NIBBBLES.length; j++) {
char invalidNibble = INVALID_NIBBBLES[j];
invalidEscapeSequences.add("\\" + validNibble + invalidNibble);
invalidEscapeSequences.add("\\" + invalidNibble + validNibble);
}
// Also do a test case where we only have one character in the escape sequence.
invalidEscapeSequences.add("\\" + validNibble);
}
List<String[]> allParameters = new ArrayList<String[]>();
for (String invalidEscape : invalidEscapeSequences) {
// Exact match
allParameters.add(new String[]{"(sn=" + invalidEscape + ")"});
allParameters.add(new String[]{"(sn=" + invalidEscape});
// Substring
allParameters.add(new String[]{"(sn=" + invalidEscape + "*" + invalidEscape + "*" + invalidEscape + ")"});
allParameters.add(new String[]{"(sn=" + invalidEscape + "*" + invalidEscape + "*" + invalidEscape});
// <=
allParameters.add(new String[]{"(sn<=" + invalidEscape + ")"});
allParameters.add(new String[]{"(sn<=" + invalidEscape});
// >=
allParameters.add(new String[]{"(sn>=" + invalidEscape + ")"});
allParameters.add(new String[]{"(sn>=" + invalidEscape});
// =~
allParameters.add(new String[]{"(sn>=" + invalidEscape + ")"});
allParameters.add(new String[]{"(sn>=" + invalidEscape});
// =~
allParameters.add(new String[]{"(sn:caseExactMatch:=" + invalidEscape + ")"});
allParameters.add(new String[]{"(sn:caseExactMatch:=" + invalidEscape});
}
return (Object[][]) allParameters.toArray(new String[][]{});
}
/**
* @return a value that can be used in an LDAP filter.
*/
private String getFilterValueForChar(byte value) {
if (((value & 0x7F) != value) || // Not 7-bit clean
(value <= 0x1F) || // Below the printable character range
(value == 0x28) || // Open parenthesis
(value == 0x29) || // Close parenthesis
(value == 0x2A) || // Asterisk
(value == 0x5C) || // Backslash
(value == 0x7F)) // Delete character
{
return "\\" + StaticUtils.byteToHex(value);
} else {
return "" + ((char)value);
}
}
@Test(dataProvider = "escapeSequenceFilters")
public void testRecreateFilterWithEscape(
String originalFilter,
String expectedToStringFilter
) throws DirectoryException {
runRecreateFilterTest(originalFilter, expectedToStringFilter);
}
@Test(dataProvider = "invalidEscapeSequenceFilters",
expectedExceptions = DirectoryException.class)
public void testFilterWithInvalidEscape(
String filterWithInvalidEscape)
throws DirectoryException {
// This should fail with a parse error.
SearchFilter.createFilterFromString(filterWithInvalidEscape);
}
// -------------------------------------------------------------------------
//
// Test invalid filters.
//
// -------------------------------------------------------------------------
//
// Invalid filters that are detected.
//
@DataProvider(name = "invalidFilters")
public Object[][] invalidFilters() {
return new Object[][]{
{null},
{"(cn)"},
{"()"},
{"("},
{"(&(sn=test)"},
{"(|(sn=test)"},
{"(!(sn=test)"},
{"(&(sn=test)))"},
{"(|(sn=test)))"},
{"(!(sn=test)))"},
{"(sn=\\A)"},
{"(sn=\\1H)"},
{"(sn=\\H1)"},
{"(!(sn=test)(cn=test))"},
{"(!)"},
{"(:dn:=Sally)"}
};
}
@Test(dataProvider = "invalidFilters",
expectedExceptions = DirectoryException.class)
public void testCreateFilterFromStringInvalidFilters(String invalidFilter)
throws DirectoryException {
SearchFilter.createFilterFromString(invalidFilter).toString();
}
//
// This is more or less the same as what's above, but it's for invalid
// filters that are not currently detected by the parser. To turn these
// on, remove them from the broken group. As the code is modified to handle
// these cases, please add these test cases to the
// paramsCreateFilterFromStringInvalidFilters DataProvider.
//
@DataProvider(name = "uncaughtInvalidFilters")
public Object[][] paramsCreateFilterFromStringUncaughtInvalidFilters() {
return new Object[][]{
{"(cn=**)"},
{"( sn = test )"},
{"&(cn=*)"},
{"(!(sn=test)(sn=test2))"},
{"(objectclass=**)"},
};
}
@Test(dataProvider = "uncaughtInvalidFilters",
expectedExceptions = DirectoryException.class,
// FIXME: These currently aren't detected
enabled = false)
public void testCreateFilterFromStringUncaughtInvalidFilters(String invalidFilter)
throws DirectoryException {
SearchFilter.createFilterFromString(invalidFilter).toString();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
//
// matches
//
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private static final String JOHN_SMITH_LDIF = TestCaseUtils.makeLdif(
"dn: cn=John Smith,dc=example,dc=com",
"objectclass: inetorgperson",
"cn: John Smith",
"cn;lang-en: Jonathan Smith",
"sn: Smith",
"givenname: John",
"internationaliSDNNumber: 12345",
"displayName: *",
"title: tattoos",
"labeledUri: http://opends.org/john"
);
@DataProvider(name = "matchesParams")
public Object[][] matchesParams() {
return new Object[][]{
{JOHN_SMITH_LDIF, "(objectclass=inetorgperson)", true},
{JOHN_SMITH_LDIF, "(objectclass=iNetOrgPeRsOn)", true},
{JOHN_SMITH_LDIF, "(objectclass=*)", true},
{JOHN_SMITH_LDIF, "(objectclass=person)", false},
{JOHN_SMITH_LDIF, "(cn=John Smith)", true},
{JOHN_SMITH_LDIF, "(cn=Jonathan Smith)", true},
{JOHN_SMITH_LDIF, "(cn=JOHN SmITh)", true},
{JOHN_SMITH_LDIF, "(cn=*)", true},
{JOHN_SMITH_LDIF, "(cn=*John Smith*)", true},
{JOHN_SMITH_LDIF, "(cn=*Jo*ith*)", true},
{JOHN_SMITH_LDIF, "(cn=*Jo*i*th*)", true},
{JOHN_SMITH_LDIF, "(cn=*Joh*ohn*)", false}, // this shouldn't match
{JOHN_SMITH_LDIF, "(internationaliSDNNumber=*23*34*)", false}, // this shouldn't match
{JOHN_SMITH_LDIF, "(cn=*o*n*)", true},
{JOHN_SMITH_LDIF, "(cn=*n*o*)", false},
// attribute options
{JOHN_SMITH_LDIF, "(cn;lang-en=Jonathan Smith)", true},
{JOHN_SMITH_LDIF, "(cn;lang-EN=Jonathan Smith)", true},
{JOHN_SMITH_LDIF, "(cn;lang-en=Jonathan Smithe)", false},
{JOHN_SMITH_LDIF, "(cn;lang-fr=Jonathan Smith)", false},
{JOHN_SMITH_LDIF, "(cn;lang-en=*jon*an*)", true},
{JOHN_SMITH_LDIF, "(cn;lAnG-En=*jon*an*)", true},
// attribute subtypes. Enable this once 593 is fixed.
{JOHN_SMITH_LDIF, "(name=John Smith)", true},
{JOHN_SMITH_LDIF, "(name=*Smith*)", true},
{JOHN_SMITH_LDIF, "(name;lang-en=Jonathan Smith)", true},
{JOHN_SMITH_LDIF, "(name;lang-EN=Jonathan Smith)", true},
{JOHN_SMITH_LDIF, "(name;lang-en=*Jonathan*)", true},
// Enable this once
// {JOHN_SMITH_LDIF, "(cn=*Jo**i*th*)", true},
{JOHN_SMITH_LDIF, "(cn=\\4Aohn*)", true}, // \4A = J
{JOHN_SMITH_LDIF, "(|(cn=Jane Smith)(cn=John Smith))", true},
{JOHN_SMITH_LDIF, "(title~=tattoos)", true},
{JOHN_SMITH_LDIF, "(title~=tattos)", true},
{JOHN_SMITH_LDIF, "(labeledUri=http://opends.org/john)", true},
{JOHN_SMITH_LDIF, "(labeledUri=http://opends.org/JOHN)", false},
{JOHN_SMITH_LDIF, "(labeledUri=http://*/john)", true},
{JOHN_SMITH_LDIF, "(labeledUri=http://*/JOHN)", false},
{JOHN_SMITH_LDIF, "(cn>=John Smith)", true},
{JOHN_SMITH_LDIF, "(cn>=J)", true},
{JOHN_SMITH_LDIF, "(cn<=J)", false},
{JOHN_SMITH_LDIF, "(cn=Jane Smith)", false},
{JOHN_SMITH_LDIF, "(displayName=\\2A)", true}, // \2A = *
// 2.5.4.4 is Smith
{JOHN_SMITH_LDIF, "(2.5.4.4=Smith)", true},
{JOHN_SMITH_LDIF, "(sn:caseExactMatch:=Smith)", true},
{JOHN_SMITH_LDIF, "(sn:caseExactMatch:=smith)", false},
// Test cases for 730
{JOHN_SMITH_LDIF, "(internationaliSDNNumber=*12*45*)", true},
{JOHN_SMITH_LDIF, "(internationaliSDNNumber=*45*12*)", false},
// TODO: open a bug for all of these.
// {JOHN_SMITH_LDIF, "(:caseExactMatch:=Smith)", true},
// {JOHN_SMITH_LDIF, "(:caseExactMatch:=NotSmith)", false},
// Look at 4515 for some more examples. Ask Neil.
// {JOHN_SMITH_LDIF, "(:dn:caseExactMatch:=example)", true},
// {JOHN_SMITH_LDIF, "(:dn:caseExactMatch:=notexample)", false},
};
}
@Test(dataProvider = "matchesParams")
public void testMatches(String ldifEntry, String filterStr, boolean expectMatch) throws Exception {
runMatchTest(ldifEntry, filterStr, expectMatch);
}
private void runMatchTest(String ldifEntry, String filterStr, boolean expectMatch) throws Exception {
Entry entry = TestCaseUtils.entryFromLdifString(ldifEntry);
runSingleMatchTest(entry, filterStr, expectMatch);
runSingleMatchTest(entry, "(|" + filterStr + ")", expectMatch);
runSingleMatchTest(entry, "(&" + filterStr + ")", expectMatch);
runSingleMatchTest(entry, "(!" + filterStr + ")", !expectMatch);
}
private void runSingleMatchTest(Entry entry, String filterStr, boolean expectMatch) throws Exception {
final SearchFilter filter = SearchFilter.createFilterFromString(filterStr);
boolean matches = filter.matchesEntry(entry);
Assert.assertEquals(matches, expectMatch, "Filter=" + filter + "\nEntry=" + entry);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
//
// Filter construction
//
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/**
*
*/
private static final String makeSimpleLdif(String givenname, String sn) {
String cn = givenname + " " + sn;
return TestCaseUtils.makeLdif(
"dn: cn=" + cn + ",dc=example,dc=com",
"objectclass: inetorgperson",
"cn: " + cn,
"sn: " + sn,
"givenname: " + givenname
);
}
private static final String JANE_SMITH_LDIF = makeSimpleLdif("Jane", "Smith");
private static final String JANE_AUSTIN_LDIF = makeSimpleLdif("Jane", "Austin");
private static final String JOE_SMITH_LDIF = makeSimpleLdif("Joe", "Smith");
private static final String JOE_AUSTIN_LDIF = makeSimpleLdif("Joe", "Austin");
private static final List<String> ALL_ENTRIES_LDIF =
Collections.unmodifiableList(asList(JANE_SMITH_LDIF,
JANE_AUSTIN_LDIF,
JOE_SMITH_LDIF,
JOE_AUSTIN_LDIF));
/**
*
*/
private List<String> getEntriesExcluding(List<String> matchedEntries) {
List<String> unmatched = new ArrayList<String>(ALL_ENTRIES_LDIF);
unmatched.removeAll(matchedEntries);
return unmatched;
}
/**
*
*/
private static class FilterDescription {
private SearchFilter searchFilter;
private List<String> matchedEntriesLdif;
private List<String> unmatchedEntriesLdif;
private FilterType filterType;
private LinkedHashSet<SearchFilter> filterComponents = new LinkedHashSet<SearchFilter>();
private SearchFilter notComponent;
private AttributeValue assertionValue;
private AttributeType attributeType;
private ByteString subInitialElement;
private List<ByteString> subAnyElements = new ArrayList<ByteString>();
private ByteString subFinalElement;
private String matchingRuleId;
private boolean dnAttributes;
/**
*
*/
public void validateFilterFields() throws AssertionError {
if (!searchFilter.getFilterType().equals(filterType)) {
throwUnequalError("filterTypes");
}
if (!searchFilter.getFilterComponents().equals(filterComponents)) {
throwUnequalError("filterComponents");
}
if (!objectsAreEqual(searchFilter.getNotComponent(), notComponent)) {
throwUnequalError("notComponent");
}
if (!objectsAreEqual(searchFilter.getAssertionValue(), assertionValue)) {
throwUnequalError("assertionValue");
}
if (!objectsAreEqual(searchFilter.getAttributeType(), attributeType)) {
throwUnequalError("attributeType");
}
if (!objectsAreEqual(searchFilter.getSubInitialElement(), subInitialElement)) {
throwUnequalError("subInitial");
}
if (!objectsAreEqual(searchFilter.getSubAnyElements(), subAnyElements)) {
throwUnequalError("subAny");
}
if (!objectsAreEqual(searchFilter.getSubFinalElement(), subFinalElement)) {
throwUnequalError("subFinal");
}
if (!objectsAreEqual(searchFilter.getMatchingRuleID(), matchingRuleId)) {
throwUnequalError("matchingRuleId");
}
if (searchFilter.getDNAttributes() != dnAttributes) {
throwUnequalError("dnAttributes");
}
}
/**
*
*/
private void throwUnequalError(String message) throws AssertionError {
throw new AssertionError("Filter differs from what is expected '" + message + "' differ.\n" + toString());
}
/**
*
*/
@Override
public String toString() {
return "FilterDescription: \n" +
"\tsearchFilter=" + searchFilter + "\n" +
"\tfilterType = " + filterType + "\n" +
"\tfilterComponents = " + filterComponents + "\n" +
"\tnotComponent = " + notComponent + "\n" +
"\tassertionValue = " + assertionValue + "\n" +
"\tattributeType = " + attributeType + "\n" +
"\tsubInitialElement = " + subInitialElement + "\n" +
"\tsubAnyElements = " + subAnyElements + "\n" +
"\tsubFinalElement = " + subFinalElement + "\n" +
"\tmatchingRuleId = " + dnAttributes + "\n";
}
/**
*
*/
private FilterDescription negate() {
FilterDescription negation = new FilterDescription();
negation.searchFilter = SearchFilter.createNOTFilter(searchFilter);
// Flip-flop these
negation.matchedEntriesLdif = unmatchedEntriesLdif;
negation.unmatchedEntriesLdif = matchedEntriesLdif;
negation.filterType = FilterType.NOT;
negation.notComponent = searchFilter;
return negation;
}
/**
*
*/
public FilterDescription clone() {
FilterDescription that = new FilterDescription();
that.searchFilter = this.searchFilter;
that.matchedEntriesLdif = this.matchedEntriesLdif;
that.unmatchedEntriesLdif = this.unmatchedEntriesLdif;
that.filterType = this.filterType;
that.filterComponents = this.filterComponents;
that.notComponent = this.notComponent;
that.assertionValue = this.assertionValue;
that.attributeType = this.attributeType;
that.subInitialElement = this.subInitialElement;
that.subAnyElements = this.subAnyElements;
that.subFinalElement = this.subFinalElement;
that.matchingRuleId = this.matchingRuleId;
that.dnAttributes = this.dnAttributes;
return that;
}
}
/**
*
*/
private FilterDescription assertionFilterDescription(FilterType filterType,
String attributeType,
String attributeValue,
List<String> matchedEntries) {
FilterDescription description = new FilterDescription();
description.filterType = filterType;
description.attributeType = DirectoryServer.getAttributeType(attributeType);
description.assertionValue = AttributeValues.create(description.attributeType, attributeValue);
if (filterType == FilterType.EQUALITY) {
description.searchFilter = SearchFilter.createEqualityFilter(description.attributeType,
description.assertionValue);
} else if (filterType == FilterType.LESS_OR_EQUAL) {
description.searchFilter = SearchFilter.createLessOrEqualFilter(description.attributeType,
description.assertionValue);
} else if (filterType == FilterType.GREATER_OR_EQUAL) {
description.searchFilter = SearchFilter.createGreaterOrEqualFilter(description.attributeType,
description.assertionValue);
} else if (filterType == FilterType.APPROXIMATE_MATCH) {
description.searchFilter = SearchFilter.createApproximateFilter(description.attributeType,
description.assertionValue);
} else {
fail(filterType + " is not handled.");
}
description.matchedEntriesLdif = matchedEntries;
description.unmatchedEntriesLdif = getEntriesExcluding(matchedEntries);
return description;
}
/**
*
*/
private FilterDescription equalityFilterDescription(String attributeType,
String attributeValue,
List<String> matchedEntries) {
return assertionFilterDescription(FilterType.EQUALITY, attributeType, attributeValue, matchedEntries);
}
/**
*
*/
private FilterDescription lessEqualFilterDescription(String attributeType,
String attributeValue,
List<String> matchedEntries) {
return assertionFilterDescription(FilterType.LESS_OR_EQUAL, attributeType, attributeValue, matchedEntries);
}
/**
*
*/
private FilterDescription greaterEqualFilterDescription(String attributeType,
String attributeValue,
List<String> matchedEntries) {
return assertionFilterDescription(FilterType.GREATER_OR_EQUAL, attributeType, attributeValue, matchedEntries);
}
/**
*
*/
private FilterDescription approximateFilterDescription(String attributeType,
String attributeValue,
List<String> matchedEntries) {
return assertionFilterDescription(FilterType.APPROXIMATE_MATCH, attributeType, attributeValue, matchedEntries);
}
/**
*
*/
private FilterDescription substringFilterDescription(String attributeType,
String subInitial,
List<String> subAny,
String subFinal,
List<String> matchedEntries) {
FilterDescription description = new FilterDescription();
description.filterType = FilterType.SUBSTRING;
description.attributeType = DirectoryServer.getAttributeType(attributeType);
description.subInitialElement = ByteString.valueOf(subInitial);
description.subAnyElements = new ArrayList<ByteString>();
for (int i = 0; (subAny != null) && (i < subAny.size()); i++) {
String s = subAny.get(i);
description.subAnyElements.add(ByteString.valueOf(s));
}
description.subFinalElement = ByteString.valueOf(subFinal);
description.searchFilter = SearchFilter.createSubstringFilter(description.attributeType,
description.subInitialElement,
description.subAnyElements,
description.subFinalElement);
description.matchedEntriesLdif = matchedEntries;
description.unmatchedEntriesLdif = getEntriesExcluding(matchedEntries);
return description;
}
/**
*
*/
private List<FilterDescription> getNotFilters(List<FilterDescription> filters) {
List<FilterDescription> notFilters = new ArrayList<FilterDescription>();
for (FilterDescription filter: filters) {
notFilters.add(filter.negate());
}
return notFilters;
}
/**
*
*/
private FilterDescription getAndFilter(List<FilterDescription> filters) {
FilterDescription andFilter = new FilterDescription();
List<String> matchedEntries = new ArrayList<String>(ALL_ENTRIES_LDIF);
List<SearchFilter> filterComponents = new ArrayList<SearchFilter>();
for (FilterDescription filter: filters) {
matchedEntries.retainAll(filter.matchedEntriesLdif);
filterComponents.add(filter.searchFilter);
}
andFilter.searchFilter = SearchFilter.createANDFilter(filterComponents);
andFilter.filterComponents = new LinkedHashSet<SearchFilter>(filterComponents);
andFilter.filterType = FilterType.AND;
andFilter.matchedEntriesLdif = matchedEntries;
andFilter.unmatchedEntriesLdif = getEntriesExcluding(matchedEntries);
return andFilter;
}
/**
*
*/
private List<FilterDescription> getAndFilters(List<FilterDescription> filters) {
List<FilterDescription> andFilters = new ArrayList<FilterDescription>();
for (FilterDescription first: filters) {
for (FilterDescription second: filters) {
andFilters.add(getAndFilter(asList(first, second)));
}
}
return andFilters;
}
/**
*
*/
private FilterDescription getOrFilter(List<FilterDescription> filters) {
FilterDescription orFilter = new FilterDescription();
List<String> unmatchedEntries = new ArrayList<String>(ALL_ENTRIES_LDIF);
List<SearchFilter> filterComponents = new ArrayList<SearchFilter>();
for (FilterDescription filter: filters) {
unmatchedEntries.retainAll(filter.unmatchedEntriesLdif);
filterComponents.add(filter.searchFilter);
}
orFilter.searchFilter = SearchFilter.createORFilter(filterComponents);
orFilter.filterComponents = new LinkedHashSet<SearchFilter>(filterComponents);
orFilter.filterType = FilterType.OR;
// Since we're not using Sets, we've whittled down unmatched entries from
// the full set instead of adding to matchedEntries, which would lead
// to duplicates.
orFilter.unmatchedEntriesLdif = unmatchedEntries;
orFilter.matchedEntriesLdif = getEntriesExcluding(unmatchedEntries);
return orFilter;
}
/**
*
*/
private List<FilterDescription> getOrFilters(List<FilterDescription> filters) {
List<FilterDescription> orFilters = new ArrayList<FilterDescription>();
for (FilterDescription first: filters) {
for (FilterDescription second: filters) {
orFilters.add(getOrFilter(asList(first, second)));
}
}
return orFilters;
}
/**
*
*/
private List<FilterDescription> getEqualityFilters() throws Exception {
List<FilterDescription> descriptions = new ArrayList<FilterDescription>();
descriptions.add(equalityFilterDescription("sn", "Smith",
asList(JANE_SMITH_LDIF, JOE_SMITH_LDIF)));
descriptions.add(equalityFilterDescription("givenname", "Jane",
asList(JANE_SMITH_LDIF, JANE_AUSTIN_LDIF)));
return descriptions;
}
/**
*
*/
private List<FilterDescription> getApproximateFilters() throws Exception {
List<FilterDescription> descriptions = new ArrayList<FilterDescription>();
descriptions.add(approximateFilterDescription("sn", "Smythe",
asList(JANE_SMITH_LDIF, JOE_SMITH_LDIF)));
return descriptions;
}
/**
*
*/
private List<FilterDescription> getSubstringFilters() throws Exception {
List<FilterDescription> descriptions = new ArrayList<FilterDescription>();
descriptions.add(substringFilterDescription(
"sn",
"S", asList("i"), "th", // S*i*th
asList(JANE_SMITH_LDIF, JOE_SMITH_LDIF)));
return descriptions;
}
/**
*
*/
private List<FilterDescription> getInequalityFilters() throws Exception {
List<FilterDescription> descriptions = new ArrayList<FilterDescription>();
descriptions.add(lessEqualFilterDescription("sn", "Aus",
(List<String>)(new ArrayList<String>())));
descriptions.add(greaterEqualFilterDescription("sn", "Aus",
asList(JANE_AUSTIN_LDIF, JOE_AUSTIN_LDIF,
JANE_SMITH_LDIF, JOE_SMITH_LDIF)));
descriptions.add(lessEqualFilterDescription("sn", "Smi",
asList(JANE_AUSTIN_LDIF, JOE_AUSTIN_LDIF)));
descriptions.add(greaterEqualFilterDescription("sn", "Smi",
asList(JANE_SMITH_LDIF, JOE_SMITH_LDIF)));
descriptions.add(lessEqualFilterDescription("sn", "Smith",
asList(JANE_AUSTIN_LDIF, JOE_AUSTIN_LDIF,
JANE_SMITH_LDIF, JOE_SMITH_LDIF)));
descriptions.add(greaterEqualFilterDescription("sn", "Smith",
asList(JANE_SMITH_LDIF, JOE_SMITH_LDIF)));
return descriptions;
}
/**
* Updates to this should also be made in getMinimalFilterDescriptionList.
* @see #getMinimalFilterDescriptionList
*/
private List<FilterDescription> getFilterDescriptionList() throws Exception {
List<FilterDescription> baseDescriptions = new ArrayList<FilterDescription>();
baseDescriptions.addAll(getEqualityFilters());
baseDescriptions.addAll(getInequalityFilters());
baseDescriptions.addAll(getApproximateFilters());
baseDescriptions.addAll(getSubstringFilters());
baseDescriptions.addAll(getNotFilters(baseDescriptions));
List<FilterDescription> allDescriptions = new ArrayList<FilterDescription>();
allDescriptions.addAll(getAndFilters(baseDescriptions));
allDescriptions.addAll(getOrFilters(baseDescriptions));
allDescriptions.addAll(baseDescriptions);
return allDescriptions;
}
/**
*
*/
protected List<FilterDescription> getMinimalFilterDescriptionList() throws Exception {
List<FilterDescription> baseDescriptions = new ArrayList<FilterDescription>();
List<FilterDescription> allDescriptions = new ArrayList<FilterDescription>();
baseDescriptions.addAll(getEqualityFilters().subList(0, 1));
baseDescriptions.addAll(getInequalityFilters().subList(0, 2));
baseDescriptions.addAll(getSubstringFilters().subList(0, 1));
baseDescriptions.addAll(getNotFilters(baseDescriptions).subList(0, 1));
allDescriptions.addAll(baseDescriptions);
allDescriptions.addAll(getAndFilters(baseDescriptions).subList(0, 2));
allDescriptions.addAll(getOrFilters(baseDescriptions).subList(0, 2));
return allDescriptions;
}
/**
*
*/
@DataProvider(name = "filterDescriptions")
public Object[][] getFilterDescriptions() throws Exception {
List<FilterDescription> allDescriptions = getFilterDescriptionList();
// Now convert to [][]
FilterDescription[][] descriptionArray = new FilterDescription[allDescriptions.size()][];
for (int i = 0; i < allDescriptions.size(); i++) {
FilterDescription description = allDescriptions.get(i);
descriptionArray[i] = new FilterDescription[]{description};
}
return descriptionArray;
}
@Test(dataProvider = "filterDescriptions")
public void testFilterConstruction(FilterDescription description) throws Exception {
description.validateFilterFields();
for (String ldif: description.matchedEntriesLdif) {
Entry entry = TestCaseUtils.entryFromLdifString(ldif);
if (!description.searchFilter.matchesEntry(entry)) {
fail("Expected to match entry. " + description + entry);
}
}
for (String ldif: description.unmatchedEntriesLdif) {
Entry entry = TestCaseUtils.entryFromLdifString(ldif);
if (description.searchFilter.matchesEntry(entry)) {
fail("Should not have matched entry. " + description + entry);
}
}
}
// TODO: test more on extensible match and attribute options
// TODO: test that we fail when creating filters without specifying all of the parameters
// TODO: we need to test attribute options!
// TODO: test the audio attribute since it's octetStringMatch
// TODO: test the homePhone attribute since EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch
// TODO: test labeledURI since it's caseExactMatch SUBSTR caseExactSubstringsMatch
// TODO: test mail since it's EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch
// TODO: test secretary since it's distinguishedNameMatch
// TODO: test x500UniqueIdentifier since it's bitStringMatch
private static final Object[][] TEST_EQUALS_PARAMS = new Object[][]{
// These have duplicates, and their String representation should even reflect that.
{"(&(sn=Smith))", "(&(sn=Smith)(sn=Smith))", true, true},
{"(|(sn=Smith))", "(|(sn=Smith)(sn=Smith))", true, true},
// These are reordered, so they are equivalent, but their String representations will differ
{"(&(sn=Smith)(sn<=Aus))", "(&(sn<=Aus)(sn=Smith))", true, false},
{"(|(sn=Smith)(sn<=Aus))", "(|(sn<=Aus)(sn=Smith))", true, false},
// These should be case insensitive
{"(SN=Smith)", "(sn=Smith)", true, true},
{"(sn=smith)", "(sn=Smith)", true, false},
{"(SN=S*th)", "(sn=S*th)", true, true},
{"(sn:caseExactMatch:=Smith)", "(sn:caseExactMatch:=Smith)", true, true},
// This demonstrates bug 704.
{"(sn:caseExactMatch:=Smith)", "(sn:caseExactMatch:=smith)", false, false},
// Ensure that ":dn:" is treated in a case-insensitive manner.
{"(:dn:caseExactMatch:=example)", "(:DN:caseExactMatch:=example)", true, true}, // ? String not match
// 2.5.4.4 is 'sn'
{"(2.5.4.4=Smith)", "(2.5.4.4=Smith)", true, true},
{"(2.5.4.4=Smith)", "(sn=Smith)", true, true},
{"(sn;lang-en=Smith)", "(sn;lang-en=Smith)", true, true},
// This demonstrates bug 706
{"(sn;lang-en=Smith)", "(sn=Smith)", false, false},
// This demonstrates bug 705.
{"(sn=s*t*h)", "(sn=S*T*H)", true, false},
// These should be case sensitive
{"(labeledURI=http://opends.org)", "(labeledURI=http://OpenDS.org)", false, false},
{"(labeledURI=http://opends*)", "(labeledURI=http://OpenDS*)", false, false},
// These are WYSIWIG
{"(sn=*)", "(sn=*)", true, true},
{"(sn=S*)", "(sn=S*th)", false, false},
{"(sn=*S)", "(sn=S*th)", false, false},
{"(sn=S*t)", "(sn=S*th)", false, false},
{"(sn=*i*t*)", "(sn=*i*t*)", true, true},
{"(sn=*t*i*)", "(sn=*i*t*)", false, false}, // Test case for 695
{"(sn=S*i*t)", "(sn=S*th)", false, false},
{"(sn=Smith)", "(sn=Smith)", true, true},
{"(sn=Smith)", "(sn<=Aus)", false, false},
{"(sn=Smith)", "(sn>=Aus)", false, false},
{"(sn=Smith)", "(sn=S*i*th)", false, false},
{"(sn=Smith)", "(!(sn=Smith))", false, false},
{"(sn=Smith)", "(&(sn=Smith)(sn<=Aus))", false, false},
{"(sn=Smith)", "(|(sn=Smith)(sn<=Aus))", false, false},
{"(sn<=Aus)", "(sn<=Aus)", true, true},
{"(sn<=Aus)", "(sn>=Aus)", false, false},
{"(sn<=Aus)", "(sn=S*i*th)", false, false},
{"(sn<=Aus)", "(!(sn=Smith))", false, false},
{"(sn<=Aus)", "(&(sn=Smith)(sn=Smith))", false, false},
{"(sn<=Aus)", "(&(sn=Smith)(sn<=Aus))", false, false},
{"(sn<=Aus)", "(|(sn=Smith)(sn=Smith))", false, false},
{"(sn<=Aus)", "(|(sn=Smith)(sn<=Aus))", false, false},
{"(sn>=Aus)", "(sn>=Aus)", true, true},
{"(sn>=Aus)", "(sn=S*i*th)", false, false},
{"(sn>=Aus)", "(!(sn=Smith))", false, false},
{"(sn>=Aus)", "(&(sn=Smith)(sn=Smith))", false, false},
{"(sn>=Aus)", "(&(sn=Smith)(sn<=Aus))", false, false},
{"(sn>=Aus)", "(|(sn=Smith)(sn=Smith))", false, false},
{"(sn>=Aus)", "(|(sn=Smith)(sn<=Aus))", false, false},
{"(sn=S*i*th)", "(sn=S*i*th)", true, true},
{"(sn=S*i*th)", "(!(sn=Smith))", false, false},
{"(sn=S*i*th)", "(&(sn=Smith)(sn=Smith))", false, false},
{"(sn=S*i*th)", "(&(sn=Smith)(sn<=Aus))", false, false},
{"(sn=S*i*th)", "(|(sn=Smith)(sn=Smith))", false, false},
{"(sn=S*i*th)", "(|(sn=Smith)(sn<=Aus))", false, false},
{"(!(sn=Smith))", "(!(sn=Smith))", true, true},
{"(!(sn=Smith))", "(&(sn=Smith)(sn=Smith))", false, false},
{"(!(sn=Smith))", "(&(sn=Smith)(sn<=Aus))", false, false},
{"(!(sn=Smith))", "(|(sn=Smith)(sn=Smith))", false, false},
{"(!(sn=Smith))", "(|(sn=Smith)(sn<=Aus))", false, false},
{"(&(sn=Smith)(sn=Smith))", "(&(sn=Smith)(sn=Smith))", true, true},
{"(&(sn=Smith)(sn=Smith))", "(&(sn=Smith)(sn<=Aus))", false, false},
{"(&(sn=Smith)(sn=Smith))", "(|(sn=Smith)(sn=Smith))", false, false},
{"(&(sn=Smith)(sn=Smith))", "(|(sn=Smith)(sn<=Aus))", false, false},
{"(&(sn=Smith)(sn<=Aus))", "(&(sn=Smith)(sn<=Aus))", true, true},
{"(&(sn=Smith)(sn<=Aus))", "(|(sn=Smith)(sn=Smith))", false, false},
{"(&(sn=Smith)(sn<=Aus))", "(|(sn=Smith)(sn<=Aus))", false, false},
{"(|(sn=Smith)(sn=Smith))", "(|(sn=Smith)(sn=Smith))", true, true},
{"(|(sn=Smith)(sn=Smith))", "(|(sn=Smith)(sn<=Aus))", false, false},
{"(|(sn=Smith)(sn<=Aus))", "(|(sn=Smith)(sn<=Aus))", true, true},
{"(&(sn=Smith)(sn<=Aus))", "(&(sn=Smith)(sn>=Aus))", false, false},
{"(|(sn=Smith)(sn<=Aus))", "(|(sn=Smith)(sn>=Aus))", false, false},
// Test cases for issue #1245
{"(cn=*bowen*)", "(cn=*bowen*)", true, true},
{"(cn=*bowen*)", "(sn=*bowen*)", false, false}
};
/**
*
*/
@DataProvider(name = "equalsTest")
public Object[][] getEqualsTests() throws Exception {
return TEST_EQUALS_PARAMS;
}
/**
*
*/
@Test(dataProvider = "equalsTest")
public void testEquals(String stringFilter1, String stringFilter2, boolean expectEquals, boolean expectStringEquals) throws Exception {
SearchFilter filter1 = SearchFilter.createFilterFromString(stringFilter1);
SearchFilter filter2 = SearchFilter.createFilterFromString(stringFilter2);
boolean actualEquals = filter1.equals(filter2);
assertEquals(actualEquals, expectEquals,
"Expected " + filter1 + (expectEquals ? " == " : " != ") + filter2);
// Test symmetry
actualEquals = filter2.equals(filter1);
assertEquals(actualEquals, expectEquals,
"Expected " + filter1 + (expectEquals ? " == " : " != ") + filter2);
if (expectEquals) {
assertEquals(filter1.hashCode(), filter2.hashCode(),
"Hash codes differ for " + filter1 + " and " + filter2);
}
// Test toString
actualEquals = filter2.toString().equals(filter1.toString());
assertEquals(actualEquals, expectStringEquals,
"Expected " + filter1 + (expectStringEquals ? " == " : " != ") + filter2);
}
}