/*
* 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
* 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.
*/
/**
* 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.
*/
/**
* 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.
*/
{
if (exitCode != 0)
{
}
}
/**
* 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.
*/
{
false);
try
{
sourceLDIF = new StringArgument(
"sourceldif", 's', "sourceLDIF", true,
targetLDIF = new StringArgument(
"targetldif", 't', "targetLDIF", true,
outputLDIF = new StringArgument(
"outputldif", 'o', "outputLDIF", false,
ignoreAttrsFile = new StringArgument(
"ignoreattrs", 'a', "ignoreAttrs", false,
ignoreEntriesFile = new StringArgument(
"ignoreentries", 'e', "ignoreEntries", false,
new BooleanArgument(
"overwriteexisting", 'O',
"overwriteExisting",
new BooleanArgument(
"singlevaluechanges", 'S', "singleValueChanges",
new BooleanArgument(
false, true,
null,
configFile.setHidden(true);
OPTION_LONG_CONFIG_CLASS, false,
false, true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
configClass.setHidden(true);
"useCompareResultCode",
}
catch (ArgumentException ae)
{
return OPERATIONS_ERROR;
}
// Parse the command-line arguments provided to the program.
try
{
}
catch (ArgumentException ae)
{
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 (scriptName == null)
{
scriptName = "ldif-diff";
}
}
if (! serverInitialized)
{
// Bootstrap the Directory Server configuration for use as a client.
// If we're to use the configuration then initialize it, along with the
// schema.
if (checkSchema)
{
try
{
}
catch (Exception e)
{
printWrappedText(err, ERR_LDIFDIFF_CANNOT_INITIALIZE_JMX.get(configFile.getValue(), e.getMessage()));
return OPERATIONS_ERROR;
}
try
{
configFile.getValue());
}
catch (Exception e)
{
printWrappedText(err, ERR_LDIFDIFF_CANNOT_INITIALIZE_CONFIG.get(configFile.getValue(), e.getMessage()));
return OPERATIONS_ERROR;
}
try
{
}
catch (Exception e)
{
printWrappedText(err, ERR_LDIFDIFF_CANNOT_INITIALIZE_SCHEMA.get(configFile.getValue(), e.getMessage()));
return OPERATIONS_ERROR;
}
}
}
// Read in ignored entries and attributes if any
{
try
{
ignReader = new BufferedReader(
{
}
}
catch (Exception e)
{
printWrappedText(err, ERR_LDIFDIFF_CANNOT_READ_FILE_IGNORE_ATTRIBS.get(ignoreAttrsFile.getValue(), e));
return OPERATIONS_ERROR;
}
finally
{
}
}
{
try
{
ignReader = new BufferedReader(
{
try
{
}
catch (DirectoryException e)
{
}
}
}
catch (Exception e)
{
printWrappedText(err, ERR_LDIFDIFF_CANNOT_READ_FILE_IGNORE_ENTRIES.get(ignoreEntriesFile.getValue(), e));
return OPERATIONS_ERROR;
}
finally
{
}
}
// Open the source LDIF file and read it into a tree map.
try
{
}
catch (Exception e)
{
return OPERATIONS_ERROR;
}
try
{
while (true)
{
{
break;
}
{
}
}
}
catch (Exception e)
{
return OPERATIONS_ERROR;
}
finally
{
}
// Open the target LDIF file and read it into a tree map.
try
{
}
catch (Exception e)
{
return OPERATIONS_ERROR;
}
try
{
while (true)
{
{
break;
}
{
}
}
}
catch (Exception e)
{
return OPERATIONS_ERROR;
}
finally
{
}
// Open the output writer that we'll use to write the differences.
try
{
if (outputLDIF.isPresent())
{
if (overwriteExisting.isPresent())
{
}
else
{
}
}
else
{
}
}
catch (Exception e)
{
return OPERATIONS_ERROR;
}
try
{
boolean differenceFound;
// Check to see if either or both of the source and target maps are empty.
{
{
// They're both empty, so there are no differences.
differenceFound = false;
}
else
{
// The target isn't empty, so they're all adds.
while (targetIterator.hasNext())
{
}
differenceFound = true;
}
}
{
// The source isn't empty, so they're all deletes.
while (sourceIterator.hasNext())
{
}
differenceFound = true;
}
else
{
differenceFound = false;
// Iterate through all the entries in the source and target maps and
// identify the differences.
while (true)
{
// Compare the DNs to determine the relative order of the
// entries.
if (comparatorValue < 0)
{
// The source entry should be before the target entry, which means
// that the source entry has been deleted.
differenceFound = true;
if (sourceIterator.hasNext())
{
}
else
{
// There are no more source entries, so if there are more target
// entries then they're all adds.
while (targetIterator.hasNext())
{
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.
differenceFound = true;
if (targetIterator.hasNext())
{
}
else
{
// There are no more target entries so all of the remaining source
// entries are deletes.
differenceFound = true;
while (sourceIterator.hasNext())
{
}
break;
}
}
else
{
// The DNs are the same, so check to see if the entries are the
// same or have been modified.
{
differenceFound = true;
}
if (sourceIterator.hasNext())
{
}
else
{
// There are no more source entries, so if there are more target
// entries then they're all adds.
while (targetIterator.hasNext())
{
differenceFound = true;
}
break;
}
if (targetIterator.hasNext())
{
}
else
{
// There are no more target entries so all of the remaining source
// entries are deletes.
differenceFound = true;
while (sourceIterator.hasNext())
{
}
break;
}
}
}
}
if (!differenceFound)
{
}
if (useCompareResultCode.isPresent())
{
}
}
catch (IOException e)
{
return OPERATIONS_ERROR;
}
finally
{
}
// 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.
*/
throws IOException
{
}
/**
* 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.
*/
throws IOException
{
}
/**
* 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.
*/
throws IOException
{
// Create a list to hold the modifications that are found.
// Look at the set of objectclasses for the entries.
LinkedHashSet<ObjectClass> sourceClasses = new LinkedHashSet<>(sourceEntry.getObjectClasses().keySet());
LinkedHashSet<ObjectClass> targetClasses = new LinkedHashSet<>(targetEntry.getObjectClasses().keySet());
while (sourceClassIterator.hasNext())
{
{
}
}
if (!sourceClasses.isEmpty())
{
// Whatever is left must have been deleted.
{
}
.toAttribute()));
}
if (! targetClasses.isEmpty())
{
// Whatever is left must have been added.
{
}
.toAttribute()));
}
// Look at the user attributes for the entries.
LinkedHashSet<AttributeType> sourceTypes = new LinkedHashSet<>(sourceEntry.getUserAttributes().keySet());
while (sourceTypeIterator.hasNext())
{
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)
{
}
}
else
{
// Check the attributes for differences. We'll ignore differences in
// the order of the values since that isn't significant.
{
while (attrIterator.hasNext())
{
{
targetAttr = a;
break;
}
}
if (targetAttr == null)
{
// The attribute doesn't exist in the target list, so it has been
// deleted.
sourceAttr));
}
else
{
// Compare the values.
if (!addedValues.isEmpty())
{
}
if (!deletedValues.isEmpty())
{
}
}
}
// Any remaining target attributes have been added.
{
}
}
}
// Any remaining target attribute types have been added.
{
for (Attribute a : targetAttrs)
{
}
}
// Remove ignored attributes
if (! ignoreAttrs.isEmpty())
{
{
{
}
}
}
// Write the modification change record.
if (modifications.isEmpty())
{
return false;
}
if (singleValueChanges)
{
for (Modification m : modifications)
{
Attribute a = m.getAttribute();
if (a.isEmpty())
{
}
else
{
for (ByteString v : a)
{
}
}
}
}
else
{
}
return true;
}
}