mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
07.35.2006 931dbfe08cd94960728174b822369fde927da257
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:

- Modify DN operations are not supported
- Operations that would attempt to delete or alter an entry after adding it

Also, this capability has been integrated into the Directory Server startup
process so that it is now possible to have a config/config-changes.ldif that
will automatically be applied to the config/config.ldif file before it gets
processed by the server. This makes it easy to make configuration changes with
the server offline without directly altering the configuration file, and can be
used to make changes to the configuration needed for running test cases.

OpenDS Issue Numbers: 622, 623
3 files added
6 files modified
1634 ■■■■■ changed files
opends/resource/bin/ldifmodify.bat 54 ●●●●● patch | view | raw | blame | history
opends/resource/bin/ldifmodify.sh 74 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigConstants.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigFileHandler.java 128 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ConfigMessages.java 40 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 108 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ToolMessages.java 326 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/LDIFModify.java 654 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Entry.java 241 ●●●●● patch | view | raw | blame | history
opends/resource/bin/ldifmodify.bat
New file
@@ -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
opends/resource/bin/ldifmodify.sh
New file
@@ -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 "$@"
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.
   */
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
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 " +
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,
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.");
  }
}
opends/src/server/org/opends/server/tools/LDIFModify.java
New file
@@ -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);
  }
}
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:
   *