From f37f462c314da4e3e05ee893e285b8055d923f74 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Thu, 07 Sep 2006 19:35:27 +0000
Subject: [PATCH] Add a new ldifmodify tool that works like ldapmodify except that it alters data in an LDIF file rather than over LDAP.  There are some limitations, including:

---
 opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java  |   40 +
 opendj-sdk/opends/resource/bin/ldifmodify.sh                                 |   74 ++
 opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java    |  108 +++
 opendj-sdk/opends/src/server/org/opends/server/config/ConfigFileHandler.java |  128 ++++
 opendj-sdk/opends/resource/bin/ldifmodify.bat                                |   54 +
 opendj-sdk/opends/src/server/org/opends/server/types/Entry.java              |  241 ++++++++
 opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java   |    9 
 opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java    |  326 +++++++++++
 opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java         |  654 +++++++++++++++++++++++
 9 files changed, 1,633 insertions(+), 1 deletions(-)

diff --git a/opendj-sdk/opends/resource/bin/ldifmodify.bat b/opendj-sdk/opends/resource/bin/ldifmodify.bat
new file mode 100755
index 0000000..2f20be5
--- /dev/null
+++ b/opendj-sdk/opends/resource/bin/ldifmodify.bat
@@ -0,0 +1,54 @@
+
+@echo off
+rem CDDL HEADER START
+rem
+rem The contents of this file are subject to the terms of the
+rem Common Development and Distribution License, Version 1.0 only
+rem (the "License").  You may not use this file except in compliance
+rem with the License.
+rem
+rem You can obtain a copy of the license at
+rem trunk/opends/resource/legal-notices/OpenDS.LICENSE
+rem or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+rem See the License for the specific language governing permissions
+rem and limitations under the License.
+rem
+rem When distributing Covered Code, include this CDDL HEADER in each
+rem file and include the License file at
+rem trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+rem add the following below this CDDL HEADER, with the fields enclosed
+rem by brackets "[]" replaced with your own identifying * information:
+rem      Portions Copyright [yyyy] [name of copyright owner]
+rem
+rem CDDL HEADER END
+rem
+rem
+rem      Portions Copyright 2006 Sun Microsystems, Inc.
+
+setlocal
+
+set DIR_HOME=%~dP0..
+
+if "%JAVA_BIN%" == "" goto noJavaBin
+goto setClassPath
+
+:noJavaBin
+if "%JAVA_HOME%" == "" goto noJavaHome
+if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
+set JAVA_BIN="%JAVA_HOME%\bin\java.exe"
+goto setClassPath
+
+:noJavaHome
+echo Error: JAVA_HOME environment variable is not set.
+echo        Please set it to a valid Java 5 installation.
+goto end
+
+
+:setClassPath
+FOR %%x in (%DIR_HOME%\lib\*.jar) DO call "%DIR_HOME%\bin\setcp.bat" %%x
+
+%JAVA_BIN% %JAVA_ARGS% -classpath "%CLASSPATH%" org.opends.server.tools.LDIFModify -c %DIR_HOME%\config\config.ldif %*
+
+
+:end
+
diff --git a/opendj-sdk/opends/resource/bin/ldifmodify.sh b/opendj-sdk/opends/resource/bin/ldifmodify.sh
new file mode 100755
index 0000000..e464bbc
--- /dev/null
+++ b/opendj-sdk/opends/resource/bin/ldifmodify.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# 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.
+
+
+# Capture the current working directory so that we can change to it later.
+# Then capture the location of this script and the Directory Server instance
+# root so that we can use them to create appropriate paths.
+WORKING_DIR=`pwd`
+
+cd `dirname $0`
+SCRIPT_DIR=`pwd`
+
+cd ..
+INSTANCE_ROOT=`pwd`
+export INSTANCE_ROOT
+
+cd ${WORKING_DIR}
+
+
+# See if JAVA_HOME is set.  If not, then see if there is a java executable in
+# the path and try to figure it out.
+if test -z "${JAVA_BIN}"
+then
+  if test -z "${JAVA_HOME}"
+  then
+    JAVA_BIN=`which java 2> /dev/null`
+    if test $? -eq 0
+    then
+      export JAVA_BIN
+    else
+      echo "Please set JAVA_HOME to the root of a Java 5.0 installation."
+      exit 1
+    fi
+  else
+    JAVA_BIN=${JAVA_HOME}/bin/java
+    export JAVA_BIN
+  fi
+fi
+
+CLASSPATH=${INSTANCE_ROOT}/classes
+for JAR in ${INSTANCE_ROOT}/lib/*.jar
+do
+  CLASSPATH=${CLASSPATH}:${JAR}
+done
+export CLASSPATH
+
+
+${JAVA_BIN} ${JAVA_ARGS} org.opends.server.tools.LDIFModify \
+            -c ${INSTANCE_ROOT}/config/config.ldif "$@" 
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java b/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java
index 12875ff..44e2c25 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -2417,6 +2417,15 @@
 
 
   /**
+   * The base name (with no path information) of the file that may contain
+   * changes in LDIF form to apply to the configuration before the configuration
+   * is loaded and initialized.
+   */
+  public static final String CONFIG_CHANGES_NAME = "config-changes.ldif";
+
+
+
+  /**
    * The name of the directory that will hold the configuration file for the
    * Directory Server.
    */
diff --git a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigFileHandler.java b/opendj-sdk/opends/src/server/org/opends/server/config/ConfigFileHandler.java
index 33bea8b..c23021b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigFileHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/config/ConfigFileHandler.java
@@ -32,6 +32,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
+import java.io.IOException;
 import java.io.OutputStream;
 import java.security.MessageDigest;
 import java.util.Arrays;
@@ -69,6 +70,7 @@
 import org.opends.server.core.ModifyOperation;
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.SearchOperation;
+import org.opends.server.tools.LDIFModify;
 import org.opends.server.types.BackupConfig;
 import org.opends.server.types.BackupDirectory;
 import org.opends.server.types.BackupInfo;
@@ -180,10 +182,10 @@
 
     // Make sure that the configuration file exists.
     this.configFile = configFile;
