From 0dc1115939d4eda4ad6559d64e2628b86ad29119 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 28 Mar 2013 11:50:36 +0000
Subject: [PATCH] Fix OPENDJ-354: Implement a RequestHandler which provides an in-memory backend

---
 opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java |  209 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 200 insertions(+), 9 deletions(-)

diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
index de5fcb4..7511b6b 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
+++ b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
@@ -22,13 +22,21 @@
  *
  *
  *      Copyright 2010 Sun Microsystems, Inc.
- *      Portions copyright 2011-2012 ForgeRock AS
+ *      Portions copyright 2011-2013 ForgeRock AS
  */
 
 package org.forgerock.opendj.ldap;
 
 import static org.forgerock.opendj.ldap.AttributeDescription.objectClass;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_DUPLICATE_VALUES;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_NO_SUCH_VALUE;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE;
+import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -37,6 +45,7 @@
 import java.util.Set;
 
 import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl;
 import org.forgerock.opendj.ldap.requests.ModifyRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.schema.ObjectClass;
@@ -179,14 +188,16 @@
         /**
          * {@inheritDoc}
          */
-        public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
+        @Override
+        public AttributeParser parseAttribute(final AttributeDescription attributeDescription) {
             return entry.parseAttribute(attributeDescription);
         }
 
         /**
          * {@inheritDoc}
          */
-        public AttributeParser parseAttribute(String attributeDescription) {
+        @Override
+        public AttributeParser parseAttribute(final String attributeDescription) {
             return entry.parseAttribute(attributeDescription);
         }
 
@@ -251,6 +262,13 @@
 
     }
 
+    private static final Comparator<Entry> COMPARATOR = new Comparator<Entry>() {
+        @Override
+        public int compare(final Entry o1, final Entry o2) {
+            return o1.getName().compareTo(o2.getName());
+        }
+    };
+
     private static final Function<Attribute, Attribute, Void> UNMODIFIABLE_ATTRIBUTE_FUNCTION =
             new Function<Attribute, Attribute, Void>() {
 
@@ -261,12 +279,6 @@
 
             };
 
