From 91a823b7845f825827463db27f759d12fe58f937 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Tue, 31 May 2016 15:40:09 +0000
Subject: [PATCH] OPENDJ-2987 Fix OID equality and first component matching rules
---
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleTest.java | 74 ++++++++
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java | 13
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleTest.java | 153 +++++++++++++++++
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleImpl.java | 241 ++++++++++++++++++++++----
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java | 57 ------
5 files changed, 438 insertions(+), 100 deletions(-)
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleImpl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleImpl.java
index 8d37d45..c353472 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleImpl.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleImpl.java
@@ -12,65 +12,232 @@
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2009 Sun Microsystems, Inc.
- * Portions copyright 2011-2015 ForgeRock AS.
+ * Portions copyright 2011-2016 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.schema;
-import static org.forgerock.opendj.ldap.schema.SchemaConstants.*;
-import static org.forgerock.opendj.ldap.schema.SchemaOptions.*;
-import static org.forgerock.opendj.ldap.schema.SchemaUtils.*;
+import static com.forgerock.opendj.util.StaticUtils.toLowerCase;
+import static org.forgerock.opendj.ldap.schema.SchemaConstants.EMR_OID_NAME;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.DecodeException;
-
-import com.forgerock.opendj.util.StaticUtils;
-import com.forgerock.opendj.util.SubstringReader;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+import org.forgerock.opendj.ldap.spi.Indexer;
+import org.forgerock.opendj.ldap.spi.IndexingOptions;
/**
* This class defines the objectIdentifierMatch matching rule defined in X.520
- * and referenced in RFC 2252. This expects to work on OIDs and will match
+ * and referenced in RFC 4517. This expects to work on OIDs and will match
* either an attribute/objectclass name or a numeric OID. NOTE: This matching
* rule requires a schema to lookup object identifiers in the descriptor form.
*/
-final class ObjectIdentifierEqualityMatchingRuleImpl extends AbstractEqualityMatchingRuleImpl {
+final class ObjectIdentifierEqualityMatchingRuleImpl extends AbstractMatchingRuleImpl {
+ /**
+ * NOTE: this matching rule is used by the "objectClass" attribute type which is frequently used in filters and
+ * which is also usually indexed for equality. Unfortunately, it is non-trivial to implement correctly and there
+ * are a number of subtleties that need to be understood.
+ *
+ * Comparing two values for equality
+ * =================================
+ *
+ * The LDAP RFCs describe the algorithm for comparing assertion values with attribute values, but never discusses
+ * how to compare two attribute values for equality in order to detect duplicate or missing values during updates.
+ *
+ * Given two schema elements:
+ *
+ * - object class with OID 1.2.3 and name "xxx"
+ * - attribute type with OID 4.5.6 and name "xxx"
+ *
+ * When:
+ *
+ * - an attribute "oids" whose syntax is OID and which has the value "xxx"
+ *
+ * Then:
+ *
+ * - the filter "(oids=1.2.3)" matches, as does "(oids=4.5.6)", as does "(oids=xxx)".
+ *
+ * However, if the attribute's value is "1.2.3" then only the filters "(oids=1.2.3)" and "(oids=xxx)" match. In
+ * order to compare two values with each other when enforcing set semantics in an LDAP attribute we need a function
+ * which is reflexive, symmetric, and transitive. While the first two are possible, the last is not:
+ *
+ * - reflexive: 1.2.3 == 1.2.3, xxx == xxx
+ * - symmetric: 1.2.3 == xxx, xxx == 1.2.3
+ * - transitive: 1.2.3 == xxx, xxx = 4.5.6, 1.2.3 != 4.5.6
+ *
+ * The implication is that it is impossible to implement a reliable normalization method. There are three
+ * options:
+ *
+ * 1) avoid resolving OID names to numeric OIDs during normalization and, instead, convert the OID to lower-case.
+ * This approach has the undesirable effect of allowing users to add equivalent object classes to entries. For
+ * example, the OIDs "2.5.6.6" and "person" are equivalent, but would have different normalized representations
+ *
+ * 2) resolve OID names to their equivalent numeric OID. For example, the normalized representation of "Person"
+ * would be "2.5.6.6". Unfortunately, there are rare cases where two schema elements share the same name - one
+ * can imagine having a custom object class called "manager" which would clash with the standard attribute
+ * type "manager". In this situation, the algorithm must choose whether to convert the value to the object
+ * class's numeric OID or the attribute type's. Unfortunately, this approach suffers from the same problem as
+ * the first if it happens to prioritize the wrong type of schema element
+ *
+ * 3) as (2) but resolve numeric OIDs to their "primary" OID name. For example, the normalized representation of
+ * "2.5.6.6" would be "person". The drawback with this approach is that an OID valued attribute cannot
+ * contain two values having the same "primary" name. In the example above, the attribute "oids" cannot
+ * contain the values "1.2.3" and "4.5.6" because they both share the same normalized primary name "xxx". Such
+ * collisions will be extremely rare in practice because attributes rarely reference a heterogeneous set of
+ * schema elements, such as a mix of attribute types and object classes. For example, the "objectClass"
+ * attribute should only contain object class OIDs whose names should never collide.
+ *
+ * Option (3) has been chosen as the normalization strategy as it provides the best compromise.
+ *
+ * Indexes
+ * =======
+ *
+ * Indexing poses a different problem because indexes are persistent and rebuilding them is a relatively
+ * expensive operation. If index keys depend on the schema, as is the case for the normalization algorithm (3)
+ * above, then changes to the schema, e.g. the addition or removal of schema elements, may require an index
+ * rebuild. In the above example, changing the primary name of one of the schema elements from "xxx" to "yyy"
+ * invalidates any indexes. To avoid the need to rebuild indexes whenever the schema changes we use a simple
+ * normalization algorithm - option (1) above - for generating index keys and employ a more complex algorithm for
+ * querying the indexes during search operations:
+ *
+ * 1) compute all the possible aliases of the assertion value's OID using the current schema. In the earlier
+ * example, a filter of the form "(oids=xxx)" would yield the keys "xxx", "1.2.3", and "4.5.6", whereas the
+ * filter "(oids=1.2.3)" would only yield the keys "1.2.3" and "xxx"
+ *
+ * 2) perform an exact match index query against the index for each key
+ *
+ * 3) perform a union of the combined results.
+ *
+ * This algorithm may yield false positives in very rare cases when the schema contains many schema elements
+ * sharing the same name.
+ */
ObjectIdentifierEqualityMatchingRuleImpl() {
- super(EMR_OID_NAME);
+ // Nothing to do.
}
- static String resolveNames(final Schema schema, final String oidOrName) throws DecodeException {
- if (StaticUtils.isDigit(oidOrName.charAt(0))) {
- return oidOrName;
- }
- // Do a best effort attempt to normalize names to OIDs.
- final String lowerCaseName = StaticUtils.toLowerCase(oidOrName);
- try {
- final String oid = schema.getOIDForName(lowerCaseName);
- if (oid != null) {
- return oid;
+ @Override
+ public Assertion getAssertion(final Schema schema, final ByteSequence assertionValue) throws DecodeException {
+ return getAssertion(schema, EMR_OID_NAME, assertionValue);
+ }
+
+ @Override
+ public Collection<? extends Indexer> createIndexers(final IndexingOptions options) {
+ return Collections.singleton(new Indexer() {
+ @Override
+ public String getIndexID() {
+ return EMR_OID_NAME;
}
- } catch (UnknownSchemaElementException e) {
- throw DecodeException.error(e.getMessageObject(), e);
+
+ @Override
+ public void createKeys(final Schema schema, final ByteSequence value, final Collection<ByteString> keys)
+ throws DecodeException {
+ // TODO: optimize - avoid converting to/from string + validate syntax.
+ final String oid = toLowerCase(value.toString()).trim();
+ keys.add(ByteString.valueOfUtf8(oid));
+ }
+
+ @Override
+ public String keyToHumanReadableString(final ByteSequence key) {
+ return key.toByteString().toString();
+ }
+ });
+ }
+
+ static Assertion getAssertion(final Schema schema, final String indexId, final ByteSequence assertionValue) {
+ return new Assertion() {
+ // TODO: optimize.
+ final String oid = toLowerCase(assertionValue.toString()).trim();
+ final List<ByteString> candidates = getCandidates(schema, oid);
+
+ @Override
+ public ConditionResult matches(final ByteSequence normalizedAttributeValue) {
+ return ConditionResult.valueOf(candidates.contains(normalizedAttributeValue.toByteString()));
+ }
+
+ @Override
+ public <T> T createIndexQuery(final IndexQueryFactory<T> factory) throws DecodeException {
+ final List<T> subQueries = new ArrayList<>(candidates.size());
+ for (final ByteString candidate : candidates) {
+ subQueries.add(factory.createExactMatchQuery(indexId, candidate));
+ }
+ return factory.createUnionQuery(subQueries);
+ }
+ };
+ }
+
+ private static List<ByteString> getCandidates(final Schema schema, final String oid) {
+ // TODO: optimize - avoid double lookups.
+
+ // The set of candidates is likely to be small, usually 2 or 3, so avoid the memory overhead of using a Set
+ // and instead store the candidates in a small array.
+ final List<ByteString> candidates = new ArrayList<>(3);
+ candidates.add(ByteString.valueOfUtf8(oid));
+ if (schema.hasObjectClass(oid)) { // Careful of placeholders
+ final ObjectClass oc = schema.getObjectClass(oid);
+ addCandidates(candidates, oc.getOID(), oc.getNames());
}
- return lowerCaseName;
+ if (schema.hasAttributeType(oid)) { // Careful of placeholders
+ final AttributeType at = schema.getAttributeType(oid);
+ addCandidates(candidates, at.getOID(), at.getNames());
+ }
+ if (schema.hasSyntax(oid)) {
+ final Syntax syntax = schema.getSyntax(oid);
+ addCandidates(candidates, syntax.getOID(), Collections.<String>emptyList());
+ }
+ if (schema.hasMatchingRule(oid)) {
+ final MatchingRule mr = schema.getMatchingRule(oid);
+ addCandidates(candidates, mr.getOID(), mr.getNames());
+ }
+ if (schema.hasNameForm(oid)) {
+ final NameForm nf = schema.getNameForm(oid);
+ addCandidates(candidates, nf.getOID(), nf.getNames());
+ }
+ return candidates;
+ }
+
+
+ private static void addCandidates(final List<ByteString> candidates,
+ final String numericOid,
+ final List<String> names) {
+ addIfNotPresent(candidates, ByteString.valueOfUtf8(numericOid));
+ for (final String name : names) {
+ addIfNotPresent(candidates, ByteString.valueOfUtf8(toLowerCase(name)));
+ }
+ }
+
+ private static void addIfNotPresent(final List<ByteString> candidates, final ByteString candidate) {
+ if (!candidates.contains(candidate)) {
+ candidates.add(candidate);
+ }
}
@Override
public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value) throws DecodeException {
- return normalizeAttributeValuePrivate(schema, value);
- }
-
- static ByteString normalizeAttributeValuePrivate(final Schema schema, final ByteSequence value)
- throws DecodeException {
- final String definition = value.toString();
- final SubstringReader reader = new SubstringReader(definition);
- final String oid = readOID(reader, schema.getOption(ALLOW_MALFORMED_NAMES_AND_OPTIONS));
- return ByteString.valueOfUtf8(resolveNames(schema, oid));
- }
-
- @Override
- String keyToHumanReadableString(ByteSequence key) {
- return key.toByteString().toString();
+ // TODO: optimize - avoid converting to/from string + validate syntax + avoid double lookups.
+ final String oid = toLowerCase(value.toString()).trim();
+ if (schema.hasObjectClass(oid)) { // Careful of placeholders
+ return ByteString.valueOfUtf8(toLowerCase(schema.getObjectClass(oid).getNameOrOID()));
+ }
+ if (schema.hasAttributeType(oid)) { // Careful of placeholders
+ return ByteString.valueOfUtf8(toLowerCase(schema.getAttributeType(oid).getNameOrOID()));
+ }
+ if (schema.hasSyntax(oid)) {
+ return ByteString.valueOfUtf8(toLowerCase(schema.getSyntax(oid).getOID()));
+ }
+ if (schema.hasMatchingRule(oid)) {
+ return ByteString.valueOfUtf8(toLowerCase(schema.getMatchingRule(oid).getNameOrOID()));
+ }
+ if (schema.hasNameForm(oid)) {
+ return ByteString.valueOfUtf8(toLowerCase(schema.getNameForm(oid).getNameOrOID()));
+ }
+ return ByteString.valueOfUtf8(oid);
}
}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java
index 4c10cd1..ffbbd01 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java
@@ -12,13 +12,12 @@
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2009 Sun Microsystems, Inc.
- * Portions copyright 2011-2015 ForgeRock AS.
+ * Portions copyright 2011-2016 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.schema;
import static com.forgerock.opendj.ldap.CoreMessages.*;
-
-import static org.forgerock.opendj.ldap.schema.ObjectIdentifierEqualityMatchingRuleImpl.*;
+import static com.forgerock.opendj.util.StaticUtils.toLowerCase;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.*;
import static org.forgerock.opendj.ldap.schema.SchemaOptions.*;
import static org.forgerock.opendj.ldap.schema.SchemaUtils.*;
@@ -33,7 +32,7 @@
/**
* This class implements the objectIdentifierFirstComponentMatch matching rule
- * defined in X.520 and referenced in RFC 2252. This rule is intended for use
+ * defined in X.520 and referenced in RFC 4517. This rule is intended for use
* with attributes whose values contain a set of parentheses enclosing a
* space-delimited set of names and/or name-value pairs (like attribute type or
* objectclass descriptions) in which the "first component" is the first item
@@ -47,7 +46,9 @@
@Override
public Assertion getAssertion(final Schema schema, final ByteSequence assertionValue) throws DecodeException {
- return defaultAssertion(normalizeAttributeValuePrivate(schema, assertionValue));
+ return ObjectIdentifierEqualityMatchingRuleImpl.getAssertion(schema,
+ EMR_OID_FIRST_COMPONENT_NAME,
+ assertionValue);
}
@Override
@@ -77,6 +78,6 @@
// The next set of characters must be the OID.
final String oid = readOID(reader, schema.getOption(ALLOW_MALFORMED_NAMES_AND_OPTIONS));
- return ByteString.valueOfUtf8(resolveNames(schema, oid));
+ return ByteString.valueOfUtf8(toLowerCase(oid, new StringBuilder(oid.length())).toString().trim());
}
}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
index 9e182d0..f83bc99 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
@@ -75,8 +75,6 @@
Syntax getDefaultSyntax();
- String getOIDForName(String lowerCaseName);
-
AttributeType getAttributeType(Schema schema, String nameOrOid);
AttributeType getAttributeType(String nameOrOid, Syntax syntax);
@@ -189,11 +187,6 @@
}
@Override
- public String getOIDForName(final String lowerCaseName) {
- return strictImpl.getOIDForName(lowerCaseName);
- }
-
- @Override
public AttributeType getAttributeType(final Schema schema, final String nameOrOid) {
return getAttributeType0(nameOrOid, schema.getDefaultSyntax(), schema.getDefaultMatchingRule());
}
@@ -502,45 +495,6 @@
}
@Override
- public String getOIDForName(String lowerCaseName) {
- AttributeType attributeType = getAttributeType0(lowerCaseName);
- if (attributeType != null) {
- return attributeType.getOID();
- }
- try {
- return getObjectClass(lowerCaseName).getOID();
- } catch (UnknownSchemaElementException ignore) {
- // try next schema element
- }
- try {
- return getSyntax(null, lowerCaseName).getOID();
- } catch (UnknownSchemaElementException ignore) {
- // try next schema element
- }
- try {
- return getMatchingRule(lowerCaseName).getOID();
- } catch (UnknownSchemaElementException ignore) {
- // try next schema element
- }
- try {
- return getNameForm(lowerCaseName).getOID();
- } catch (UnknownSchemaElementException ignore) {
- // try next schema element
- }
- try {
- return getDITContentRule(lowerCaseName).getStructuralClassOID();
- } catch (UnknownSchemaElementException ignore) {
- // try next schema element
- }
- try {
- return getMatchingRuleUse(lowerCaseName).getMatchingRuleOID();
- } catch (UnknownSchemaElementException ignore) {
- // try next schema element
- }
- return null;
- }
-
- @Override
public AttributeType getAttributeType(String nameOrOid, Syntax syntax) {
return getAttributeType(null, nameOrOid);
}
@@ -1149,17 +1103,6 @@
}
/**
- * Return the numerical OID matching the lowerCaseName.
- * @param lowerCaseName The lower case name
- * @return OID matching the name or null if name doesn't match to an OID
- * @throws UnknownSchemaElementException if multiple OID are matching
- * lowerCaseName
- */
- String getOIDForName(String lowerCaseName) {
- return impl.getOIDForName(lowerCaseName);
- }
-
- /**
* Returns the attribute type for the specified name or numeric OID.
* <p>
* If the requested attribute type is not registered in this schema and this
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleTest.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleTest.java
new file mode 100644
index 0000000..bc93754
--- /dev/null
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierEqualityMatchingRuleTest.java
@@ -0,0 +1,153 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ *
+ */
+
+package org.forgerock.opendj.ldap.schema;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.forgerock.opendj.ldap.ConditionResult.FALSE;
+import static org.forgerock.opendj.ldap.ConditionResult.TRUE;
+import static org.forgerock.opendj.ldap.schema.SchemaConstants.EMR_OID_NAME;
+import static org.forgerock.opendj.ldap.schema.SchemaConstants.EMR_OID_OID;
+import static org.mockito.Matchers.anyCollection;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+import org.forgerock.opendj.ldap.spi.Indexer;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test
+public class ObjectIdentifierEqualityMatchingRuleTest extends MatchingRuleTest {
+ @Override
+ @DataProvider(name = "matchingRuleInvalidAttributeValues")
+ public Object[][] createMatchingRuleInvalidAttributeValues() {
+ return new Object[][] {};
+ }
+
+ @Override
+ @DataProvider(name = "matchingrules")
+ public Object[][] createMatchingRuleTest() {
+ return new Object[][] {
+ // value, assertion, expected result
+
+ // numeric OIDs match directly.
+ { "2.5.4.3", "2.5.4.3", TRUE },
+ { "2.5.4.3", "2.5.4.4", FALSE }, // OID is known
+ { "2.5.4.3", "9.9.9.999", FALSE }, // OID is unknown
+ { "9.9.9.999", "9.9.9.999", TRUE },
+ { "9.9.9.999", "9.9.9.9999", FALSE },
+
+ // OID 'descr' form where assertion values are known to the schema.
+ { "2.5.4.3", "cn", TRUE },
+ { "2.5.4.3", "commonName", TRUE },
+ { "2.5.4.3", "l", FALSE },
+
+ { "cn", "2.5.4.3", TRUE },
+ { "commonName", "2.5.4.3", TRUE },
+ { "l", "2.5.4.3", FALSE },
+
+ { "cn", "cn", TRUE },
+ { "commonName", "cn", TRUE },
+ { "cn", "commonName", TRUE },
+ { "commonName", "commonName", TRUE },
+
+ { "l", "cn", FALSE },
+ { "cn", "l", FALSE },
+
+ /* THESE TESTS FAIL BECAUSE THE ACTUAL RESULT IS FALSE
+
+ // These are undefined because assertion values are unknown to the server. See RFC 4517 4.2.26.
+ { "2.5.4.3", "dummy", UNDEFINED },
+ { "2.5.4.3", "xxx", UNDEFINED },
+ { "9.9.9.999", "foo", UNDEFINED },
+
+ */
+
+ // Strictly speaking this should evaluate to UNDEFINED since 'DUMMY' is not recognized.
+ { "dummy", "DUMMY", TRUE },
+
+ // 2.5.4.3 is recognized (it's a numeric OID) so matching can be performed.
+ { "dummy", "2.5.4.3", FALSE },
+ };
+ }
+
+ @Override
+ protected MatchingRule getRule() {
+ return Schema.getCoreSchema().getMatchingRule(EMR_OID_OID);
+ }
+
+ @Test
+ public void indexingShouldNotBeSensitiveToSchemaChanges() throws Exception {
+ // 1) keys should be stable and not impacted by schema changes
+ // 2) index queries should work even when the schema element has been removed.
+
+ final String customDefinition = "( 9.9.9.999 NAME ( 'foo' 'bar' ) SUP name )";
+ final Schema customSchema = new SchemaBuilder(Schema.getCoreSchema())
+ .addAttributeType(customDefinition, true)
+ .toSchema();
+ final List<ByteString> attributeValues = Arrays.asList(b("9.9.9.999"), b("FOO"), b("cn"));
+
+ // Indexing should be insensitive to the schema.
+ checkIndexContainsExpectedKeys(Schema.getCoreSchema(), attributeValues, b("9.9.9.999"), b("foo"), b("cn"));
+ checkIndexContainsExpectedKeys(customSchema, attributeValues, b("9.9.9.999"), b("foo"), b("cn"));
+
+ // Index queries should take advantage of the schema by testing alternative schema element names.
+ final ByteString assertionValue = b("bar");
+ checkKeysUsedForIndexQuerying(Schema.getCoreSchema(), assertionValue, b("bar"));
+ checkKeysUsedForIndexQuerying(customSchema, assertionValue, b("bar"), b("9.9.9.999"), b("foo"));
+ }
+
+ private void checkIndexContainsExpectedKeys(final Schema schema,
+ final List<ByteString> attributeValues,
+ final ByteString... expectedKeys) throws DecodeException {
+ final MatchingRule mr = schema.getMatchingRule(EMR_OID_OID);
+ final Indexer indexer = mr.createIndexers(null).iterator().next();
+ final ArrayList<ByteString> keys = new ArrayList<>();
+ for (final ByteString value : attributeValues) {
+ indexer.createKeys(schema, value, keys);
+ }
+ assertThat(keys).containsExactly(expectedKeys);
+ }
+
+ private void checkKeysUsedForIndexQuerying(final Schema schema,
+ final ByteString assertionValue,
+ final ByteString... expectedKeys) throws DecodeException {
+ final MatchingRule mr = schema.getMatchingRule(EMR_OID_OID);
+ final Assertion assertion = mr.getAssertion(assertionValue);
+ final IndexQueryFactory indexQueryFactory = mock(IndexQueryFactory.class);
+ assertion.createIndexQuery(indexQueryFactory);
+ for (final ByteString key : expectedKeys) {
+ verify(indexQueryFactory).createExactMatchQuery(EMR_OID_NAME, key);
+ }
+ verify(indexQueryFactory).createUnionQuery(anyCollection());
+ verifyNoMoreInteractions(indexQueryFactory);
+ }
+
+
+ private ByteString b(final String value) {
+ return ByteString.valueOfUtf8(value);
+ }
+}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleTest.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleTest.java
new file mode 100644
index 0000000..70c6946
--- /dev/null
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleTest.java
@@ -0,0 +1,74 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ *
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import static org.forgerock.opendj.ldap.ConditionResult.FALSE;
+import static org.forgerock.opendj.ldap.ConditionResult.TRUE;
+import static org.forgerock.opendj.ldap.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test
+public class ObjectIdentifierFirstComponentEqualityMatchingRuleTest extends MatchingRuleTest {
+ @Override
+ @DataProvider(name = "matchingRuleInvalidAttributeValues")
+ public Object[][] createMatchingRuleInvalidAttributeValues() {
+ return new Object[][] {};
+ }
+
+ @Override
+ @DataProvider(name = "matchingrules")
+ public Object[][] createMatchingRuleTest() {
+ // This is defined in the core schema so the names 'cn' and 'commonName' will be recognized. However 'dummy'
+ // is not part of the core schema definition so it won't be recognized by the server.
+ final String cnDefinition = "( 2.5.4.3 NAME ( 'cn' 'commonName' 'dummy' ) SUP name )";
+
+ // Matching should be supported against the numeric OID, but the names are not in the core schema so cannot
+ // be used in assertion values.
+ final String customDefinition = "( 9.9.9.999 NAME ( 'foo' 'bar' ) SUP name )";
+
+ return new Object[][] {
+ // numeric OIDs match directly.
+ { cnDefinition, "2.5.4.3", TRUE },
+ { cnDefinition, "2.5.4.4", FALSE }, // OID is known
+ { cnDefinition, "9.9.9.999", FALSE }, // OID is unknown, but it is numeric
+ { customDefinition, "9.9.9.999", TRUE },
+ { customDefinition, "9.9.9.9999", FALSE },
+
+ // OID 'descr' form where assertion values are known to the schema.
+ { cnDefinition, "cn", TRUE },
+ { cnDefinition, "commonName", TRUE },
+ { cnDefinition, "l", FALSE },
+ { customDefinition, "cn", FALSE },
+
+ /* THESE TESTS FAIL BECAUSE THE ACTUAL RESULT IS FALSE
+
+ // These are undefined because the assertion values are unknown to the server. See RFC 4517 4.2.26.
+ { cnDefinition, "dummy", UNDEFINED },
+ { cnDefinition, "xxx", UNDEFINED },
+ { customDefinition, "foo", UNDEFINED }
+
+ */
+ };
+ }
+
+ @Override
+ protected MatchingRule getRule() {
+ return Schema.getCoreSchema().getMatchingRule(EMR_OID_FIRST_COMPONENT_OID);
+ }
+}
--
Gitblit v1.10.0