+    File f = new File(configFile);
 
     try
     {
-      File f = new File(configFile);
       if (! f.exists())
       {
         int    msgID   = MSGID_CONFIG_FILE_DOES_NOT_EXIST;
@@ -210,6 +212,27 @@
     // Fixme -- Should we add a hash or signature check here?
 
 
+    // See if there is a config changes file.  If there is, then try to apply
+    // the changes contained in it.
+    File changesFile = new File(f.getParent(), CONFIG_CHANGES_NAME);
+    try
+    {
+      if (changesFile.exists())
+      {
+        applyChangesFile(f, changesFile);
+      }
+    }
+    catch (Exception e)
+    {
+      assert debugException(CLASS_NAME, "initializeConfigHandler", e);
+
+      int    msgID   = MSGID_CONFIG_UNABLE_TO_APPLY_STARTUP_CHANGES;
+      String message = getMessage(msgID, changesFile.getAbsolutePath(),
+                                  String.valueOf(e));
+      throw new InitializationException(msgID, message, e);
+    }
+
+
     // We will use the LDIF reader to read the configuration file.  Create an
     // LDIF import configuration to do this and then get the reader.
     LDIFReader reader;
@@ -588,6 +611,109 @@
 
 
   /**
+   * Applies the updates in the provided changes file to the content in the
+   * specified source file.  The result will be written to a temporary file, the
+   * current source file will be moved out of place, and then the updated file
+   * will be moved into the place of the original file.  The changes file will
+   * also be renamed so it won't be applied again.
+   * <BR><BR>
+   * If any problems are encountered, then the config initialization process
+   * will be aborted.
+   *
+   * @param  sourceFile   The LDIF file containing the source data.
+   * @param  changesFile  The LDIF file containing the changes to apply.
+   *
+   * @throws  IOException  If a problem occurs while performing disk I/O.
+   *
+   * @throws  LDIFException  If a problem occurs while trying to interpret the
+   *                         data.
+   */
+  private void applyChangesFile(File sourceFile, File changesFile)
+          throws IOException, LDIFException
+  {
+    assert debugEnter(CLASS_NAME, "applyChangesFile",
+                      String.valueOf(sourceFile), String.valueOf(changesFile));
+
+
+    // FIXME -- Do we need to do anything special for configuration archiving?
+
+
+    // Create the appropriate LDIF readers and writer.
+    LDIFImportConfig importConfig =
+         new LDIFImportConfig(sourceFile.getAbsolutePath());
+    importConfig.setValidateSchema(false);
+    LDIFReader sourceReader = new LDIFReader(importConfig);
+
+    importConfig = new LDIFImportConfig(changesFile.getAbsolutePath());
+    importConfig.setValidateSchema(false);
+    LDIFReader changesReader = new LDIFReader(importConfig);
+
+    String tempFile = changesFile.getAbsolutePath() + ".tmp";
+    LDIFExportConfig exportConfig =
+         new LDIFExportConfig(tempFile, ExistingFileBehavior.OVERWRITE);
+    LDIFWriter targetWriter = new LDIFWriter(exportConfig);
+
+
+    // Apply the changes and make sure there were no errors.
+    LinkedList<String> errorList = new LinkedList<String>();
+    boolean successful = LDIFModify.modifyLDIF(sourceReader, changesReader,
+                                               targetWriter, errorList);
+
+    try
+    {
+      sourceReader.close();
+    } catch (Exception e) {}
+
+    try
+    {
+      changesReader.close();
+    } catch (Exception e) {}
+
+    try
+    {
+      targetWriter.close();
+    } catch (Exception e) {}
+
+    if (! successful)
+    {
+      // FIXME -- Log each error message and throw an exception.
+      for (String s : errorList)
+      {
+        int    msgID   = MSGID_CONFIG_ERROR_APPLYING_STARTUP_CHANGE;
+        String message = getMessage(msgID, s);
+        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
+                 msgID, message);
+      }
+
+      int    msgID   = MSGID_CONFIG_UNABLE_TO_APPLY_CHANGES_FILE;
+      String message = getMessage(msgID);
+      throw new LDIFException(msgID, message);
+    }
+
+
+    // Move the current config file out of the way and replace it with the
+    // updated version.
+    File oldSource = new File(sourceFile.getAbsolutePath() + ".prechanges");
+    if (oldSource.exists())
+    {
+      oldSource.delete();
+    }
+    sourceFile.renameTo(oldSource);
+    new File(tempFile).renameTo(sourceFile);
+
+
+    // Move the changes file out of the way so it doesn't get applied again.
+    File newChanges = new File(changesFile.getAbsolutePath() + ".applied");
+    if (newChanges.exists())
+    {
+      newChanges.delete();
+    }
+    changesFile.renameTo(newChanges);
+  }
+
+
+
+  /**
    * Finalizes this configuration handler so that it will release any resources
    * associated with it so that it will no longer be used.  This will be called
    * when the Directory Server is shutting down, as well as in the startup
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java
index fdfb12d..02ec5d7 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java
@@ -6094,6 +6094,37 @@
 
 
   /**
+   * The message ID for the message that will be used an error occurs while
+   * attempting to apply a set of changes on server startup.  This takes two
+   * arguments, which are the path to the changes file and a message explaining
+   * the problem that occurred.
+   */
+  public static final int MSGID_CONFIG_UNABLE_TO_APPLY_STARTUP_CHANGES =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_FATAL_ERROR | 563;
+
+
+
+  /**
+   * The message ID for the message that will be used to report an error that
+   * occurred while processing a startup changes file.  This takes a single
+   * argument, which is a message explaining the problem that occurred.
+   */
+  public static final int MSGID_CONFIG_ERROR_APPLYING_STARTUP_CHANGE =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_FATAL_ERROR | 564;
+
+
+
+  /**
+   * The message ID for the message that will be used to indicate that a problem
+   * occurred while applying the startup changes.  This does not take any
+   * arguments.
+   */
+  public static final int MSGID_CONFIG_UNABLE_TO_APPLY_CHANGES_FILE =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_FATAL_ERROR | 565;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -6260,6 +6291,10 @@
     registerMessage(MSGID_CONFIG_FILE_CANNOT_VERIFY_EXISTENCE,
                     "An unexpected error occurred while attempting to " +
                     "determine whether configuration file %s exists:  %s.");
+    registerMessage(MSGID_CONFIG_UNABLE_TO_APPLY_STARTUP_CHANGES,
+                    "An error occurred while attempting to apply the changes " +
+                    "contained in file %s to the server configuration at " +
+                    "startup:  %s.");
     registerMessage(MSGID_CONFIG_FILE_CANNOT_OPEN_FOR_READ,
                     "An error occurred while attempting to open the " +
                     "configuration file %s for reading:  %s.");
@@ -6314,6 +6349,11 @@
                     "An unexpected error occurred while trying to register " +
                     "the configuration handler base DN \"%s\" as a private " +
                     "suffix with the Directory Server:  %s.");
