/*
|
* 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 legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* 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 legal-notices/CDDLv1_0.txt.
|
* 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
|
*
|
*
|
* Copyright 2006-2009 Sun Microsystems, Inc.
|
* Portions Copyright 2013-2015 ForgeRock AS.
|
*/
|
package org.opends.server.tools;
|
import java.io.BufferedReader;
|
import java.io.FileReader;
|
import java.io.IOException;
|
import java.io.OutputStream;
|
import java.io.PrintStream;
|
import java.util.Collection;
|
import java.util.HashSet;
|
import java.util.Iterator;
|
import java.util.LinkedHashSet;
|
import java.util.LinkedList;
|
import java.util.List;
|
import java.util.ListIterator;
|
import java.util.TreeMap;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.opends.server.core.DirectoryServer;
|
import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
|
import org.opends.server.extensions.ConfigFileHandler;
|
import org.opends.server.loggers.JDKLogging;
|
import org.opends.server.types.Attribute;
|
import org.opends.server.types.AttributeBuilder;
|
import org.opends.server.types.AttributeType;
|
import org.forgerock.opendj.ldap.ByteString;
|
import org.opends.server.types.DN;
|
import org.opends.server.types.DirectoryException;
|
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.forgerock.opendj.ldap.ModificationType;
|
import org.opends.server.types.NullOutputStream;
|
import org.opends.server.types.ObjectClass;
|
import org.opends.server.util.LDIFReader;
|
import org.opends.server.util.LDIFWriter;
|
import org.opends.server.util.StaticUtils;
|
|
import com.forgerock.opendj.cli.ArgumentException;
|
import com.forgerock.opendj.cli.ArgumentParser;
|
import com.forgerock.opendj.cli.BooleanArgument;
|
import com.forgerock.opendj.cli.CommonArguments;
|
import com.forgerock.opendj.cli.StringArgument;
|
|
import static org.opends.messages.ToolMessages.*;
|
import static org.opends.server.protocols.ldap.LDAPResultCode.*;
|
import static com.forgerock.opendj.cli.ArgumentConstants.*;
|
import static org.opends.server.util.ServerConstants.*;
|
import static com.forgerock.opendj.cli.Utils.filterExitCode;
|
|
|
|
/**
|
* This class provides a program that may be used to determine the differences
|
* between two LDIF files, generating the output in LDIF change format. There
|
* are several things to note about the operation of this program:
|
* <BR>
|
* <UL>
|
* <LI>This program is only designed for cases in which both LDIF files to be
|
* compared will fit entirely in memory at the same time.</LI>
|
* <LI>This program will only compare live data in the LDIF files and will
|
* ignore comments and other elements that do not have any real impact on
|
* the way that the data is interpreted.</LI>
|
* <LI>The differences will be generated in such a way as to provide the
|
* maximum amount of information, so that there will be enough information
|
* for the changes to be reversed (i.e., it will not use the "replace"
|
* modification type but only the "add" and "delete" types, and contents
|
* of deleted entries will be included as comments).</LI>
|
* </UL>
|
*
|
*
|
* Note
|
* that this is only an option for cases in which both LDIF files can fit in
|
* memory. Also note that this will only compare live data in the LDIF files
|
* and will ignore comments and other elements that do not have any real impact
|
* on the way that the data is interpreted.
|
*/
|
public class LDIFDiff
|
{
|
/**
|
* The fully-qualified name of this class.
|
*/
|
private static final String CLASS_NAME = "org.opends.server.tools.LDIFDiff";
|
|
|
|
/**
|
* Provides the command line arguments to the <CODE>mainDiff</CODE> method
|
* so that they can be processed.
|
*
|
* @param args The command line arguments provided to this program.
|
*/
|
public static void main(String[] args)
|
{
|
int exitCode = mainDiff(args, false, System.out, System.err);
|
if (exitCode != 0)
|
{
|
System.exit(filterExitCode(exitCode));
|
}
|
}
|
|
|
|
/**
|
* Parses the provided command line arguments and performs the appropriate
|
* LDIF diff operation.
|
*
|
* @param args The command line arguments provided to this
|
* program.
|
* @param serverInitialized Indicates whether the Directory Server has
|
* already been initialized (and therefore should
|
* not be initialized a second time).
|
* @param outStream The output stream to use for standard output, or
|
* {@code null} if standard output is not needed.
|
* @param errStream The output stream to use for standard error, or
|
* {@code null} if standard error is not needed.
|
*
|
* @return The return code for this operation. A value of zero indicates
|
* that all processing completed successfully. A nonzero value
|
* indicates that some problem occurred during processing.
|
*/
|
public static int mainDiff(String[] args, boolean serverInitialized,
|
OutputStream outStream, OutputStream errStream)
|
{
|
PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
|
PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
|
JDKLogging.disableLogging();
|
|
BooleanArgument overwriteExisting;
|
BooleanArgument showUsage;
|
BooleanArgument useCompareResultCode;
|
BooleanArgument singleValueChanges;
|
BooleanArgument doCheckSchema;
|
StringArgument configClass;
|
StringArgument configFile;
|
StringArgument outputLDIF;
|
StringArgument sourceLDIF;
|
StringArgument targetLDIF;
|
StringArgument ignoreAttrsFile;
|
StringArgument ignoreEntriesFile;
|
|
|
LocalizableMessage toolDescription = INFO_LDIFDIFF_TOOL_DESCRIPTION.get();
|
ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
|
false);
|
argParser.setVersionHandler(new DirectoryServerVersionHandler());
|
try
|
{
|
sourceLDIF = new StringArgument(
|
"sourceldif", 's', "sourceLDIF", true,
|
false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
|
INFO_LDIFDIFF_DESCRIPTION_SOURCE_LDIF.get());
|
argParser.addArgument(sourceLDIF);
|
|
targetLDIF = new StringArgument(
|
"targetldif", 't', "targetLDIF", true,
|
false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
|
INFO_LDIFDIFF_DESCRIPTION_TARGET_LDIF.get());
|
argParser.addArgument(targetLDIF);
|
|
outputLDIF = new StringArgument(
|
"outputldif", 'o', "outputLDIF", false,
|
false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
|
INFO_LDIFDIFF_DESCRIPTION_OUTPUT_LDIF.get());
|
argParser.addArgument(outputLDIF);
|
|
ignoreAttrsFile = new StringArgument(
|
"ignoreattrs", 'a', "ignoreAttrs", false,
|
false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
|
INFO_LDIFDIFF_DESCRIPTION_IGNORE_ATTRS.get());
|
argParser.addArgument(ignoreAttrsFile);
|
|
ignoreEntriesFile = new StringArgument(
|
"ignoreentries", 'e', "ignoreEntries", false,
|
false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
|
INFO_LDIFDIFF_DESCRIPTION_IGNORE_ENTRIES.get());
|
argParser.addArgument(ignoreEntriesFile);
|
|
overwriteExisting =
|
new BooleanArgument(
|
"overwriteexisting", 'O',
|
"overwriteExisting",
|
INFO_LDIFDIFF_DESCRIPTION_OVERWRITE_EXISTING.get());
|
argParser.addArgument(overwriteExisting);
|
|
singleValueChanges =
|
new BooleanArgument(
|
"singlevaluechanges", 'S', "singleValueChanges",
|
INFO_LDIFDIFF_DESCRIPTION_SINGLE_VALUE_CHANGES.get());
|
argParser.addArgument(singleValueChanges);
|
|
doCheckSchema =
|
new BooleanArgument(
|
"checkschema", null, "checkSchema",
|
INFO_LDIFDIFF_DESCRIPTION_CHECK_SCHEMA.get());
|
argParser.addArgument(doCheckSchema);
|
|
configFile = new StringArgument("configfile", 'c', "configFile", false,
|
false, true,
|
INFO_CONFIGFILE_PLACEHOLDER.get(), null,
|
null,
|
INFO_DESCRIPTION_CONFIG_FILE.get());
|
configFile.setHidden(true);
|
argParser.addArgument(configFile);
|
|
configClass = new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS,
|
OPTION_LONG_CONFIG_CLASS, false,
|
false, true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
|
ConfigFileHandler.class.getName(), null,
|
INFO_DESCRIPTION_CONFIG_CLASS.get());
|
configClass.setHidden(true);
|
argParser.addArgument(configClass);
|
|
showUsage = CommonArguments.getShowUsage();
|
argParser.addArgument(showUsage);
|
|
useCompareResultCode =
|
new BooleanArgument("usecompareresultcode", 'r',
|
"useCompareResultCode",
|
INFO_LDIFDIFF_DESCRIPTION_USE_COMPARE_RESULT.get());
|
argParser.addArgument(useCompareResultCode);
|
|
argParser.setUsageArgument(showUsage);
|
}
|
catch (ArgumentException ae)
|
{
|
LocalizableMessage message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
|
err.println(message);
|
return OPERATIONS_ERROR;
|
}
|
|
|
// Parse the command-line arguments provided to the program.
|
try
|
{
|
argParser.parseArguments(args);
|
}
|
catch (ArgumentException ae)
|
{
|
LocalizableMessage message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
|
err.println(message);
|
err.println(argParser.getUsage());
|
return CLIENT_SIDE_PARAM_ERROR;
|
}
|
|
|
// If we should just display usage or version information,
|
// then print it and exit.
|
if (argParser.usageOrVersionDisplayed())
|
{
|
return SUCCESS;
|
}
|
|
if (doCheckSchema.isPresent() && !configFile.isPresent())
|
{
|
String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
|
if (scriptName == null)
|
{
|
scriptName = "ldif-diff";
|
}
|
LocalizableMessage message = WARN_LDIFDIFF_NO_CONFIG_FILE.get(scriptName);
|
err.println(message);
|
}
|
|
|
boolean checkSchema = configFile.isPresent() && doCheckSchema.isPresent();
|
if (! serverInitialized)
|
{
|
// 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.
|
if (checkSchema)
|
{
|
try
|
{
|
DirectoryServer.initializeJMX();
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_CANNOT_INITIALIZE_JMX.get(configFile.getValue(), e.getMessage()));
|
return OPERATIONS_ERROR;
|
}
|
|
try
|
{
|
directoryServer.initializeConfiguration(configClass.getValue(),
|
configFile.getValue());
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_CANNOT_INITIALIZE_CONFIG.get(configFile.getValue(), e.getMessage()));
|
return OPERATIONS_ERROR;
|
}
|
|
try
|
{
|
directoryServer.initializeSchema();
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_CANNOT_INITIALIZE_SCHEMA.get(configFile.getValue(), e.getMessage()));
|
return OPERATIONS_ERROR;
|
}
|
}
|
}
|
|
// Read in ignored entries and attributes if any
|
BufferedReader ignReader = null;
|
Collection<DN> ignoreEntries = new HashSet<DN>();
|
Collection<String> ignoreAttrs = new HashSet<String>();
|
|
if (ignoreAttrsFile.getValue() != null)
|
{
|
try
|
{
|
ignReader = new BufferedReader(
|
new FileReader(ignoreAttrsFile.getValue()));
|
String line = null;
|
while ((line = ignReader.readLine()) != null)
|
{
|
ignoreAttrs.add(line.toLowerCase());
|
}
|
ignReader.close();
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_CANNOT_READ_FILE_IGNORE_ATTRIBS.get(ignoreAttrsFile.getValue(), e));
|
return OPERATIONS_ERROR;
|
}
|
finally
|
{
|
StaticUtils.close(ignReader);
|
}
|
}
|
|
if (ignoreEntriesFile.getValue() != null)
|
{
|
try
|
{
|
ignReader = new BufferedReader(
|
new FileReader(ignoreEntriesFile.getValue()));
|
String line = null;
|
while ((line = ignReader.readLine()) != null)
|
{
|
try
|
{
|
DN dn = DN.valueOf(line);
|
ignoreEntries.add(dn);
|
}
|
catch (DirectoryException e)
|
{
|
LocalizableMessage message = INFO_LDIFDIFF_CANNOT_PARSE_STRING_AS_DN.get(
|
line, ignoreEntriesFile.getValue());
|
err.println(message);
|
}
|
}
|
ignReader.close();
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_CANNOT_READ_FILE_IGNORE_ENTRIES.get(ignoreEntriesFile.getValue(), e));
|
return OPERATIONS_ERROR;
|
}
|
finally
|
{
|
StaticUtils.close(ignReader);
|
}
|
}
|
|
// Open the source LDIF file and read it into a tree map.
|
LDIFReader reader;
|
LDIFImportConfig importConfig = new LDIFImportConfig(sourceLDIF.getValue());
|
try
|
{
|
reader = new LDIFReader(importConfig);
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_CANNOT_OPEN_SOURCE_LDIF.get(sourceLDIF.getValue(), e));
|
return OPERATIONS_ERROR;
|
}
|
|
TreeMap<DN,Entry> sourceMap = new TreeMap<DN,Entry>();
|
try
|
{
|
while (true)
|
{
|
Entry entry = reader.readEntry(checkSchema);
|
if (entry == null)
|
{
|
break;
|
}
|
|
if (! ignoreEntries.contains(entry.getName()))
|
{
|
sourceMap.put(entry.getName(), entry);
|
}
|
}
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_ERROR_READING_SOURCE_LDIF.get(sourceLDIF.getValue(), e));
|
return OPERATIONS_ERROR;
|
}
|
finally
|
{
|
StaticUtils.close(reader);
|
}
|
|
|
// Open the target LDIF file and read it into a tree map.
|
importConfig = new LDIFImportConfig(targetLDIF.getValue());
|
try
|
{
|
reader = new LDIFReader(importConfig);
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_CANNOT_OPEN_TARGET_LDIF.get(targetLDIF.getValue(), e));
|
return OPERATIONS_ERROR;
|
}
|
|
TreeMap<DN,Entry> targetMap = new TreeMap<DN,Entry>();
|
try
|
{
|
while (true)
|
{
|
Entry entry = reader.readEntry(checkSchema);
|
if (entry == null)
|
{
|
break;
|
}
|
|
if (! ignoreEntries.contains(entry.getName()))
|
{
|
targetMap.put(entry.getName(), entry);
|
}
|
}
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_ERROR_READING_TARGET_LDIF.get(targetLDIF.getValue(), e));
|
return OPERATIONS_ERROR;
|
}
|
finally
|
{
|
StaticUtils.close(reader);
|
}
|
|
|
// Open the output writer that we'll use to write the differences.
|
LDIFWriter writer;
|
try
|
{
|
LDIFExportConfig exportConfig;
|
if (outputLDIF.isPresent())
|
{
|
if (overwriteExisting.isPresent())
|
{
|
exportConfig = new LDIFExportConfig(outputLDIF.getValue(),
|
ExistingFileBehavior.OVERWRITE);
|
}
|
else
|
{
|
exportConfig = new LDIFExportConfig(outputLDIF.getValue(),
|
ExistingFileBehavior.APPEND);
|
}
|
}
|
else
|
{
|
exportConfig = new LDIFExportConfig(out);
|
}
|
|
writer = new LDIFWriter(exportConfig);
|
}
|
catch (Exception e)
|
{
|
err.println(ERR_LDIFDIFF_CANNOT_OPEN_OUTPUT.get(e));
|
return OPERATIONS_ERROR;
|
}
|
|
|
try
|
{
|
boolean differenceFound;
|
|
// Check to see if either or both of the source and target maps are empty.
|
if (sourceMap.isEmpty())
|
{
|
if (targetMap.isEmpty())
|
{
|
// They're both empty, so there are no differences.
|
differenceFound = false;
|
}
|
else
|
{
|
// The target isn't empty, so they're all adds.
|
Iterator<DN> targetIterator = targetMap.keySet().iterator();
|
while (targetIterator.hasNext())
|
{
|
writeAdd(writer, targetMap.get(targetIterator.next()));
|
}
|
differenceFound = true;
|
}
|
}
|
else if (targetMap.isEmpty())
|
{
|
// The source isn't empty, so they're all deletes.
|
Iterator<DN> sourceIterator = sourceMap.keySet().iterator();
|
while (sourceIterator.hasNext())
|
{
|
writeDelete(writer, sourceMap.get(sourceIterator.next()));
|
}
|
differenceFound = true;
|
}
|
else
|
{
|
differenceFound = false;
|
// Iterate through all the entries in the source and target maps and
|
// identify the differences.
|
Iterator<DN> sourceIterator = sourceMap.keySet().iterator();
|
Iterator<DN> targetIterator = targetMap.keySet().iterator();
|
DN sourceDN = sourceIterator.next();
|
DN targetDN = targetIterator.next();
|
Entry sourceEntry = sourceMap.get(sourceDN);
|
Entry targetEntry = targetMap.get(targetDN);
|
|
while (true)
|
{
|
// Compare the DNs to determine the relative order of the
|
// entries.
|
int comparatorValue = sourceDN.compareTo(targetDN);
|
if (comparatorValue < 0)
|
{
|
// The source entry should be before the target entry, which means
|
// that the source entry has been deleted.
|
writeDelete(writer, sourceEntry);
|
differenceFound = true;
|
if (sourceIterator.hasNext())
|
{
|
sourceDN = sourceIterator.next();
|
sourceEntry = sourceMap.get(sourceDN);
|
}
|
else
|
{
|
// There are no more source entries, so if there are more target
|
// entries then they're all adds.
|
writeAdd(writer, targetEntry);
|
|
while (targetIterator.hasNext())
|
{
|
targetDN = targetIterator.next();
|
targetEntry = targetMap.get(targetDN);
|
writeAdd(writer, targetEntry);
|
differenceFound = true;
|
}
|
|
break;
|
}
|
}
|
else if (comparatorValue > 0)
|
{
|
// The target entry should be before the source entry, which means
|
// that the target entry has been added.
|
writeAdd(writer, targetEntry);
|
differenceFound = true;
|
if (targetIterator.hasNext())
|
{
|
targetDN = targetIterator.next();
|
targetEntry = targetMap.get(targetDN);
|
}
|
else
|
{
|
// There are no more target entries so all of the remaining source
|
// entries are deletes.
|
writeDelete(writer, sourceEntry);
|
differenceFound = true;
|
while (sourceIterator.hasNext())
|
{
|
sourceDN = sourceIterator.next();
|
sourceEntry = sourceMap.get(sourceDN);
|
writeDelete(writer, sourceEntry);
|
}
|
|
break;
|
}
|
}
|
else
|
{
|
// The DNs are the same, so check to see if the entries are the
|
// same or have been modified.
|
if (writeModify(writer, sourceEntry, targetEntry, ignoreAttrs,
|
singleValueChanges.isPresent()))
|
{
|
differenceFound = true;
|
}
|
|
if (sourceIterator.hasNext())
|
{
|
sourceDN = sourceIterator.next();
|
sourceEntry = sourceMap.get(sourceDN);
|
}
|
else
|
{
|
// There are no more source entries, so if there are more target
|
// entries then they're all adds.
|
while (targetIterator.hasNext())
|
{
|
targetDN = targetIterator.next();
|
targetEntry = targetMap.get(targetDN);
|
writeAdd(writer, targetEntry);
|
differenceFound = true;
|
}
|
|
break;
|
}
|
|
if (targetIterator.hasNext())
|
{
|
targetDN = targetIterator.next();
|
targetEntry = targetMap.get(targetDN);
|
}
|
else
|
{
|
// There are no more target entries so all of the remaining source
|
// entries are deletes.
|
writeDelete(writer, sourceEntry);
|
differenceFound = true;
|
while (sourceIterator.hasNext())
|
{
|
sourceDN = sourceIterator.next();
|
sourceEntry = sourceMap.get(sourceDN);
|
writeDelete(writer, sourceEntry);
|
}
|
|
break;
|
}
|
}
|
}
|
}
|
|
if (!differenceFound)
|
{
|
LocalizableMessage message = INFO_LDIFDIFF_NO_DIFFERENCES.get();
|
writer.writeComment(message, 0);
|
}
|
if (useCompareResultCode.isPresent())
|
{
|
return !differenceFound ? COMPARE_TRUE : COMPARE_FALSE;
|
}
|
}
|
catch (IOException e)
|
{
|
err.println(ERR_LDIFDIFF_ERROR_WRITING_OUTPUT.get(e));
|
return OPERATIONS_ERROR;
|
}
|
finally
|
{
|
StaticUtils.close(writer);
|
}
|
|
|
// If we've gotten to this point, then everything was successful.
|
return SUCCESS;
|
}
|
|
|
|
/**
|
* Writes an add change record to the LDIF writer.
|
*
|
* @param writer The writer to which the add record should be written.
|
* @param entry The entry that has been added.
|
*
|
* @throws IOException If a problem occurs while attempting to write the add
|
* record.
|
*/
|
private static void writeAdd(LDIFWriter writer, Entry entry)
|
throws IOException
|
{
|
writer.writeAddChangeRecord(entry);
|
writer.flush();
|
}
|
|
|
|
/**
|
* Writes a delete change record to the LDIF writer, including a comment
|
* with the contents of the deleted entry.
|
*
|
* @param writer The writer to which the delete record should be written.
|
* @param entry The entry that has been deleted.
|
*
|
* @throws IOException If a problem occurs while attempting to write the
|
* delete record.
|
*/
|
private static void writeDelete(LDIFWriter writer, Entry entry)
|
throws IOException
|
{
|
writer.writeDeleteChangeRecord(entry, true);
|
writer.flush();
|
}
|
|
|
|
/**
|
* Writes a modify change record to the LDIF writer. Note that this will
|
* handle all the necessary logic for determining if the entries are actually
|
* different, and if they are the same then no output will be generated. Also
|
* note that this will only look at differences between the objectclasses and
|
* user attributes. It will ignore differences in the DN and operational
|
* attributes.
|
*
|
* @param writer The writer to which the modify record should be
|
* written.
|
* @param sourceEntry The source form of the entry.
|
* @param targetEntry The target form of the entry.
|
* @param ignoreAttrs Attributes that are ignored while calculating
|
* the differences.
|
* @param singleValueChanges Indicates whether each attribute-level change
|
* should be written in a separate modification
|
* per attribute value.
|
*
|
* @return <CODE>true</CODE> if there were any differences found between the
|
* source and target entries, or <CODE>false</CODE> if not.
|
*
|
* @throws IOException If a problem occurs while attempting to write the
|
* change record.
|
*/
|
private static boolean writeModify(LDIFWriter writer, Entry sourceEntry,
|
Entry targetEntry, Collection ignoreAttrs,
|
boolean singleValueChanges)
|
throws IOException
|
{
|
// Create a list to hold the modifications that are found.
|
LinkedList<Modification> modifications = new LinkedList<Modification>();
|
|
|
// Look at the set of objectclasses for the entries.
|
LinkedHashSet<ObjectClass> sourceClasses =
|
new LinkedHashSet<ObjectClass>(
|
sourceEntry.getObjectClasses().keySet());
|
LinkedHashSet<ObjectClass> targetClasses =
|
new LinkedHashSet<ObjectClass>(
|
targetEntry.getObjectClasses().keySet());
|
Iterator<ObjectClass> sourceClassIterator = sourceClasses.iterator();
|
while (sourceClassIterator.hasNext())
|
{
|
ObjectClass sourceClass = sourceClassIterator.next();
|
if (targetClasses.remove(sourceClass))
|
{
|
sourceClassIterator.remove();
|
}
|
}
|
|
if (!sourceClasses.isEmpty())
|
{
|
// Whatever is left must have been deleted.
|
AttributeType attrType = DirectoryServer.getObjectClassAttributeType();
|
AttributeBuilder builder = new AttributeBuilder(attrType);
|
for (ObjectClass oc : sourceClasses)
|
{
|
builder.add(oc.getNameOrOID());
|
}
|
|
modifications.add(new Modification(ModificationType.DELETE, builder
|
.toAttribute()));
|
}
|
|
if (! targetClasses.isEmpty())
|
{
|
// Whatever is left must have been added.
|
AttributeType attrType = DirectoryServer.getObjectClassAttributeType();
|
AttributeBuilder builder = new AttributeBuilder(attrType);
|
for (ObjectClass oc : targetClasses)
|
{
|
builder.add(oc.getNameOrOID());
|
}
|
|
modifications.add(new Modification(ModificationType.ADD, builder
|
.toAttribute()));
|
}
|
|
|
// Look at the user attributes for the entries.
|
LinkedHashSet<AttributeType> sourceTypes =
|
new LinkedHashSet<AttributeType>(
|
sourceEntry.getUserAttributes().keySet());
|
Iterator<AttributeType> sourceTypeIterator = sourceTypes.iterator();
|
while (sourceTypeIterator.hasNext())
|
{
|
AttributeType type = sourceTypeIterator.next();
|
List<Attribute> sourceAttrs = sourceEntry.getUserAttribute(type);
|
List<Attribute> targetAttrs = targetEntry.getUserAttribute(type);
|
sourceEntry.removeAttribute(type);
|
|
if (targetAttrs == null)
|
{
|
// The target entry doesn't have this attribute type, so it must have
|
// been deleted. In order to make the delete reversible, delete each
|
// value individually.
|
for (Attribute a : sourceAttrs)
|
{
|
modifications.add(new Modification(ModificationType.DELETE, a));
|
}
|
}
|
else
|
{
|
// Check the attributes for differences. We'll ignore differences in
|
// the order of the values since that isn't significant.
|
targetEntry.removeAttribute(type);
|
|
for (Attribute sourceAttr : sourceAttrs)
|
{
|
Attribute targetAttr = null;
|
Iterator<Attribute> attrIterator = targetAttrs.iterator();
|
while (attrIterator.hasNext())
|
{
|
Attribute a = attrIterator.next();
|
if (a.optionsEqual(sourceAttr.getOptions()))
|
{
|
targetAttr = a;
|
attrIterator.remove();
|
break;
|
}
|
}
|
|
if (targetAttr == null)
|
{
|
// The attribute doesn't exist in the target list, so it has been
|
// deleted.
|
modifications.add(new Modification(ModificationType.DELETE,
|
sourceAttr));
|
}
|
else
|
{
|
// Compare the values.
|
AttributeBuilder addedValuesBuilder = new AttributeBuilder(
|
targetAttr);
|
addedValuesBuilder.removeAll(sourceAttr);
|
Attribute addedValues = addedValuesBuilder.toAttribute();
|
if (!addedValues.isEmpty())
|
{
|
modifications.add(new Modification(ModificationType.ADD,
|
addedValues));
|
}
|
|
AttributeBuilder deletedValuesBuilder = new AttributeBuilder(
|
sourceAttr);
|
deletedValuesBuilder.removeAll(targetAttr);
|
Attribute deletedValues = deletedValuesBuilder.toAttribute();
|
if (!deletedValues.isEmpty())
|
{
|
modifications.add(new Modification(ModificationType.DELETE,
|
deletedValues));
|
}
|
}
|
}
|
|
|
// Any remaining target attributes have been added.
|
for (Attribute targetAttr: targetAttrs)
|
{
|
modifications.add(new Modification(ModificationType.ADD, targetAttr));
|
}
|
}
|
}
|
|
// Any remaining target attribute types have been added.
|
for (AttributeType type : targetEntry.getUserAttributes().keySet())
|
{
|
List<Attribute> targetAttrs = targetEntry.getUserAttribute(type);
|
for (Attribute a : targetAttrs)
|
{
|
modifications.add(new Modification(ModificationType.ADD, a));
|
}
|
}
|
|
// Remove ignored attributes
|
if (! ignoreAttrs.isEmpty())
|
{
|
ListIterator<Modification> modIter = modifications.listIterator();
|
while (modIter.hasNext())
|
{
|
String name = modIter.next().getAttribute().getName().toLowerCase();
|
if (ignoreAttrs.contains(name))
|
{
|
modIter.remove();
|
}
|
}
|
}
|
|
// Write the modification change record.
|
if (modifications.isEmpty())
|
{
|
return false;
|
}
|
else
|
{
|
if (singleValueChanges)
|
{
|
for (Modification m : modifications)
|
{
|
Attribute a = m.getAttribute();
|
if (a.isEmpty())
|
{
|
LinkedList<Modification> attrMods = new LinkedList<Modification>();
|
attrMods.add(m);
|
writer.writeModifyChangeRecord(sourceEntry.getName(), attrMods);
|
}
|
else
|
{
|
LinkedList<Modification> attrMods = new LinkedList<Modification>();
|
for (ByteString v : a)
|
{
|
AttributeBuilder builder = new AttributeBuilder(a, true);
|
builder.add(v);
|
Attribute attr = builder.toAttribute();
|
|
attrMods.clear();
|
attrMods.add(new Modification(m.getModificationType(), attr));
|
writer.writeModifyChangeRecord(sourceEntry.getName(), attrMods);
|
}
|
}
|
}
|
}
|
else
|
{
|
writer.writeModifyChangeRecord(sourceEntry.getName(), modifications);
|
}
|
|
return true;
|
}
|
}
|
}
|