From 19f4078a1c392ef3a8423139745c474ee8facd59 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 05 Jun 2014 15:01:30 +0000
Subject: [PATCH] Fix OPENDJ-1481: Consider adding method for comparing entries for exact differences, ignoring matching rules
---
opendj-core/src/main/java/org/forgerock/opendj/ldap/Entries.java | 367 ++++++++++++++++++--
opendj-core/src/test/java/org/forgerock/opendj/ldap/EntriesTestCase.java | 604 +++++++++++++++++++++++++----------
opendj-core/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java | 18
3 files changed, 765 insertions(+), 224 deletions(-)
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/Entries.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/Entries.java
index b976318..b3f0c0c 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/Entries.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/Entries.java
@@ -22,15 +22,13 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2011-2013 ForgeRock AS
+ * Portions copyright 2011-2014 ForgeRock AS
*/
package org.forgerock.opendj.ldap;
-import static org.forgerock.opendj.ldap.AttributeDescription.objectClass;
-
import static com.forgerock.opendj.ldap.CoreMessages.*;
-
+import static org.forgerock.opendj.ldap.AttributeDescription.objectClass;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.util.ArrayList;
@@ -39,6 +37,7 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -63,6 +62,142 @@
* @see Entry
*/
public final class Entries {
+ /**
+ * Options for controlling the behavior of the
+ * {@link Entries#diffEntries(Entry, Entry, DiffOptions) diffEntries}
+ * method. {@code DiffOptions} specify which attributes are compared, how
+ * they are compared, and the type of modifications generated.
+ *
+ * @see Entries#diffEntries(Entry, Entry, DiffOptions)
+ */
+ public static final class DiffOptions {
+ /**
+ * Selects which attributes will be compared. By default all user
+ * attributes will be compared.
+ */
+ private AttributeFilter attributeFilter = USER_ATTRIBUTES_ONLY_FILTER;
+
+ /**
+ * When true, attribute values are compared byte for byte, otherwise
+ * they are compared using their matching rules.
+ */
+ private boolean useExactMatching = false;
+
+ /**
+ * When greater than 0, modifications with REPLACE type will be
+ * generated for the new attributes containing at least
+ * "useReplaceMaxValues" attribute values. Otherwise, modifications with
+ * DELETE + ADD types will be generated.
+ */
+ private int useReplaceMaxValues = 0;
+
+ private DiffOptions() {
+ // Nothing to do.
+ }
+
+ /**
+ * Specifies an attribute filter which will be used to determine which
+ * attributes will be compared. By default only user attributes will be
+ * compared.
+ *
+ * @param attributeFilter
+ * The filter which will be used to determine which
+ * attributes will be compared.
+ * @return A reference to this set of options.
+ */
+ public DiffOptions attributes(final AttributeFilter attributeFilter) {
+ Reject.ifNull(attributeFilter);
+ this.attributeFilter = attributeFilter;
+ return this;
+ }
+
+ /**
+ * Specifies the list of attributes to be compared. By default only user
+ * attributes will be compared.
+ *
+ * @param attributeDescriptions
+ * The names of the attributes to be compared.
+ * @return A reference to this set of options.
+ */
+ public DiffOptions attributes(final String... attributeDescriptions) {
+ return attributes(new AttributeFilter(attributeDescriptions));
+ }
+
+ /**
+ * Requests that attribute values should be compared byte for byte,
+ * rather than using their matching rules. This is useful when a client
+ * wishes to perform trivial changes to an attribute value which would
+ * otherwise be ignored by the matching rule, such as removing extra
+ * white space from an attribute, or capitalizing a user's name.
+ *
+ * @return A reference to this set of options.
+ */
+ public DiffOptions useExactMatching() {
+ this.useExactMatching = true;
+ return this;
+ }
+
+ /**
+ * Requests that all generated changes should use the
+ * {@link ModificationType#REPLACE REPLACE} modification type, rather
+ * than a combination of {@link ModificationType#DELETE DELETE} and
+ * {@link ModificationType#ADD ADD}.
+ * <p>
+ * Note that the generated changes will not be reversible, nor will they
+ * be efficient for attributes containing many values (such as groups).
+ * Enabling this option may result in more efficient updates for single
+ * valued attributes and reduce the amount of replication meta-data that
+ * needs to be maintained..
+ *
+ * @return A reference to this set of options.
+ */
+ public DiffOptions alwaysReplaceAttributes() {
+ return replaceMaxValues(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Requests that the generated changes should use the
+ * {@link ModificationType#REPLACE REPLACE} modification type when the
+ * new attribute contains at most one attribute value. All other changes
+ * will use a combination of {@link ModificationType#DELETE DELETE} then
+ * {@link ModificationType#ADD ADD}.
+ * <p>
+ * Specifying this option will usually provide the best overall
+ * performance for single and multi-valued attribute updates, but the
+ * generated changes will probably not be reversible.
+ *
+ * @return A reference to this set of options.
+ */
+ public DiffOptions replaceSingleValuedAttributes() {
+ return replaceMaxValues(1);
+ }
+
+ /**
+ * Requests that the generated changes should use the
+ * {@link ModificationType#REPLACE REPLACE} modification type when the
+ * new attribute contains {@code maxValues} attribute values or less.
+ * All other changes will use a combination of
+ * {@link ModificationType#DELETE DELETE} then
+ * {@link ModificationType#ADD ADD}.
+ *
+ * @param maxValues
+ * The maximum number of attribute values a modified
+ * attribute can contain before reversible changes will be
+ * generated.
+ * @return A reference to this set of options.
+ */
+ private DiffOptions replaceMaxValues(final int maxValues) {
+ // private until we can think of a good use case and better name.
+ Reject.ifFalse(maxValues >= 0, "maxValues must be >= 0");
+ this.useReplaceMaxValues = maxValues;
+ return this;
+ }
+
+ private Entry filter(final Entry entry) {
+ return attributeFilter.filteredViewOf(entry);
+ }
+
+ }
private static final class UnmodifiableEntry implements Entry {
private final Entry entry;
@@ -269,6 +404,9 @@
}
};
+ private static final AttributeFilter USER_ATTRIBUTES_ONLY_FILTER = new AttributeFilter();
+ private static final DiffOptions DEFAULT_DIFF_OPTIONS = new DiffOptions();
+
private static final Function<Attribute, Attribute, Void> UNMODIFIABLE_ATTRIBUTE_FUNCTION =
new Function<Attribute, Attribute, Void>() {
@@ -409,12 +547,15 @@
* Creates a new modify request containing a list of modifications which can
* be used to transform {@code fromEntry} into entry {@code toEntry}.
* <p>
- * The modify request is reversible: it will contain only modifications of
- * type {@link ModificationType#ADD ADD} and {@link ModificationType#DELETE
- * DELETE}.
+ * The changes will be generated using a default set of {@link DiffOptions
+ * options}. More specifically, only user attributes will be compared,
+ * attributes will be compared using their matching rules, and all generated
+ * changes will be reversible: it will contain only modifications of type
+ * {@link ModificationType#DELETE DELETE} then {@link ModificationType#ADD
+ * ADD}.
* <p>
* Finally, the modify request will use the distinguished name taken from
- * {@code fromEntry}. Moreover, this method will not check to see if both
+ * {@code fromEntry}. This method will not check to see if both
* {@code fromEntry} and {@code toEntry} have the same distinguished name.
* <p>
* This method is equivalent to:
@@ -423,35 +564,62 @@
* ModifyRequest request = Requests.newModifyRequest(fromEntry, toEntry);
* </pre>
*
+ * Or:
+ *
+ * <pre>
+ * ModifyRequest request = diffEntries(fromEntry, toEntry, Entries.diffOptions());
+ * </pre>
+ *
* @param fromEntry
* The source entry.
* @param toEntry
* The destination entry.
* @return A modify request containing a list of modifications which can be
* used to transform {@code fromEntry} into entry {@code toEntry}.
+ * The returned request will always be non-{@code null} but may not
+ * contain any modifications.
* @throws NullPointerException
* If {@code fromEntry} or {@code toEntry} were {@code null}.
* @see Requests#newModifyRequest(Entry, Entry)
*/
public static ModifyRequest diffEntries(final Entry fromEntry, final Entry toEntry) {
- Reject.ifNull(fromEntry, toEntry);
+ return diffEntries(fromEntry, toEntry, DEFAULT_DIFF_OPTIONS);
+ }
+
+ /**
+ * Creates a new modify request containing a list of modifications which can
+ * be used to transform {@code fromEntry} into entry {@code toEntry}.
+ * <p>
+ * The changes will be generated using the provided set of
+ * {@link DiffOptions}.
+ * <p>
+ * Finally, the modify request will use the distinguished name taken from
+ * {@code fromEntry}. This method will not check to see if both
+ * {@code fromEntry} and {@code toEntry} have the same distinguished name.
+ *
+ * @param fromEntry
+ * The source entry.
+ * @param toEntry
+ * The destination entry.
+ * @param options
+ * The set of options which will control which attributes are
+ * compared, how they are compared, and the type of modifications
+ * generated.
+ * @return A modify request containing a list of modifications which can be
+ * used to transform {@code fromEntry} into entry {@code toEntry}.
+ * The returned request will always be non-{@code null} but may not
+ * contain any modifications.
+ * @throws NullPointerException
+ * If {@code fromEntry}, {@code toEntry}, or {@code options}
+ * were {@code null}.
+ */
+ public static ModifyRequest diffEntries(final Entry fromEntry, final Entry toEntry,
+ final DiffOptions options) {
+ Reject.ifNull(fromEntry, toEntry, options);
final ModifyRequest request = Requests.newModifyRequest(fromEntry.getName());
-
- final TreeMapEntry tfrom;
- if (fromEntry instanceof TreeMapEntry) {
- tfrom = (TreeMapEntry) fromEntry;
- } else {
- tfrom = new TreeMapEntry(fromEntry);
- }
-
- final TreeMapEntry tto;
- if (toEntry instanceof TreeMapEntry) {
- tto = (TreeMapEntry) toEntry;
- } else {
- tto = new TreeMapEntry(toEntry);
- }
-
+ final Entry tfrom = toFilteredTreeMapEntry(fromEntry, options);
+ final Entry tto = toFilteredTreeMapEntry(toEntry, options);
final Iterator<Attribute> ifrom = tfrom.getAllAttributes().iterator();
final Iterator<Attribute> ito = tto.getAllAttributes().iterator();
@@ -461,62 +629,101 @@
while (afrom != null && ato != null) {
final AttributeDescription adfrom = afrom.getAttributeDescription();
final AttributeDescription adto = ato.getAttributeDescription();
+
final int cmp = adfrom.compareTo(adto);
if (cmp == 0) {
/*
- * Attribute is in both entries. Compute the set of values to be
- * added and removed. We won't replace the attribute because
- * this is not reversible.
+ * Attribute is in both entries so compute the differences
+ * between the old and new.
*/
- final Attribute addedValues = new LinkedAttribute(ato);
- addedValues.removeAll(afrom);
- if (!addedValues.isEmpty()) {
- request.addModification(new Modification(ModificationType.ADD, addedValues));
- }
+ if (options.useReplaceMaxValues > ato.size()) {
+ // This attribute is a candidate for replacing.
+ if (diffAttributeNeedsReplacing(afrom, ato, options)) {
+ request.addModification(new Modification(ModificationType.REPLACE, ato));
+ }
+ } else if (afrom.size() == 1 && ato.size() == 1) {
+ // Fast-path for single valued attributes.
+ if (diffFirstValuesAreDifferent(options, afrom, ato)) {
+ diffDeleteValues(request, afrom);
+ diffAddValues(request, ato);
+ }
+ } else if (options.useExactMatching) {
+ /*
+ * Compare multi-valued attributes using exact matching. Use
+ * a hash sets for membership checking rather than the
+ * attributes in order to avoid matching rule based
+ * comparisons.
+ */
+ final Set<ByteString> oldValues = new LinkedHashSet<ByteString>(afrom);
+ final Set<ByteString> newValues = new LinkedHashSet<ByteString>(ato);
- final Attribute deletedValues = new LinkedAttribute(afrom);
- deletedValues.removeAll(ato);
- if (!deletedValues.isEmpty()) {
- request.addModification(new Modification(ModificationType.DELETE, deletedValues));
+ final Set<ByteString> deletedValues = new LinkedHashSet<ByteString>(oldValues);
+ deletedValues.removeAll(newValues);
+ diffDeleteValues(request, deletedValues.size() == afrom.size() ? afrom
+ : new LinkedAttribute(adfrom, deletedValues));
+
+ final Set<ByteString> addedValues = newValues;
+ addedValues.removeAll(oldValues);
+ diffAddValues(request, addedValues.size() == ato.size() ? ato
+ : new LinkedAttribute(adto, addedValues));
+ } else {
+ // Compare multi-valued attributes using matching rules.
+ final Attribute deletedValues = new LinkedAttribute(afrom);
+ deletedValues.removeAll(ato);
+ diffDeleteValues(request, deletedValues);
+
+ final Attribute addedValues = new LinkedAttribute(ato);
+ addedValues.removeAll(afrom);
+ diffAddValues(request, addedValues);
}
afrom = ifrom.hasNext() ? ifrom.next() : null;
ato = ito.hasNext() ? ito.next() : null;
} else if (cmp < 0) {
// afrom in source, but not destination.
- request.addModification(new Modification(ModificationType.DELETE, afrom));
+ diffDeleteAttribute(request, afrom, options);
afrom = ifrom.hasNext() ? ifrom.next() : null;
} else {
// ato in destination, but not in source.
- request.addModification(new Modification(ModificationType.ADD, ato));
+ diffAddAttribute(request, ato, options);
ato = ito.hasNext() ? ito.next() : null;
}
}
// Additional attributes in source entry: these must be deleted.
if (afrom != null) {
- request.addModification(new Modification(ModificationType.DELETE, afrom));
+ diffDeleteAttribute(request, afrom, options);
}
-
while (ifrom.hasNext()) {
- final Attribute a = ifrom.next();
- request.addModification(new Modification(ModificationType.DELETE, a));
+ diffDeleteAttribute(request, ifrom.next(), options);
}
// Additional attributes in destination entry: these must be added.
if (ato != null) {
- request.addModification(new Modification(ModificationType.ADD, ato));
+ diffAddAttribute(request, ato, options);
}
-
while (ito.hasNext()) {
- final Attribute a = ito.next();
- request.addModification(new Modification(ModificationType.ADD, a));
+ diffAddAttribute(request, ito.next(), options);
}
return request;
}
/**
+ * Returns a new set of options which may be used to control how entries are
+ * compared and changes generated using
+ * {@link #diffEntries(Entry, Entry, DiffOptions)}. By default only user
+ * attributes will be compared, matching rules will be used for comparisons,
+ * and all generated changes will be reversible.
+ *
+ * @return A new set of options which may be used to control how entries are
+ * compared and changes generated.
+ */
+ public static DiffOptions diffOptions() {
+ return new DiffOptions();
+ }
+
+ /**
* Returns an unmodifiable set containing the object classes associated with
* the provided entry. This method will ignore unrecognized object classes.
* <p>
@@ -617,7 +824,7 @@
* <p>
* Sample usage:
* <pre>
- * List<Entry> smiths = TestCaseUtils.makeEntries(
+ * List<Entry> smiths = TestCaseUtils.makeEntries(
* "dn: cn=John Smith,dc=example,dc=com",
* "objectclass: inetorgperson",
* "cn: John Smith",
@@ -816,6 +1023,64 @@
}
}
+ private static void diffAddAttribute(final ModifyRequest request, final Attribute ato,
+ final DiffOptions diffOptions) {
+ if (diffOptions.useReplaceMaxValues > 0) {
+ request.addModification(new Modification(ModificationType.REPLACE, ato));
+ } else {
+ request.addModification(new Modification(ModificationType.ADD, ato));
+ }
+ }
+
+ private static void diffAddValues(final ModifyRequest request, final Attribute addedValues) {
+ if (addedValues != null && !addedValues.isEmpty()) {
+ request.addModification(new Modification(ModificationType.ADD, addedValues));
+ }
+ }
+
+ private static boolean diffAttributeNeedsReplacing(final Attribute afrom, final Attribute ato,
+ final DiffOptions options) {
+ if (afrom.size() != ato.size()) {
+ return true;
+ } else if (afrom.size() == 1) {
+ return diffFirstValuesAreDifferent(options, afrom, ato);
+ } else if (options.useExactMatching) {
+ /*
+ * Use a hash set for membership checking rather than the attribute
+ * in order to avoid matching rule based comparisons.
+ */
+ final Set<ByteString> oldValues = new LinkedHashSet<ByteString>(afrom);
+ return !oldValues.containsAll(ato);
+ } else {
+ return !afrom.equals(ato);
+ }
+ }
+
+ private static void diffDeleteAttribute(final ModifyRequest request, final Attribute afrom,
+ final DiffOptions diffOptions) {
+ if (diffOptions.useReplaceMaxValues > 0) {
+ request.addModification(new Modification(ModificationType.REPLACE, Attributes
+ .emptyAttribute(afrom.getAttributeDescription())));
+ } else {
+ request.addModification(new Modification(ModificationType.DELETE, afrom));
+ }
+ }
+
+ private static void diffDeleteValues(final ModifyRequest request, final Attribute deletedValues) {
+ if (deletedValues != null && !deletedValues.isEmpty()) {
+ request.addModification(new Modification(ModificationType.DELETE, deletedValues));
+ }
+ }
+
+ private static boolean diffFirstValuesAreDifferent(final DiffOptions diffOptions,
+ final Attribute afrom, final Attribute ato) {
+ if (diffOptions.useExactMatching) {
+ return !afrom.firstValue().equals(ato.firstValue());
+ } else {
+ return !afrom.contains(ato.firstValue());
+ }
+ }
+
private static void incrementAttribute(final Entry entry, final Attribute change)
throws ErrorResultException {
// First parse the change.
@@ -893,6 +1158,14 @@
return entry;
}
+ private static Entry toFilteredTreeMapEntry(final Entry entry, final DiffOptions options) {
+ if (entry instanceof TreeMapEntry) {
+ return options.filter(entry);
+ } else {
+ return new TreeMapEntry(options.filter(entry));
+ }
+ }
+
// Prevent instantiation.
private Entries() {
// Nothing to do.
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java
index 238ce1f..39ddc63 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java
@@ -893,9 +893,13 @@
* Creates a new modify request containing a list of modifications which can
* be used to transform {@code fromEntry} into entry {@code toEntry}.
* <p>
- * The modify request is reversible: it will contain only modifications of
- * type {@link ModificationType#ADD ADD} and {@link ModificationType#DELETE
- * DELETE}.
+ * The changes will be generated using a default set of
+ * {@link org.forgerock.opendj.ldap.Entries.DiffOptions options}. More
+ * specifically, only user attributes will be compared, attributes will be
+ * compared using their matching rules, and all generated changes will be
+ * reversible: it will contain only modifications of type
+ * {@link ModificationType#DELETE DELETE} then {@link ModificationType#ADD
+ * ADD}.
* <p>
* Finally, the modify request will use the distinguished name taken from
* {@code fromEntry}. Moreover, this method will not check to see if both
@@ -907,12 +911,20 @@
* ModifyRequest request = Entries.diffEntries(fromEntry, toEntry);
* </pre>
*
+ * Or:
+ *
+ * <pre>
+ * ModifyRequest request = Entries.diffEntries(fromEntry, toEntry, Entries.diffOptions());
+ * </pre>
+ *
* @param fromEntry
* The source entry.
* @param toEntry
* The destination entry.
* @return A modify request containing a list of modifications which can be
* used to transform {@code fromEntry} into entry {@code toEntry}.
+ * The returned request will always be non-{@code null} but may not
+ * contain any modifications.
* @throws NullPointerException
* If {@code fromEntry} or {@code toEntry} were {@code null}.
* @see Entries#diffEntries(Entry, Entry)
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/EntriesTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/EntriesTestCase.java
index aa92855..0126560 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/EntriesTestCase.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/EntriesTestCase.java
@@ -22,10 +22,14 @@
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
+ * Portions copyright 2014 ForgeRock AS.
*/
package org.forgerock.opendj.ldap;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.forgerock.opendj.ldap.Entries.diffEntries;
+import static org.forgerock.opendj.ldap.Entries.diffOptions;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
@@ -34,8 +38,6 @@
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.schema.Schema;
-import org.testng.Assert;
-import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
@@ -43,188 +45,442 @@
*/
@SuppressWarnings("javadoc")
public final class EntriesTestCase extends SdkTestCase {
- /**
- * Creates test data for {@link #testDiffEntries}.
- *
- * @return The test data.
- */
- @DataProvider(name = "createTestDiffEntriesData")
- public Object[][] createTestDiffEntriesData() {
- // @formatter:off
- Entry empty = new LinkedHashMapEntry(
- "dn: cn=test",
- "objectClass: top",
- "objectClass: test"
- );
- Entry from = new LinkedHashMapEntry(
- "dn: cn=test",
- "objectClass: top",
- "objectClass: test",
- "fromOnly: fromOnlyValue",
- "bothSame: one",
- "bothSame: two",
- "bothSame: three",
- "bothDifferentDeletes: common",
- "bothDifferentDeletes: fromOnly1",
- "bothDifferentDeletes: fromOnly2",
- "bothDifferentAdds: common",
- "bothDifferentAddsAndDeletes: common",
- "bothDifferentAddsAndDeletes: fromOnly",
- "bothDifferentReplace: fromOnly1",
- "bothDifferentReplace: fromOnly2"
- );
+ @Test
+ public void testContainsObjectClass() throws Exception {
+ Entry entry =
+ new LinkedHashMapEntry("dn: cn=test", "objectClass: top", "objectClass: person");
+ Schema schema = Schema.getDefaultSchema();
- Entry to = new LinkedHashMapEntry(
- "dn: cn=test",
- "objectClass: top",
- "objectClass: test",
- "toOnly: toOnlyValue",
- "bothSame: one",
- "bothSame: two",
- "bothSame: three",
- "bothDifferentDeletes: common",
- "bothDifferentAdds: common",
- "bothDifferentAdds: toOnly1",
- "bothDifferentAdds: toOnly2",
- "bothDifferentAddsAndDeletes: common",
- "bothDifferentAddsAndDeletes: toOnly",
- "bothDifferentReplace: toOnly1",
- "bothDifferentReplace: toOnly2"
- );
-
- ModifyRequest diffFromEmpty = Requests.newModifyRequest(
- "dn: cn=test",
- "changetype: modify",
- "delete: bothDifferentAdds",
- "bothDifferentAdds: common",
- "-",
- "delete: bothDifferentAddsAndDeletes",
- "bothDifferentAddsAndDeletes: common",
- "bothDifferentAddsAndDeletes: fromOnly",
- "-",
- "delete: bothDifferentDeletes",
- "bothDifferentDeletes: common",
- "bothDifferentDeletes: fromOnly1",
- "bothDifferentDeletes: fromOnly2",
- "-",
- "delete: bothDifferentReplace",
- "bothDifferentReplace: fromOnly1",
- "bothDifferentReplace: fromOnly2",
- "-",
- "delete: bothSame",
- "bothSame: one",
- "bothSame: two",
- "bothSame: three",
- "-",
- "delete: fromOnly",
- "fromOnly: fromOnlyValue"
- );
-
- ModifyRequest diffEmptyTo = Requests.newModifyRequest(
- "dn: cn=test",
- "changetype: modify",
- "add: bothDifferentAdds",
- "bothDifferentAdds: common",
- "bothDifferentAdds: toOnly1",
- "bothDifferentAdds: toOnly2",
- "-",
- "add: bothDifferentAddsAndDeletes",
- "bothDifferentAddsAndDeletes: common",
- "bothDifferentAddsAndDeletes: toOnly",
- "-",
- "add: bothDifferentDeletes",
- "bothDifferentDeletes: common",
- "-",
- "add: bothDifferentReplace",
- "bothDifferentReplace: toOnly1",
- "bothDifferentReplace: toOnly2",
- "-",
- "add: bothSame",
- "bothSame: one",
- "bothSame: two",
- "bothSame: three",
- "-",
- "add: toOnly",
- "toOnly: toOnlyValue"
- );
-
- ModifyRequest diffFromTo = Requests.newModifyRequest(
- "dn: cn=test",
- "changetype: modify",
- "add: bothDifferentAdds",
- "bothDifferentAdds: toOnly1",
- "bothDifferentAdds: toOnly2",
- "-",
- "add: bothDifferentAddsAndDeletes",
- "bothDifferentAddsAndDeletes: toOnly",
- "-",
- "delete: bothDifferentAddsAndDeletes",
- "bothDifferentAddsAndDeletes: fromOnly",
- "-",
- "delete: bothDifferentDeletes",
- "bothDifferentDeletes: fromOnly1",
- "bothDifferentDeletes: fromOnly2",
- "-",
- "add: bothDifferentReplace",
- "bothDifferentReplace: toOnly1",
- "bothDifferentReplace: toOnly2",
- "-",
- "delete: bothDifferentReplace",
- "bothDifferentReplace: fromOnly1",
- "bothDifferentReplace: fromOnly2",
- "-",
- "delete: fromOnly",
- "fromOnly: fromOnlyValue",
- "-",
- "add: toOnly",
- "toOnly: toOnlyValue"
- );
-
- // From, to, diff.
- return new Object[][] {
- { from, empty, diffFromEmpty },
- { empty, to, diffEmptyTo },
- { from, to, diffFromTo }
- };
-
- // @formatter:on
+ assertTrue("should contain top", Entries.containsObjectClass(entry, schema
+ .getObjectClass("top")));
+ assertTrue("should contain person", Entries.containsObjectClass(entry, schema
+ .getObjectClass("person")));
+ assertFalse("should not contain country", Entries.containsObjectClass(entry, schema
+ .getObjectClass("country")));
}
- /**
- * Tests {@link Entries#diffEntries(Entry, Entry)}.
- *
- * @param from
- * Source entry.
- * @param to
- * Destination entry.
- * @param expected
- * Expected modifications.
- */
- @Test(dataProvider = "createTestDiffEntriesData")
- public void testDiffEntries(final Entry from, final Entry to, final ModifyRequest expected) {
- ModifyRequest actual = Entries.diffEntries(from, to);
+ @Test
+ public void testDiffEntriesAddDeleteAddIntermediateAttribute() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "sn: ignore");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value",
+ "sn: ignore");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "add: description",
+ "description: value");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
- Assert.assertEquals(from.getName(), actual.getName());
- Assert.assertEquals(actual.getModifications().size(), expected.getModifications().size());
+ @Test
+ public void testDiffEntriesAddDeleteAddTrailingAttributes() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: ignore");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: ignore",
+ "description: value",
+ "sn: value");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "add: description",
+ "description: value",
+ "-",
+ "add: sn",
+ "sn: value");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteDeleteIntermediateAttribute() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value",
+ "sn: ignore");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "sn: ignore");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "delete: description",
+ "description: value");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteDeleteTrailingAttributes() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: ignore",
+ "description: value",
+ "sn: value");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: ignore");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "delete: description",
+ "description: value",
+ "-",
+ "delete: sn",
+ "sn: value");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteMultiValueAddSingleValue() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1",
+ "description: value2");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "add: description",
+ "description: value2");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteMultiValueDeleteSingleValue() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1",
+ "description: value2");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "delete: description",
+ "description: value2");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteMultiValueSameSizeDifferentValues() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1",
+ "description: value2");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: VALUE2",
+ "description: VALUE3");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "delete: description",
+ "description: value1",
+ "-",
+ "add: description",
+ "description: VALUE3");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteMultiValueSameSizeDifferentValuesExact() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1",
+ "description: value2");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: VALUE2",
+ "description: VALUE3");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "delete: description",
+ "description: value1",
+ "description: value2",
+
+ "-",
+ "add: description",
+ "description: VALUE2",
+ "description: VALUE3");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().useExactMatching()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteSingleValue() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: from");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: to");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "delete: description",
+ "description: from",
+ "-",
+ "add: description",
+ "description: to");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteSingleValueExactMatch() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: VALUE");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "delete: description",
+ "description: value",
+ "-",
+ "add: description",
+ "description: VALUE");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().useExactMatching()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesAddDeleteSingleValueNoChange() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: VALUE");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify");
+ // @formatter:on
+ assertEquals(diffEntries(from, to), expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceAddTrailingAttributes() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: ignore");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: ignore",
+ "description: value",
+ "sn: value");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "replace: description",
+ "description: value",
+ "-",
+ "replace: sn",
+ "sn: value");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceDeleteTrailingAttributes() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: ignore",
+ "description: value",
+ "sn: value");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: ignore");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "replace: description",
+ "-",
+ "replace: sn");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceFilteredAttributes() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: from");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "cn: to",
+ "description: value",
+ "sn: value");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "replace: cn",
+ "cn: to",
+ "-",
+ "replace: sn",
+ "sn: value");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes().attributes("cn", "sn")),
+ expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceMultiValueChangeSize() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1",
+ "description: value2");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "replace: description",
+ "description: value1",
+ "description: value2");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceMultiValueSameSize() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1",
+ "description: value2");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: VALUE2",
+ "description: VALUE3");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "replace: description",
+ "description: VALUE2",
+ "description: VALUE3");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceMultiValueSameSizeExact() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value1",
+ "description: value2");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value2",
+ "description: value3");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "replace: description",
+ "description: value2",
+ "description: value3");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes().useExactMatching()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceSingleValue() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: from");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: to");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "replace: description",
+ "description: to");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceSingleValueExactMatch() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: VALUE");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify",
+ "replace: description",
+ "description: VALUE");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes().useExactMatching()), expected);
+ }
+
+ @Test
+ public void testDiffEntriesReplaceSingleValueNoChange() {
+ // @formatter:off
+ Entry from = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: value");
+ Entry to = new LinkedHashMapEntry(
+ "dn: cn=test",
+ "description: VALUE");
+ ModifyRequest expected = Requests.newModifyRequest(
+ "dn: cn=test",
+ "changetype: modify");
+ // @formatter:on
+ assertEquals(diffEntries(from, to, diffOptions().alwaysReplaceAttributes()), expected);
+ }
+
+ private void assertEquals(ModifyRequest actual, ModifyRequest expected) {
+ assertThat((Object) actual.getName()).isEqualTo(expected.getName());
+ assertThat(actual.getModifications()).hasSize(expected.getModifications().size());
Iterator<Modification> i1 = actual.getModifications().iterator();
Iterator<Modification> i2 = expected.getModifications().iterator();
while (i1.hasNext()) {
Modification m1 = i1.next();
Modification m2 = i2.next();
-
- Assert.assertEquals(m1.getModificationType(), m2.getModificationType());
- Assert.assertEquals(m1.getAttribute(), m2.getAttribute());
+ assertThat(m1.getModificationType()).isEqualTo(m2.getModificationType());
+ assertThat(m1.getAttribute()).isEqualTo(m2.getAttribute());
}
}
-
- @Test
- public void testContainsObjectClass() throws Exception {
- Entry entry = new LinkedHashMapEntry("dn: cn=test", "objectClass: top", "objectClass: person");
- Schema schema = Schema.getDefaultSchema();
-
- assertTrue("should contain top", Entries.containsObjectClass(entry, schema.getObjectClass("top")));
- assertTrue("should contain person", Entries.containsObjectClass(entry, schema.getObjectClass("person")));
- assertFalse("should not contain country", Entries.containsObjectClass(entry, schema.getObjectClass("country")));
- }
}
--
Gitblit v1.10.0