All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.unboundidds.tools.ParallelUpdate Maven / Gradle / Ivy

/*
 * Copyright 2008-2024 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2008-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) 2008-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.ldap.sdk.unboundidds.tools;



import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;

import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.ExtendedResult;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.ldap.sdk.LDAPConnectionPool;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler;
import com.unboundid.ldap.sdk.Version;
import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl;
import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl;
import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl;
import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            AssuredReplicationRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.AssuredReplicationLocalLevel;
import com.unboundid.ldap.sdk.unboundidds.controls.
            AssuredReplicationRemoteLevel;
import com.unboundid.ldap.sdk.unboundidds.controls.HardDeleteRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            IgnoreNoUserModificationRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            NameWithEntryUUIDRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            OperationPurposeRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            PasswordUpdateBehaviorRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            PasswordUpdateBehaviorRequestControlProperties;
import com.unboundid.ldap.sdk.unboundidds.controls.
            ReplicationRepairRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.SoftDeleteRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            SuppressOperationalAttributeUpdateRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            SuppressReferentialIntegrityUpdatesRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.SuppressType;
import com.unboundid.ldif.LDIFChangeRecord;
import com.unboundid.ldif.LDIFException;
import com.unboundid.ldif.LDIFReader;
import com.unboundid.ldif.LDIFWriter;
import com.unboundid.util.Debug;
import com.unboundid.util.FixedRateBarrier;
import com.unboundid.util.LDAPCommandLineTool;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.PassphraseEncryptedOutputStream;
import com.unboundid.util.PassphraseEncryptedStreamHeader;
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.ControlArgument;
import com.unboundid.util.args.DNArgument;
import com.unboundid.util.args.DurationArgument;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.IntegerArgument;
import com.unboundid.util.args.StringArgument;

import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;



/**
 * This class provides a command-line tool that can be used to read change
 * records for add, delete, modify and modify DN operations from an LDIF file,
 * and then apply them in parallel using multiple threads for higher throughput.
 * 
*
* NOTE: This class, and other classes within the * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only * supported for use against Ping Identity, UnboundID, and * Nokia/Alcatel-Lucent 8661 server products. These classes provide support * for proprietary functionality or for external specifications that are not * considered stable or mature enough to be guaranteed to work in an * interoperable way with other types of LDAP servers. *
*

* Changes in the LDIF file to be processed should be ordered such that if there * are any dependencies between changes, prerequisite changes come before the * changes that depend on them (for example, if one add change record creates a * parent entry and another creates a child entry, the add change record that * creates the parent entry must come before the one that creates the child * entry). When this tool is preparing to process a change, it will determine * whether the new change depends on any other changes that are currently in * progress, and if so, will delay processing that change until its dependencies * have been satisfied. If a change does not depend on any other changes that * are currently being processed, then it can be processed in parallel with * those changes. *

* The tool will keep track of any changes that fail in a way that indicates * they succeed if re-tried later (for example, an attempt to add an entry that * fails because its parent does not exist, but its parent may be created later * in the set of LDIF changes), and can optionally re-try those changes after * processing is complete. Any changes that are not retried, as well as changes * that still fail after the retry attempts, will be written to a rejects file * with information about the reason for the failure so that an administrator * can take any necessary further action upon them. */ @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) public final class ParallelUpdate extends LDAPCommandLineTool implements UnsolicitedNotificationHandler { /** * The column at which long lines should be wrapped. */ private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; /** * The name of the password update behavior key that may be used to specify * whether an update should be treated as a self-change. */ @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_IS_SELF_CHANGE = "is-self-change"; /** * The name of the password update behavior key that may be used to specify * whether an update should allow the password to be provided in pre-encoded * form. */ @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_ALLOW_PRE_ENCODED_PW = "allow-pre-encoded-password"; /** * The name of the password update behavior key that may be used to specify * whether the server should skip validation for the password. */ @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_SKIP_PW_VALIDATION = "skip-password-validation"; /** * The name of the password update behavior key that may be used to specify * whether the server should ignore the password history when determining * whether to accept the new password. */ @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_IGNORE_PW_HISTORY = "ignore-password-history"; /** * The name of the password update behavior key that may be used to specify * whether the server should ignore the minimum password age when determining * whether to allow the password change. */ @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_IGNORE_MIN_PW_AGE = "ignore-minimum-password-age"; /** * The name of the password update behavior key that may be used to specify * the password storage scheme that should be used to encode the new password. */ @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_PW_STORAGE_SCHEME = "password-storage-scheme"; /** * The name of the password update behavior key that may be used to specify * whether the user must change their password on the next successful * authentication. */ @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_MUST_CHANGE_PW = "must-change-password"; /** * The assured replication local level value that indicates that no assurance * is needed. */ @NotNull private static final String ASSURED_REPLICATION_LOCAL_LEVEL_NONE = "none"; /** * The assured replication local level value that indicates that the response * should be delayed until the change has been received by at least one other * local server. */ @NotNull private static final String ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER = "received-any-server"; /** * The assured replication local level value that indicates that the response * should be delayed until the change has been processed by all available * local servers. */ @NotNull private static final String ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS = "processed-all-servers"; /** * The assured replication remote level value that indicates that no assurance * is needed. */ @NotNull private static final String ASSURED_REPLICATION_REMOTE_LEVEL_NONE = "none"; /** * The assured replication remote level value that indicates that the response * should be delayed until the change has been received by at least one server * in at least one remote location. */ @NotNull private static final String ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION = "received-any-remote-location"; /** * The assured replication remote level value that indicates that the response * should be delayed until the change has been received by at least one server * in all remote locations. */ @NotNull private static final String ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS = "received-all-remote-locations"; /** * The assured replication remote level value that indicates that the response * should be delayed until the change has been processed by all available * servers in all remote locations. */ @NotNull private static final String ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS = "processed-all-remote-servers"; /** * The suppress operational attribute update value that indicates that updates * to the last access time should be suppressed. */ @NotNull private static final String SUPPRESS_OP_ATTR_LAST_ACCESS_TIME = "last-access-time"; /** * The suppress operational attribute update value that indicates that updates * to the last login time should be suppressed. */ @NotNull private static final String SUPPRESS_OP_ATTR_LAST_LOGIN_TIME = "last-login-time"; /** * The suppress operational attribute update value that indicates that updates * to the last login IP address should be suppressed. */ @NotNull private static final String SUPPRESS_OP_ATTR_LAST_LOGIN_IP = "last-login-ip"; /** * The suppress operational attribute update value that indicates that updates * to the lastmod attributes (creatorsName, createTimestamp, modifiersName, * modifyTimestamp) should be suppressed. */ @NotNull private static final String SUPPRESS_OP_ATTR_LASTMOD = "lastmod"; // Indicates whether an error has occurred and that processing should be // aborted. @NotNull private final AtomicBoolean shouldAbort; // Counters used to keep track of statistical information about processing. @NotNull private final AtomicLong opsAttempted; @NotNull private final AtomicLong opsRejected; @NotNull private final AtomicLong opsSucceeded; @NotNull private final AtomicLong totalOpDurationMillis; private volatile long initialAttempted; private volatile long initialSucceeded; // Variables pertaining to operations to be retried. @NotNull private final AtomicLong retryQueueSize; @NotNull private final Map>> retryQueue; // The result code for the first operation that was rejected, if any. @NotNull private final AtomicReference firstRejectResultCode; // The completion message for this tool, if available. @NotNull private final AtomicReference completionMessage; // The rate limiter for this tool, if any. @Nullable private FixedRateBarrier rateLimiter; // Writers used to write rejects and log messages. @Nullable private LDIFWriter rejectWriter; @Nullable private PrintWriter logWriter; // Variables used to keep track of data about processing intervals for use in // periodic status updates. private volatile long lastOpsAttempted; private volatile long lastTotalDurationMillis; private volatile long lastUpdateTimeMillis; private volatile long processingStartTimeMillis; // Thread-local date formatters used to format message timestamps. @NotNull private final ThreadLocal timestampFormatters; // The set of command-line arguments for this program. @Nullable private BooleanArgument allowUndeleteArg; @Nullable private BooleanArgument defaultAddArg; @Nullable private BooleanArgument followReferralsArg; @Nullable private BooleanArgument hardDeleteArg; @Nullable private BooleanArgument ignoreNoUserModificationArg; @Nullable private BooleanArgument isCompressedArg; @Nullable private BooleanArgument nameWithEntryUUIDArg; @Nullable private BooleanArgument neverRetryArg; @Nullable private BooleanArgument replicationRepairArg; @Nullable private BooleanArgument softDeleteArg; @Nullable private BooleanArgument suppressReferentialIntegrityUpdatesArg; @Nullable private BooleanArgument useAssuredReplicationArg; @Nullable private BooleanArgument useFirstRejectResultCodeAsExitCodeArg; @Nullable private BooleanArgument useManageDsaITArg; @Nullable private BooleanArgument usePermissiveModifyArg; @Nullable private ControlArgument addControlArg; @Nullable private ControlArgument bindControlArg; @Nullable private ControlArgument deleteControlArg; @Nullable private ControlArgument modifyControlArg; @Nullable private ControlArgument modifyDNControlArg; @Nullable private DNArgument proxyV1AsArg; @Nullable private DurationArgument assuredReplicationTimeoutArg; @Nullable private FileArgument encryptionPassphraseFileArg; @Nullable private FileArgument ldifFileArg; @Nullable private FileArgument logFileArg; @Nullable private FileArgument rejectFileArg; @Nullable private IntegerArgument numThreadsArg; @Nullable private IntegerArgument ratePerSecondArg; @Nullable private StringArgument assuredReplicationLocalLevelArg; @Nullable private StringArgument assuredReplicationRemoteLevelArg; @Nullable private StringArgument operationPurposeArg; @Nullable private StringArgument passwordUpdateBehaviorArg; @Nullable private StringArgument proxyAsArg; @Nullable private StringArgument suppressOperationalAttributeUpdatesArg; /** * Parses the provided set of command-line arguments and then performs the * necessary processing. * * @param args The command-line arguments provided to this program. */ public static void main(@NotNull final String... args) { final ResultCode resultCode = main(System.out, System.err, args); if (resultCode != ResultCode.SUCCESS) { System.exit(Math.min(resultCode.intValue(), 255)); } } /** * Parses the provided set of command-line arguments and then performs the * necessary processing. * * @param out The output stream to which standard output should be written. * It may be {@code null} if standard output should be * suppressed. * @param err The output stream to which standard error should be written. * It may be {@code null} if standard error should be * suppressed. * @param args The command-line arguments provided to this program. * * @return Zero if all processing completed successfully, or nonzero if an * error occurred. */ @NotNull() public static ResultCode main(@Nullable final OutputStream out, @Nullable final OutputStream err, @NotNull final String... args) { final ParallelUpdate parallelupdate = new ParallelUpdate(out, err); return parallelupdate.runTool(args); } /** * Creates a new instance of this tool with the provided output and error * streams. * * @param out The output stream to which standard output should be written. * It may be {@code null} if standard output should be * suppressed. * @param err The output stream to which standard error should be written. * It may be {@code null} if standard error should be * suppressed. */ public ParallelUpdate(@Nullable final OutputStream out, @Nullable final OutputStream err) { super(out, err); shouldAbort = new AtomicBoolean(false); opsAttempted = new AtomicLong(0L); opsRejected = new AtomicLong(0L); opsSucceeded = new AtomicLong(0L); totalOpDurationMillis = new AtomicLong(0L); initialAttempted = 0L; initialSucceeded = 0L; retryQueueSize = new AtomicLong(0L); retryQueue = new TreeMap<>(); firstRejectResultCode = new AtomicReference<>(); completionMessage = new AtomicReference<>(); rejectWriter = null; logWriter = null; lastOpsAttempted = 0L; lastTotalDurationMillis = 0L; lastUpdateTimeMillis = 0L; processingStartTimeMillis = System.currentTimeMillis(); timestampFormatters = new ThreadLocal<>(); allowUndeleteArg = null; defaultAddArg = null; followReferralsArg = null; hardDeleteArg = null; ignoreNoUserModificationArg = null; isCompressedArg = null; nameWithEntryUUIDArg = null; neverRetryArg = null; replicationRepairArg = null; softDeleteArg = null; suppressReferentialIntegrityUpdatesArg = null; useAssuredReplicationArg = null; useFirstRejectResultCodeAsExitCodeArg = null; useManageDsaITArg = null; usePermissiveModifyArg = null; addControlArg = null; bindControlArg = null; deleteControlArg = null; modifyControlArg = null; modifyDNControlArg = null; proxyV1AsArg = null; assuredReplicationTimeoutArg = null; encryptionPassphraseFileArg = null; ldifFileArg = null; logFileArg = null; rejectFileArg = null; numThreadsArg = null; ratePerSecondArg = null; assuredReplicationLocalLevelArg = null; assuredReplicationRemoteLevelArg = null; operationPurposeArg = null; passwordUpdateBehaviorArg = null; proxyAsArg = null; suppressOperationalAttributeUpdatesArg = null; } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolName() { return "parallel-update"; } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolDescription() { return INFO_PARALLEL_UPDATE_TOOL_DESCRIPTION_1.get(); } /** * {@inheritDoc} */ @Override() @NotNull() public List getAdditionalDescriptionParagraphs() { return Collections.unmodifiableList(Arrays.asList( INFO_PARALLEL_UPDATE_TOOL_DESCRIPTION_2.get(), INFO_PARALLEL_UPDATE_TOOL_DESCRIPTION_3.get())); } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolVersion() { return Version.NUMERIC_VERSION_STRING; } /** * {@inheritDoc} */ @Override() public void addNonLDAPArguments(@NotNull final ArgumentParser parser) throws ArgumentException { ldifFileArg = new FileArgument('l', "ldifFile", true, 1, null, INFO_PARALLEL_UPDATE_ARG_DESC_LDIF_FILE.get(), true, true, true, false); ldifFileArg.addLongIdentifier("ldif-file", true); ldifFileArg.addLongIdentifier("inputFile", true); ldifFileArg.addLongIdentifier("input-file", true); ldifFileArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(ldifFileArg); isCompressedArg = new BooleanArgument('c', "isCompressed", 1, INFO_PARALLEL_UPDATE_ARG_DESC_IS_COMPRESSED.get()); isCompressedArg.addLongIdentifier("is-compressed", true); isCompressedArg.addLongIdentifier("compressed", true); isCompressedArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); isCompressedArg.setHidden(true); parser.addArgument(isCompressedArg); encryptionPassphraseFileArg = new FileArgument(null, "encryptionPassphraseFile", false, 1, null, INFO_PARALLEL_UPDATE_ARG_DESC_ENCRYPTION_PASSPHRASE_FILE.get(), true, true, true, false); encryptionPassphraseFileArg.addLongIdentifier( "encryption-passphrase-file", true); encryptionPassphraseFileArg.addLongIdentifier( "encryptionPasswordFile", true); encryptionPassphraseFileArg.addLongIdentifier( "encryption-password-file", true); encryptionPassphraseFileArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(encryptionPassphraseFileArg); rejectFileArg = new FileArgument('R', "rejectFile", true, 1, null, INFO_PARALLEL_UPDATE_ARG_DESC_REJECT_FILE.get(), false, true, true, false); rejectFileArg.addLongIdentifier("reject-file", true); rejectFileArg.addLongIdentifier("rejectsFile", true); rejectFileArg.addLongIdentifier("rejects-file", true); rejectFileArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(rejectFileArg); useFirstRejectResultCodeAsExitCodeArg = new BooleanArgument(null, "useFirstRejectResultCodeAsExitCode", 1, INFO_PARALLEL_UPDATE_USE_FIRST_REJECT_RC.get()); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "use-first-reject-result-code-as-exit-code", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "useFirstRejectResultCode", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "use-first-reject-result-code", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "useFirstRejectResult", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "use-first-reject-result", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "useRejectResultCodeAsExitCode", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "use-reject-result-code-as-exit-code", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "useRejectResultCode", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "use-reject-result-code", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "useRejectResult", true); useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( "use-reject-result", true); useFirstRejectResultCodeAsExitCodeArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(useFirstRejectResultCodeAsExitCodeArg); neverRetryArg = new BooleanArgument('r', "neverRetry", 1, INFO_PARALLEL_UPDATE_ARG_DESC_NEVER_RETRY.get()); neverRetryArg.addLongIdentifier("never-retry", true); neverRetryArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(neverRetryArg); logFileArg = new FileArgument('L', "logFile", false, 1, null, INFO_PARALLEL_UPDATE_ARG_DESC_LOG_FILE.get(), false, true, true, false); logFileArg.addLongIdentifier("log-file", true); logFileArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(logFileArg); defaultAddArg = new BooleanArgument('a', "defaultAdd", 1, INFO_PARALLEL_UPDATE_ARG_DESC_DEFAULT_ADD.get()); defaultAddArg.addLongIdentifier("default-add", true); defaultAddArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(defaultAddArg); followReferralsArg = new BooleanArgument(null, "followReferrals", 1, INFO_PARALLEL_UPDATE_ARG_DESC_FOLLOW_REFERRALS.get()); followReferralsArg.addLongIdentifier("follow-referrals", true); followReferralsArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(followReferralsArg); numThreadsArg = new IntegerArgument('t', "numThreads", true, 1, null, INFO_PARALLEL_UPDATE_ARG_DESC_NUM_THREADS.get(), 1, Integer.MAX_VALUE, 8); numThreadsArg.addLongIdentifier("num-threads", true); numThreadsArg.addLongIdentifier("threads", true); numThreadsArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(numThreadsArg); ratePerSecondArg = new IntegerArgument('s', "ratePerSecond", false, 1, null, INFO_PARALLEL_UPDATE_ARG_DESC_RATE_PER_SECOND.get(), 1, Integer.MAX_VALUE); ratePerSecondArg.addLongIdentifier("rate-per-second", true); ratePerSecondArg.addLongIdentifier("requestsPerSecond", true); ratePerSecondArg.addLongIdentifier("requests-per-second", true); ratePerSecondArg.addLongIdentifier("operationsPerSecond", true); ratePerSecondArg.addLongIdentifier("operations-per-second", true); ratePerSecondArg.addLongIdentifier("opsPerSecond", true); ratePerSecondArg.addLongIdentifier("ops-per-second", true); ratePerSecondArg.addLongIdentifier("rate", true); ratePerSecondArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); parser.addArgument(ratePerSecondArg); usePermissiveModifyArg = new BooleanArgument('M', "usePermissiveModify", 1, INFO_PARALLEL_UPDATE_ARG_DESC_USE_PERMISSIVE_MODIFY.get()); usePermissiveModifyArg.addLongIdentifier("use-permissive-modify", true); usePermissiveModifyArg.addLongIdentifier("permissiveModify", true); usePermissiveModifyArg.addLongIdentifier("permissive-modify", true); usePermissiveModifyArg.addLongIdentifier("permissive", true); usePermissiveModifyArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(usePermissiveModifyArg); ignoreNoUserModificationArg = new BooleanArgument(null, "ignoreNoUserModification", 1, INFO_PARALLEL_UPDATE_ARG_DESC_IGNORE_NO_USER_MOD.get()); ignoreNoUserModificationArg.addLongIdentifier("ignore-no-user-modification", true); ignoreNoUserModificationArg.addLongIdentifier("ignoreNoUserMod", true); ignoreNoUserModificationArg.addLongIdentifier("ignore-no-user-mod", true); ignoreNoUserModificationArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(ignoreNoUserModificationArg); proxyAsArg = new StringArgument('Y', "proxyAs", false, 1, INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_PROXY_AS.get(), INFO_PARALLEL_UPDATE_ARG_DESC_PROXY_AS.get()); proxyAsArg.addLongIdentifier("proxy-as", true); proxyAsArg.addLongIdentifier("proxyV2As", true); proxyAsArg.addLongIdentifier("proxy-v2-as", true); proxyAsArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(proxyAsArg); proxyV1AsArg = new DNArgument(null, "proxyV1As", false, 1, null, INFO_PARALLEL_UPDATE_ARG_DESC_PROXY_V1_AS.get()); proxyV1AsArg.addLongIdentifier("proxy-v1-as", true); proxyV1AsArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(proxyV1AsArg); passwordUpdateBehaviorArg = new StringArgument(null, "passwordUpdateBehavior", false, 0, INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_PW_UPDATE_BEHAVIOR.get(), INFO_PARALLEL_UPDATE_ARG_DESC_PW_UPDATE_BEHAVIOR.get()); passwordUpdateBehaviorArg.addLongIdentifier("password-update-behavior", true); passwordUpdateBehaviorArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(passwordUpdateBehaviorArg); operationPurposeArg = new StringArgument(null, "operationPurpose", false, 1, INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_OPERATION_PURPOSE.get(), INFO_PARALLEL_UPDATE_ARG_DESC_OPERATION_PURPOSE.get()); operationPurposeArg.addLongIdentifier("operation-purpose", true); operationPurposeArg.addLongIdentifier("purpose", true); operationPurposeArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(operationPurposeArg); useManageDsaITArg = new BooleanArgument(null, "useManageDsaIT", 1, INFO_PARALLEL_UPDATE_ARG_DESC_USE_MANAGE_DSA_IT.get()); useManageDsaITArg.addLongIdentifier("use-manage-dsa-it", true); useManageDsaITArg.addLongIdentifier("manageDsaIT", true); useManageDsaITArg.addLongIdentifier("manage-dsa-it", true); useManageDsaITArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(useManageDsaITArg); nameWithEntryUUIDArg = new BooleanArgument(null, "nameWithEntryUUID", 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_NAME_WITH_ENTRY_UUID.get()); nameWithEntryUUIDArg.addLongIdentifier("name-with-entryuuid", true); nameWithEntryUUIDArg.addLongIdentifier("name-with-entry-uuid", true); nameWithEntryUUIDArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(nameWithEntryUUIDArg); softDeleteArg = new BooleanArgument(null, "useSoftDelete", 1, INFO_PARALLEL_UPDATE_ARG_DESC_SOFT_DELETE.get()); softDeleteArg.addLongIdentifier("use-soft-delete", true); softDeleteArg.addLongIdentifier("softDelete", true); softDeleteArg.addLongIdentifier("soft-delete", true); softDeleteArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(softDeleteArg); hardDeleteArg = new BooleanArgument(null, "useHardDelete", 1, INFO_PARALLEL_UPDATE_ARG_DESC_HARD_DELETE.get()); hardDeleteArg.addLongIdentifier("use-hard-delete", true); hardDeleteArg.addLongIdentifier("hardDelete", true); hardDeleteArg.addLongIdentifier("hard-delete", true); hardDeleteArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(hardDeleteArg); allowUndeleteArg = new BooleanArgument(null, "allowUndelete", 1, INFO_PARALLEL_UPDATE_ARG_DESC_ALLOW_UNDELETE.get()); allowUndeleteArg.addLongIdentifier("allow-undelete", true); allowUndeleteArg.addLongIdentifier("undelete", true); allowUndeleteArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(allowUndeleteArg); useAssuredReplicationArg = new BooleanArgument(null, "useAssuredReplication", 1, INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION.get()); useAssuredReplicationArg.addLongIdentifier("use-assured-replication", true); useAssuredReplicationArg.addLongIdentifier("assuredReplication", true); useAssuredReplicationArg.addLongIdentifier("assured-replication", true); useAssuredReplicationArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(useAssuredReplicationArg); assuredReplicationLocalLevelArg = new StringArgument(null, "assuredReplicationLocalLevel", false, 1, INFO_LDAPDELETE_ARG_PLACEHOLDER_ASSURED_REPLICATION_LOCAL_LEVEL.get(), INFO_LDAPDELETE_ARG_DESC_ASSURED_REPLICATION_LOCAL_LEVEL.get(), StaticUtils.setOf( ASSURED_REPLICATION_LOCAL_LEVEL_NONE, ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER, ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS), (String) null); assuredReplicationLocalLevelArg.addLongIdentifier( "assured-replication-local-level", true); assuredReplicationLocalLevelArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(assuredReplicationLocalLevelArg); assuredReplicationRemoteLevelArg = new StringArgument(null, "assuredReplicationRemoteLevel", false, 1, INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_ASSURED_REPLICATION_REMOTE_LEVEL. get(), INFO_PARALLEL_UPDATE_ARG_DESC_ASSURED_REPLICATION_REMOTE_LEVEL.get(), StaticUtils.setOf( ASSURED_REPLICATION_REMOTE_LEVEL_NONE, ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION, ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS, ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS), (String) null); assuredReplicationRemoteLevelArg.addLongIdentifier( "assured-replication-remote-level", true); assuredReplicationRemoteLevelArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(assuredReplicationRemoteLevelArg); assuredReplicationTimeoutArg = new DurationArgument(null, "assuredReplicationTimeout", false, null, INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION_TIMEOUT.get()); assuredReplicationTimeoutArg.addLongIdentifier( "assured-replication-timeout", true); assuredReplicationTimeoutArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(assuredReplicationTimeoutArg); replicationRepairArg = new BooleanArgument(null, "replicationRepair", 1, INFO_PARALLEL_UPDATE_ARG_DESC_USE_REPLICATION_REPAIR.get()); replicationRepairArg.addLongIdentifier("replication-repair", true); replicationRepairArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(replicationRepairArg); suppressOperationalAttributeUpdatesArg = new StringArgument(null, "suppressOperationalAttributeUpdates", false, 0, INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_SUPPRESS_OP_ATTR_UPDATES.get(), INFO_PARALLEL_UPDATE_ARG_DESC_SUPPRESS_OP_ATTR_UPDATES.get(), StaticUtils.setOf( SUPPRESS_OP_ATTR_LAST_ACCESS_TIME, SUPPRESS_OP_ATTR_LAST_LOGIN_TIME, SUPPRESS_OP_ATTR_LAST_LOGIN_IP, SUPPRESS_OP_ATTR_LASTMOD), (String) null); suppressOperationalAttributeUpdatesArg.addLongIdentifier( "suppress-operational-attribute-updates", true); suppressOperationalAttributeUpdatesArg.addLongIdentifier( "suppressOperationalAttributeUpdate", true); suppressOperationalAttributeUpdatesArg.addLongIdentifier( "suppress-operational-attribute-update", true); suppressOperationalAttributeUpdatesArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(suppressOperationalAttributeUpdatesArg); suppressReferentialIntegrityUpdatesArg = new BooleanArgument(null, "suppressReferentialIntegrityUpdates", 1, INFO_PARALLEL_UPDATE_ARG_DESC_SUPPRESS_REFERENTIAL_INTEGRITY_UPDATES. get()); suppressReferentialIntegrityUpdatesArg.addLongIdentifier( "suppress-referential-integrity-updates", true); suppressReferentialIntegrityUpdatesArg.addLongIdentifier( "suppressReferentialIntegrityUpdate", true); suppressReferentialIntegrityUpdatesArg.addLongIdentifier( "suppress-referential-integrity-update", true); suppressReferentialIntegrityUpdatesArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(suppressReferentialIntegrityUpdatesArg); addControlArg = new ControlArgument(null, "addControl", false, 0, null, INFO_PARALLEL_UPDATE_ARG_DESC_ADD_CONTROL.get()); addControlArg.addLongIdentifier("add-control", true); addControlArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(addControlArg); bindControlArg = new ControlArgument(null, "bindControl", false, 0, null, INFO_PARALLEL_UPDATE_ARG_DESC_BIND_CONTROL.get()); bindControlArg.addLongIdentifier("bind-control", true); bindControlArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(bindControlArg); deleteControlArg = new ControlArgument(null, "deleteControl", false, 0, null, INFO_PARALLEL_UPDATE_ARG_DESC_DELETE_CONTROL.get()); deleteControlArg.addLongIdentifier("delete-control", true); deleteControlArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(deleteControlArg); modifyControlArg = new ControlArgument(null, "modifyControl", false, 0, null, INFO_PARALLEL_UPDATE_ARG_DESC_MODIFY_CONTROL.get()); modifyControlArg.addLongIdentifier("modify-control", true); modifyControlArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(modifyControlArg); modifyDNControlArg = new ControlArgument(null, "modifyDNControl", false, 0, null, INFO_PARALLEL_UPDATE_ARG_DESC_MODIFY_DN_CONTROL.get()); modifyDNControlArg.addLongIdentifier("modify-dn-control", true); modifyDNControlArg.setArgumentGroupName( INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); parser.addArgument(modifyDNControlArg); parser.addExclusiveArgumentSet(followReferralsArg, useManageDsaITArg); parser.addExclusiveArgumentSet(proxyAsArg, proxyV1AsArg); parser.addExclusiveArgumentSet(softDeleteArg, hardDeleteArg); parser.addDependentArgumentSet(assuredReplicationLocalLevelArg, useAssuredReplicationArg); parser.addDependentArgumentSet(assuredReplicationRemoteLevelArg, useAssuredReplicationArg); parser.addDependentArgumentSet(assuredReplicationTimeoutArg, useAssuredReplicationArg); parser.addExclusiveArgumentSet(useAssuredReplicationArg, replicationRepairArg); } /** * {@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() protected boolean supportsAuthentication() { return true; } /** * {@inheritDoc} */ @Override() protected boolean defaultToPromptForBindPassword() { return true; } /** * {@inheritDoc} */ @Override() protected boolean supportsSASLHelp() { return true; } /** * {@inheritDoc} */ @Override() protected boolean includeAlternateLongIdentifiers() { return true; } /** * {@inheritDoc} */ @Override() @NotNull() protected List getBindControls() { final List bindControls = new ArrayList<>(); if ((bindControlArg != null) && bindControlArg.isPresent()) { bindControls.addAll(bindControlArg.getValues()); } if ((suppressOperationalAttributeUpdatesArg != null) && suppressOperationalAttributeUpdatesArg.isPresent()) { final EnumSet suppressTypes = EnumSet.noneOf(SuppressType.class); for (final String s : suppressOperationalAttributeUpdatesArg.getValues()) { if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_ACCESS_TIME)) { suppressTypes.add(SuppressType.LAST_ACCESS_TIME); } else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_TIME)) { suppressTypes.add(SuppressType.LAST_LOGIN_TIME); } else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_IP)) { suppressTypes.add(SuppressType.LAST_LOGIN_IP); } } bindControls.add(new SuppressOperationalAttributeUpdateRequestControl( true, suppressTypes)); } return Collections.emptyList(); } /** * {@inheritDoc} */ @Override() protected boolean supportsMultipleServers() { return true; } /** * {@inheritDoc} */ @Override() protected boolean supportsSSLDebugging() { return true; } /** * {@inheritDoc} */ @Override() @NotNull() public LDAPConnectionOptions getConnectionOptions() { final LDAPConnectionOptions options = new LDAPConnectionOptions(); options.setUseSynchronousMode(true); options.setFollowReferrals( ((followReferralsArg != null) && followReferralsArg.isPresent())); options.setUnsolicitedNotificationHandler(this); options.setResponseTimeoutMillis(0L); return options; } /** * {@inheritDoc} */ @Override() protected boolean logToolInvocationByDefault() { return true; } /** * {@inheritDoc} */ @Override() @Nullable() public String getToolCompletionMessage() { return completionMessage.get(); } /** * {@inheritDoc} */ @Override() @NotNull() public ResultCode doToolProcessing() { // Create the sets of controls to include in each type of request. final Control[] addControls; final Control[] deleteControls; final Control[] modifyControls; final Control[] modifyDNControls; try { final List addControlList = new ArrayList<>(); final List deleteControlList = new ArrayList<>(); final List modifyControlList = new ArrayList<>(); final List modifyDNControlList = new ArrayList<>(); getOperationControls(addControlList, deleteControlList, modifyControlList, modifyDNControlList); addControls = StaticUtils.toArray(addControlList, Control.class); deleteControls = StaticUtils.toArray(deleteControlList, Control.class); modifyControls = StaticUtils.toArray(modifyControlList, Control.class); modifyDNControls = StaticUtils.toArray(modifyDNControlList, Control.class); } catch (final LDAPException e) { Debug.debugException(e); logCompletionMessage(true, e.getMessage()); return e.getResultCode(); } // Get the connection pool to use to communicate with the directory // server(s). final LDAPConnectionPool connectionPool; final int numThreads = numThreadsArg.getValue(); try { connectionPool = getConnectionPool(numThreads, numThreads, 1, null, null, true, null); connectionPool.setConnectionPoolName("parallel-update"); connectionPool.setRetryFailedOperationsDueToInvalidConnections( (! neverRetryArg.isPresent())); } catch (final LDAPException e) { Debug.debugException(e); logCompletionMessage(true, ERR_PARALLEL_UPDATE_CANNOT_CREATE_POOL.get( StaticUtils.getExceptionMessage(e))); return e.getResultCode(); } // Create the LDIF reader that will read the changes to process. final LDIFReader ldifReader; final String encryptionPassphrase; try { final ObjectPair ldifReaderPair = createLDIFReader(); ldifReader = ldifReaderPair.getFirst(); encryptionPassphrase = ldifReaderPair.getSecond(); } catch (final LDAPException e) { Debug.debugException(e); logCompletionMessage(true, e.getMessage()); connectionPool.close(); return e.getResultCode(); } final AtomicReference resultCodeRef = new AtomicReference<>(ResultCode.SUCCESS); try { // If the LDIF file is encrypted, then get the ID of the encryption // settings definition (if any) used to generate the encryption key. final String encryptionSettingsDefinitionID; if (encryptionPassphrase == null) { encryptionSettingsDefinitionID = null; } else { encryptionSettingsDefinitionID = getEncryptionSettingsDefinitionID(); } // Create the LDIF writer that will be used to write rejects. try { rejectWriter = createRejectWriter(encryptionPassphrase, encryptionSettingsDefinitionID); } catch (final LDAPException e) { Debug.debugException(e); logCompletionMessage(true, e.getMessage()); return ResultCode.LOCAL_ERROR; } // If appropriate, create the log writer that will be used to provide a // log of the changes that are attempted. if (logFileArg.isPresent()) { try { logWriter = new PrintWriter(logFileArg.getValue()); } catch (final Exception e) { Debug.debugException(e); logCompletionMessage(true, ERR_PARALLEL_UPDATE_ERROR_CREATING_LOG_WRITER.get( logFileArg.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e))); return ResultCode.LOCAL_ERROR; } } // Create The queue that will hold the operations to process. final ParallelUpdateOperationQueue operationQueue = new ParallelUpdateOperationQueue(this, numThreads, (2 * numThreads)); // Create the rate limiter, if appropriate. if (ratePerSecondArg.isPresent()) { rateLimiter = new FixedRateBarrier(1000L, ratePerSecondArg.getValue()); } else { rateLimiter = null; } // Create and start all of the threads that will be used to process // requests. final List operationThreadList = new ArrayList<>(numThreads); for (int i=1; i <= numThreads; i++) { final ParallelUpdateOperationThread operationThread = new ParallelUpdateOperationThread(this, connectionPool, operationQueue, i, rateLimiter, addControls, deleteControls, modifyControls, modifyDNControls, allowUndeleteArg.isPresent()); operationThreadList.add(operationThread); operationThread.start(); } // Create a progress monitor that will be used to report periodic status // updates about the processing that has been performed. final ParallelUpdateProgressMonitor progressMonitor = new ParallelUpdateProgressMonitor(this); try { processingStartTimeMillis = System.currentTimeMillis(); progressMonitor.start(); while (! shouldAbort.get()) { final LDIFChangeRecord changeRecord; try { changeRecord = ldifReader.readChangeRecord( defaultAddArg.isPresent()); } catch (final LDIFException e) { Debug.debugException(e); if (e.mayContinueReading()) { final String message = ERR_PARALLEL_UPDATE_RECOVERABLE_LDIF_EXCEPTION.get( ldifFileArg.getValue().getAbsolutePath(), e.getMessage()); logMessage(message); reject(null, new LDAPException(ResultCode.DECODING_ERROR, message, e)); opsAttempted.incrementAndGet(); continue; } else { shouldAbort.set(true); final String message = ERR_PARALLEL_UPDATE_UNRECOVERABLE_LDIF_EXCEPTION.get( ldifFileArg.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e)); reject(null, new LDAPException(ResultCode.DECODING_ERROR, message, e)); logCompletionMessage(true, message); return ResultCode.DECODING_ERROR; } } catch (final Exception e) { Debug.debugException(e); shouldAbort.set(true); final String message = ERR_PARALLEL_UPDATE_ERROR_READING_LDIF_FILE.get( ldifFileArg.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e)); reject(null, new LDAPException(ResultCode.LOCAL_ERROR, message, e)); logCompletionMessage(true, message); return ResultCode.LOCAL_ERROR; } if (changeRecord == null) { // We've reached the end of the LDIF file. break; } else { try { operationQueue.addChangeRecord(changeRecord); } catch (final Exception e) { Debug.debugException(e); // This indicates that the attempt to enqueue the change record // was interrupted. This shouldn't happen, but if it does, then // mark it to be retried. final LDAPException le = new LDAPException(ResultCode.LOCAL_ERROR, ERR_PARALLEL_UPDATE_ENQUEUE_FAILED.get( StaticUtils.getExceptionMessage(e)), e); retry(changeRecord, le); } } } // If a failure was encountered, then abort. if (shouldAbort.get()) { resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); return resultCodeRef.get(); } // Indicate that we've reached the end of the LDIF file. out(); wrapOut(0, WRAP_COLUMN, INFO_PARALLEL_UPDATE_END_OF_LDIF.get()); out(); // Wait for the operation queue to become idle so that we know there // are no more outstanding operations to be processed. operationQueue.waitUntilIdle(); initialAttempted = opsAttempted.get(); initialSucceeded = opsSucceeded.get(); // If there are any operations to retry, then do so now. Map>> retryQueueCopy; synchronized (retryQueue) { retryQueueCopy = new TreeMap<>(retryQueue); retryQueue.clear(); } int lastRetryQueueSize = 0; while ((! retryQueueCopy.isEmpty()) && (retryQueueCopy.size() != lastRetryQueueSize)) { out(); wrapOut(0, WRAP_COLUMN, INFO_PARALLEL_UPDATE_BEGINNING_RETRY.get(retryQueueCopy.size())); out(); for (final Map.Entry>> e : retryQueueCopy.entrySet()) { for (final ObjectPair p : e.getValue()) { if (shouldAbort.get()) { resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); return resultCodeRef.get(); } final LDIFChangeRecord changeRecord = p.getFirst(); try { operationQueue.addChangeRecord(changeRecord); retryQueueSize.decrementAndGet(); } catch (final Exception ex) { Debug.debugException(ex); // This indicates that the attempt to enqueue the change record // was interrupted. This shouldn't happen, but if it does, then // mark it to be retried. final LDAPException le = new LDAPException( ResultCode.LOCAL_ERROR, ERR_PARALLEL_UPDATE_ENQUEUE_FAILED.get( StaticUtils.getExceptionMessage(ex)), ex); retry(changeRecord, le); } } } operationQueue.waitUntilIdle(); lastRetryQueueSize = retryQueueCopy.size(); synchronized (retryQueue) { retryQueueCopy = new TreeMap<>(retryQueue); retryQueue.clear(); } } // If we've gotten here, then it means that either the retry queue // (NOTE: we actually need to use retryQueueCopy) is empty or none of // the retry attempts succeeded on the last pass. If it's the latter, // then reject any of the remaining operations. synchronized (retryQueue) { final int remainingToRetry = retryQueueCopy.size(); if (remainingToRetry > 0) { if (remainingToRetry == 1) { wrapErr(0, WRAP_COLUMN, ERR_PARALLEL_UPDATE_NO_PROGRESS_ONE.get()); } else { wrapErr(0, WRAP_COLUMN, ERR_PARALLEL_UPDATE_NO_PROGRESS_MULTIPLE.get( remainingToRetry)); } } for (final Map.Entry>> e : retryQueueCopy.entrySet()) { for (final ObjectPair p : e.getValue()) { reject(p.getFirst(), p.getSecond()); retryQueueSize.decrementAndGet(); } } } } finally { operationQueue.setEndOfLDIF(); operationQueue.waitUntilIdle(); for (final ParallelUpdateOperationThread operationThread : operationThreadList) { try { operationThread.join(); } catch (final Exception e) { Debug.debugException(e); logCompletionMessage(true, ERR_PARALLEL_UPDATE_CANNOT_JOIN_THREAD.get( operationThread.getName(), StaticUtils.getExceptionMessage(e))); resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); } } try { progressMonitor.stopRunning(); progressMonitor.join(); } catch (final Exception e) { Debug.debugException(e); logCompletionMessage(true, ERR_PARALLEL_UPDATE_CANNOT_JOIN_PROGRESS_MONITOR.get( StaticUtils.getExceptionMessage(e))); resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); } } } finally { connectionPool.close(); if (rejectWriter != null) { try { rejectWriter.close(); } catch (final Exception e) { Debug.debugException(e); logCompletionMessage(true, ERR_PARALLEL_UPDATE_ERROR_CLOSING_REJECT_WRITER.get( rejectFileArg.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e))); resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); } } if (logWriter != null) { try { logWriter.close(); } catch (final Exception e) { Debug.debugException(e); logCompletionMessage(true, ERR_PARALLEL_UPDATE_ERROR_CLOSING_LOG_WRITER.get( logFileArg.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e))); resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); } } try { ldifReader.close(); } catch (final Exception e) { Debug.debugException(e); logCompletionMessage(true, WARN_PARALLEL_UPDATE_ERROR_CLOSING_READER.get( ldifFileArg.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e))); resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); } } // If we've gotten here, then processing has completed. Print some summary // messages and return an appropriate result code. final long processingDurationMillis = System.currentTimeMillis() - processingStartTimeMillis; final long numAttempts = opsAttempted.get(); final long numSuccesses = opsSucceeded.get(); final long numRejects = opsRejected.get(); final long retryAttempts = numAttempts - initialAttempted; final long retrySuccesses = numSuccesses - initialSucceeded; out(INFO_PARALLEL_UPDATE_DONE.get(getToolName())); out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_ATTEMPTED.get(numAttempts)); if (retryAttempts > 0L) { out(INFO_PARALLEL_UPDATE_SUMMARY_INITIAL_ATTEMPTS.get(initialAttempted)); out(INFO_PARALLEL_UPDATE_SUMMARY_RETRY_ATTEMPTS.get(retryAttempts)); } out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_SUCCEEDED.get(numSuccesses)); if (retryAttempts > 0) { out(INFO_PARALLEL_UPDATED_OPS_SUCCEEDED_INITIAL.get(initialSucceeded)); out(INFO_PARALLEL_UPDATED_OPS_SUCCEEDED_RETRY.get(retrySuccesses)); } out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_REJECTED.get(numRejects)); out(INFO_PARALLEL_UPDATE_SUMMARY_DURATION.get( StaticUtils.millisToHumanReadableDuration(processingDurationMillis))); if ((numAttempts > 0L) && (processingDurationMillis > 0L)) { final double attemptsPerSecond = numAttempts * 1_000.0d / processingDurationMillis; final DecimalFormat decimalFormat = new DecimalFormat("0.000"); out(INFO_PARALLEL_UPDATE_SUMMARY_RATE.get( decimalFormat.format(attemptsPerSecond))); } if (numRejects == 0L) { completionMessage.compareAndSet(null, INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_ALL_SUCCEEDED.get( getToolName())); } else if (numRejects == 1L) { completionMessage.compareAndSet(null, INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_ONE_REJECTED.get( getToolName())); } else { completionMessage.compareAndSet(null, INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_MULTIPLE_REJECTED.get( getToolName(), numRejects)); } ResultCode finalResultCode = resultCodeRef.get(); if ((finalResultCode == ResultCode.SUCCESS) && useFirstRejectResultCodeAsExitCodeArg.isPresent() && (firstRejectResultCode.get() != null)) { finalResultCode = firstRejectResultCode.get(); } return finalResultCode; } /** * Updates the provided lists with the appropriate controls to include in * each type of request. * * @param addControls The list that should be updated with controls to * include in add requests. It must not be * {@code null} and must be updatable. * @param deleteControls The list that should be updated with controls to * include in delete requests. It must not be * {@code null} and must be updatable. * @param modifyControls The list that should be updated with controls to * include in modify requests. It must not be * {@code null} and must be updatable. * @param modifyDNControls The list that should be updated with controls to * include in modify DN requests. It must not be * {@code null} and must be updatable. * * @throws LDAPException If a problem is encountered while creating any of * the controls. */ private void getOperationControls( @NotNull final List addControls, @NotNull final List deleteControls, @NotNull final List modifyControls, @NotNull final List modifyDNControls) throws LDAPException { if (addControlArg.isPresent()) { addControls.addAll(addControlArg.getValues()); } if (deleteControlArg.isPresent()) { deleteControls.addAll(deleteControlArg.getValues()); } if (modifyControlArg.isPresent()) { modifyControls.addAll(modifyControlArg.getValues()); } if (modifyDNControlArg.isPresent()) { modifyDNControls.addAll(modifyDNControlArg.getValues()); } if (proxyAsArg.isPresent()) { final ProxiedAuthorizationV2RequestControl c = new ProxiedAuthorizationV2RequestControl(proxyAsArg.getValue()); addControls.add(c); deleteControls.add(c); modifyControls.add(c); modifyDNControls.add(c); } else if (proxyV1AsArg.isPresent()) { final ProxiedAuthorizationV1RequestControl c = new ProxiedAuthorizationV1RequestControl(proxyV1AsArg.getValue()); addControls.add(c); deleteControls.add(c); modifyControls.add(c); modifyDNControls.add(c); } if (usePermissiveModifyArg.isPresent()) { modifyControls.add(new PermissiveModifyRequestControl(true)); } if (ignoreNoUserModificationArg.isPresent()) { final IgnoreNoUserModificationRequestControl c = new IgnoreNoUserModificationRequestControl(); addControls.add(c); modifyControls.add(c); } if (useManageDsaITArg.isPresent()) { final ManageDsaITRequestControl c = new ManageDsaITRequestControl(true); addControls.add(c); deleteControls.add(c); modifyControls.add(c); modifyDNControls.add(c); } if (nameWithEntryUUIDArg.isPresent()) { addControls.add(new NameWithEntryUUIDRequestControl(true)); } if (softDeleteArg.isPresent()) { deleteControls.add(new SoftDeleteRequestControl(true, true)); } else if (hardDeleteArg.isPresent()) { deleteControls.add(new HardDeleteRequestControl(true)); } if (operationPurposeArg.isPresent()) { final OperationPurposeRequestControl c = new OperationPurposeRequestControl(false, "parallel-update", Version.NUMERIC_VERSION_STRING, ParallelUpdate.class.getName() + ".getOperationControls", operationPurposeArg.getValue()); addControls.add(c); deleteControls.add(c); modifyControls.add(c); modifyDNControls.add(c); } if (replicationRepairArg.isPresent()) { final ReplicationRepairRequestControl c = new ReplicationRepairRequestControl(); addControls.add(c); deleteControls.add(c); modifyControls.add(c); modifyDNControls.add(c); } if (suppressReferentialIntegrityUpdatesArg.isPresent()) { final SuppressReferentialIntegrityUpdatesRequestControl c = new SuppressReferentialIntegrityUpdatesRequestControl(true); deleteControls.add(c); modifyDNControls.add(c); } if (useAssuredReplicationArg.isPresent()) { final AssuredReplicationLocalLevel localLevel; if (assuredReplicationLocalLevelArg.isPresent()) { final String localLevelStr = StaticUtils.toLowerCase( assuredReplicationLocalLevelArg.getValue()); switch (localLevelStr) { case ASSURED_REPLICATION_LOCAL_LEVEL_NONE: localLevel = AssuredReplicationLocalLevel.NONE; break; case ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER: localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER; break; case ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS: localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS; break; default: // This should never happen. localLevel = null; break; } } else { localLevel = null; } final AssuredReplicationRemoteLevel remoteLevel; if (assuredReplicationRemoteLevelArg.isPresent()) { final String remoteLevelStr = StaticUtils.toLowerCase( assuredReplicationRemoteLevelArg.getValue()); switch (remoteLevelStr) { case ASSURED_REPLICATION_REMOTE_LEVEL_NONE: remoteLevel = AssuredReplicationRemoteLevel.NONE; break; case ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION: remoteLevel = AssuredReplicationRemoteLevel. RECEIVED_ANY_REMOTE_LOCATION; break; case ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS: remoteLevel = AssuredReplicationRemoteLevel. RECEIVED_ALL_REMOTE_LOCATIONS; break; case ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS: remoteLevel = AssuredReplicationRemoteLevel. PROCESSED_ALL_REMOTE_SERVERS; break; default: // This should never happen. remoteLevel = null; break; } } else { remoteLevel = null; } final Long timeoutMillis; if (assuredReplicationTimeoutArg.isPresent()) { timeoutMillis = assuredReplicationTimeoutArg.getValue( TimeUnit.MILLISECONDS); } else { timeoutMillis = null; } final AssuredReplicationRequestControl c = new AssuredReplicationRequestControl(true, localLevel, localLevel, remoteLevel, remoteLevel, timeoutMillis, false); addControls.add(c); deleteControls.add(c); modifyControls.add(c); modifyDNControls.add(c); } if (passwordUpdateBehaviorArg.isPresent()) { final PasswordUpdateBehaviorRequestControlProperties properties = new PasswordUpdateBehaviorRequestControlProperties(); for (final String argValue : passwordUpdateBehaviorArg.getValues()) { final int equalPos = argValue.indexOf('='); if (equalPos < 0) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_PARALLEL_UPDATE_MALFORMED_PW_UPDATE_VALUE.get( argValue, passwordUpdateBehaviorArg.getIdentifierString())); } final String propertyName = argValue.substring(0, equalPos).trim(); final String lowerName = StaticUtils.toLowerCase(propertyName); switch (lowerName) { case PW_UPDATE_BEHAVIOR_NAME_IS_SELF_CHANGE: properties.setIsSelfChange( getBooleanPWUpdateBehaviorValue(argValue)); break; case PW_UPDATE_BEHAVIOR_NAME_ALLOW_PRE_ENCODED_PW: properties.setAllowPreEncodedPassword( getBooleanPWUpdateBehaviorValue(argValue)); break; case PW_UPDATE_BEHAVIOR_NAME_SKIP_PW_VALIDATION: properties.setSkipPasswordValidation( getBooleanPWUpdateBehaviorValue(argValue)); break; case PW_UPDATE_BEHAVIOR_NAME_IGNORE_PW_HISTORY: properties.setIgnorePasswordHistory( getBooleanPWUpdateBehaviorValue(argValue)); break; case PW_UPDATE_BEHAVIOR_NAME_IGNORE_MIN_PW_AGE: properties.setIgnoreMinimumPasswordAge( getBooleanPWUpdateBehaviorValue(argValue)); break; case PW_UPDATE_BEHAVIOR_NAME_MUST_CHANGE_PW: properties.setMustChangePassword( getBooleanPWUpdateBehaviorValue(argValue)); break; case PW_UPDATE_BEHAVIOR_NAME_PW_STORAGE_SCHEME: final String propertyValue = argValue.substring(equalPos+1).trim(); properties.setPasswordStorageScheme(propertyValue); break; default: throw new LDAPException(ResultCode.PARAM_ERROR, ERR_PARALLEL_UPDATE_UNKNOWN_PW_UPDATE_PROP.get(argValue, passwordUpdateBehaviorArg.getIdentifierString(), PW_UPDATE_BEHAVIOR_NAME_IS_SELF_CHANGE, PW_UPDATE_BEHAVIOR_NAME_ALLOW_PRE_ENCODED_PW, PW_UPDATE_BEHAVIOR_NAME_SKIP_PW_VALIDATION, PW_UPDATE_BEHAVIOR_NAME_IGNORE_PW_HISTORY, PW_UPDATE_BEHAVIOR_NAME_IGNORE_MIN_PW_AGE, PW_UPDATE_BEHAVIOR_NAME_PW_STORAGE_SCHEME, PW_UPDATE_BEHAVIOR_NAME_MUST_CHANGE_PW)); } } final PasswordUpdateBehaviorRequestControl c = new PasswordUpdateBehaviorRequestControl(properties, true); addControls.add(c); modifyControls.add(c); } if (suppressOperationalAttributeUpdatesArg.isPresent()) { final EnumSet suppressTypes = EnumSet.noneOf(SuppressType.class); for (final String s : suppressOperationalAttributeUpdatesArg.getValues()) { if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_ACCESS_TIME)) { suppressTypes.add(SuppressType.LAST_ACCESS_TIME); } else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_TIME)) { suppressTypes.add(SuppressType.LAST_LOGIN_TIME); } else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_IP)) { suppressTypes.add(SuppressType.LAST_LOGIN_IP); } } final SuppressOperationalAttributeUpdateRequestControl c = new SuppressOperationalAttributeUpdateRequestControl(true, suppressTypes); addControls.add(c); deleteControls.add(c); modifyControls.add(c); modifyDNControls.add(c); } } /** * Retrieves the value from the provided name-value pair and parses it as a * boolean. * * @param nameValuePair The name-value pair to be parsed. It must not be * {@code null} and it must contain an equal sign. * * @return The boolean value parsed from the provided name-value pair. * * @throws LDAPException If the value could not be parsed as a boolean. */ private boolean getBooleanPWUpdateBehaviorValue( @NotNull final String nameValuePair) throws LDAPException { final int equalPos = nameValuePair.indexOf('='); final String propertyValue = nameValuePair.substring(equalPos+1).trim(); final String lowerValue = StaticUtils.toLowerCase(propertyValue); switch (lowerValue) { case "true": case "t": case "yes": case "on": case "1": return true; case "false": case "f": case "no": case "off": case "0": return false; default: final String propertyName = nameValuePair.substring(0, equalPos).trim(); throw new LDAPException(ResultCode.PARAM_ERROR, ERR_PARALLEL_UPDATE_PW_UPDATE_VALUE_NOT_BOOLEAN.get(nameValuePair, passwordUpdateBehaviorArg.getIdentifierString(), propertyName)); } } /** * Creates the LDIF reader to use to read the changes to process. * * @return An object pair in which the first element is the LDIF reader and * the second element is the passphrase used to encrypt the contents * of the LDIF file (or {@code null} if the LDIF file is not * encrypted). * * @throws LDAPException If a problem occurs while trying to create the LDIF * reader. */ @NotNull() private ObjectPair createLDIFReader() throws LDAPException { final File ldifFile = ldifFileArg.getValue(); try { final String encryptionPassphraseFromFile; if (encryptionPassphraseFileArg.isPresent()) { final char[] pwChars = getPasswordFileReader().readPassword( encryptionPassphraseFileArg.getValue()); encryptionPassphraseFromFile = new String(pwChars); Arrays.fill(pwChars, '\u0000'); } else { encryptionPassphraseFromFile = null; } final ObjectPair inputStreamPair = ToolUtils.getInputStreamForLDIFFiles( Collections.singletonList(ldifFile), encryptionPassphraseFromFile, getOut(), getErr()); final LDIFReader ldifReader = new LDIFReader(inputStreamPair.getFirst()); final String encryptionPassphrase = inputStreamPair.getSecond(); return new ObjectPair<>(ldifReader, encryptionPassphrase); } catch (final Exception e) { Debug.debugException(e); throw new LDAPException(ResultCode.LOCAL_ERROR, ERR_PARALLEL_UPDATE_CANNOT_CREATE_LDIF_READER.get( ldifFile.getAbsolutePath(), StaticUtils.getExceptionMessage(e)), e); } } /** * Attempts to retrieve the ID of the encryption settings definition used to * encrypt the LDIF file. This method should only be used if the LDIF file is * known to be encrypted. * * @return The ID of the encryption settings definition used to encrypt the * LDIF file, or {code null} if it was not encrypted with a * passphrase obtained from an encryption settings definition (or if * an error occurred while attempting to retrieve the ID). */ @Nullable() private String getEncryptionSettingsDefinitionID() { try (FileInputStream inputStream = new FileInputStream(ldifFileArg.getValue())) { final PassphraseEncryptedStreamHeader encryptionHeader = PassphraseEncryptedStreamHeader.readFrom(inputStream, null); return encryptionHeader.getKeyIdentifier(); } catch (final Exception e) { Debug.debugException(e); return null; } } /** * Creates the LDIF writer that will be used to write information about * rejected entries. If the LDIF input file was encrypted, then the reject * file will be encrypted with the same settings, and it will also be * compressed (regardless of whether the input file was compressed). * * @param encryptionPassphrase The passphrase used to encrypt the * input file. This may be * {@code null} if the input file was * not encrypted. * @param encryptionSettingsDefinitionID The ID for the encryption settings * definition with which the * passphrase is associated. It may * be {@code null} if the input file * was not encrypted, or if the * encryption passphrase was not * obtained from an encryption * settings definition. * * @return The LDIF writer to which rejects should be written. * * @throws LDAPException If a problem occurs while creating the reject * writer. */ @NotNull() private LDIFWriter createRejectWriter( @Nullable final String encryptionPassphrase, @Nullable final String encryptionSettingsDefinitionID) throws LDAPException { final File rejectFile = rejectFileArg.getValue(); OutputStream outputStream = null; try { outputStream = new FileOutputStream(rejectFile); if (encryptionPassphrase != null) { outputStream = new PassphraseEncryptedOutputStream(encryptionPassphrase, outputStream, encryptionSettingsDefinitionID, true, true); outputStream = new GZIPOutputStream(outputStream); } final LDIFWriter ldifWriter = new LDIFWriter(outputStream); // Set the wrap column to the maximum allowed value to ensure that // comments dont' get wrapped. ldifWriter.setWrapColumn(Integer.MAX_VALUE); return ldifWriter; } catch (final Exception e) { Debug.debugException(e); if (outputStream != null) { try { outputStream.close(); } catch (final Exception e2) { Debug.debugException(e2); } } throw new LDAPException(ResultCode.LOCAL_ERROR, ERR_PARALLEL_UPDATE_ERROR_CREATING_REJECT_WRITER.get( rejectFile.getAbsolutePath(), StaticUtils.getExceptionMessage(e)), e); } } /** * Writes the provided message to standard output or standard error and sets * it as the completion message if there isn't one already. * * @param isError Indicates whether the message represents an error. * @param message The message to log. */ private void logCompletionMessage(final boolean isError, @NotNull final String message) { completionMessage.compareAndSet(null, message); logMessage(message); if (isError) { wrapErr(0, WRAP_COLUMN, message); } else { wrapOut(0, WRAP_COLUMN, message); } } /** * Logs the provided message if logging is enabled. * * @param messageElements The elements that make up the message to log. */ private void logMessage(@NotNull final Object... messageElements) { if (logWriter != null) { SimpleDateFormat timestampFormatter = timestampFormatters.get(); if (timestampFormatter == null) { timestampFormatter = new SimpleDateFormat("'['dd/MMM/yyyy:HH:mm:ss Z']'"); timestampFormatters.set(timestampFormatter); } final String timestamp = timestampFormatter.format(new Date()); final StringBuilder message = new StringBuilder(); message.append('['); message.append(timestamp); message.append("] "); for (final Object o : messageElements) { message.append(String.valueOf(o)); } try { logWriter.println(message); } catch (final Exception e) { Debug.debugException(e); final String errorMessage = ERR_PARALLEL_UPDATE_CANNOT_WRITE_LOG_MESSAGE.get( message.toString(), logFileArg.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e)); wrapErr(0, WRAP_COLUMN, errorMessage); completionMessage.compareAndSet(null, errorMessage); shouldAbort.set(true); } } } /** * Indicates that processing for an operation completed successfully. * * @param changeRecord The LDIF change record for the operation. * It must not be {@code null}. * @param processingDurationMillis The length of time required to process * the operation, in milliseconds. */ void opCompletedSuccessfully(@NotNull final LDIFChangeRecord changeRecord, final long processingDurationMillis) { opsAttempted.incrementAndGet(); opsSucceeded.incrementAndGet(); totalOpDurationMillis.addAndGet(processingDurationMillis); logMessage(changeRecord.getDN(), " ", changeRecord.getChangeType().getName(), " SUCCESS 0 "); } /** * Indicates that processing for an operation failed. Depending on the nature * of the failure, it either be added to the retry queue if it is potentially * an operation that could be retried (e.g., an operation that failed because * it depended on an entry that was added later in the LDIF), or will be added * to the reject file if it is determined that it is not a failure that may be * resolved by a later change. * * @param changeRecord The LDIF change record for the operation. * It must not be {@code null}. * @param ldapException The LDAP exception that was caught to * indicate that the operation failed. It * must not be {@code null}. * @param processingDurationMillis The length of time required to process * the operation, in milliseconds. */ void opFailed(@NotNull final LDIFChangeRecord changeRecord, @NotNull final LDAPException ldapException, final long processingDurationMillis) { opsAttempted.incrementAndGet(); totalOpDurationMillis.addAndGet(processingDurationMillis); switch (ldapException.getResultCode().intValue()) { case ResultCode.NO_SUCH_OBJECT_INT_VALUE: case ResultCode.BUSY_INT_VALUE: case ResultCode.UNAVAILABLE_INT_VALUE: case ResultCode.NOT_ALLOWED_ON_NONLEAF_INT_VALUE: case ResultCode.SERVER_DOWN_INT_VALUE: case ResultCode.CONNECT_ERROR_INT_VALUE: retry(changeRecord, ldapException); break; default: reject(changeRecord, ldapException); break; } } /** * Adds the provided operation to the retry queue. * * @param changeRecord The LDIF change record for the operation. * @param ldapException The LDAP exception that was caught to indicate that * the operation failed. */ private void retry(@NotNull final LDIFChangeRecord changeRecord, @NotNull final LDAPException ldapException) { if (neverRetryArg.isPresent()) { reject(changeRecord, ldapException); return; } final DN parsedDN; try { parsedDN = changeRecord.getParsedDN(); } catch (final LDAPException e) { Debug.debugException(e); // This should never happen, but if it does, then reject the change. reject(changeRecord, ldapException); return; } logMessage(changeRecord.getDN(), " ", changeRecord.getChangeType().getName(), " RETRY ", ldapException.getResultCode(), " ", ldapException.getMessage()); synchronized (retryQueue) { List> changeList = retryQueue.get(parsedDN); if (changeList == null) { changeList = new LinkedList<>(); retryQueue.put(parsedDN, changeList); } changeList.add(new ObjectPair<>(changeRecord, ldapException)); retryQueueSize.incrementAndGet(); } } /** * Adds the provided operation to the reject file. * * @param changeRecord The LDIF change record for the operation. * @param ldapException The LDAP exception that was caught to indicate that * the operation failed. */ void reject(@Nullable final LDIFChangeRecord changeRecord, @NotNull final LDAPException ldapException) { opsRejected.incrementAndGet(); final ResultCode resultCode = ldapException.getResultCode(); if (resultCode != ResultCode.SUCCESS) { firstRejectResultCode.compareAndSet(null, resultCode); } final StringBuilder commentBuffer = new StringBuilder(); for (final String line : ResultUtils.formatResult(ldapException, false, 0, 0)) { if (commentBuffer.length() > 0) { commentBuffer.append(StaticUtils.EOL); } commentBuffer.append(line); } final String comment = commentBuffer.toString(); try { if (changeRecord != null) { logMessage(changeRecord.getDN(), " ", changeRecord.getChangeType().getName(), " REJECT ", resultCode, " ", ldapException.getMessage()); synchronized (rejectWriter) { rejectWriter.writeChangeRecord(changeRecord, comment); rejectWriter.flush(); } } else { synchronized (rejectWriter) { rejectWriter.writeComment(comment, true, true); rejectWriter.flush(); } } } catch (final Exception e) { Debug.debugException(e); final String errorMessage = ERR_PARALLEL_UPDATE_CANNOT_WRITE_REJECT.get( changeRecord.toString(), ERR_PARALLEL_UPDATE_REJECT_COMMENT.get(String.valueOf(resultCode), ldapException.getMessage()), StaticUtils.getExceptionMessage(e)); wrapErr(0, WRAP_COLUMN, errorMessage); completionMessage.compareAndSet(null, errorMessage); shouldAbort.set(true); } } /** * Prints information about the processing performed by this program to * standard output. */ void printIntervalData() { final long currentAttempts = opsAttempted.get(); final long currentSuccesses = opsSucceeded.get(); final long currentReject = opsRejected.get(); final long currentRetry = retryQueueSize.get(); final long currentDurationMillis = totalOpDurationMillis.get(); final long currentTimeMillis = System.currentTimeMillis(); final long totalDurationMillis = currentTimeMillis - processingStartTimeMillis; final long avgRate; if (totalDurationMillis == 0L) { avgRate = 0L; } else { avgRate = 1000L * currentAttempts / totalDurationMillis; } final long avgDurationMillis; if (currentAttempts == 0L) { avgDurationMillis = 0L; } else { avgDurationMillis = currentDurationMillis / currentAttempts; } final long recentDurationMillis; if (currentAttempts == lastOpsAttempted) { recentDurationMillis = 0L; } else { recentDurationMillis = (currentDurationMillis - lastTotalDurationMillis) / (currentAttempts - lastOpsAttempted); } final long recentRate; if (lastOpsAttempted == 0) { out(" Attempts Successes Rejects ToRetry AvgOps/S " + " RctOps/S AvgDurMS RctDurMS"); out("--------- --------- --------- --------- --------- " + "--------- --------- ---------"); recentRate = avgRate; } else if (currentTimeMillis == lastUpdateTimeMillis) { recentRate = 0L; } else { recentRate = 1000L * (currentAttempts - lastOpsAttempted) / (currentTimeMillis - lastUpdateTimeMillis); } final StringBuilder buffer = new StringBuilder(80); appendJustified(currentAttempts, buffer, true); appendJustified(currentSuccesses, buffer, true); appendJustified(currentReject, buffer, true); appendJustified(currentRetry, buffer, true); appendJustified(avgRate, buffer, true); appendJustified(recentRate, buffer, true); appendJustified(avgDurationMillis, buffer, true); appendJustified(recentDurationMillis, buffer, false); out(buffer.toString()); lastOpsAttempted = currentAttempts; lastTotalDurationMillis = currentDurationMillis; lastUpdateTimeMillis = currentTimeMillis; } /** * Appends the provided number to the buffer, right justified in nine columns. * * @param value The value to be appended to the buffer. * @param buffer The buffer to which the value should be appended. * @param addSpace Indicates whether to append a space after the number. */ static void appendJustified(final long value, @NotNull final StringBuilder buffer, final boolean addSpace) { final String valueStr = String.valueOf(value); switch (valueStr.length()) { case 1: buffer.append(" "); break; case 2: buffer.append(" "); break; case 3: buffer.append(" "); break; case 4: buffer.append(" "); break; case 5: buffer.append(" "); break; case 6: buffer.append(" "); break; case 7: buffer.append(" "); break; case 8: buffer.append(' '); break; } buffer.append(value); if (addSpace) { buffer.append(' '); } } /** * Retrieves the total number of operations attempted. This should only be * called after all tool processing has completed. * * @return The total number of operations attempted. */ public long getTotalAttemptCount() { return opsAttempted.get(); } /** * Retrieves the number of operations attempted on the initial pass through * the LDIF file (that is, operations for which no retry attempts was made). * This should only be called after all tool processing has completed. * * @return The number of operations attempted on the initial pass through the * LDIF file. */ public long getInitialAttemptCount() { return initialAttempted; } /** * Retrieves the number of retry attempts made for operations that did not * complete successfully on their first attempt. This should only be called * after all tool processing has completed. * * @return The number of retry attempts made for operations that did not * complete successfully on their first attempt. */ public long getRetryAttemptCount() { return opsAttempted.get() - initialAttempted; } /** * Retrieves the total number of operations that completed successfully. This * should only be called after all tool processing has completed. * * @return The total number of operations that completed successfully. */ public long getTotalSuccessCount() { return opsSucceeded.get(); } /** * Retrieves the number of operations that completed successfully on their * first attempt. This should only be called after all tool processing has * completed. * * @return The total number of operations that completed successfully on * their first attempt. */ public long getInitialSuccessCount() { return initialSucceeded; } /** * Retrieves the number of operations that did not complete completed * successfully on their initial attempt but did succeed on a retry attempt. * This should only be called after all tool processing has completed. * * @return The number of operations that completed successfully on a retry * attempt. */ public long getRetrySuccessCount() { return opsSucceeded.get() - initialSucceeded; } /** * Retrieves the number of operations that were rejected and did not complete * successfully during any of the attempts. This should only be called after * all tool processing has completed. * * @return The number of operations that were rejected. */ public long getRejectCount() { return opsRejected.get(); } /** * Retrieves the total length of time, in milliseconds, spent processing * operations. This should only be called after all tool processing has * completed. Note that when running with multiple threads, this can exceed * the length of time spent running the tool because multiple operations can * be processed in parallel. * * @return The total length of time, in milliseconds, spent processing * operations. */ public long getTotalOpDurationMillis() { return totalOpDurationMillis.get(); } /** * {@inheritDoc} */ @Override() protected boolean registerShutdownHook() { return true; } /** * {@inheritDoc} */ @Override() protected void doShutdownHookProcessing(@Nullable final ResultCode resultCode) { shouldAbort.set(true); final FixedRateBarrier b = rateLimiter; if (b != null) { b.shutdownRequested(); } } /** * {@inheritDoc} */ @Override() public void handleUnsolicitedNotification( @NotNull final LDAPConnection connection, @NotNull final ExtendedResult notification) { final String message; if (notification.getDiagnosticMessage() == null) { message = INFO_PARALLEL_UPDATE_UNSOLICITED_NOTIFICATION_NO_MESSAGE.get( getToolName(), String.valueOf(notification.getResultCode()), notification.getOID()); } else { message = INFO_PARALLEL_UPDATE_UNSOLICITED_NOTIFICATION_NO_MESSAGE.get( getToolName(), String.valueOf(notification.getResultCode()), notification.getOID(), notification.getDiagnosticMessage()); } out(); wrapOut(0, WRAP_COLUMN, message); out(); } /** * {@inheritDoc} */ @Override() @NotNull() public LinkedHashMap getExampleUsages() { final LinkedHashMap examples = new LinkedHashMap<>(); examples.put( new String[] { "--hostname", "server.example.com", "--port", "636", "--useSSL", "--bindDN", "uid=admin,dc=example,dc=com", "--promptForBindPassword", "--ldifFile", "changes.ldif", "--rejectFile", "rejects.ldif", "--defaultAdd", "--numThreads", "10", "--ratePerSecond", "5000" }, INFO_PARALLEL_UPDATE_EXAMPLE_DESC.get()); return examples; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy