From 13243b7d59ccb89dbd12fdf50b6eb56e16b07f26 Mon Sep 17 00:00:00 2001
From: davidely <davidely@localhost>
Date: Wed, 17 Jan 2007 04:28:07 +0000
Subject: [PATCH] Fixes for several small SearchFilter and Attribute matching issues (730, 695, 688, 689, 693). This also includes tests for the SearchFilter class. I've also elimiated a race condition from the operation test cases.
---
opends/src/server/org/opends/server/types/AttributeValue.java | 9
opends/src/server/org/opends/server/util/StaticUtils.java | 19
opends/src/server/org/opends/server/core/BackendConfigManager.java | 1
opends/src/server/org/opends/server/types/Attribute.java | 4
opends/src/server/org/opends/server/loggers/LoggerThread.java | 7
opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java | 3
opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java | 1140 +++++++++++++++++++++++++++++++++++++++++++
opends/src/server/org/opends/server/core/PluginConfigManager.java | 1
opends/src/server/org/opends/server/schema/DoubleMetaphoneApproximateMatchingRule.java | 141 ++--
opends/src/server/org/opends/server/types/SearchFilter.java | 172 +-----
opends/src/server/org/opends/server/util/Validator.java | 2
opends/src/server/org/opends/server/core/SynchronizationProviderConfigManager.java | 1
opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java | 3
opends/src/server/org/opends/server/loggers/DirectoryDebugLogger.java | 2
opends/build.xml | 29
opends/src/server/org/opends/server/core/ConnectionHandlerConfigManager.java | 1
opends/src/server/org/opends/server/controls/MatchedValuesFilter.java | 9
17 files changed, 1,314 insertions(+), 230 deletions(-)
diff --git a/opends/build.xml b/opends/build.xml
index f633ecb..9d430ef 100644
--- a/opends/build.xml
+++ b/opends/build.xml
@@ -32,8 +32,6 @@
</description>
-
-
<!-- General server-wide properties -->
<property name="src.dir" location="src/server" />
<property name="build.dir" location="build" />
@@ -120,11 +118,9 @@
<property name="dynconstants.stubfile"
location="${resource.dir}/DynamicConstants.java.stubs" />
+
<property file="PRODUCT" />
-
-
-
<!-- Create a package bundle containing the DSML library. -->
<target name="dsml" depends="predsml,package"
description="Build a Directory Server package bundle with DSML.">
@@ -807,6 +803,12 @@
</not>
</condition>
+ <condition property="test.diff.verbose" value="false">
+ <not>
+ <isset property="test.diff.verbose" />
+ </not>
+ </condition>
+
<condition property="test.diff.enabled" value="false">
<isset property="test.diff.disable" />
</condition>
@@ -827,7 +829,8 @@
outputpath="${cvgdiff.report.dir}"
diffpath="${basedir}/${test.diff.srcpath}"
svnpath="${test.diff.svnpath}"
- enabled="${test.diff.enabled}" />
+ enabled="${test.diff.enabled}"
+ verbose="${test.diff.verbose}" />
</target>
@@ -837,39 +840,37 @@
</target>
<!-- Execute Directory Server TestNG unit tests specified from CLI -->
- <target name="testcustom"
- description="Execute the Directory Server TestNG unit tests specified from CLI.">
+ <target name="testcustom">
<echo message="This target is deprecated. Please use the test target as it now supports the test.* properties." />
</target>
<!-- Execute all of the Directory Server TestNG unit tests in text mode. -->
<target name="testall"
depends="enableTestNGAssertions,prepdefaultalltest,testinit,runtests"
- description="Run all of the TestNG tests.">
+ description="Run all of the TestNG tests with assertions enabled. See 'testwithcoverage' for properties you can set.">
</target>
<!-- Execute the Directory Server TestNG unit tests in text mode. -->
<target name="test"
depends="testinit,runtests"
- description="Execute the Directory Server TestNG unit tests in text mode.">
+ description="Execute the Directory Server TestNG unit tests in text mode. Set '-Dorg.opends.test.suppressOutput=true' to suppress output from the unit tests.">
</target>
<!-- Execute the Directory Server TestNG unit tests in text mode with a coverage report. -->
<target name="testwithcoverage"
depends="coverage,test,coveragediff"
- description="Execute the Directory Server TestNG unit tests in text mode with a coverage report.">
+ description="Execute the Directory Server TestNG unit tests in text mode with a coverage report. Use -Dtest.packages, -Dtest.classes, or -Dtest.methods to control which unit tests are run. Use -Dtest.diff.srcpath to control which src files show up in the coverage diff. See the 'test' package for other properties you can set.">
</target>
<!-- Execute the Directory Server TestNG unit tests in text mode with a coverage report and slow tests. -->
<target name="testallwithcoverage"
depends="coverage,testall,coveragediff"
- description="Execute the Directory Server TestNG unit tests in text mode with a coverage report.">
+ description="The same as 'testwithcoverage' except 'testall' is run instead of 'test'.">
</target>
<!-- Execute the Directory Server TestNG unit tests specified from CLI in text mode with a coverage report. -->
- <target name="testcustomwithcoverage"
- description="Execute the Directory Server TestNG unit tests specified from CLI in text mode with a coverage report.">
+ <target name="testcustomwithcoverage">
<echo message="This target is deprecated. Please use the testwithcoverage target as it now supports the test.* properties." />
</target>
diff --git a/opends/src/server/org/opends/server/controls/MatchedValuesFilter.java b/opends/src/server/org/opends/server/controls/MatchedValuesFilter.java
index f818c3f..be8965b 100644
--- a/opends/src/server/org/opends/server/controls/MatchedValuesFilter.java
+++ b/opends/src/server/org/opends/server/controls/MatchedValuesFilter.java
@@ -1844,9 +1844,12 @@
{
try
{
- return approximateMatchingRule.approximatelyMatch(
- assertionValue.getNormalizedValue(),
- value.getNormalizedValue());
+ ByteString nv1 = approximateMatchingRule.normalizeValue(
+ assertionValue.getNormalizedValue());
+ ByteString nv2 = approximateMatchingRule.normalizeValue(
+ value.getNormalizedValue());
+
+ return approximateMatchingRule.approximatelyMatch(nv1, nv2);
}
catch (Exception e)
{
diff --git a/opends/src/server/org/opends/server/core/BackendConfigManager.java b/opends/src/server/org/opends/server/core/BackendConfigManager.java
index 32b13ed..f4e4160 100644
--- a/opends/src/server/org/opends/server/core/BackendConfigManager.java
+++ b/opends/src/server/org/opends/server/core/BackendConfigManager.java
@@ -62,7 +62,6 @@
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
diff --git a/opends/src/server/org/opends/server/core/ConnectionHandlerConfigManager.java b/opends/src/server/org/opends/server/core/ConnectionHandlerConfigManager.java
index 65ecb12..d547141 100644
--- a/opends/src/server/org/opends/server/core/ConnectionHandlerConfigManager.java
+++ b/opends/src/server/org/opends/server/core/ConnectionHandlerConfigManager.java
@@ -56,7 +56,6 @@
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
diff --git a/opends/src/server/org/opends/server/core/PluginConfigManager.java b/opends/src/server/org/opends/server/core/PluginConfigManager.java
index 130e695..f5065b5 100644
--- a/opends/src/server/org/opends/server/core/PluginConfigManager.java
+++ b/opends/src/server/org/opends/server/core/PluginConfigManager.java
@@ -78,7 +78,6 @@
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.PluginMessages.*;
-import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
diff --git a/opends/src/server/org/opends/server/core/SynchronizationProviderConfigManager.java b/opends/src/server/org/opends/server/core/SynchronizationProviderConfigManager.java
index a5c60b7..420102d 100644
--- a/opends/src/server/org/opends/server/core/SynchronizationProviderConfigManager.java
+++ b/opends/src/server/org/opends/server/core/SynchronizationProviderConfigManager.java
@@ -52,7 +52,6 @@
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
diff --git a/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java b/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java
index ba635fa..c607e8f 100644
--- a/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java
+++ b/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java
@@ -553,7 +553,8 @@
}
catch (InterruptedException ie)
{
- assert debugException(CLASS_NAME, "retryNextOperation", ie);
+ // This is somewhat expected so don't log.
+ // assert debugException(CLASS_NAME, "retryNextOperation", ie);
// If this occurs, then the worker thread must have been interrupted for
// some reason. This could be because the Directory Server is shutting
diff --git a/opends/src/server/org/opends/server/loggers/DirectoryDebugLogger.java b/opends/src/server/org/opends/server/loggers/DirectoryDebugLogger.java
index 1ad02d0..7ea6470 100644
--- a/opends/src/server/org/opends/server/loggers/DirectoryDebugLogger.java
+++ b/opends/src/server/org/opends/server/loggers/DirectoryDebugLogger.java
@@ -481,7 +481,7 @@
{
StringBuilder buffer = new StringBuilder();
buffer.append("classname=").append(className).
- append(" methodname=").append(methodName).append(' ').
+ append(" methodname=").append(methodName).append("\n").
append( StaticUtils.stackTraceToString(exception));
debugLogger.log(DirectoryLogLevel.ERROR, buffer.toString());
diff --git a/opends/src/server/org/opends/server/loggers/LoggerThread.java b/opends/src/server/org/opends/server/loggers/LoggerThread.java
index b2ab23f..74be7f8 100644
--- a/opends/src/server/org/opends/server/loggers/LoggerThread.java
+++ b/opends/src/server/org/opends/server/loggers/LoggerThread.java
@@ -97,7 +97,12 @@
try
{
sleep(time);
- } catch(Exception e)
+ }
+ catch(InterruptedException e)
+ {
+ // We expect this to happen.
+ }
+ catch(Exception e)
{
assert debugException(CLASS_NAME, "run", e);
}
diff --git a/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java b/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java
index be16f37..7673611 100644
--- a/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java
+++ b/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java
@@ -34,6 +34,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
+import java.util.Collection;
import org.opends.server.api.MatchingRule;
import org.opends.server.core.DirectoryServer;
@@ -175,7 +176,7 @@
{
case AND:
case OR:
- List<SearchFilter> comps = filter.getFilterComponents();
+ Collection<SearchFilter> comps = filter.getFilterComponents();
filterComponents = new ArrayList<LDAPFilter>(comps.size());
for (SearchFilter f : comps)
{
diff --git a/opends/src/server/org/opends/server/schema/DoubleMetaphoneApproximateMatchingRule.java b/opends/src/server/org/opends/server/schema/DoubleMetaphoneApproximateMatchingRule.java
index 50acb91..bdc1240 100644
--- a/opends/src/server/org/opends/server/schema/DoubleMetaphoneApproximateMatchingRule.java
+++ b/opends/src/server/org/opends/server/schema/DoubleMetaphoneApproximateMatchingRule.java
@@ -276,7 +276,7 @@
// neither an 'E' nor an 'I' except in "BACHER" and "MACHER".
if ((pos > 1) &&
(! isVowel(posMinusTwo = valueString.charAt(pos-2))) &&
- hasSubstring(valueString, pos-1, pos+2, "ACH") &&
+ hasSubstring(valueString, pos-1, "ACH") &&
((posPlusTwo = valueString.charAt(pos+2)) != 'I') &&
((posPlusTwo != 'E') ||
((valueString.charAt(pos+3) == 'R') &&
@@ -289,7 +289,7 @@
// Check for a special case of "caesar", which will be maped to 'S'.
- if ((pos == 0) && hasSubstring(valueString, pos+1, pos+5, "AESAR"))
+ if ((pos == 0) && hasSubstring(valueString, pos+1, "AESAR"))
{
metaphone.append("S");
pos += 2;
@@ -301,7 +301,7 @@
if ((posPlusOne = valueString.charAt(pos+1)) == 'H')
{
// Check for "chia" as in "chianti" and map to 'K'.
- if (hasSubstring(valueString, pos+2, pos+4, "IA"))
+ if (hasSubstring(valueString, pos+2, "IA"))
{
metaphone.append("K");
pos += 2;
@@ -309,7 +309,7 @@
}
// Check for "chae" as in "michael" and map to 'K'.
- if (hasSubstring(valueString, pos+2, pos+4, "AE"))
+ if (hasSubstring(valueString, pos+2, "AE"))
{
metaphone.append("K");
pos += 2;
@@ -318,13 +318,13 @@
// Check for a Greek root at the beginning of the value like
// chemistry or chorus and map to 'K'.
- if ((pos == 0) && (! hasSubstring(valueString, 2, 5, "ORE")) &&
- (hasSubstring(valueString, 2, 6, "ARAC") ||
- hasSubstring(valueString, 2, 6, "ARIS") ||
- hasSubstring(valueString, 2, 4, "OR") ||
- hasSubstring(valueString, 2, 4, "YM") ||
- hasSubstring(valueString, 2, 4, "IA") ||
- hasSubstring(valueString, 2, 4, "EM")))
+ if ((pos == 0) && (! hasSubstring(valueString, 2, "ORE")) &&
+ (hasSubstring(valueString, 2, "ARAC") ||
+ hasSubstring(valueString, 2, "ARIS") ||
+ hasSubstring(valueString, 2, "OR") ||
+ hasSubstring(valueString, 2, "YM") ||
+ hasSubstring(valueString, 2, "IA") ||
+ hasSubstring(valueString, 2, "EM")))
{
metaphone.append("K");
pos += 2;
@@ -335,9 +335,9 @@
// Check for "CH" values that produce a "KH" sound that will be
// mapped to 'K'.
if (isGermanic(valueString) ||
- hasSubstring(valueString, pos-2, pos+4, "ORCHES") ||
- hasSubstring(valueString, pos-2, pos+4, "ARCHIT") ||
- hasSubstring(valueString, pos-2, pos+4, "ORCHID") ||
+ hasSubstring(valueString, pos-2, "ORCHES") ||
+ hasSubstring(valueString, pos-2, "ARCHIT") ||
+ hasSubstring(valueString, pos-2, "ORCHID") ||
((posPlusTwo = valueString.charAt(pos+2)) == 'T') ||
(posPlusTwo == 'S') ||
(((pos == 0) ||
@@ -359,7 +359,7 @@
// All other "CH" values.
if (pos > 0)
{
- if (hasSubstring(valueString, 0, 2, "MC"))
+ if (hasSubstring(valueString, 0, "MC"))
{
metaphone.append("K");
}
@@ -380,7 +380,7 @@
// Check for "CZ" as in "czerny" but not "wicz" and map to 'S'.
if ((posPlusOne == 'Z') &&
- (! hasSubstring(valueString, pos-2, pos, "WI")))
+ (! hasSubstring(valueString, pos-2, "WI")))
{
metaphone.append("S");
pos += 2;
@@ -406,8 +406,8 @@
(! ((posPlusTwo == 'H') && valueString.charAt(pos+3) == 'U')))
{
if (((pos == 1) && (valueString.charAt(pos-1) == 'A')) ||
- hasSubstring(valueString, pos-1, pos+3, "UCCEE") ||
- hasSubstring(valueString, pos-1, pos+3, "UCCES"))
+ hasSubstring(valueString, pos-1, "UCCEE") ||
+ hasSubstring(valueString, pos-1, "UCCES"))
{
// Values like "accident", "accede", and "succeed".
metaphone.append("K");
@@ -619,7 +619,7 @@
}
else
{
- if ((! hasSubstring(valueString, pos+2, pos+4, "EY")) &&
+ if ((! hasSubstring(valueString, pos+2, "EY")) &&
(! isSlavoGermanic(valueString)))
{
metaphone.append("N");
@@ -666,11 +666,11 @@
(posPlusOne == 'Y')) &&
((posMinusOne = valueString.charAt(pos-1)) != 'E') &&
(posMinusOne != 'I') &&
- (! hasSubstring(valueString, 0, 6, "DANGER")) &&
- (! hasSubstring(valueString, 0, 6, "RANGER")) &&
- (! hasSubstring(valueString, 0, 6, "MANGER")) &&
- (! hasSubstring(valueString, pos-1, pos+2, "RGY")) &&
- (! hasSubstring(valueString, pos-1, pos+2, "OGY")))
+ (! hasSubstring(valueString, 0, "DANGER")) &&
+ (! hasSubstring(valueString, 0, "RANGER")) &&
+ (! hasSubstring(valueString, 0, "MANGER")) &&
+ (! hasSubstring(valueString, pos-1, "RGY")) &&
+ (! hasSubstring(valueString, pos-1, "OGY")))
{
metaphone.append("K");
pos += 2;
@@ -681,12 +681,12 @@
// Check for Italian uses like 'biaggi" and map to 'J'.
if ((posPlusOne == 'E') || (posPlusOne == 'I') ||
(posPlusOne == 'Y') ||
- hasSubstring(valueString, pos-1, pos+3, "AGGI") ||
- hasSubstring(valueString, pos-1, pos+3, "OGGI"))
+ hasSubstring(valueString, pos-1, "AGGI") ||
+ hasSubstring(valueString, pos-1, "OGGI"))
{
// Germanic uses will be mapped to 'K'.
if (isGermanic(valueString) ||
- hasSubstring(valueString, pos+1, pos+3, "ET"))
+ hasSubstring(valueString, pos+1, "ET"))
{
metaphone.append("K");
}
@@ -732,14 +732,14 @@
case 'J':
// Take care of obvious Spanish uses that should map to 'H'.
- if (hasSubstring(valueString, 0, 4, "SAN "))
+ if (hasSubstring(valueString, 0, "SAN "))
{
metaphone.append("H");
pos++;
break;
}
- if (hasSubstring(valueString, pos, pos+4, "JOSE"))
+ if (hasSubstring(valueString, pos, "JOSE"))
{
if ((pos == 0) && (valueString.charAt(pos+4) == ' '))
{
@@ -803,10 +803,10 @@
{
pos++;
}
- else if (hasSubstring(valueString, pos-1, pos+2, "UMB"))
+ else if (hasSubstring(valueString, pos-1, "UMB"))
{
if (((pos+1) == last) ||
- hasSubstring(valueString, pos+2, pos+4, "ER"))
+ hasSubstring(valueString, pos+2, "ER"))
{
pos++;
}
@@ -868,9 +868,9 @@
case 'R':
// Ignore R at the end of French words.
if ((pos == last) && (! isSlavoGermanic(valueString)) &&
- hasSubstring(valueString, pos-2, pos, "IE") &&
- (! hasSubstring(valueString, pos-4, pos-2, "ME")) &&
- (! hasSubstring(valueString, pos-4, pos-2, "MA")))
+ hasSubstring(valueString, pos-2, "IE") &&
+ (! hasSubstring(valueString, pos-4, "ME")) &&
+ (! hasSubstring(valueString, pos-4, "MA")))
{
pos++;
break;
@@ -891,8 +891,8 @@
case 'S':
// Special cases like isle and carlysle will be silent.
- if (hasSubstring(valueString, pos-1, pos+2, "ISL") ||
- hasSubstring(valueString, pos-1, pos+2, "YSL"))
+ if (hasSubstring(valueString, pos-1, "ISL") ||
+ hasSubstring(valueString, pos-1, "YSL"))
{
pos++;
break;
@@ -900,7 +900,7 @@
// Special case of sugar mapped to 'X'.
- if (hasSubstring(valueString, pos+1, pos+5, "UGAR"))
+ if (hasSubstring(valueString, pos+1, "UGAR"))
{
metaphone.append("X");
pos++;
@@ -911,10 +911,10 @@
// SH is generally mapped to 'X', but not in Germanic cases.
if ((posPlusOne = valueString.charAt(pos+1)) == 'H')
{
- if (hasSubstring(valueString, pos+1, pos+5, "HEIM") ||
- hasSubstring(valueString, pos+1, pos+5, "HOEK") ||
- hasSubstring(valueString, pos+1, pos+5, "HOLM") ||
- hasSubstring(valueString, pos+1, pos+5, "HOLZ"))
+ if (hasSubstring(valueString, pos+1, "HEIM") ||
+ hasSubstring(valueString, pos+1, "HOEK") ||
+ hasSubstring(valueString, pos+1, "HOLM") ||
+ hasSubstring(valueString, pos+1, "HOLZ"))
{
metaphone.append("S");
}
@@ -929,8 +929,8 @@
// Italian and Armenian cases will map to "S".
- if (hasSubstring(valueString, pos+1, pos+3, "IO") ||
- hasSubstring(valueString, pos+1, pos+3, "IA"))
+ if (hasSubstring(valueString, pos+1, "IO") ||
+ hasSubstring(valueString, pos+1, "IA"))
{
metaphone.append("S");
pos += 3;
@@ -964,10 +964,10 @@
{
if ((posPlusTwo = valueString.charAt(pos+2)) == 'H')
{
- if (hasSubstring(valueString, pos+3, pos+5, "OO") ||
- hasSubstring(valueString, pos+3, pos+5, "UY") ||
- hasSubstring(valueString, pos+3, pos+5, "ED") ||
- hasSubstring(valueString, pos+3, pos+5, "EM"))
+ if (hasSubstring(valueString, pos+3, "OO") ||
+ hasSubstring(valueString, pos+3, "UY") ||
+ hasSubstring(valueString, pos+3, "ED") ||
+ hasSubstring(valueString, pos+3, "EM"))
{
metaphone.append("SK");
}
@@ -997,8 +997,8 @@
// Ignore a trailing S in French words. All others will be mapped to
// 'S'.
if (! ((pos == last) &&
- (hasSubstring(valueString, pos-2, pos, "AI") ||
- hasSubstring(valueString, pos-2, pos, "OI"))))
+ (hasSubstring(valueString, pos-2, "AI") ||
+ hasSubstring(valueString, pos-2, "OI"))))
{
metaphone.append("S");
}
@@ -1014,9 +1014,9 @@
case 'T':
// "TION", "TIA", and "TCH" will be mapped to 'X'.
- if (hasSubstring(valueString, pos, pos+4, "TION") ||
- hasSubstring(valueString, pos, pos+3, "TIA") ||
- hasSubstring(valueString, pos, pos+3, "TCH"))
+ if (hasSubstring(valueString, pos, "TION") ||
+ hasSubstring(valueString, pos, "TIA") ||
+ hasSubstring(valueString, pos, "TCH"))
{
metaphone.append("X");
pos += 3;
@@ -1030,8 +1030,8 @@
((posPlusOne == 'T') && (valueString.charAt(pos+2) == 'H')))
{
if (isGermanic(valueString) ||
- hasSubstring(valueString, pos+2, pos+4, "OM") ||
- hasSubstring(valueString, pos+2, pos+4, "AM"))
+ hasSubstring(valueString, pos+2, "OM") ||
+ hasSubstring(valueString, pos+2, "AM"))
{
metaphone.append("T");
}
@@ -1092,8 +1092,8 @@
// A Polish value like WICZ or WITZ should be mapped to TS.
- if (hasSubstring(valueString, pos+1, pos+4, "WICZ") ||
- hasSubstring(valueString, pos+1, pos+4, "WITZ"))
+ if (hasSubstring(valueString, pos+1, "WICZ") ||
+ hasSubstring(valueString, pos+1, "WITZ"))
{
metaphone.append("TS");
pos += 4;
@@ -1109,10 +1109,10 @@
case 'X':
// X maps to KS except at the end of French words.
if (! ((pos == last) &&
- (hasSubstring(valueString, pos-3, pos, "IAU") ||
- hasSubstring(valueString, pos-3, pos, "EAU") ||
- hasSubstring(valueString, pos-2, pos, "AU") ||
- hasSubstring(valueString, pos-2, pos, "OU"))))
+ (hasSubstring(valueString, pos-3, "IAU") ||
+ hasSubstring(valueString, pos-3, "EAU") ||
+ hasSubstring(valueString, pos-2, "AU") ||
+ hasSubstring(valueString, pos-2, "OU"))))
{
metaphone.append("KS");
}
@@ -1206,24 +1206,35 @@
* determination.
* @param start The position in the value at which to start the
* comparison.
- * @param end The position in the value at which to stop the
- * comparison. This character will not actually be
- * compared against the provided substring.
* @param substring The substring to compare against the specified value
* range.
*
* @return <CODE>true</CODE> if the specified portion of the value matches
* the given substring, or <CODE>false</CODE> if it does not.
*/
- private boolean hasSubstring(String value, int start, int end,
+ private boolean hasSubstring(String value, int start,
String substring)
{
assert debugEnter(CLASS_NAME, "hasSubstring", String.valueOf(value),
- String.valueOf(start), String.valueOf(end),
+ String.valueOf(start),
String.valueOf(substring));
try
{
+ // This can happen since a lot of the rules "look behind" and
+ // rightfully don't check if it's the first character
+ if (start < 0) {
+ return false;
+ }
+
+ int end = start + substring.length();
+
+ // value isn't big enough to do the comparison
+ if (end > value.length())
+ {
+ return false;
+ }
+
for (int i=0,pos=start; pos < end; i++,pos++)
{
if (value.charAt(pos) != substring.charAt(i))
diff --git a/opends/src/server/org/opends/server/types/Attribute.java b/opends/src/server/org/opends/server/types/Attribute.java
index fec73c5..8e4a567 100644
--- a/opends/src/server/org/opends/server/types/Attribute.java
+++ b/opends/src/server/org/opends/server/types/Attribute.java
@@ -787,7 +787,7 @@
ByteString normalizedValue;
try
{
- normalizedValue = value.getNormalizedValue();
+ normalizedValue = matchingRule.normalizeValue(value.getValue());
}
catch (Exception e)
{
@@ -803,7 +803,7 @@
{
try
{
- ByteString nv = v.getNormalizedValue();
+ ByteString nv = matchingRule.normalizeValue(v.getValue());
if (matchingRule.approximatelyMatch(nv, normalizedValue))
{
return ConditionResult.TRUE;
diff --git a/opends/src/server/org/opends/server/types/AttributeValue.java b/opends/src/server/org/opends/server/types/AttributeValue.java
index ba9ddd2..288bafd 100644
--- a/opends/src/server/org/opends/server/types/AttributeValue.java
+++ b/opends/src/server/org/opends/server/types/AttributeValue.java
@@ -347,7 +347,14 @@
{
if (attributeType == null)
{
- return normalizedValue.hashCode();
+ if (normalizedValue != null)
+ {
+ return normalizedValue.hashCode();
+ }
+ else
+ {
+ return value.hashCode();
+ }
}
else
{
diff --git a/opends/src/server/org/opends/server/types/SearchFilter.java b/opends/src/server/org/opends/server/types/SearchFilter.java
index 1d91d29..f3ce839 100644
--- a/opends/src/server/org/opends/server/types/SearchFilter.java
+++ b/opends/src/server/org/opends/server/types/SearchFilter.java
@@ -34,6 +34,9 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.LinkedHashSet;
+import java.util.Collection;
+import java.util.Collections;
import org.opends.server.api.MatchingRule;
import org.opends.server.core.DirectoryServer;
@@ -64,38 +67,38 @@
// The attribute type for this filter.
- private AttributeType attributeType;
+ private final AttributeType attributeType;
// The assertion value for this filter.
- private AttributeValue assertionValue;
+ private final AttributeValue assertionValue;
// Indicates whether to match on DN attributes for extensible match
// filters.
- private boolean dnAttributes;
+ private final boolean dnAttributes;
// The subFinal element for substring filters.
- private ByteString subFinalElement;
+ private final ByteString subFinalElement;
// The subInitial element for substring filters.
- private ByteString subInitialElement;
+ private final ByteString subInitialElement;
// The search filter type for this filter.
- private FilterType filterType;
+ private final FilterType filterType;
// The set of subAny components for substring filters.
- private List<ByteString> subAnyElements;
+ private final List<ByteString> subAnyElements;
// The set of filter components for AND and OR filters.
- private List<SearchFilter> filterComponents;
+ private final LinkedHashSet<SearchFilter> filterComponents;
// The not filter component for this search filter.
- private SearchFilter notComponent;
+ private final SearchFilter notComponent;
// The set of options for the attribute type in this filter.
- private Set<String> attributeOptions;
+ private final Set<String> attributeOptions;
// The matching rule ID for this search filter.
- private String matchingRuleID;
+ private final String matchingRuleID;
@@ -122,9 +125,11 @@
* @param dnAttributes Indicates whether to match on DN
* attributes for extensible match
* filters.
+ *
+ * FIXME: this should be private.
*/
public SearchFilter(FilterType filterType,
- List<SearchFilter> filterComponents,
+ Collection<SearchFilter> filterComponents,
SearchFilter notComponent,
AttributeType attributeType,
Set<String> attributeOptions,
@@ -150,9 +155,21 @@
String.valueOf(dnAttributes)
});
+ // This used to happen in getSubAnyElements, but we do it here
+ // so that we can make this.subAnyElements final.
+ if (subAnyElements == null) {
+ subAnyElements = new ArrayList<ByteString>(0);
+ }
+
+ // This used to happen in getFilterComponents, but we do it here
+ // so that we can make this.filterComponents final.
+ if (filterComponents == null) {
+ filterComponents = Collections.emptyList();
+ }
this.filterType = filterType;
- this.filterComponents = filterComponents;
+ this.filterComponents =
+ new LinkedHashSet<SearchFilter>(filterComponents);
this.notComponent = notComponent;
this.attributeType = attributeType;
this.attributeOptions = attributeOptions;
@@ -165,7 +182,6 @@
}
-
/**
* Creates a new AND search filter with the provided information.
*
@@ -2185,19 +2201,14 @@
/**
* Retrieves the set of filter components for this AND or OR filter.
- * The returned list may be modified by the caller.
+ * The returned list can be modified by the caller.
*
* @return The set of filter components for this AND or OR filter.
*/
- public List<SearchFilter> getFilterComponents()
+ public Set<SearchFilter> getFilterComponents()
{
assert debugEnter(CLASS_NAME, "getFilterComponents");
- if (filterComponents == null)
- {
- filterComponents = new ArrayList<SearchFilter>(0);
- }
-
return filterComponents;
}
@@ -2234,21 +2245,6 @@
/**
- * Specifies the attribute type for this filter.
- *
- * @param attributeType The attribute type for this filter.
- */
- public void setAttributeType(AttributeType attributeType)
- {
- assert debugEnter(CLASS_NAME, "setAttributeType",
- String.valueOf(attributeType));
-
- this.attributeType = attributeType;
- }
-
-
-
- /**
* Retrieves the assertion value for this filter.
*
* @return The assertion value for this filter, or
@@ -2264,21 +2260,6 @@
/**
- * Specifies the assertion value for this filter.
- *
- * @param assertionValue The assertion value for this filter.
- */
- public void setAssertionValue(AttributeValue assertionValue)
- {
- assert debugEnter(CLASS_NAME, "setAssertionValue",
- String.valueOf(assertionValue));
-
- this.assertionValue = assertionValue;
- }
-
-
-
- /**
* Retrieves the subInitial element for this substring filter.
*
* @return The subInitial element for this substring filter, or
@@ -2294,22 +2275,6 @@
/**
- * Specifies the subInitial element for this substring filter.
- *
- * @param subInitialElement The subInitial element for this
- * substring filter.
- */
- public void setSubInitialElement(ByteString subInitialElement)
- {
- assert debugEnter(CLASS_NAME, "setSubInitialElement",
- String.valueOf(subInitialElement));
-
- this.subInitialElement = subInitialElement;
- }
-
-
-
- /**
* Retrieves the set of subAny elements for this substring filter.
* The returned list may be altered by the caller.
*
@@ -2319,11 +2284,6 @@
{
assert debugEnter(CLASS_NAME, "getSubAnyElements");
- if (subAnyElements == null)
- {
- subAnyElements = new ArrayList<ByteString>(0);
- }
-
return subAnyElements;
}
@@ -2344,22 +2304,6 @@
/**
- * Specifies the subFinal element for this substring filter.
- *
- * @param subFinalElement The subFinal element for this substring
- * filter.
- */
- public void setSubFinalElement(ByteString subFinalElement)
- {
- assert debugEnter(CLASS_NAME, "setSubFinalElement",
- String.valueOf(subFinalElement));
-
- this.subFinalElement = subFinalElement;
- }
-
-
-
- /**
* Retrieves the matching rule ID for this extensible matching
* filter.
*
@@ -2376,23 +2320,6 @@
/**
- * Specifies the matching rule ID for this extensible matching
- * filter.
- *
- * @param matchingRuleID The matching rule ID for this extensible
- * matching filter.
- */
- public void setMatchingRuleID(String matchingRuleID)
- {
- assert debugEnter(CLASS_NAME, "setMatchingRuleID",
- String.valueOf(matchingRuleID));
-
- this.matchingRuleID = matchingRuleID;
- }
-
-
-
- /**
* Retrieves the dnAttributes flag for this extensible matching
* filter.
*
@@ -2409,23 +2336,6 @@
/**
- * Specifies the value of the dnAttributes flag for this extensible
- * matching filter.
- *
- * @param dnAttributes The value of the dnAttributes flag for this
- * extensible matching filter.
- */
- public void setDNAttributes(boolean dnAttributes)
- {
- assert debugEnter(CLASS_NAME, "setDNAttributes",
- String.valueOf(dnAttributes));
-
- this.dnAttributes = dnAttributes;
- }
-
-
-
- /**
* Indicates whether this search filter matches the provided entry.
*
* @param entry The entry for which to make the determination.
@@ -3619,7 +3529,6 @@
}
-
/**
* Indicates whether this search filter is equal to the provided
* object.
@@ -3721,18 +3630,12 @@
return false;
}
-outerSubAnyLoop:
- for (ByteString outer : subAnyElements)
- {
- for (ByteString inner : f.subAnyElements)
- {
- if (outer.equals(inner))
- {
- continue outerSubAnyLoop;
- }
+ for (int i = 0; i < subAnyElements.size(); i++) {
+ ByteString sub1 = subAnyElements.get(i);
+ ByteString sub2 = f.subAnyElements.get(i);
+ if (!sub1.equals(sub2)) {
+ return false;
}
-
- return false;
}
return true;
@@ -3805,7 +3708,6 @@
}
-
/**
* Retrieves the hash code for this search filter.
*
diff --git a/opends/src/server/org/opends/server/util/StaticUtils.java b/opends/src/server/org/opends/server/util/StaticUtils.java
index d14109b..cce743e 100644
--- a/opends/src/server/org/opends/server/util/StaticUtils.java
+++ b/opends/src/server/org/opends/server/util/StaticUtils.java
@@ -1376,6 +1376,24 @@
}
+ /**
+ * Return true if and only if o1 and o2 are both null or o1.equals(o2).
+ *
+ * @param o1 the first object to compare
+ * @param o2 the second object to compare
+ * @return true iff o1 and o2 are equal
+ */
+ public static boolean objectsAreEqual(Object o1, Object o2)
+ {
+ if (o1 == null)
+ {
+ return (o2 == null);
+ }
+ else
+ {
+ return o1.equals(o2);
+ }
+ }
/**
* Retrieves a stack trace from the provided exception as a single-line
@@ -3115,7 +3133,6 @@
}
-
/**
* Attempts to delete the specified file or directory. If it is a directory,
* then any files or subdirectories that it contains will be recursively
diff --git a/opends/src/server/org/opends/server/util/Validator.java b/opends/src/server/org/opends/server/util/Validator.java
index c357927..152adc1 100644
--- a/opends/src/server/org/opends/server/util/Validator.java
+++ b/opends/src/server/org/opends/server/util/Validator.java
@@ -65,7 +65,7 @@
* happens before the method is invoked cannot be eliminated, e.g.
* <code>Validator.ensureTrue(someExpensiveCheck())</code> will always invoke
* <code>someExpensiveCheck()</code>. When this code is on the critical path,
- * and we do not expect the validation to failure, you can guard the call with
+ * and we do not expect the validation to fail, you can guard the call with
* an <code>assert</code> because each method returns true, and this code will
* only be executed when asserts are enabled.
* <p>
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java
new file mode 100644
index 0000000..0b3d437
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java
@@ -0,0 +1,1140 @@
+/*
+ * 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
+ *
+ *
+ * Portions Copyright 2006 Sun Microsystems, Inc.
+ */
+package org.opends.server.types;
+
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+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)))"},
+ // TODO: open a bug for this.
+// {"(!(sn=test)))"},
+ {"(sn=\\A)"},
+ {"(sn=\\1H)"},
+ {"(sn=\\H1)"},
+ };
+ }
+
+ @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 Smithe)", false},
+ {JOHN_SMITH_LDIF, "(cn;lang-fr=Jonathan Smith)", false},
+ {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}, // ? maybe not
+// {JOHN_SMITH_LDIF, "(name;lang-en=*Jonathan*)", true}, // ? maybe not
+
+ // 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 = new AttributeValue(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 = new ASN1OctetString(subInitial);
+ description.subAnyElements = new ArrayList<ByteString>();
+ for (int i = 0; (subAny != null) && (i < subAny.size()); i++) {
+ String s = subAny.get(i);
+ description.subAnyElements.add(new ASN1OctetString(s));
+ }
+ description.subFinalElement = new ASN1OctetString(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;
+ }
+
+
+ /**
+ *
+ */
+ public 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},
+
+ // TODO: open a bug for this.
+// {"(: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},
+
+
+ };
+
+
+ /**
+ *
+ */
+ @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);
+ }
+}
+
--
Gitblit v1.10.0