+    registerMessage(MSGID_CONFIG_ERROR_APPLYING_STARTUP_CHANGE,
+                    "Unable to apply a change at server startup:  %s.");
+    registerMessage(MSGID_CONFIG_UNABLE_TO_APPLY_CHANGES_FILE,
+                    "One or more errors occurred while applying changes on " +
+                    "server startup.");
     registerMessage(MSGID_CONFIG_FILE_ADD_ALREADY_EXISTS,
                     "Entry %s cannot be added to the Directory Server " +
                     "configuration because another configuration entry " +
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
index f5df4dc..9b600f3 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -5845,6 +5845,88 @@
 
 
   /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add an attribute with one or more values that conflict with existing
+   * values.  This takes a single argument, which is the name of the attribute.
+   */
+  public static final int MSGID_ENTRY_DUPLICATE_VALUES =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 559;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * remove an attribute value that doesn't exist in the entry.  This takes a
+   * single argument, which is the name of the attribute.
+   */
+  public static final int MSGID_ENTRY_NO_SUCH_VALUE =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 560;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform an increment on the objectClass attribute.  This does not take any
+   * arguments.
+   */
+  public static final int MSGID_ENTRY_OC_INCREMENT_NOT_SUPPORTED =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 561;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform a modify with an unknown modification type.  This does not take any
+   * arguments.
+   */
+  public static final int MSGID_ENTRY_UNKNOWN_MODIFICATION_TYPE =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 562;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * increment an attribute that does not exist.  This takes a single argument,
+   * which is the name of the attribute.
+   */
+  public static final int MSGID_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 562;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * increment an attribute that has multiple values.  This takes a single
+   * argument, which is the name of the attribute.
+   */
+  public static final int MSGID_ENTRY_INCREMENT_MULTIPLE_VALUES =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 563;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform an increment but there was not exactly one value in the provided
+   * modification. This takes a single argument, which is the name of the
+   * attribute.
+   */
+  public static final int MSGID_ENTRY_INCREMENT_INVALID_VALUE_COUNT =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 564;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform an increment but either the current value or the increment cannot
+   * be parsed as an integer.  This takes a single argument, which is the name
+   * of the attribute.
+   */
+  public static final int MSGID_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 565;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined
    * in this class.
    */
@@ -6307,6 +6389,32 @@
     registerMessage(MSGID_ENTRY_ADD_DUPLICATE_OC,
                     "Objectclass %s is already present in entry %s and " +
                     "cannot be added a second time.");
+    registerMessage(MSGID_ENTRY_DUPLICATE_VALUES,
+                    "Unable to add one or more values to attribute %s " +
+                    "because at least one of the values already exists.");
+    registerMessage(MSGID_ENTRY_NO_SUCH_VALUE,
+                    "Unable to remove one or more values from attribute %s " +
+                    "because at least one of the attributes does not exist " +
+                    "in the entry.");
+    registerMessage(MSGID_ENTRY_OC_INCREMENT_NOT_SUPPORTED,
+                    "The increment operation is not supported for the " +
+                    "objectClass attribute.");
+    registerMessage(MSGID_ENTRY_UNKNOWN_MODIFICATION_TYPE,
+                    "Unknown modification type %s requested.");
+    registerMessage(MSGID_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE,
+                    "Unable to increment the value of attribute %s because " +
+                    "that attribute does not exist in the entry.");
+    registerMessage(MSGID_ENTRY_INCREMENT_MULTIPLE_VALUES,
+                    "Unable to increment the value of attribute %s because " +
+                    "there are multiple values for that attribute.");
+    registerMessage(MSGID_ENTRY_INCREMENT_INVALID_VALUE_COUNT,
+                    "Unable to increment the value of attribute %s because " +
+                    "the provided modification did not have exactly one " +
+                    "value to use as the increment.");
+    registerMessage(MSGID_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT,
+                    "Unable to increment the value of attribute %s because " +
+                    "either the current value or the increment could not " +
+                    "be parsed as an integer.");
 
 
     registerMessage(MSGID_SEARCH_FILTER_NULL,
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java
index d4335f1..ee87283 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java
@@ -6003,6 +6003,254 @@
 
 
   /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add an entry twice in the same set of changes.  This takes a single
+   * argument, which is the DN of the entry.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_ADD_ENTRY_TWICE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 610;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * delete an entry that had just been added in the same set of changes.  This
+   * takes a single argument, which is the DN of the entry.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_DELETE_AFTER_ADD =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 611;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * modify an entry that had just been added or deleted in the same set of
+   * changes.  This takes a single argument, which is the DN of the entry.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_MODIFY_ADDED_OR_DELETED =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 612;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform a modify DN operation.  This takes a single argument, which is the
+   * DN of the entry.
+   */
+  public static final int MSGID_LDIFMODIFY_MODDN_NOT_SUPPORTED =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 613;
+
+
+
+  /**
+   * The message ID for the message that will be used if a change record has an
+   * unknown changetype.  This takes two arguments, which are the DN of the
+   * entry and the specified changetype.
+   */
+  public static final int MSGID_LDIFMODIFY_UNKNOWN_CHANGETYPE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 614;
+
+
+
+  /**
+   * The message ID for the message that will be used if an entry to be added
+   * already exists in the data.  This takes a single argument, which is the DN
+   * of the entry.
+   */
+  public static final int MSGID_LDIFMODIFY_ADD_ALREADY_EXISTS =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 615;
+
+
+
+  /**
+   * The message ID for the message that will be used if an entry cannot be
+   * deleted because it does not exist in the data set.  This takes a single
+   * argument, which is the DN of the entry.
+   */
+  public static final int MSGID_LDIFMODIFY_DELETE_NO_SUCH_ENTRY =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 616;
+
+
+
+  /**
+   * The message ID for the message that will be used if an entry cannot be
+   * modified because it does not exist in the data set.  This takes a single
+   * argument, which is the DN of the entry.
+   */
+  public static final int MSGID_LDIFMODIFY_MODIFY_NO_SUCH_ENTRY =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 617;
+
+
+  /**
+   * The message ID for the message that will be used as the description of the
+   * configFile argument.  This does not take any arguments.
+   */
+  public static final int MSGID_LDIFMODIFY_DESCRIPTION_CONFIG_FILE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 618;
+
+
+  /**
+   * The message ID for the message that will be used as the description of the
+   * configClass argument.  This does not take any arguments.
+   */
+  public static final int MSGID_LDIFMODIFY_DESCRIPTION_CONFIG_CLASS =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 619;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * sourceLDIF argument.  It does not take any arguments.
+   */
+  public static final int MSGID_LDIFMODIFY_DESCRIPTION_SOURCE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 620;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * changesLDIF argument.  It does not take any arguments.
+   */
+  public static final int MSGID_LDIFMODIFY_DESCRIPTION_CHANGES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 621;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * targetLDIF argument.  It does not take any arguments.
+   */
+  public static final int MSGID_LDIFMODIFY_DESCRIPTION_TARGET =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 622;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * help argument.  It does not take any arguments.
+   */
+  public static final int MSGID_LDIFMODIFY_DESCRIPTION_HELP =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 623;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to initialize the command-line argument parser.  This takes a
+   * single argument, which is a message explaining the problem that occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_INITIALIZE_ARGS =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 624;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * parsing the provided command-line arguments.  This takes a single argument,
+   * which is a message explaining the problem that occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_ERROR_PARSING_ARGS =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 625;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * initializing the Directory Server JMX subsystem.  This takes two arguments,
+   * which are the path to the Directory Server configuration file and a message
+   * explaining the problem that occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_INITIALIZE_JMX =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 626;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * initializing the Directory Server configuration.  This takes two
+   * arguments, which are the path to the Directory Server configuration file
+   * and a message explaining the problem that occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_INITIALIZE_CONFIG =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 627;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * initializing the Directory Server schema.  This takes two arguments, which
+   * are the path to the Directory Server configuration file and a message
+   * explaining the problem that occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_INITIALIZE_SCHEMA =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 628;
+
+
+
+  /**
+   * The message ID for the message that will be used if the source LDIF file
+   * does not exist.  This takes a single argument, which is the path to the
+   * source LDIF file.
+   */
+  public static final int MSGID_LDIFMODIFY_SOURCE_DOES_NOT_EXIST =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 629;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to open the source LDIF file.  This takes two arguments, which are
+   * the path to the source LDIF file and a message explaining the problem that
+   * occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_OPEN_SOURCE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 630;
+
+
+
+  /**
+   * The message ID for the message that will be used if the changes LDIF file
+   * does not exist.  This takes a single argument, which is the path to the
+   * changes LDIF file.
+   */
+  public static final int MSGID_LDIFMODIFY_CHANGES_DOES_NOT_EXIST =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 631;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to open the changes LDIF file.  This takes two arguments, which are
+   * the path to the changes LDIF file and a message explaining the problem that
+   * occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_OPEN_CHANGES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 632;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to open the target LDIF file.  This takes two arguments, which are
+   * the path to the target LDIF file and a message explaining the problem that
+   * occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_CANNOT_OPEN_TARGET =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 633;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * processing the changes.  This takes a single argument, which is a message
+   * explaining the problem that occurred.
+   */
+  public static final int MSGID_LDIFMODIFY_ERROR_PROCESSING_LDIF =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 634;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -7977,6 +8225,84 @@
                     "LDIF:  %s.");
     registerMessage(MSGID_MAKELDIF_PROCESSING_COMPLETE,
                     "LDIF processing complete.  %d entries written.");
+
+
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_ADD_ENTRY_TWICE,
+                    "Entry %s is added twice in the set of changes to apply, " +
+                    "which is not supported by the LDIF modify tool.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_DELETE_AFTER_ADD,
+                    "Entry %s cannot be deleted because it was previously " +
+                    "added in the set of changes.  This is not supported by " +
+                    "the LDIF modify tool.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_MODIFY_ADDED_OR_DELETED,
+                    "Cannot modify entry %s because it was previously added " +
+                    "or deleted in the set of changes.  This is not " +
+                    "supported by the LDIF modify tool.");
+    registerMessage(MSGID_LDIFMODIFY_MODDN_NOT_SUPPORTED,
+                    "The modify DN operation targeted at entry %s cannot be " +
+                    "processed because modify DN operations are not " +
+                    "supported by the LDIF modify tool.");
+    registerMessage(MSGID_LDIFMODIFY_UNKNOWN_CHANGETYPE,
+                    "Entry %s has an unknown changetype of %s.");
+    registerMessage(MSGID_LDIFMODIFY_ADD_ALREADY_EXISTS,
+                    "Unable to add entry %s because it already exists in " +
+                    "the data set.");
+    registerMessage(MSGID_LDIFMODIFY_DELETE_NO_SUCH_ENTRY,
+                    "Unable to delete entry %s because it does not exist " +
+                    "in the data set.");
+    registerMessage(MSGID_LDIFMODIFY_MODIFY_NO_SUCH_ENTRY,
+                    "Unable to modify entry %s because it does not exist " +
+                    "in the data set.");
+    registerMessage(MSGID_LDIFMODIFY_DESCRIPTION_CONFIG_FILE,
+                    "The path to the Directory Server configuration file, " +
+                    "which will enable the use of the schema definitions " +
+                    "when processing the updates.  If it is not provided, " +
+                    "then schema processing will not be available.");
+    registerMessage(MSGID_LDIFMODIFY_DESCRIPTION_CONFIG_CLASS,
+                    "The fully-qualified name of the Java class to use as " +
+                    "the Directory Server configuration handler.  If this is " +
+                    "not provided, then a default of " +
+                    ConfigFileHandler.class.getName() + " will be used.");
+    registerMessage(MSGID_LDIFMODIFY_DESCRIPTION_SOURCE,
+                    "Specifies the LDIF file containing the data to be " +
+                    "updated.");
+    registerMessage(MSGID_LDIFMODIFY_DESCRIPTION_CHANGES,
+                    "Specifies he LDIF file containing the changes to apply.");
+    registerMessage(MSGID_LDIFMODIFY_DESCRIPTION_TARGET,
+                    "Specifies he file to which the updated data should be " +
+                    "written.");
+    registerMessage(MSGID_LDIFMODIFY_DESCRIPTION_HELP,
+                    "Displays this usage information.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_INITIALIZE_ARGS,
+                    "An unexpected error occurred while attempting to " +
+                    "initialize the command-line arguments:  %s.");
+    registerMessage(MSGID_LDIFMODIFY_ERROR_PARSING_ARGS,
+                    "An error occurred while parsing the command-line " +
+                    "arguments:  %s.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_INITIALIZE_JMX,
+                    "An error occurred while attempting to initialize the " +
+                    "Directory Server JMX subsystem based on the information " +
+                    "in configuration file %s:  %s.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_INITIALIZE_CONFIG,
+                    "An error occurred while attempting to process the " +
+                    "Directory Server configuration file %s:  %s.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_INITIALIZE_SCHEMA,
+                    "An error occurred while attempting to initialize the " +
+                    "Directory Server schema based on the information in " +
+                    "configuration file %s:  %s.");
+    registerMessage(MSGID_LDIFMODIFY_SOURCE_DOES_NOT_EXIST,
+                    "The source LDIF file %s does not exist.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_OPEN_SOURCE,
+                    "Unable to open the source LDIF file %s:  %s.");
+    registerMessage(MSGID_LDIFMODIFY_CHANGES_DOES_NOT_EXIST,
+                    "The changes LDIF file %s does not exist.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_OPEN_CHANGES,
+                    "Unable to open the changes LDIF file %s:  %s.");
+    registerMessage(MSGID_LDIFMODIFY_CANNOT_OPEN_TARGET,
+                    "Unable to open the target LDIF file %s for writing:  %s.");
+    registerMessage(MSGID_LDIFMODIFY_ERROR_PROCESSING_LDIF,
+                    "An error occurred while processing the requested " +
+                    "changes:  %s.");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java b/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java
new file mode 100644
index 0000000..6c582be
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java
@@ -0,0 +1,654 @@
+/*
+ * 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.tools;
+
+
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.config.ConfigFileHandler;
+import org.opends.server.core.DirectoryException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.ldap.LDAPException;
+import org.opends.server.protocols.ldap.LDAPModification;
+import org.opends.server.protocols.ldap.LDAPResultCode;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ExistingFileBehavior;
+import org.opends.server.types.LDIFExportConfig;
+import org.opends.server.types.LDIFImportConfig;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.util.AddChangeRecordEntry;
+import org.opends.server.util.ChangeRecordEntry;
+import org.opends.server.util.DeleteChangeRecordEntry;
+import org.opends.server.util.LDIFException;
+import org.opends.server.util.LDIFReader;
+import org.opends.server.util.LDIFWriter;
+import org.opends.server.util.ModifyChangeRecordEntry;
+import org.opends.server.util.args.ArgumentException;
+import org.opends.server.util.args.ArgumentParser;
+import org.opends.server.util.args.BooleanArgument;
+import org.opends.server.util.args.StringArgument;
+
+import static org.opends.server.messages.MessageHandler.*;
+import static org.opends.server.messages.ToolMessages.*;
+import static org.opends.server.util.StaticUtils.*;
+
+
+
+/**
+ * This class provides a program that may be used to apply a set of changes (in
+ * LDIF change format) to an LDIF file.  It will first read all of the changes
+ * into memory, and then will iterate through an LDIF file and apply them to the
+ * entries contained in it.  Note that because of the manner in which it
+ * processes the changes, certain types of operations will not be allowed,
+ * including:
+ * <BR>
+ * <UL>
+ *   <LI>Modify DN operations</LI>
+ *   <LI>Deleting an entry that has been added</LI>
+ *   <LI>Modifying an entry that has been added</LI>
+ * </UL>
+ */
+public class LDIFModify
+{
+  /**
+   * The fully-qualified name of this class for debugging purposes.
+   */
+  private static final String CLASS_NAME = "org.opends.server.tools.LDIFModify";
+
+
+
+  /**
+   * Applies the specified changes to the source LDIF, writing the modified
+   * file to the specified target.  Neither the readers nor the writer will be
+   * closed.
+   *
+   * @param  sourceReader  The LDIF reader that will be used to read the LDIF
+   *                       content to be modified.
+   * @param  changeReader  The LDIF reader that will be used to read the changes
+   *                       to be applied.
+   * @param  targetWriter  The LDIF writer that will be used to write the
+   *                       modified LDIF.
+   * @param  errorList     A list into which any error messages generated while
+   *                       processing changes may be added.
+   *
+   * @return  <CODE>true</CODE> if all updates were successfully applied, or
+   *          <CODE>false</CODE> if any errors were encountered.
+   *
+   * @throws  IOException  If a problem occurs while attempting to read the
+   *                       source or changes, or write the target.
+   *
+   * @throws  LDIFException  If a problem occurs while attempting to decode the
+   *                         source or changes, or trying to determine whether
+   *                         to include the entry in the output.
+   */
+  public static boolean modifyLDIF(LDIFReader sourceReader,
+                                   LDIFReader changeReader,
+                                   LDIFWriter targetWriter,
+                                   List<String> errorList)
+         throws IOException, LDIFException
+  {
+    // Read the changes into memory.
+    HashMap<DN,AddChangeRecordEntry> adds =
+         new HashMap<DN,AddChangeRecordEntry>();
+    HashMap<DN,DeleteChangeRecordEntry> deletes =
+         new HashMap<DN,DeleteChangeRecordEntry>();
+    HashMap<DN,LinkedList<Modification>> modifications =
+         new HashMap<DN,LinkedList<Modification>>();
+
+    while (true)
+    {
+      ChangeRecordEntry changeRecord;
+      try
+      {
+        changeRecord = changeReader.readChangeRecord(false);
+      }
+      catch (LDIFException le)
+      {
+        if (le.canContinueReading())
+        {
+          errorList.add(le.getMessage());
+          continue;
+        }
+        else
+        {
+          throw le;
+        }
+      }
+
+      if (changeRecord == null)
+      {
+        break;
+      }
+
+      DN changeDN = changeRecord.getDN();
+      switch (changeRecord.getChangeOperationType())
+      {
+        case ADD:
+          // The entry must not exist in the add list.
+          if (adds.containsKey(changeDN))
+          {
+            int msgID = MSGID_LDIFMODIFY_CANNOT_ADD_ENTRY_TWICE;
+            errorList.add(getMessage(msgID, String.valueOf(changeDN)));
+            continue;
+          }
+          else
+          {
+            adds.put(changeDN, (AddChangeRecordEntry) changeRecord);
+          }
+          break;
+
+        case DELETE:
+          // The entry must not exist in the add list.  If it exists in the
+          // modify list, then remove the changes since we won't need to apply
+          // them.
+          if (adds.containsKey(changeDN))
+          {
+            int msgID = MSGID_LDIFMODIFY_CANNOT_DELETE_AFTER_ADD;
+            errorList.add(getMessage(msgID, String.valueOf(changeDN)));
+            continue;
+          }
+          else
+          {
+            modifications.remove(changeDN);
+            deletes.put(changeDN, (DeleteChangeRecordEntry) changeRecord);
+          }
+          break;
+
+        case MODIFY:
+          // The entry must not exist in the add or delete lists.
+          if (adds.containsKey(changeDN) || deletes.containsKey(changeDN))
+          {
+            int msgID = MSGID_LDIFMODIFY_CANNOT_MODIFY_ADDED_OR_DELETED;
+            errorList.add(getMessage(msgID, String.valueOf(changeDN)));
+            continue;
+          }
+          else
+          {
+            LinkedList<Modification> mods =
+                 modifications.get(changeDN);
+            if (mods == null)
+            {
+              mods = new LinkedList<Modification>();
+              modifications.put(changeDN, mods);
+            }
+
+            for (LDAPModification mod :
+                 ((ModifyChangeRecordEntry) changeRecord).getModifications())
+            {
+              try
+              {
+                mods.add(mod.toModification());
+              }
+              catch (LDAPException le)
+              {
+                errorList.add(le.getMessage());
+                continue;
+              }
+            }
+          }
+          break;
+
+        case MODIFY_DN:
+          int msgID = MSGID_LDIFMODIFY_MODDN_NOT_SUPPORTED;
+          errorList.add(getMessage(msgID, String.valueOf(changeDN)));
+          continue;
+
+        default:
+          msgID = MSGID_LDIFMODIFY_UNKNOWN_CHANGETYPE;
+          errorList.add(getMessage(msgID, String.valueOf(changeDN),
+               String.valueOf(changeRecord.getChangeOperationType())));
+          continue;
+      }
+    }
+
+
+    // Read the source an entry at a time and apply any appropriate changes
+    // before writing to the target LDIF.
+    while (true)
+    {
+      Entry entry;
+      try
+      {
+        entry = sourceReader.readEntry();
+      }
+      catch (LDIFException le)
+      {
+        if (le.canContinueReading())
+        {
+          errorList.add(le.getMessage());
+          continue;
+        }
+        else
+        {
+          throw le;
+        }
+      }
+
+      if (entry == null)
+      {
+        break;
+      }
+
+
+      // If the entry is to be deleted, then just skip over it without writing
+      // it to the output.
+      DN entryDN = entry.getDN();
+      if (deletes.remove(entryDN) != null)
+      {
+        continue;
+      }
+
+
+      // If the entry is to be added, then that's an error, since it already
+      // exists.
+      if (adds.remove(entryDN) != null)
+      {
+        int msgID = MSGID_LDIFMODIFY_ADD_ALREADY_EXISTS;
+        errorList.add(getMessage(msgID, String.valueOf(entryDN)));
+        continue;
+      }
+
+
+      // If the entry is to be modified, then process the changes.
+      LinkedList<Modification> mods = modifications.remove(entryDN);
+      if ((mods != null) && (! mods.isEmpty()))
+      {
+        try
+        {
+          entry.applyModifications(mods);
+        }
+        catch (DirectoryException de)
+        {
+          errorList.add(de.getErrorMessage());
+          continue;
+        }
+      }
+
+
+      // If we've gotten here, then the (possibly updated) entry should be
+      // written to the output LDIF.
+      targetWriter.writeEntry(entry);
+    }
+
+
+    // Perform any adds that may be necessary.
+    for (AddChangeRecordEntry add : adds.values())
+    {
+      Map<ObjectClass,String> objectClasses =
+           new LinkedHashMap<ObjectClass,String>();
+      Map<AttributeType,List<Attribute>> userAttributes =
+           new LinkedHashMap<AttributeType,List<Attribute>>();
+      Map<AttributeType,List<Attribute>> operationalAttributes =
+           new LinkedHashMap<AttributeType,List<Attribute>>();
+
+      for (Attribute a : add.getAttributes())
+      {
+        AttributeType t = a.getAttributeType();
+        if (t.isObjectClassType())
+        {
+          for (AttributeValue v : a.getValues())
+          {
+            String stringValue = v.getStringValue();
+            String lowerValue  = toLowerCase(stringValue);
+            ObjectClass oc = DirectoryServer.getObjectClass(lowerValue, true);
+            objectClasses.put(oc, stringValue);
+          }
+        }
+        else if (t.isOperational())
+        {
+          List<Attribute> attrList = operationalAttributes.get(t);
+          if (attrList == null)
+          {
+            attrList = new LinkedList<Attribute>();
+            operationalAttributes.put(t, attrList);
+          }
+          attrList.add(a);
+        }
+        else
+        {
+          List<Attribute> attrList = userAttributes.get(t);
+          if (attrList == null)
+          {
+            attrList = new LinkedList<Attribute>();
+            userAttributes.put(t, attrList);
+          }
+          attrList.add(a);
+        }
+      }
+
+      Entry e = new Entry(add.getDN(), objectClasses, userAttributes,
+                          operationalAttributes);
+      targetWriter.writeEntry(e);
+    }
+
+
+    // If there are any entries left in the delete or modify lists, then that's
+    // a problem because they didn't exist.
+    if (! deletes.isEmpty())
+    {
+      for (DN dn : deletes.keySet())
+      {
+        int msgID = MSGID_LDIFMODIFY_DELETE_NO_SUCH_ENTRY;
+        errorList.add(getMessage(msgID, String.valueOf(dn)));
+      }
+    }
+
+    if (! modifications.isEmpty())
+    {
+      for (DN dn : modifications.keySet())
+      {
+        int msgID = MSGID_LDIFMODIFY_MODIFY_NO_SUCH_ENTRY;
+        errorList.add(getMessage(msgID, String.valueOf(dn)));
+      }
+    }
+
+    return errorList.isEmpty();
+  }
+
+
+
+  /**
+   * Invokes <CODE>ldifModifyMain</CODE> to perform the appropriate processing.
+   *
+   * @param  args  The command-line arguments provided to the client.
+   */
+  public static void main(String[] args)
+  {
+    int returnCode = ldifModifyMain(args);
+    if (returnCode != 0)
+    {
+      System.exit(returnCode);
+    }
+  }
+
+
+
+  /**
+   * Processes the command-line arguments and makes the appropriate updates to
+   * the LDIF file.
+   *
+   * @param  args  The command-line arguments provided to the client.
+   *
+   * @return  A value of zero if everything completed properly, or nonzero if
+   *          any problem(s) occurred.
+   */
+  public static int ldifModifyMain(String[] args)
+  {
+    // Prepare the argument parser.
+    BooleanArgument showUsage;
+    StringArgument  changesFile;
+    StringArgument  configClass;
+    StringArgument  configFile;
+    StringArgument  sourceFile;
+    StringArgument  targetFile;
+
+    ArgumentParser argParser = new ArgumentParser(CLASS_NAME, false);
+
+    try
+    {
+      configFile = new StringArgument("configfile", 'c', "configFile", true,
+                                      false, true, "{configFile}", null, null,
+                                      MSGID_LDIFMODIFY_DESCRIPTION_CONFIG_FILE);
+      argParser.addArgument(configFile);
+
+
+      configClass = new StringArgument("configclass", 'C', "configClass", false,
+                             false, true, "{configClass}",
+                             ConfigFileHandler.class.getName(), null,
+                             MSGID_LDIFMODIFY_DESCRIPTION_CONFIG_CLASS);
+      argParser.addArgument(configClass);
+
+
+      sourceFile = new StringArgument("sourceldif", 's', "sourceLDIF", true,
+                                      false, true, "{file}", null, null,
+                                      MSGID_LDIFMODIFY_DESCRIPTION_SOURCE);
+      argParser.addArgument(sourceFile);
+
+
+      changesFile = new StringArgument("changesldif", 'm', "changesLDIF", true,
+                                       false, true, "{file}", null, null,
+                                       MSGID_LDIFMODIFY_DESCRIPTION_CHANGES);
+      argParser.addArgument(changesFile);
+
+
+      targetFile = new StringArgument("targetldif", 't', "targetLDIF", true,
+                                      false, true, "{file}", null, null,
+                                      MSGID_LDIFMODIFY_DESCRIPTION_TARGET);
+      argParser.addArgument(targetFile);
+
+
+      showUsage = new BooleanArgument("help", 'H', "help",
+                                      MSGID_LDIFMODIFY_DESCRIPTION_HELP);
+      argParser.addArgument(showUsage);
+      argParser.setUsageArgument(showUsage);
+    }
+    catch (ArgumentException ae)
+    {
+      int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_ARGS;
+      String message = getMessage(msgID, ae.getMessage());
+      System.err.println(message);
+      return 1;
+    }
+
+
+    // Parse the command-line arguments provided to the program.
+    try
+    {
+      argParser.parseArguments(args);
+    }
+    catch (ArgumentException ae)
+    {
+      int    msgID   = MSGID_LDIFMODIFY_ERROR_PARSING_ARGS;
+      String message = getMessage(msgID, ae.getMessage());
+
+      System.err.println(message);
+      System.err.println(argParser.getUsage());
+      return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+    }
+
+
+    // If we should just display usage information, then print it and exit.
+    if (showUsage.isPresent())
+    {
+      return 0;
+    }
+
+
+    // Bootstrap the Directory Server configuration for use as a client.
+    DirectoryServer directoryServer = DirectoryServer.getInstance();
+    directoryServer.bootstrapClient();
+
+
+    // If we're to use the configuration then initialize it, along with the
+    // schema.
+    boolean checkSchema = configFile.isPresent();
+    if (checkSchema)
+    {
+      try
+      {
+        directoryServer.initializeJMX();
+      }
+      catch (Exception e)
+      {
+        int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_JMX;
+        String message = getMessage(msgID,
+                                    String.valueOf(configFile.getValue()),
+                                    e.getMessage());
+        System.err.println(message);
+        return 1;
+      }
+
+      try
+      {
+        directoryServer.initializeConfiguration(configClass.getValue(),
+                                                configFile.getValue());
+      }
+      catch (Exception e)
+      {
+        int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_CONFIG;
+        String message = getMessage(msgID,
+                                    String.valueOf(configFile.getValue()),
+                                    e.getMessage());
+        System.err.println(message);
+        return 1;
+      }
+
+      try
+      {
+        directoryServer.initializeSchema();
+      }
+      catch (Exception e)
+      {
+        int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_SCHEMA;
+        String message = getMessage(msgID,
+                                    String.valueOf(configFile.getValue()),
+                                    e.getMessage());
+        System.err.println(message);
+        return 1;
+      }
+    }
+
+
+    // Create the LDIF readers and writer from the arguments.
+    File source = new File(sourceFile.getValue());
+    if (! source.exists())
+    {
+      int    msgID   = MSGID_LDIFMODIFY_SOURCE_DOES_NOT_EXIST;
+      String message = getMessage(msgID, sourceFile.getValue());
+      System.err.println(message);
+      return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+    }
+
+    LDIFImportConfig importConfig = new LDIFImportConfig(sourceFile.getValue());
+    LDIFReader sourceReader;
+    try
+    {
+      sourceReader = new LDIFReader(importConfig);
+    }
+    catch (IOException ioe)
+    {
+      int    msgID   = MSGID_LDIFMODIFY_CANNOT_OPEN_SOURCE;
+      String message = getMessage(msgID, sourceFile.getValue(),
+                                  String.valueOf(ioe));
+      System.err.println(message);
+      return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
+    }
+
+
+    File changes = new File(changesFile.getValue());
+    if (! changes.exists())
+    {
+      int    msgID   = MSGID_LDIFMODIFY_CHANGES_DOES_NOT_EXIST;
+      String message = getMessage(msgID, changesFile.getValue());
+      System.err.println(message);
+      return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+    }
+
+    importConfig = new LDIFImportConfig(changesFile.getValue());
+    LDIFReader changeReader;
+    try
+    {
+      changeReader = new LDIFReader(importConfig);
+    }
+    catch (IOException ioe)
+    {
+      int    msgID   = MSGID_LDIFMODIFY_CANNOT_OPEN_CHANGES;
+      String message = getMessage(msgID, sourceFile.getValue());
+      System.err.println(message);
+      return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
+    }
+
+
+    LDIFExportConfig exportConfig =
+         new LDIFExportConfig(targetFile.getValue(),
+                              ExistingFileBehavior.OVERWRITE);
+    LDIFWriter targetWriter;
+    try
+    {
+      targetWriter = new LDIFWriter(exportConfig);
+    }
+    catch (IOException ioe)
+    {
+      int    msgID   = MSGID_LDIFMODIFY_CANNOT_OPEN_TARGET;
+      String message = getMessage(msgID, sourceFile.getValue());
+      System.err.println(message);
+      return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
+    }
+
+
+    // Actually invoke the LDIF procesing.
+    LinkedList<String> errorList = new LinkedList<String>();
+    boolean successful;
+    try
+    {
+      successful = modifyLDIF(sourceReader, changeReader, targetWriter,
+                              errorList);
+    }
+    catch (Exception e)
+    {
+      int    msgID   = MSGID_LDIFMODIFY_ERROR_PROCESSING_LDIF;
+      String message = getMessage(msgID, String.valueOf(e));
+      System.err.println(message);
+
+      successful = false;
+    }
+
+    try
+    {
+      sourceReader.close();
+    } catch (Exception e) {}
+
+    try
+    {
+      changeReader.close();
+    } catch (Exception e) {}
+
+    try
+    {
+      targetWriter.close();
+    } catch (Exception e) {}
+
+    for (String s : errorList)
+    {
+      System.err.println(s);
+    }
+    return (successful ? 0 : 1);
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java b/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
index 6d65386..fa2ccd2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
@@ -1765,6 +1765,247 @@
 
 
   /**
+   * Applies the provided modification to this entry.  No schema
+   * checking will be performed.
+   *
+   * @param  mod  The modification to apply to this entry.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to apply the modification.  Note
+   *                              that even if a problem occurs, then
+   *                              the entry may have been altered in
+   *                              some way.
+   */
+  public void applyModification(Modification mod)
+         throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "applyModification",
+                      String.valueOf(mod));
+
+    Attribute     a = mod.getAttribute();
+    AttributeType t = a.getAttributeType();
+
+    // We'll need to handle changes to the objectclass attribute in a
+    // special way.
+    if (t.isObjectClassType())
+    {
+      LinkedHashMap<ObjectClass,String> ocs = new
+             LinkedHashMap<ObjectClass,String>();
+      for (AttributeValue v : a.getValues())
+      {
+        String ocName    = v.getStringValue();
+        String lowerName = toLowerCase(ocName);
+        ObjectClass oc   =
+             DirectoryServer.getObjectClass(lowerName, true);
+        ocs.put(oc, ocName);
+      }
+
+      switch (mod.getModificationType())
+      {
+        case ADD:
+          for (ObjectClass oc : ocs.keySet())
+          {
+            if (objectClasses.containsKey(oc))
+            {
+              int    msgID   = MSGID_ENTRY_DUPLICATE_VALUES;
+              String message = getMessage(msgID, a.getName());
+              throw new DirectoryException(
+                             ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
+                             message, msgID);
+            }
+            else
+            {
+              objectClasses.put(oc, ocs.get(oc));
+            }
+          }
+          break;
+
+        case DELETE:
+          for (ObjectClass oc : ocs.keySet())
+          {
+            if (objectClasses.remove(oc) == null)
+            {
+              int    msgID   = MSGID_ENTRY_NO_SUCH_VALUE;
+              String message = getMessage(msgID, a.getName());
+              throw new DirectoryException(
+                             ResultCode.NO_SUCH_ATTRIBUTE, message,
+                             msgID);
+            }
+          }
+          break;
+
+        case REPLACE:
+          objectClasses = ocs;
+          break;
+
+        case INCREMENT:
+          int msgID = MSGID_ENTRY_OC_INCREMENT_NOT_SUPPORTED;
+          String message = getMessage(msgID);
+          throw new DirectoryException(
+                         ResultCode.UNWILLING_TO_PERFORM, message,
+                         msgID);
+
+        default:
+          msgID   = MSGID_ENTRY_UNKNOWN_MODIFICATION_TYPE;
+          message = getMessage(msgID,
+                         String.valueOf(mod.getModificationType()));
+          throw new DirectoryException(
+                         ResultCode.UNWILLING_TO_PERFORM, message,
+                         msgID);
+      }
+
+      return;
+    }
+
+    switch (mod.getModificationType())
+    {
+      case ADD:
+        LinkedList<AttributeValue> duplicateValues =
+             new LinkedList<AttributeValue>();
+        addAttribute(a, duplicateValues);
+        if (! duplicateValues.isEmpty())
+        {
+          int    msgID   = MSGID_ENTRY_DUPLICATE_VALUES;
+          String message = getMessage(msgID, a.getName());
+          throw new DirectoryException(
+                         ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
+                         message, msgID);
+        }
+        break;
+
+      case DELETE:
+        LinkedList<AttributeValue> missingValues =
+             new LinkedList<AttributeValue>();
+        removeAttribute(a, missingValues);
+        if (! missingValues.isEmpty())
+        {
+          int    msgID   = MSGID_ENTRY_NO_SUCH_VALUE;
+          String message = getMessage(msgID, a.getName());
+          throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
+                                       message, msgID);
+        }
+        break;
+
+      case REPLACE:
+        removeAttribute(t, a.getOptions());
+
+        if (a.hasValue())
+        {
+          // We know that we won't have any duplicate values, so  we
+          // don't kneed to worry about checking for them.
+          duplicateValues = new LinkedList<AttributeValue>();
+          addAttribute(a, duplicateValues);
+        }
+        break;
+
+      case INCREMENT:
+        List<Attribute> attrList = getAttribute(t);
+        if ((attrList == null) || attrList.isEmpty())
+        {
+          int    msgID   = MSGID_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE;
+          String message = getMessage(msgID, a.getName());
+          throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
+                                       message, msgID);
+        }
+        else if (attrList.size() != 1)
+        {
+          int    msgID   = MSGID_ENTRY_INCREMENT_MULTIPLE_VALUES;
+          String message = getMessage(msgID, a.getName());
+          throw new DirectoryException(
+                         ResultCode.CONSTRAINT_VIOLATION, message,
+                         msgID);
+        }
+
+        LinkedHashSet<AttributeValue> values =
+             attrList.get(0).getValues();
+        if (values.isEmpty())
+        {
+          int    msgID   = MSGID_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE;
+          String message = getMessage(msgID, a.getName());
+          throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
+                                       message, msgID);
+        }
+        else if (values.size() > 1)
+        {
+          int    msgID   = MSGID_ENTRY_INCREMENT_MULTIPLE_VALUES;
+          String message = getMessage(msgID, a.getName());
+          throw new DirectoryException(
+                         ResultCode.CONSTRAINT_VIOLATION, message,
+                         msgID);
+        }
+
+        LinkedHashSet<AttributeValue> newValues = a.getValues();
+        if (newValues.size() != 1)
+        {
+          int msgID = MSGID_ENTRY_INCREMENT_INVALID_VALUE_COUNT;
+          String message = getMessage(msgID);
+          throw new DirectoryException(
+                         ResultCode.CONSTRAINT_VIOLATION, message,
+                         msgID);
+        }
+
+        long newValue;
+        try
+        {
+          String s = values.iterator().next().getStringValue();
+          long currentValue = Long.parseLong(s);
+
+          s = a.getValues().iterator().next().getStringValue();
+          long increment = Long.parseLong(s);
+
+          newValue = currentValue+increment;
+        }
+        catch (NumberFormatException nfe)
+        {
+          int msgID = MSGID_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT;
+          String message = getMessage(msgID);
+          throw new DirectoryException(
+                         ResultCode.CONSTRAINT_VIOLATION, message,
+                         msgID);
+        }
+
+        values.clear();
+        values.add(new AttributeValue(t, String.valueOf(newValue)));
+        break;
+
+      default:
+        int    msgID   = MSGID_ENTRY_UNKNOWN_MODIFICATION_TYPE;
+        String message =
+             getMessage(msgID,
+                        String.valueOf(mod.getModificationType()));
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                     message, msgID);
+    }
+  }
+
+
+
+  /**
+   * Applies all of the provided modifications to this entry.
+   *
+   * @param  mods  The modifications to apply to this entry.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to apply the modifications.  Note
+   *                              that even if a problem occurs, then
+   *                              the entry may have been altered in
+   *                              some way.
+   */
+  public void applyModifications(List<Modification> mods)
+         throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "applyModifications",
+                      String.valueOf(mods));
+
+    for (Modification m : mods)
+    {
+      applyModification(m);
+    }
+  }
+
+
+
+  /**
    * Indicates whether this entry conforms to the server's schema
    * requirements.  The checks performed by this method include:
    *

--
Gitblit v1.10.0