-    private static final Comparator<Entry> COMPARATOR = new Comparator<Entry>() {
-        public int compare(Entry o1, Entry o2) {
-            return o1.getName().compareTo(o2.getName());
-        }
-    };
-
     /**
      * Returns a {@code Comparator} which can be used to compare entries by name
      * using the natural order for DN comparisons (parent before children).
@@ -576,6 +588,126 @@
     }
 
     /**
+     * Applies the provided modification to an entry. This method implements
+     * "permissive" modify semantics, ignoring attempts to add duplicate values
+     * or attempts to remove values which do not exist.
+     *
+     * @param entry
+     *            The entry to be modified.
+     * @param change
+     *            The modification to be applied to the entry.
+     * @return A reference to the updated entry.
+     * @throws ErrorResultException
+     *             If an error occurred while performing the change such as an
+     *             attempt to increment a value which is not a number. The entry
+     *             will not have been modified.
+     */
+    public static Entry modifyEntry(final Entry entry, final Modification change)
+            throws ErrorResultException {
+        return modifyEntry(entry, change, null);
+    }
+
+    /**
+     * Applies the provided modification to an entry. This method implements
+     * "permissive" modify semantics, recording attempts to add duplicate values
+     * or attempts to remove values which do not exist in the provided
+     * collection if provided.
+     *
+     * @param entry
+     *            The entry to be modified.
+     * @param change
+     *            The modification to be applied to the entry.
+     * @param conflictingValues
+     *            A collection into which duplicate or missing values will be
+     *            added, or {@code null} if conflicting values should not be
+     *            saved.
+     * @return A reference to the updated entry.
+     * @throws ErrorResultException
+     *             If an error occurred while performing the change such as an
+     *             attempt to increment a value which is not a number. The entry
+     *             will not have been modified.
+     */
+    public static Entry modifyEntry(final Entry entry, final Modification change,
+            final Collection<? super ByteString> conflictingValues) throws ErrorResultException {
+        final ModificationType modType = change.getModificationType();
+        if (modType.equals(ModificationType.ADD)) {
+            entry.addAttribute(change.getAttribute(), conflictingValues);
+        } else if (modType.equals(ModificationType.DELETE)) {
+            entry.removeAttribute(change.getAttribute(), conflictingValues);
+        } else if (modType.equals(ModificationType.REPLACE)) {
+            entry.replaceAttribute(change.getAttribute());
+        } else if (modType.equals(ModificationType.INCREMENT)) {
+            incrementAttribute(entry, change.getAttribute());
+        } else {
+            throw newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
+                    ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(String.valueOf(modType)).toString());
+        }
+        return entry;
+    }
+
+    /**
+     * Applies the provided modification request to an entry. This method will
+     * utilize "permissive" modify semantics if the request contains the
+     * {@link PermissiveModifyRequestControl}.
+     *
+     * @param entry
+     *            The entry to be modified.
+     * @param changes
+     *            The modification request to be applied to the entry.
+     * @return A reference to the updated entry.
+     * @throws ErrorResultException
+     *             If an error occurred while performing the changes such as an
+     *             attempt to add duplicate values, remove values which do not
+     *             exist, or increment a value which is not a number. The entry
+     *             may have been modified.
+     */
+    public static Entry modifyEntry(final Entry entry, final ModifyRequest changes)
+            throws ErrorResultException {
+        final boolean isPermissive = changes.containsControl(PermissiveModifyRequestControl.OID);
+        return modifyEntry0(entry, changes.getModifications(), isPermissive);
+    }
+
+    /**
+     * Applies the provided modifications to an entry using "permissive" modify
+     * semantics.
+     *
+     * @param entry
+     *            The entry to be modified.
+     * @param changes
+     *            The modification request to be applied to the entry.
+     * @return A reference to the updated entry.
+     * @throws ErrorResultException
+     *             If an error occurred while performing the changes such as an
+     *             attempt to increment a value which is not a number. The entry
+     *             may have been modified.
+     */
+    public static Entry modifyEntryPermissive(final Entry entry,
+            final Collection<Modification> changes) throws ErrorResultException {
+        return modifyEntry0(entry, changes, true);
+    }
+
+    /**
+     * Applies the provided modifications to an entry using "strict" modify
+     * semantics. Attempts to add duplicate values or attempts to remove values
+     * which do not exist will cause the update to fail.
+     *
+     * @param entry
+     *            The entry to be modified.
+     * @param changes
+     *            The modification request to be applied to the entry.
+     * @return A reference to the updated entry.
+     * @throws ErrorResultException
+     *             If an error occurred while performing the changes such as an
+     *             attempt to add duplicate values, remove values which do not
+     *             exist, or increment a value which is not a number. The entry
+     *             may have been modified.
+     */
+    public static Entry modifyEntryStrict(final Entry entry, final Collection<Modification> changes)
+            throws ErrorResultException {
+        return modifyEntry0(entry, changes, false);
+    }
+
+    /**
      * Returns a read-only view of {@code entry} and its attributes. Query
      * operations on the returned entry and its attributes "read-through" to the
      * underlying entry or attribute, and attempts to modify the returned entry
@@ -596,6 +728,65 @@
         }
     }
 
+    private static void incrementAttribute(final Entry entry, final Attribute change)
+            throws ErrorResultException {
+        // First parse the change.
+        final AttributeDescription deltaAd = change.getAttributeDescription();
+        if (change.size() != 1) {
+            throw newErrorResult(ResultCode.CONSTRAINT_VIOLATION,
+                    ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.get(deltaAd.toString()).toString());
+        }
+        final long delta;
+        try {
+            delta = change.parse().asLong();
+        } catch (final Exception e) {
+            throw newErrorResult(ResultCode.CONSTRAINT_VIOLATION,
+                    ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(deltaAd.toString()).toString());
+        }
+
+        // Now apply the increment to the attribute.
+        final Attribute oldAttribute = entry.getAttribute(deltaAd);
+        if (oldAttribute == null) {
+            throw newErrorResult(ResultCode.NO_SUCH_ATTRIBUTE,
+                    ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE.get(deltaAd.toString()).toString());
+        }
+
+        // Re-use existing attribute description in case it differs in case, etc.
+        final Attribute newAttribute = new LinkedAttribute(oldAttribute.getAttributeDescription());
+        try {
+            for (final Long value : oldAttribute.parse().asSetOfLong()) {
+                newAttribute.add(value + delta);
+            }
+        } catch (final Exception e) {
+            throw newErrorResult(ResultCode.CONSTRAINT_VIOLATION,
+                    ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(deltaAd.toString()).toString());
+        }
+        entry.replaceAttribute(newAttribute);
+    }
+
+    private static Entry modifyEntry0(final Entry entry, final Collection<Modification> changes,
+            final boolean isPermissive) throws ErrorResultException {
+        final Collection<ByteString> conflictingValues =
+                isPermissive ? null : new ArrayList<ByteString>(0);
+        for (final Modification change : changes) {
+            modifyEntry(entry, change, conflictingValues);
+            if (!isPermissive && !conflictingValues.isEmpty()) {
+                if (change.getModificationType().equals(ModificationType.ADD)) {
+                    // Duplicate values.
+                    throw newErrorResult(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
+                            ERR_ENTRY_DUPLICATE_VALUES.get(
+                                    change.getAttribute().getAttributeDescriptionAsString())
+                                    .toString());
+                } else {
+                    // Missing values.
+                    throw newErrorResult(ResultCode.NO_SUCH_ATTRIBUTE, ERR_ENTRY_NO_SUCH_VALUE.get(
+                            change.getAttribute().getAttributeDescriptionAsString()).toString());
+                }
+            }
+        }
+        return entry;
+    }
+
     // Prevent instantiation.
     private Entries() {
         // Nothing to do.

--
Gitblit v1.10.0