Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2020-2024 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2020-2024 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2020-2024 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldif;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.ChangeType;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.InternalSDKHelper;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.Version;
import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.ldap.sdk.unboundidds.tools.ToolUtils;
import com.unboundid.util.CommandLineTool;
import com.unboundid.util.Debug;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.PassphraseEncryptedOutputStream;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.BooleanArgument;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.FilterArgument;
import com.unboundid.util.args.StringArgument;
import static com.unboundid.ldif.LDIFMessages.*;
/**
* This class provides a command-line tool that can be used to identify the
* differences between two LDIF files. The output will itself be an LDIF file
* that contains the add, delete, and modify operations that can be processed
* against the source LDIF file to result in the target LDIF file.
*/
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public final class LDIFDiff
extends CommandLineTool
{
/**
* The server root directory for the Ping Identity Directory Server (or
* related Ping Identity server product) that contains this tool, if
* applicable.
*/
@Nullable private static final File PING_SERVER_ROOT =
InternalSDKHelper.getPingIdentityServerRoot();
/**
* Indicates whether the tool is running as part of a Ping Identity Directory
* Server (or related Ping Identity Server Product) installation.
*/
private static final boolean PING_SERVER_AVAILABLE =
(PING_SERVER_ROOT != null);
/**
* The column at which to wrap long lines.
*/
private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
/**
* The change type name used to indicate that add operations should be
* included in the output.
*/
@NotNull
private static final String CHANGE_TYPE_ADD = "add";
/**
* The change type name used to indicate that delete operations should be
* included in the output.
*/
@NotNull private static final String CHANGE_TYPE_DELETE = "delete";
/**
* The change type name used to indicate that modify operations should be
* included in the output.
*/
@NotNull private static final String CHANGE_TYPE_MODIFY = "modify";
// The completion message for this tool.
@NotNull private final AtomicReference completionMessage;
// Encryption passphrases used thus far.
@NotNull private final List encryptionPassphrases;
// The command-line arguments supported by this tool.
@Nullable private BooleanArgument byteForByte;
@Nullable private BooleanArgument compressOutput;
@Nullable private BooleanArgument encryptOutput;
@Nullable private BooleanArgument excludeNoUserModificationAttributes;
@Nullable private BooleanArgument includeOperationalAttributes;
@Nullable private BooleanArgument nonReversibleModifications;
@Nullable private BooleanArgument overwriteExistingOutputLDIF;
@Nullable private BooleanArgument singleValueChanges;
@Nullable private BooleanArgument stripTrailingSpaces;
@Nullable private FileArgument outputEncryptionPassphraseFile;
@Nullable private FileArgument outputLDIF;
@Nullable private FileArgument schemaPath;
@Nullable private FileArgument sourceEncryptionPassphraseFile;
@Nullable private FileArgument sourceLDIF;
@Nullable private FileArgument targetEncryptionPassphraseFile;
@Nullable private FileArgument targetLDIF;
@Nullable private FilterArgument excludeFilter;
@Nullable private FilterArgument includeFilter;
@Nullable private StringArgument changeType;
@Nullable private StringArgument excludeAttribute;
@Nullable private StringArgument includeAttribute;
/**
* Invokes this tool with the provided set of command-line arguments.
*
* @param args The set of arguments provided to this tool. It may be
* empty but must not be {@code null}.
*/
public static void main(@NotNull final String... args)
{
final ResultCode resultCode = main(System.out, System.err, args);
if (resultCode != ResultCode.SUCCESS)
{
System.exit(resultCode.intValue());
}
}
/**
* Invokes this tool with the provided set of command-line arguments, using
* the given output and error streams.
*
* @param out The output stream to use for standard output. It may be
* {@code null} if standard output should be suppressed.
* @param err The output stream to use for standard error. It may be
* {@code null} if standard error should be suppressed.
* @param args The set of arguments provided to this tool. It may be
* empty but must not be {@code null}.
*
* @return A result code indicating the status of processing. Any result
* code other than {@link ResultCode#SUCCESS} should be considered
* an error.
*/
@NotNull()
public static ResultCode main(@Nullable final OutputStream out,
@Nullable final OutputStream err,
@NotNull final String... args)
{
final LDIFDiff tool = new LDIFDiff(out, err);
return tool.runTool(args);
}
/**
* Creates a new instance of this tool with the provided output and error
* streams.
*
* @param out The output stream to use for standard output. It may be
* {@code null} if standard output should be suppressed.
* @param err The output stream to use for standard error. It may be
* {@code null} if standard error should be suppressed.
*/
public LDIFDiff(@Nullable final OutputStream out,
@Nullable final OutputStream err)
{
super(out, err);
encryptionPassphrases = new ArrayList<>(5);
completionMessage = new AtomicReference<>();
compressOutput = null;
encryptOutput = null;
excludeNoUserModificationAttributes = null;
includeOperationalAttributes = null;
nonReversibleModifications = null;
overwriteExistingOutputLDIF = null;
singleValueChanges = null;
stripTrailingSpaces = null;
outputEncryptionPassphraseFile = null;
outputLDIF = null;
schemaPath = null;
sourceEncryptionPassphraseFile = null;
sourceLDIF = null;
targetEncryptionPassphraseFile = null;
targetLDIF = null;
changeType = null;
excludeFilter = null;
includeFilter = null;
excludeAttribute = null;
includeAttribute = null;
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public String getToolName()
{
return "ldif-diff";
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public String getToolDescription()
{
return INFO_LDIF_DIFF_TOOL_DESCRIPTION_1.get();
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public List getAdditionalDescriptionParagraphs()
{
final List messages = new ArrayList<>(3);
messages.add(INFO_LDIF_DIFF_TOOL_DESCRIPTION_2.get());
messages.add(INFO_LDIF_DIFF_TOOL_DESCRIPTION_3.get());
if (PING_SERVER_AVAILABLE)
{
messages.add(INFO_LDIF_DIFF_TOOL_DESCRIPTION_4_PING_SERVER.get(
getToolName()));
}
else
{
messages.add(INFO_LDIF_DIFF_TOOL_DESCRIPTION_4_STANDALONE.get(
getToolName()));
}
return messages;
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public String getToolVersion()
{
return Version.NUMERIC_VERSION_STRING;
}
/**
* {@inheritDoc}
*/
@Override()
public boolean supportsInteractiveMode()
{
return true;
}
/**
* {@inheritDoc}
*/
@Override()
public boolean defaultsToInteractiveMode()
{
return true;
}
/**
* {@inheritDoc}
*/
@Override()
public boolean supportsPropertiesFile()
{
return true;
}
/**
* {@inheritDoc}
*/
@Override()
protected boolean supportsDebugLogging()
{
return true;
}
/**
* {@inheritDoc}
*/
@Override()
@Nullable()
protected String getToolCompletionMessage()
{
return completionMessage.get();
}
/**
* {@inheritDoc}
*/
@Override()
public void addToolArguments(@NotNull final ArgumentParser parser)
throws ArgumentException
{
sourceLDIF = new FileArgument('s', "sourceLDIF", true, 1, null,
INFO_LDIF_DIFF_ARG_DESC_SOURCE_LDIF.get(), true, true, true, false);
sourceLDIF.addLongIdentifier("source-ldif", true);
sourceLDIF.addLongIdentifier("source", true);
sourceLDIF.addLongIdentifier("sourceFile", true);
sourceLDIF.addLongIdentifier("source-file", true);
sourceLDIF.addLongIdentifier("sourceLDIFFile", true);
sourceLDIF.addLongIdentifier("source-ldif-file", true);
sourceLDIF.setArgumentGroupName(INFO_LDIF_DIFF_ARG_GROUP_SOURCE.get());
parser.addArgument(sourceLDIF);
final String sourcePWDesc;
if (PING_SERVER_AVAILABLE)
{
sourcePWDesc = INFO_LDIF_DIFF_ARG_DESC_SOURCE_PW_FILE_PING_SERVER.get();
}
else
{
sourcePWDesc = INFO_LDIF_DIFF_ARG_DESC_SOURCE_PW_FILE_STANDALONE.get();
}
sourceEncryptionPassphraseFile = new FileArgument(null,
"sourceEncryptionPassphraseFile", false, 1, null, sourcePWDesc, true,
true, true, false);
sourceEncryptionPassphraseFile.addLongIdentifier(
"source-encryption-passphrase-file", true);
sourceEncryptionPassphraseFile.addLongIdentifier("sourcePassphraseFile",
true);
sourceEncryptionPassphraseFile.addLongIdentifier("source-passphrase-file",
true);
sourceEncryptionPassphraseFile.addLongIdentifier(
"sourceEncryptionPasswordFile", true);
sourceEncryptionPassphraseFile.addLongIdentifier(
"source-encryption-password-file", true);
sourceEncryptionPassphraseFile.addLongIdentifier("sourcePasswordFile",
true);
sourceEncryptionPassphraseFile.addLongIdentifier("source-password-file",
true);
sourceEncryptionPassphraseFile.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_SOURCE.get());
parser.addArgument(sourceEncryptionPassphraseFile);
targetLDIF = new FileArgument('t', "targetLDIF", true, 1, null,
INFO_LDIF_DIFF_ARG_DESC_TARGET_LDIF.get(), true, true, true, false);
targetLDIF.addLongIdentifier("target-ldif", true);
targetLDIF.addLongIdentifier("target", true);
targetLDIF.addLongIdentifier("targetFile", true);
targetLDIF.addLongIdentifier("target-file", true);
targetLDIF.addLongIdentifier("targetLDIFFile", true);
targetLDIF.addLongIdentifier("target-ldif-file", true);
targetLDIF.setArgumentGroupName(INFO_LDIF_DIFF_ARG_GROUP_TARGET.get());
parser.addArgument(targetLDIF);
final String targetPWDesc;
if (PING_SERVER_AVAILABLE)
{
targetPWDesc = INFO_LDIF_DIFF_ARG_DESC_TARGET_PW_FILE_PING_SERVER.get();
}
else
{
targetPWDesc = INFO_LDIF_DIFF_ARG_DESC_TARGET_PW_FILE_STANDALONE.get();
}
targetEncryptionPassphraseFile = new FileArgument(null,
"targetEncryptionPassphraseFile", false, 1, null, targetPWDesc, true,
true, true, false);
targetEncryptionPassphraseFile.addLongIdentifier(
"target-encryption-passphrase-file", true);
targetEncryptionPassphraseFile.addLongIdentifier("targetPassphraseFile",
true);
targetEncryptionPassphraseFile.addLongIdentifier("target-passphrase-file",
true);
targetEncryptionPassphraseFile.addLongIdentifier(
"targetEncryptionPasswordFile", true);
targetEncryptionPassphraseFile.addLongIdentifier(
"target-encryption-password-file", true);
targetEncryptionPassphraseFile.addLongIdentifier("targetPasswordFile",
true);
targetEncryptionPassphraseFile.addLongIdentifier("target-password-file",
true);
targetEncryptionPassphraseFile.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_TARGET.get());
parser.addArgument(targetEncryptionPassphraseFile);
outputLDIF = new FileArgument('o', "outputLDIF", false, 1, null,
INFO_LDIF_DIFF_ARG_DESC_OUTPUT_LDIF.get(), false, true, true, false);
outputLDIF.addLongIdentifier("output-ldif", true);
outputLDIF.addLongIdentifier("output", true);
outputLDIF.addLongIdentifier("outputFile", true);
outputLDIF.addLongIdentifier("output-file", true);
outputLDIF.addLongIdentifier("outputLDIFFile", true);
outputLDIF.addLongIdentifier("output-ldif-file", true);
outputLDIF.setArgumentGroupName(INFO_LDIF_DIFF_ARG_GROUP_OUTPUT.get());
parser.addArgument(outputLDIF);
compressOutput = new BooleanArgument(null, "compressOutput", 1,
INFO_LDIF_DIFF_ARG_DESC_COMPRESS_OUTPUT.get());
compressOutput.addLongIdentifier("compress-output", true);
compressOutput.addLongIdentifier("compress", true);
compressOutput.setArgumentGroupName(INFO_LDIF_DIFF_ARG_GROUP_OUTPUT.get());
parser.addArgument(compressOutput);
encryptOutput = new BooleanArgument(null, "encryptOutput", 1,
INFO_LDIF_DIFF_ARG_DESC_ENCRYPT_OUTPUT.get());
encryptOutput.addLongIdentifier("encrypt-output", true);
encryptOutput.addLongIdentifier("encrypt", true);
encryptOutput.setArgumentGroupName(INFO_LDIF_DIFF_ARG_GROUP_OUTPUT.get());
parser.addArgument(encryptOutput);
outputEncryptionPassphraseFile = new FileArgument(null,
"outputEncryptionPassphraseFile", false, 1, null,
INFO_LDIF_DIFF_ARG_DESC_OUTPUT_PW_FILE.get(), true, true, true, false);
outputEncryptionPassphraseFile.addLongIdentifier(
"output-encryption-passphrase-file", true);
outputEncryptionPassphraseFile.addLongIdentifier("outputPassphraseFile",
true);
outputEncryptionPassphraseFile.addLongIdentifier("output-passphrase-file",
true);
outputEncryptionPassphraseFile.addLongIdentifier(
"outputEncryptionPasswordFile", true);
outputEncryptionPassphraseFile.addLongIdentifier(
"output-encryption-password-file", true);
outputEncryptionPassphraseFile.addLongIdentifier("outputPasswordFile",
true);
outputEncryptionPassphraseFile.addLongIdentifier("output-password-file",
true);
outputEncryptionPassphraseFile.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_OUTPUT.get());
parser.addArgument(outputEncryptionPassphraseFile);
overwriteExistingOutputLDIF = new BooleanArgument('O',
"overwriteExistingOutputLDIF", 1,
INFO_LDIF_DIFF_ARG_DESC_OVERWRITE_EXISTING.get());
overwriteExistingOutputLDIF.addLongIdentifier(
"overwrite-existing-output-ldif", true);
overwriteExistingOutputLDIF.addLongIdentifier(
"overwriteExistingOutputFile", true);
overwriteExistingOutputLDIF.addLongIdentifier(
"overwrite-existing-output-file", true);
overwriteExistingOutputLDIF.addLongIdentifier("overwriteExistingOutput",
true);
overwriteExistingOutputLDIF.addLongIdentifier("overwrite-existing-output",
true);
overwriteExistingOutputLDIF.addLongIdentifier("overwriteExisting", true);
overwriteExistingOutputLDIF.addLongIdentifier("overwrite-existing", true);
overwriteExistingOutputLDIF.addLongIdentifier("overwriteOutputLDIF", true);
overwriteExistingOutputLDIF.addLongIdentifier("overwrite-output-ldif",
true);
overwriteExistingOutputLDIF.addLongIdentifier("overwriteOutputFile", true);
overwriteExistingOutputLDIF.addLongIdentifier("overwrite-output-file",
true);
overwriteExistingOutputLDIF.addLongIdentifier("overwriteOutput", true);
overwriteExistingOutputLDIF.addLongIdentifier("overwrite-output", true);
overwriteExistingOutputLDIF.addLongIdentifier("overwrite", true);
overwriteExistingOutputLDIF.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_OUTPUT.get());
parser.addArgument(overwriteExistingOutputLDIF);
changeType = new StringArgument(null, "changeType", false, 0,
INFO_LDIF_DIFF_ARG_PLACEHOLDER_CHANGE_TYPE.get(),
INFO_LDIF_DIFF_ARG_DESC_CHANGE_TYPE.get(),
StaticUtils.setOf(
CHANGE_TYPE_ADD,
CHANGE_TYPE_DELETE,
CHANGE_TYPE_MODIFY),
Collections.unmodifiableList(Arrays.asList(
CHANGE_TYPE_ADD,
CHANGE_TYPE_DELETE,
CHANGE_TYPE_MODIFY)));
changeType.addLongIdentifier("change-type", true);
changeType.addLongIdentifier("operationType", true);
changeType.addLongIdentifier("operation-type", true);
changeType.setArgumentGroupName(INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(changeType);
includeFilter = new FilterArgument(null, "includeFilter", false, 0, null,
INFO_LDIF_DIFF_ARG_DESC_INCLUDE_FILTER.get());
includeFilter.addLongIdentifier("include-filter", true);
includeFilter.setArgumentGroupName(INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(includeFilter);
excludeFilter = new FilterArgument(null, "excludeFilter", false, 0, null,
INFO_LDIF_DIFF_ARG_DESC_EXCLUDE_FILTER.get());
excludeFilter.addLongIdentifier("exclude-filter", true);
excludeFilter.setArgumentGroupName(INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(excludeFilter);
includeAttribute = new StringArgument(null, "includeAttribute", false, 0,
INFO_LDIF_DIFF_ARG_PLACEHOLDER_ATTRIBUTE.get(),
INFO_LDIF_DIFF_ARG_DESC_INCLUDE_ATTRIBUTE.get());
includeAttribute.addLongIdentifier("include-attribute", true);
includeAttribute.addLongIdentifier("includeAttr", true);
includeAttribute.addLongIdentifier("include-attr", true);
includeAttribute.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(includeAttribute);
excludeAttribute = new StringArgument(null, "excludeAttribute", false, 0,
INFO_LDIF_DIFF_ARG_PLACEHOLDER_ATTRIBUTE.get(),
INFO_LDIF_DIFF_ARG_DESC_EXCLUDE_ATTRIBUTE.get());
excludeAttribute.addLongIdentifier("exclude-attribute", true);
excludeAttribute.addLongIdentifier("excludeAttr", true);
excludeAttribute.addLongIdentifier("exclude-attr", true);
excludeAttribute.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(excludeAttribute);
includeOperationalAttributes = new BooleanArgument('i',
"includeOperationalAttributes", 1,
INFO_LDIF_DIFF_ARG_DESC_INCLUDE_OPERATIONAL.get());
includeOperationalAttributes.addLongIdentifier(
"include-operational-attributes", true);
includeOperationalAttributes.addLongIdentifier("includeOperational", true);
includeOperationalAttributes.addLongIdentifier("include-operational", true);
includeOperationalAttributes.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(includeOperationalAttributes);
excludeNoUserModificationAttributes = new BooleanArgument('e',
"excludeNoUserModificationAttributes", 1,
INFO_LDIF_DIFF_ARG_DESC_EXCLUDE_NO_USER_MOD.get());
excludeNoUserModificationAttributes.addLongIdentifier(
"exclude-no-user-modification-attributes", true);
excludeNoUserModificationAttributes.addLongIdentifier(
"excludeNoUserModAttributes", true);
excludeNoUserModificationAttributes.addLongIdentifier(
"exclude-no-user-mod-attributes", true);
excludeNoUserModificationAttributes.addLongIdentifier(
"excludeNoUserModification", true);
excludeNoUserModificationAttributes.addLongIdentifier(
"exclude-no-user-modification", true);
excludeNoUserModificationAttributes.addLongIdentifier("excludeNoUserMod",
true);
excludeNoUserModificationAttributes.addLongIdentifier("exclude-no-user-mod",
true);
excludeNoUserModificationAttributes.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(excludeNoUserModificationAttributes);
nonReversibleModifications = new BooleanArgument(null,
"nonReversibleModifications", 1,
INFO_LDIF_DIFF_ARG_DESC_NON_REVERSIBLE_MODS.get());
nonReversibleModifications.addLongIdentifier("non-reversible-modifications",
true);
nonReversibleModifications.addLongIdentifier("nonReversibleMods", true);
nonReversibleModifications.addLongIdentifier("non-reversible-mods", true);
nonReversibleModifications.addLongIdentifier("nonReversible", true);
nonReversibleModifications.addLongIdentifier("non-reversible", true);
nonReversibleModifications.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(nonReversibleModifications);
singleValueChanges = new BooleanArgument('S', "singleValueChanges", 1,
INFO_LDIF_DIFF_ARG_DESC_SINGLE_VALUE_CHANGES.get());
singleValueChanges.addLongIdentifier("single-value-changes", true);
parser.addArgument(singleValueChanges);
stripTrailingSpaces = new BooleanArgument(null, "stripTrailingSpaces", 1,
INFO_LDIF_DIFF_ARG_DESC_STRIP_TRAILING_SPACES.get());
stripTrailingSpaces.addLongIdentifier("strip-trailing-spaces", true);
stripTrailingSpaces.addLongIdentifier("ignoreTrailingSpaces", true);
stripTrailingSpaces.addLongIdentifier("ignore-trailing-spaces", true);
stripTrailingSpaces.setArgumentGroupName(
INFO_LDIF_DIFF_ARG_GROUP_CONTENT.get());
parser.addArgument(stripTrailingSpaces);
final String schemaPathDesc;
if (PING_SERVER_AVAILABLE)
{
schemaPathDesc = INFO_LDIF_DIFF_ARG_DESC_SCHEMA_PATH_PING_SERVER.get();
}
else
{
schemaPathDesc = INFO_LDIF_DIFF_ARG_DESC_SCHEMA_PATH_STANDALONE.get();
}
schemaPath = new FileArgument(null, "schemaPath", false, 0, null,
schemaPathDesc, true, true, false, false);
schemaPath.addLongIdentifier("schema-path", true);
schemaPath.addLongIdentifier("schemaFile", true);
schemaPath.addLongIdentifier("schema-file", true);
schemaPath.addLongIdentifier("schemaDirectory", true);
schemaPath.addLongIdentifier("schema-directory", true);
schemaPath.addLongIdentifier("schema", true);
parser.addArgument(schemaPath);
byteForByte = new BooleanArgument(null, "byteForByte", 1,
INFO_LDIF_DIFF_ARG_DESC_BYTE_FOR_BYTE.get());
byteForByte.addLongIdentifier("byte-for-byte", true);
parser.addArgument(byteForByte);
parser.addDependentArgumentSet(compressOutput, outputLDIF);
parser.addDependentArgumentSet(encryptOutput, outputLDIF);
parser.addDependentArgumentSet(outputEncryptionPassphraseFile, outputLDIF);
parser.addDependentArgumentSet(overwriteExistingOutputLDIF, outputLDIF);
parser.addDependentArgumentSet(outputEncryptionPassphraseFile,
encryptOutput);
parser.addExclusiveArgumentSet(includeAttribute, excludeAttribute);
parser.addExclusiveArgumentSet(includeAttribute,
includeOperationalAttributes);
parser.addExclusiveArgumentSet(includeFilter, excludeFilter);
parser.addDependentArgumentSet(excludeNoUserModificationAttributes,
includeOperationalAttributes);
parser.addExclusiveArgumentSet(nonReversibleModifications,
singleValueChanges);
parser.addExclusiveArgumentSet(schemaPath, byteForByte);
}
/**
* {@inheritDoc}
*/
@Override()
public void doExtendedArgumentValidation()
throws ArgumentException
{
// If the LDIF file exists and either compressOutput or encryptOutput is
// present, then the overwrite argument must also be present.
final File outputFile = outputLDIF.getValue();
if ((outputFile != null) && outputFile.exists() &&
(compressOutput.isPresent() || encryptOutput.isPresent()) &&
(! overwriteExistingOutputLDIF.isPresent()))
{
throw new ArgumentException(
ERR_LDIF_DIFF_APPEND_WITH_COMPRESSION_OR_ENCRYPTION.get(
compressOutput.getIdentifierString(),
encryptOutput.getIdentifierString(),
overwriteExistingOutputLDIF.getIdentifierString()));
}
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public ResultCode doToolProcessing()
{
// Get the change types to use for processing.
final Set changeTypes = EnumSet.noneOf(ChangeType.class);
for (final String value : changeType.getValues())
{
switch (StaticUtils.toLowerCase(value))
{
case CHANGE_TYPE_ADD:
changeTypes.add(ChangeType.ADD);
break;
case CHANGE_TYPE_DELETE:
changeTypes.add(ChangeType.DELETE);
break;
case CHANGE_TYPE_MODIFY:
changeTypes.add(ChangeType.MODIFY);
break;
}
}
// Get the schema to use when performing LDIF processing.
final Schema schema;
try
{
if (schemaPath.isPresent())
{
schema = getSchema(schemaPath.getValues());
}
else if (PING_SERVER_AVAILABLE)
{
schema = getSchema(Collections.singletonList(StaticUtils.constructPath(
PING_SERVER_ROOT, "config", "schema")));
}
else
{
schema = Schema.getDefaultStandardSchema();
}
}
catch (final Exception e)
{
Debug.debugException(e);
logCompletionMessage(true,
ERR_LDIF_DIFF_CANNOT_GET_SCHEMA.get(
StaticUtils.getExceptionMessage(e)));
return ResultCode.LOCAL_ERROR;
}
// Identify the sets of include and exclude attributes.
final Set includeAttrs;
if (includeAttribute.isPresent())
{
final Set s = new HashSet<>();
for (final String includeAttr : includeAttribute.getValues())
{
final String lowerName = StaticUtils.toLowerCase(includeAttr);
s.add(lowerName);
final AttributeTypeDefinition at = schema.getAttributeType(lowerName);
if (at != null)
{
s.add(StaticUtils.toLowerCase(at.getOID()));
for (final String name : at.getNames())
{
s.add(StaticUtils.toLowerCase(name));
}
}
}
includeAttrs = Collections.unmodifiableSet(s);
}
else
{
includeAttrs = Collections.emptySet();
}
final Set excludeAttrs;
if (excludeAttribute.isPresent())
{
final Set s = new HashSet<>();
for (final String excludeAttr : excludeAttribute.getValues())
{
final String lowerName = StaticUtils.toLowerCase(excludeAttr);
s.add(lowerName);
final AttributeTypeDefinition at = schema.getAttributeType(lowerName);
if (at != null)
{
s.add(StaticUtils.toLowerCase(at.getOID()));
for (final String name : at.getNames())
{
s.add(StaticUtils.toLowerCase(name));
}
}
}
excludeAttrs = Collections.unmodifiableSet(s);
}
else
{
excludeAttrs = Collections.emptySet();
}
// Read the source and target LDIF files into memory.
final TreeMap sourceEntries;
try
{
sourceEntries = readEntries(sourceLDIF.getValue(),
sourceEncryptionPassphraseFile.getValue(), schema);
out(INFO_LDIF_DIFF_READ_FROM_SOURCE_LDIF.get(
sourceLDIF.getValue().getName(), sourceEntries.size()));
}
catch (final LDAPException e)
{
Debug.debugException(e);
logCompletionMessage(true,
ERR_LDIF_DIFF_CANNOT_READ_SOURCE_LDIF.get(
sourceLDIF.getValue().getAbsolutePath(), e.getMessage()));
return e.getResultCode();
}
final TreeMap targetEntries;
try
{
targetEntries = readEntries(targetLDIF.getValue(),
targetEncryptionPassphraseFile.getValue(), schema);
out(INFO_LDIF_DIFF_READ_FROM_TARGET_LDIF.get(
targetLDIF.getValue().getName(), targetEntries.size()));
out();
}
catch (final LDAPException e)
{
Debug.debugException(e);
logCompletionMessage(true,
ERR_LDIF_DIFF_CANNOT_READ_TARGET_LDIF.get(
targetLDIF.getValue().getAbsolutePath(), e.getMessage()));
return e.getResultCode();
}
final String outputFilePath;
if (outputLDIF.isPresent())
{
outputFilePath = outputLDIF.getValue().getAbsolutePath();
}
else
{
outputFilePath = "{STDOUT}";
}
// Open the output file for writing.
long addCount = 0L;
long deleteCount = 0L;
long modifyCount = 0L;
try (OutputStream outputStream = openOutputStream();
LDIFWriter ldifWriter = new LDIFWriter(outputStream))
{
// First, identify any entries that have been added (that is, entries in
// the target set that are not in the source set), and write them to the
// output file.
if (changeTypes.contains(ChangeType.ADD))
{
try
{
addCount = writeAdds(sourceEntries, targetEntries, ldifWriter,
schema, includeAttrs, excludeAttrs);
}
catch (final LDAPException e)
{
Debug.debugException(e);
logCompletionMessage(true,
ERR_LDIF_DIFF_ERROR_WRITING_OUTPUT.get(outputFilePath,
e.getMessage()));
return e.getResultCode();
}
}
// Next, identify any entries that have been modified (that is, entries
// that exist in both sets, but are different between those sets), and
// write them to the output file. We'll write modifies after adds because
// that allows modifications to reference newly created entries, and we'll
// write modifies before deletes because that allows modifications to
// remove references to entries that will be removed.
if (changeTypes.contains(ChangeType.MODIFY))
{
try
{
modifyCount = writeModifications(sourceEntries, targetEntries,
ldifWriter, schema, includeAttrs, excludeAttrs);
}
catch (final LDAPException e)
{
Debug.debugException(e);
logCompletionMessage(true,
ERR_LDIF_DIFF_ERROR_WRITING_OUTPUT.get(outputFilePath,
e.getMessage()));
return e.getResultCode();
}
}
// Finally, identify any deletes (entries that were only in the set of
// source entries) and write them to the output file.
if (changeTypes.contains(ChangeType.DELETE))
{
try
{
deleteCount = writeDeletes(sourceEntries, targetEntries, ldifWriter,
schema, includeAttrs, excludeAttrs);
}
catch (final LDAPException e)
{
Debug.debugException(e);
logCompletionMessage(true,
ERR_LDIF_DIFF_ERROR_WRITING_OUTPUT.get(outputFilePath,
e.getMessage()));
return e.getResultCode();
}
}
// If we've gotten here, then everything was successful.
ldifWriter.flush();
logCompletionMessage(false, INFO_LDIF_DIFF_COMPLETED.get());
if (changeTypes.contains(ChangeType.ADD))
{
out(INFO_LDIF_DIFF_COMPLETED_ADD_COUNT.get(addCount));
}
if (changeTypes.contains(ChangeType.MODIFY))
{
out(INFO_LDIF_DIFF_COMPLETED_MODIFY_COUNT.get(modifyCount));
}
if (changeTypes.contains(ChangeType.DELETE))
{
out(INFO_LDIF_DIFF_COMPLETED_DELETE_COUNT.get(deleteCount));
}
return ResultCode.SUCCESS;
}
catch (final LDAPException e)
{
Debug.debugException(e);
logCompletionMessage(true,
ERR_LDIF_DIFF_CANNOT_OPEN_OUTPUT.get(outputFilePath,
e.getMessage()));
return e.getResultCode();
}
catch (final Exception e)
{
Debug.debugException(e);
logCompletionMessage(true,
ERR_LDIF_DIFF_ERROR_WRITING_OUTPUT.get(outputFilePath,
StaticUtils.getExceptionMessage(e)));
return ResultCode.LOCAL_ERROR;
}
}
/**
* Retrieves the schema contained in the specified paths.
*
* @param paths The paths to use to access the schema.
*
* @return The schema read from the specified files.
*
* @throws Exception If a problem is encountered while loading the schema.
*/
@NotNull()
private static Schema getSchema(@NotNull final List paths)
throws Exception
{
final Set schemaFiles = new LinkedHashSet<>();
for (final File f : paths)
{
if (f.exists())
{
if (f.isFile())
{
schemaFiles.add(f);
}
else if (f.isDirectory())
{
final TreeMap sortedFiles = new TreeMap<>();
for (final File fileInDir : f.listFiles())
{
if (fileInDir.isFile())
{
sortedFiles.put(fileInDir.getName(), fileInDir);
}
}
schemaFiles.addAll(sortedFiles.values());
}
}
}
return Schema.getSchema(new ArrayList<>(schemaFiles));
}
/**
* Reads all of the entries in the specified LDIF file into a map.
*
* @param ldifFile The path to the LDIF file to read. It must not be
* {@code null}.
* @param encPWFile The path to the file containing the passphrase used to
* encrypt the LDIF file. It may be {@code null} if the
* LDIF file is not encrypted, or if the encryption key is
* to be obtained through an alternate means.
* @param schema The schema to use when reading the LDIF file. It must
* not be {@code null}.
*
* @return The map of entries read from the file.
*
* @throws LDAPException If a problem occurs while attempting to read the
* entries.
*/
@NotNull()
private TreeMap readEntries(@NotNull final File ldifFile,
@Nullable final File encPWFile,
@NotNull final Schema schema)
throws LDAPException
{
if (encPWFile != null)
{
try
{
addPassphrase(getPasswordFileReader().readPassword(encPWFile));
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_OPEN_PW_FILE.get(encPWFile.getAbsolutePath(),
StaticUtils.getExceptionMessage(e)),
e);
}
}
InputStream inputStream = null;
try
{
try
{
inputStream = new FileInputStream(ldifFile);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_OPEN_LDIF_FILE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
final ObjectPair p =
ToolUtils.getPossiblyPassphraseEncryptedInputStream(inputStream,
encryptionPassphrases, (encPWFile == null),
INFO_LDIF_DIFF_PROMPT_FOR_ENC_PW.get(ldifFile.getName()),
ERR_LDIF_DIFF_PROMPT_WRONG_ENC_PW.get(), getOut(), getErr());
inputStream = p.getFirst();
addPassphrase(p.getSecond());
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_DECRYPT_LDIF_FILE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
inputStream =
ToolUtils.getPossiblyGZIPCompressedInputStream(inputStream);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_DECOMPRESS_LDIF_FILE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try (LDIFReader reader = new LDIFReader(inputStream))
{
reader.setSchema(schema);
if (stripTrailingSpaces.isPresent())
{
reader.setTrailingSpaceBehavior(TrailingSpaceBehavior.STRIP);
}
else
{
reader.setTrailingSpaceBehavior(TrailingSpaceBehavior.REJECT);
}
final TreeMap entryMap = new TreeMap<>();
while (true)
{
final Entry entry = reader.readEntry();
if (entry == null)
{
break;
}
entryMap.put(entry.getParsedDN(), entry);
}
return entryMap;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_ERROR_READING_OR_DECODING.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
finally
{
if (inputStream != null)
{
try
{
inputStream.close();
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
}
/**
* Updates the list of encryption passphrases with the provided passphrase, if
* it is not already present.
*
* @param passphrase The passphrase to be added. It may optionally be
* {@code null} (in which case no action will be taken).
*/
private void addPassphrase(@Nullable final char[] passphrase)
{
if (passphrase == null)
{
return;
}
for (final char[] existingPassphrase : encryptionPassphrases)
{
if (Arrays.equals(existingPassphrase, passphrase))
{
return;
}
}
encryptionPassphrases.add(passphrase);
}
/**
* Opens the output stream to use to write the identified differences.
*
* @return The output stream that was opened.
*
* @throws LDAPException If a problem is encountered while opening the
* output stream.
*/
@NotNull()
private OutputStream openOutputStream()
throws LDAPException
{
if (! outputLDIF.isPresent())
{
return getOut();
}
OutputStream outputStream = null;
boolean closeOutputStream = true;
try
{
try
{
outputStream = new FileOutputStream(outputLDIF.getValue(),
(! overwriteExistingOutputLDIF.isPresent()));
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_OPEN_OUTPUT_FILE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
if (encryptOutput.isPresent())
{
try
{
final char[] passphrase;
if (outputEncryptionPassphraseFile.isPresent())
{
passphrase = getPasswordFileReader().readPassword(
outputEncryptionPassphraseFile.getValue());
}
else
{
passphrase = ToolUtils.promptForEncryptionPassphrase(false, true,
INFO_LDIF_DIFF_PROMPT_OUTPUT_FILE_ENC_PW.get(),
INFO_LDIF_DIFF_CONFIRM_OUTPUT_FILE_ENC_PW.get(), getOut(),
getErr()).toCharArray();
}
outputStream = new PassphraseEncryptedOutputStream(passphrase,
outputStream, null, true, true);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_ENCRYPT_OUTPUT_FILE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
if (compressOutput.isPresent())
{
try
{
outputStream = new GZIPOutputStream(outputStream);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_COMPRESS_OUTPUT_FILE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
closeOutputStream = false;
return outputStream;
}
finally
{
if (closeOutputStream && (outputStream != null))
{
try
{
outputStream.close();
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
}
/**
* Writes add change records for all entries contained in the given target
* entry map that are not in the source entry map.
*
* @param sourceEntries The map of entries read from the source LDIF file.
* It must not be {@code null}.
* @param targetEntries The map of entries read from the target LDIF file.
* It must not be {@code null}.
* @param writer The LDIF writer to use to write any changes. It
* must not be {@code null} and it must be open.
* @param schema The schema to use to identify operational
* attributes. It must not be {@code null}.
* @param includeAttrs A set containing all names and OIDs for all
* attribute types that should be included in the
* entry. It must not be {@code null} but may be
* empty. All values must be formatted entirely in
* lowercase.
* @param excludeAttrs A set containing all names and OIDs for all
* attribute types that should be excluded from the
* entry. It must not be {@code null} but may be
* empty. All values must be formatted entirely in
* lowercase.
*
* @return The number of added entries that were identified during
* processing.
*
* @throws LDAPException If a problem is encountered while writing the ad
* change records.
*/
private long writeAdds(@NotNull final TreeMap sourceEntries,
@NotNull final TreeMap targetEntries,
@NotNull final LDIFWriter writer,
@NotNull final Schema schema,
@NotNull final Set includeAttrs,
@NotNull final Set excludeAttrs)
throws LDAPException
{
long addCount = 0L;
for (final Map.Entry e : targetEntries.entrySet())
{
final DN entryDN = e.getKey();
final Entry entry = e.getValue();
if (! sourceEntries.containsKey(entryDN))
{
if (! includeEntryByFilter(schema, entry))
{
continue;
}
final Entry paredEntry = pareEntry(entry, schema, includeAttrs,
excludeAttrs);
if (paredEntry == null)
{
continue;
}
try
{
writer.writeChangeRecord(new LDIFAddChangeRecord(paredEntry),
INFO_LDIF_DIFF_ADD_COMMENT.get());
addCount++;
}
catch (final Exception ex)
{
Debug.debugException(ex);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_WRITE_ADD_FOR_ENTRY.get(entry.getDN(),
StaticUtils.getExceptionMessage(ex)),
ex);
}
}
}
return addCount;
}
/**
* Indicates whether the specified entry may be included in the output based
* on the include filter and exclude filter configuration.
*
* @param schema The schema to use when making the determination. It must
* not be {@code null}.
* @param entries The entries for which to make the determination. It must
* not be {@code null} or empty.
*
* @return {@code true} if the entry should be included, or {@code false} if]
* not.
*/
private boolean includeEntryByFilter(@NotNull final Schema schema,
@NotNull final Entry... entries)
{
for (final Entry entry : entries)
{
for (final Filter f : excludeFilter.getValues())
{
try
{
if (f.matchesEntry(entry, schema))
{
return false;
}
}
catch (final Exception ex)
{
Debug.debugException(ex);
}
}
}
if (includeFilter.isPresent())
{
for (final Entry entry : entries)
{
for (final Filter f : includeFilter.getValues())
{
try
{
if (f.matchesEntry(entry, schema))
{
return true;
}
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
return false;
}
return true;
}
/**
* Creates a pared-down copy of the provided entry based on the requested
* set of options.
*
* @param entry The entry to be pared down. It must not be
* {@code null}.
* @param schema The schema to use during processing. It must not be
* {@code null}.
* @param includeAttrs A set containing all names and OIDs for all attribute
* types that should be included in the entry. It must
* not be {@code null} but may be empty. All values
* must be formatted entirely in lowercase.
* @param excludeAttrs A set containing all names and OIDs for all attribute
* types that should be excluded from the entry. It
* must not be {@code null} but may be empty. All
* values must be formatted entirely in lowercase.
*
* @return A pared-down copy of the provided entry, or {@code null} if the
* pared-down entry would not include any attributes.
*/
@Nullable()
private Entry pareEntry(@NotNull final Entry entry,
@NotNull final Schema schema,
@NotNull final Set includeAttrs,
@NotNull final Set excludeAttrs)
{
final List paredAttributeList = new ArrayList<>();
for (final Attribute a : entry.getAttributes())
{
final String baseName = StaticUtils.toLowerCase(a.getBaseName());
if (excludeAttrs.contains(baseName))
{
continue;
}
if ((! includeAttrs.isEmpty()) && (! includeAttrs.contains(baseName)))
{
continue;
}
final AttributeTypeDefinition at = schema.getAttributeType(baseName);
if ((at != null) && at.isOperational())
{
if (! includeOperationalAttributes.isPresent())
{
continue;
}
if (at.isNoUserModification() &&
excludeNoUserModificationAttributes.isPresent())
{
continue;
}
}
paredAttributeList.add(a);
}
if (paredAttributeList.isEmpty())
{
return null;
}
return new Entry(entry.getDN(), paredAttributeList);
}
/**
* Identifies entries that exist in both the source and target maps and
* determines whether there are any changes between them.
*
* @param sourceEntries The map of entries read from the source LDIF file.
* It must not be {@code null}.
* @param targetEntries The map of entries read from the target LDIF file.
* It must not be {@code null}.
* @param writer The LDIF writer to use to write any changes. It
* must not be {@code null} and it must be open.
* @param schema The schema to use to identify operational
* attributes. It must not be {@code null}.
* @param includeAttrs A set containing all names and OIDs for all
* attribute types that should be included in the
* set of modifications. It must not be {@code null}
* but may be empty. All values must be formatted
* entirely in lowercase.
* @param excludeAttrs A set containing all names and OIDs for all
* attribute types that should be excluded from the
* set of modifications. It must not be {@code null}
* but may be empty. All values must be formatted
* entirely in lowercase.
*
* @return The number of modified entries that were identified during
* processing.
*
* @throws LDAPException If a problem is encountered while writing
* modified entries.
*/
private long writeModifications(
@NotNull final TreeMap sourceEntries,
@NotNull final TreeMap targetEntries,
@NotNull final LDIFWriter writer,
@NotNull final Schema schema,
@NotNull final Set includeAttrs,
@NotNull final Set excludeAttrs)
throws LDAPException
{
long modCount = 0L;
for (final Map.Entry sourceMapEntry : sourceEntries.entrySet())
{
final DN sourceDN = sourceMapEntry.getKey();
final Entry targetEntry = targetEntries.get(sourceDN);
if (targetEntry == null)
{
continue;
}
final Entry sourceEntry = sourceMapEntry.getValue();
if (! includeEntryByFilter(schema, sourceEntry, targetEntry))
{
continue;
}
final List mods = Entry.diff(sourceEntry, targetEntry,
false, (! nonReversibleModifications.isPresent()),
byteForByte.isPresent());
if (writeModifiedEntry(sourceDN, mods, writer, schema, includeAttrs,
excludeAttrs))
{
modCount++;
}
}
return modCount;
}
/**
* Writes a modified entry to the LDIF writer.
*
* @param dn The DN of the entry to write. It must not be
* {@code null}.
* @param mods The modifications to be written.
* @param writer The LDIF writer to use to write the modify change
* record(s).
* @param schema The schema to use to identify operational attributes.
* It must not be {@code null}.
* @param includeAttrs A set containing all names and OIDs for all attribute
* types that should be included in the set of
* modifications. It must not be {@code null} but may
* be empty. All values must be formatted entirely in
* lowercase.
* @param excludeAttrs A set containing all names and OIDs for all attribute
* types that should be excluded from the set of
* modifications. It must not be {@code null} but may
* be empty. All values must be formatted entirely in
* lowercase.
*
* @return {@code true} if one or more modify change records were written, or
* {@code false} if not.
*
* @throws LDAPException If a problem occurs while trying to write the
* modifications.
*/
private boolean writeModifiedEntry(@NotNull final DN dn,
@NotNull final List mods,
@NotNull final LDIFWriter writer,
@NotNull final Schema schema,
@NotNull final Set includeAttrs,
@NotNull final Set excludeAttrs)
throws LDAPException
{
if (mods.isEmpty())
{
return false;
}
final List paredMods = new ArrayList<>(mods.size());
for (final Modification m : mods)
{
final Attribute a = m.getAttribute();
final String baseName = StaticUtils.toLowerCase(a.getBaseName());
if (excludeAttrs.contains(baseName))
{
continue;
}
if ((! includeAttrs.isEmpty()) && ! includeAttrs.contains(baseName))
{
continue;
}
final AttributeTypeDefinition at =
schema.getAttributeType(a.getBaseName());
if ((at != null) && at.isOperational())
{
if (includeOperationalAttributes.isPresent())
{
if (at.isNoUserModification())
{
if (! excludeNoUserModificationAttributes.isPresent())
{
paredMods.add(m);
}
}
else
{
paredMods.add(m);
}
}
}
else
{
paredMods.add(m);
}
}
if (paredMods.isEmpty())
{
return false;
}
try
{
if (singleValueChanges.isPresent())
{
for (final Modification m : paredMods)
{
final Attribute a = m.getAttribute();
if (a.size() > 1)
{
for (final byte[] value : a.getValueByteArrays())
{
writer.writeChangeRecord(new LDIFModifyChangeRecord(dn.toString(),
new Modification(m.getModificationType(),
m.getAttributeName(), value)));
}
}
else
{
writer.writeChangeRecord(new LDIFModifyChangeRecord(dn.toString(),
m));
}
}
}
else
{
writer.writeChangeRecord(
new LDIFModifyChangeRecord(dn.toString(), paredMods),
INFO_LDIF_DIFF_MODIFY_COMMENT.get());
}
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_WRITE_MODS_TO_ENTRY.get(dn.toString(),
StaticUtils.getExceptionMessage(e)),
e);
}
return true;
}
/**
* Writes delete change records for all entries contained in the given source
* entry map that are not in the target entry map.
*
* @param sourceEntries The map of entries read from the source LDIF file.
* It must not be {@code null}.
* @param targetEntries The map of entries read from the target LDIF file.
* It must not be {@code null}.
* @param writer The LDIF writer to use to write any changes. It
* must not be {@code null} and it must be open.
* @param schema The schema to use to identify operational
* attributes. It must not be {@code null}.
* @param includeAttrs A set containing all names and OIDs for all
* attribute types that should be included in the
* entry. It must not be {@code null} but may be
* empty. All values must be formatted entirely in
* lowercase.
* @param excludeAttrs A set containing all names and OIDs for all
* attribute types that should be excluded from the
* entry. It must not be {@code null} but may be
* empty. All values must be formatted entirely in
* lowercase.
*
* @return The number of deleted entries that were identified during
* processing.
*
* @throws LDAPException If a problem is encountered while writing the
* delete change records.
*/
private long writeDeletes(@NotNull final TreeMap sourceEntries,
@NotNull final TreeMap targetEntries,
@NotNull final LDIFWriter writer,
@NotNull final Schema schema,
@NotNull final Set includeAttrs,
@NotNull final Set excludeAttrs)
throws LDAPException
{
long deleteCount = 0L;
for (final Map.Entry e : sourceEntries.descendingMap().entrySet())
{
final DN entryDN = e.getKey();
final Entry entry = e.getValue();
if (! targetEntries.containsKey(entryDN))
{
if (! includeEntryByFilter(schema, entry))
{
continue;
}
final Entry paredEntry = pareEntry(entry, schema, includeAttrs,
excludeAttrs);
if (paredEntry == null)
{
continue;
}
try
{
final String comment = INFO_LDIF_DIFF_DELETE_COMMENT.get() +
StaticUtils.EOL + paredEntry.toLDIFString(75);
writer.writeChangeRecord(
new LDIFDeleteChangeRecord(paredEntry.getDN()), comment);
deleteCount++;
}
catch (final Exception ex)
{
Debug.debugException(ex);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_LDIF_DIFF_CANNOT_WRITE_DELETE_FOR_ENTRY.get(entry.getDN(),
StaticUtils.getExceptionMessage(ex)),
ex);
}
}
}
return deleteCount;
}
/**
* Writes the provided message and sets it as the completion message.
*
* @param isError Indicates whether the message should be written to
* standard error rather than standard output.
* @param message The message to be written.
*/
private void logCompletionMessage(final boolean isError,
@NotNull final String message)
{
completionMessage.compareAndSet(null, message);
if (isError)
{
wrapErr(0, WRAP_COLUMN, message);
}
else
{
wrapOut(0, WRAP_COLUMN, message);
}
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public LinkedHashMap getExampleUsages()
{
final LinkedHashMap examples = new LinkedHashMap<>();
examples.put(
new String[]
{
"--sourceLDIF", "actual.ldif",
"--targetLDIF", "desired.ldif",
"--outputLDIF", "diff.ldif"
},
INFO_LDIF_DIFF_EXAMPLE_1.get());
examples.put(
new String[]
{
"--sourceLDIF", "actual.ldif",
"--targetLDIF", "desired.ldif",
"--outputLDIF", "diff.ldif",
"--includeOperationalAttributes",
"--excludeNoUserModificationAttributes",
"--nonReversibleModifications"
},
INFO_LDIF_DIFF_EXAMPLE_2.get());
return examples;
}
}