001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.tools; 037 038 039 040import java.io.File; 041import java.io.FileInputStream; 042import java.io.FileOutputStream; 043import java.io.InputStream; 044import java.io.OutputStream; 045import java.io.PrintWriter; 046import java.text.DecimalFormat; 047import java.text.SimpleDateFormat; 048import java.util.ArrayList; 049import java.util.Arrays; 050import java.util.Collections; 051import java.util.Date; 052import java.util.EnumSet; 053import java.util.LinkedHashMap; 054import java.util.LinkedList; 055import java.util.List; 056import java.util.Map; 057import java.util.TreeMap; 058import java.util.concurrent.TimeUnit; 059import java.util.concurrent.atomic.AtomicBoolean; 060import java.util.concurrent.atomic.AtomicLong; 061import java.util.concurrent.atomic.AtomicReference; 062import java.util.zip.GZIPOutputStream; 063 064import com.unboundid.ldap.sdk.Control; 065import com.unboundid.ldap.sdk.DN; 066import com.unboundid.ldap.sdk.ExtendedResult; 067import com.unboundid.ldap.sdk.LDAPConnection; 068import com.unboundid.ldap.sdk.LDAPConnectionOptions; 069import com.unboundid.ldap.sdk.LDAPConnectionPool; 070import com.unboundid.ldap.sdk.LDAPException; 071import com.unboundid.ldap.sdk.ResultCode; 072import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler; 073import com.unboundid.ldap.sdk.Version; 074import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl; 075import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl; 076import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl; 077import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; 078import com.unboundid.ldap.sdk.unboundidds.controls. 079 AssuredReplicationRequestControl; 080import com.unboundid.ldap.sdk.unboundidds.controls.AssuredReplicationLocalLevel; 081import com.unboundid.ldap.sdk.unboundidds.controls. 082 AssuredReplicationRemoteLevel; 083import com.unboundid.ldap.sdk.unboundidds.controls.HardDeleteRequestControl; 084import com.unboundid.ldap.sdk.unboundidds.controls. 085 IgnoreNoUserModificationRequestControl; 086import com.unboundid.ldap.sdk.unboundidds.controls. 087 NameWithEntryUUIDRequestControl; 088import com.unboundid.ldap.sdk.unboundidds.controls. 089 OperationPurposeRequestControl; 090import com.unboundid.ldap.sdk.unboundidds.controls. 091 PasswordUpdateBehaviorRequestControl; 092import com.unboundid.ldap.sdk.unboundidds.controls. 093 PasswordUpdateBehaviorRequestControlProperties; 094import com.unboundid.ldap.sdk.unboundidds.controls. 095 ReplicationRepairRequestControl; 096import com.unboundid.ldap.sdk.unboundidds.controls.SoftDeleteRequestControl; 097import com.unboundid.ldap.sdk.unboundidds.controls. 098 SuppressOperationalAttributeUpdateRequestControl; 099import com.unboundid.ldap.sdk.unboundidds.controls. 100 SuppressReferentialIntegrityUpdatesRequestControl; 101import com.unboundid.ldap.sdk.unboundidds.controls.SuppressType; 102import com.unboundid.ldif.LDIFChangeRecord; 103import com.unboundid.ldif.LDIFException; 104import com.unboundid.ldif.LDIFReader; 105import com.unboundid.ldif.LDIFWriter; 106import com.unboundid.util.Debug; 107import com.unboundid.util.FixedRateBarrier; 108import com.unboundid.util.LDAPCommandLineTool; 109import com.unboundid.util.NotNull; 110import com.unboundid.util.Nullable; 111import com.unboundid.util.ObjectPair; 112import com.unboundid.util.PassphraseEncryptedOutputStream; 113import com.unboundid.util.PassphraseEncryptedStreamHeader; 114import com.unboundid.util.StaticUtils; 115import com.unboundid.util.ThreadSafety; 116import com.unboundid.util.ThreadSafetyLevel; 117import com.unboundid.util.args.ArgumentException; 118import com.unboundid.util.args.ArgumentParser; 119import com.unboundid.util.args.BooleanArgument; 120import com.unboundid.util.args.ControlArgument; 121import com.unboundid.util.args.DNArgument; 122import com.unboundid.util.args.DurationArgument; 123import com.unboundid.util.args.FileArgument; 124import com.unboundid.util.args.IntegerArgument; 125import com.unboundid.util.args.StringArgument; 126 127import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 128 129 130 131/** 132 * This class provides a command-line tool that can be used to read change 133 * records for add, delete, modify and modify DN operations from an LDIF file, 134 * and then apply them in parallel using multiple threads for higher throughput. 135 * <BR> 136 * <BLOCKQUOTE> 137 * <B>NOTE:</B> This class, and other classes within the 138 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 139 * supported for use against Ping Identity, UnboundID, and 140 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 141 * for proprietary functionality or for external specifications that are not 142 * considered stable or mature enough to be guaranteed to work in an 143 * interoperable way with other types of LDAP servers. 144 * </BLOCKQUOTE> 145 * <BR><BR> 146 * Changes in the LDIF file to be processed should be ordered such that if there 147 * are any dependencies between changes, prerequisite changes come before the 148 * changes that depend on them (for example, if one add change record creates a 149 * parent entry and another creates a child entry, the add change record that 150 * creates the parent entry must come before the one that creates the child 151 * entry). When this tool is preparing to process a change, it will determine 152 * whether the new change depends on any other changes that are currently in 153 * progress, and if so, will delay processing that change until its dependencies 154 * have been satisfied. If a change does not depend on any other changes that 155 * are currently being processed, then it can be processed in parallel with 156 * those changes. 157 * <BR><BR> 158 * The tool will keep track of any changes that fail in a way that indicates 159 * they succeed if re-tried later (for example, an attempt to add an entry that 160 * fails because its parent does not exist, but its parent may be created later 161 * in the set of LDIF changes), and can optionally re-try those changes after 162 * processing is complete. Any changes that are not retried, as well as changes 163 * that still fail after the retry attempts, will be written to a rejects file 164 * with information about the reason for the failure so that an administrator 165 * can take any necessary further action upon them. 166 */ 167@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 168public final class ParallelUpdate 169 extends LDAPCommandLineTool 170 implements UnsolicitedNotificationHandler 171{ 172 /** 173 * The column at which long lines should be wrapped. 174 */ 175 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 176 177 178 179 /** 180 * The name of the password update behavior key that may be used to specify 181 * whether an update should be treated as a self-change. 182 */ 183 @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_IS_SELF_CHANGE = 184 "is-self-change"; 185 186 187 188 /** 189 * The name of the password update behavior key that may be used to specify 190 * whether an update should allow the password to be provided in pre-encoded 191 * form. 192 */ 193 @NotNull private static final String 194 PW_UPDATE_BEHAVIOR_NAME_ALLOW_PRE_ENCODED_PW = 195 "allow-pre-encoded-password"; 196 197 198 199 /** 200 * The name of the password update behavior key that may be used to specify 201 * whether the server should skip validation for the password. 202 */ 203 @NotNull private static final String 204 PW_UPDATE_BEHAVIOR_NAME_SKIP_PW_VALIDATION = "skip-password-validation"; 205 206 207 208 /** 209 * The name of the password update behavior key that may be used to specify 210 * whether the server should ignore the password history when determining 211 * whether to accept the new password. 212 */ 213 @NotNull private static final String 214 PW_UPDATE_BEHAVIOR_NAME_IGNORE_PW_HISTORY = "ignore-password-history"; 215 216 217 218 /** 219 * The name of the password update behavior key that may be used to specify 220 * whether the server should ignore the minimum password age when determining 221 * whether to allow the password change. 222 */ 223 @NotNull private static final String 224 PW_UPDATE_BEHAVIOR_NAME_IGNORE_MIN_PW_AGE = 225 "ignore-minimum-password-age"; 226 227 228 229 /** 230 * The name of the password update behavior key that may be used to specify 231 * the password storage scheme that should be used to encode the new password. 232 */ 233 @NotNull private static final String 234 PW_UPDATE_BEHAVIOR_NAME_PW_STORAGE_SCHEME = "password-storage-scheme"; 235 236 237 238 /** 239 * The name of the password update behavior key that may be used to specify 240 * whether the user must change their password on the next successful 241 * authentication. 242 */ 243 @NotNull private static final String PW_UPDATE_BEHAVIOR_NAME_MUST_CHANGE_PW = 244 "must-change-password"; 245 246 247 248 /** 249 * The assured replication local level value that indicates that no assurance 250 * is needed. 251 */ 252 @NotNull private static final String ASSURED_REPLICATION_LOCAL_LEVEL_NONE = 253 "none"; 254 255 256 257 /** 258 * The assured replication local level value that indicates that the response 259 * should be delayed until the change has been received by at least one other 260 * local server. 261 */ 262 @NotNull private static final String 263 ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER = 264 "received-any-server"; 265 266 267 268 /** 269 * The assured replication local level value that indicates that the response 270 * should be delayed until the change has been processed by all available 271 * local servers. 272 */ 273 @NotNull private static final String 274 ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS = 275 "processed-all-servers"; 276 277 278 279 /** 280 * The assured replication remote level value that indicates that no assurance 281 * is needed. 282 */ 283 @NotNull private static final String ASSURED_REPLICATION_REMOTE_LEVEL_NONE = 284 "none"; 285 286 287 288 /** 289 * The assured replication remote level value that indicates that the response 290 * should be delayed until the change has been received by at least one server 291 * in at least one remote location. 292 */ 293 @NotNull private static final String 294 ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION = 295 "received-any-remote-location"; 296 297 298 299 /** 300 * The assured replication remote level value that indicates that the response 301 * should be delayed until the change has been received by at least one server 302 * in all remote locations. 303 */ 304 @NotNull private static final String 305 ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS = 306 "received-all-remote-locations"; 307 308 309 310 /** 311 * The assured replication remote level value that indicates that the response 312 * should be delayed until the change has been processed by all available 313 * servers in all remote locations. 314 */ 315 @NotNull private static final String 316 ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS = 317 "processed-all-remote-servers"; 318 319 320 321 /** 322 * The suppress operational attribute update value that indicates that updates 323 * to the last access time should be suppressed. 324 */ 325 @NotNull private static final String SUPPRESS_OP_ATTR_LAST_ACCESS_TIME = 326 "last-access-time"; 327 328 329 330 /** 331 * The suppress operational attribute update value that indicates that updates 332 * to the last login time should be suppressed. 333 */ 334 @NotNull private static final String SUPPRESS_OP_ATTR_LAST_LOGIN_TIME = 335 "last-login-time"; 336 337 338 339 /** 340 * The suppress operational attribute update value that indicates that updates 341 * to the last login IP address should be suppressed. 342 */ 343 @NotNull private static final String SUPPRESS_OP_ATTR_LAST_LOGIN_IP = 344 "last-login-ip"; 345 346 347 348 /** 349 * The suppress operational attribute update value that indicates that updates 350 * to the lastmod attributes (creatorsName, createTimestamp, modifiersName, 351 * modifyTimestamp) should be suppressed. 352 */ 353 @NotNull private static final String SUPPRESS_OP_ATTR_LASTMOD = "lastmod"; 354 355 356 357 // Indicates whether an error has occurred and that processing should be 358 // aborted. 359 @NotNull private final AtomicBoolean shouldAbort; 360 361 // Counters used to keep track of statistical information about processing. 362 @NotNull private final AtomicLong opsAttempted; 363 @NotNull private final AtomicLong opsRejected; 364 @NotNull private final AtomicLong opsSucceeded; 365 @NotNull private final AtomicLong totalOpDurationMillis; 366 private volatile long initialAttempted; 367 private volatile long initialSucceeded; 368 369 // Variables pertaining to operations to be retried. 370 @NotNull private final AtomicLong retryQueueSize; 371 @NotNull private final 372 Map<DN,List<ObjectPair<LDIFChangeRecord,LDAPException>>> retryQueue; 373 374 // The result code for the first operation that was rejected, if any. 375 @NotNull private final AtomicReference<ResultCode> firstRejectResultCode; 376 377 // The completion message for this tool, if available. 378 @NotNull private final AtomicReference<String> completionMessage; 379 380 // The rate limiter for this tool, if any. 381 @Nullable private FixedRateBarrier rateLimiter; 382 383 // Writers used to write rejects and log messages. 384 @Nullable private LDIFWriter rejectWriter; 385 @Nullable private PrintWriter logWriter; 386 387 // Variables used to keep track of data about processing intervals for use in 388 // periodic status updates. 389 private volatile long lastOpsAttempted; 390 private volatile long lastTotalDurationMillis; 391 private volatile long lastUpdateTimeMillis; 392 private volatile long processingStartTimeMillis; 393 394 // Thread-local date formatters used to format message timestamps. 395 @NotNull private final ThreadLocal<SimpleDateFormat> timestampFormatters; 396 397 // The set of command-line arguments for this program. 398 @Nullable private BooleanArgument allowUndeleteArg; 399 @Nullable private BooleanArgument defaultAddArg; 400 @Nullable private BooleanArgument followReferralsArg; 401 @Nullable private BooleanArgument hardDeleteArg; 402 @Nullable private BooleanArgument ignoreNoUserModificationArg; 403 @Nullable private BooleanArgument isCompressedArg; 404 @Nullable private BooleanArgument nameWithEntryUUIDArg; 405 @Nullable private BooleanArgument neverRetryArg; 406 @Nullable private BooleanArgument replicationRepairArg; 407 @Nullable private BooleanArgument softDeleteArg; 408 @Nullable private BooleanArgument suppressReferentialIntegrityUpdatesArg; 409 @Nullable private BooleanArgument useAssuredReplicationArg; 410 @Nullable private BooleanArgument useFirstRejectResultCodeAsExitCodeArg; 411 @Nullable private BooleanArgument useManageDsaITArg; 412 @Nullable private BooleanArgument usePermissiveModifyArg; 413 @Nullable private ControlArgument addControlArg; 414 @Nullable private ControlArgument bindControlArg; 415 @Nullable private ControlArgument deleteControlArg; 416 @Nullable private ControlArgument modifyControlArg; 417 @Nullable private ControlArgument modifyDNControlArg; 418 @Nullable private DNArgument proxyV1AsArg; 419 @Nullable private DurationArgument assuredReplicationTimeoutArg; 420 @Nullable private FileArgument encryptionPassphraseFileArg; 421 @Nullable private FileArgument ldifFileArg; 422 @Nullable private FileArgument logFileArg; 423 @Nullable private FileArgument rejectFileArg; 424 @Nullable private IntegerArgument numThreadsArg; 425 @Nullable private IntegerArgument ratePerSecondArg; 426 @Nullable private StringArgument assuredReplicationLocalLevelArg; 427 @Nullable private StringArgument assuredReplicationRemoteLevelArg; 428 @Nullable private StringArgument operationPurposeArg; 429 @Nullable private StringArgument passwordUpdateBehaviorArg; 430 @Nullable private StringArgument proxyAsArg; 431 @Nullable private StringArgument suppressOperationalAttributeUpdatesArg; 432 433 434 435 /** 436 * Parses the provided set of command-line arguments and then performs the 437 * necessary processing. 438 * 439 * @param args The command-line arguments provided to this program. 440 */ 441 public static void main(@NotNull final String... args) 442 { 443 final ResultCode resultCode = main(System.out, System.err, args); 444 if (resultCode != ResultCode.SUCCESS) 445 { 446 System.exit(Math.min(resultCode.intValue(), 255)); 447 } 448 } 449 450 451 452 /** 453 * Parses the provided set of command-line arguments and then performs the 454 * necessary processing. 455 * 456 * @param out The output stream to which standard output should be written. 457 * It may be {@code null} if standard output should be 458 * suppressed. 459 * @param err The output stream to which standard error should be written. 460 * It may be {@code null} if standard error should be 461 * suppressed. 462 * @param args The command-line arguments provided to this program. 463 * 464 * @return Zero if all processing completed successfully, or nonzero if an 465 * error occurred. 466 */ 467 @NotNull() 468 public static ResultCode main(@Nullable final OutputStream out, 469 @Nullable final OutputStream err, 470 @NotNull final String... args) 471 { 472 final ParallelUpdate parallelupdate = new ParallelUpdate(out, err); 473 return parallelupdate.runTool(args); 474 } 475 476 477 478 /** 479 * Creates a new instance of this tool with the provided output and error 480 * streams. 481 * 482 * @param out The output stream to which standard output should be written. 483 * It may be {@code null} if standard output should be 484 * suppressed. 485 * @param err The output stream to which standard error should be written. 486 * It may be {@code null} if standard error should be 487 * suppressed. 488 */ 489 public ParallelUpdate(@Nullable final OutputStream out, 490 @Nullable final OutputStream err) 491 { 492 super(out, err); 493 494 shouldAbort = new AtomicBoolean(false); 495 opsAttempted = new AtomicLong(0L); 496 opsRejected = new AtomicLong(0L); 497 opsSucceeded = new AtomicLong(0L); 498 totalOpDurationMillis = new AtomicLong(0L); 499 initialAttempted = 0L; 500 initialSucceeded = 0L; 501 retryQueueSize = new AtomicLong(0L); 502 retryQueue = new TreeMap<>(); 503 firstRejectResultCode = new AtomicReference<>(); 504 completionMessage = new AtomicReference<>(); 505 rejectWriter = null; 506 logWriter = null; 507 lastOpsAttempted = 0L; 508 lastTotalDurationMillis = 0L; 509 lastUpdateTimeMillis = 0L; 510 processingStartTimeMillis = System.currentTimeMillis(); 511 timestampFormatters = new ThreadLocal<>(); 512 allowUndeleteArg = null; 513 defaultAddArg = null; 514 followReferralsArg = null; 515 hardDeleteArg = null; 516 ignoreNoUserModificationArg = null; 517 isCompressedArg = null; 518 nameWithEntryUUIDArg = null; 519 neverRetryArg = null; 520 replicationRepairArg = null; 521 softDeleteArg = null; 522 suppressReferentialIntegrityUpdatesArg = null; 523 useAssuredReplicationArg = null; 524 useFirstRejectResultCodeAsExitCodeArg = null; 525 useManageDsaITArg = null; 526 usePermissiveModifyArg = null; 527 addControlArg = null; 528 bindControlArg = null; 529 deleteControlArg = null; 530 modifyControlArg = null; 531 modifyDNControlArg = null; 532 proxyV1AsArg = null; 533 assuredReplicationTimeoutArg = null; 534 encryptionPassphraseFileArg = null; 535 ldifFileArg = null; 536 logFileArg = null; 537 rejectFileArg = null; 538 numThreadsArg = null; 539 ratePerSecondArg = null; 540 assuredReplicationLocalLevelArg = null; 541 assuredReplicationRemoteLevelArg = null; 542 operationPurposeArg = null; 543 passwordUpdateBehaviorArg = null; 544 proxyAsArg = null; 545 suppressOperationalAttributeUpdatesArg = null; 546 } 547 548 549 550 /** 551 * {@inheritDoc} 552 */ 553 @Override() 554 @NotNull() 555 public String getToolName() 556 { 557 return "parallel-update"; 558 } 559 560 561 562 /** 563 * {@inheritDoc} 564 */ 565 @Override() 566 @NotNull() 567 public String getToolDescription() 568 { 569 return INFO_PARALLEL_UPDATE_TOOL_DESCRIPTION_1.get(); 570 } 571 572 573 574 /** 575 * {@inheritDoc} 576 */ 577 @Override() 578 @NotNull() 579 public List<String> getAdditionalDescriptionParagraphs() 580 { 581 return Collections.unmodifiableList(Arrays.asList( 582 INFO_PARALLEL_UPDATE_TOOL_DESCRIPTION_2.get(), 583 INFO_PARALLEL_UPDATE_TOOL_DESCRIPTION_3.get())); 584 } 585 586 587 588 /** 589 * {@inheritDoc} 590 */ 591 @Override() 592 @NotNull() 593 public String getToolVersion() 594 { 595 return Version.NUMERIC_VERSION_STRING; 596 } 597 598 599 600 /** 601 * {@inheritDoc} 602 */ 603 @Override() 604 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 605 throws ArgumentException 606 { 607 ldifFileArg = new FileArgument('l', "ldifFile", true, 1, null, 608 INFO_PARALLEL_UPDATE_ARG_DESC_LDIF_FILE.get(), true, true, true, 609 false); 610 ldifFileArg.addLongIdentifier("ldif-file", true); 611 ldifFileArg.addLongIdentifier("inputFile", true); 612 ldifFileArg.addLongIdentifier("input-file", true); 613 ldifFileArg.setArgumentGroupName( 614 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 615 parser.addArgument(ldifFileArg); 616 617 isCompressedArg = new BooleanArgument('c', "isCompressed", 1, 618 INFO_PARALLEL_UPDATE_ARG_DESC_IS_COMPRESSED.get()); 619 isCompressedArg.addLongIdentifier("is-compressed", true); 620 isCompressedArg.addLongIdentifier("compressed", true); 621 isCompressedArg.setArgumentGroupName( 622 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 623 isCompressedArg.setHidden(true); 624 parser.addArgument(isCompressedArg); 625 626 encryptionPassphraseFileArg = new FileArgument(null, 627 "encryptionPassphraseFile", false, 1, null, 628 INFO_PARALLEL_UPDATE_ARG_DESC_ENCRYPTION_PASSPHRASE_FILE.get(), 629 true, true, true, false); 630 encryptionPassphraseFileArg.addLongIdentifier( 631 "encryption-passphrase-file", true); 632 encryptionPassphraseFileArg.addLongIdentifier( 633 "encryptionPasswordFile", true); 634 encryptionPassphraseFileArg.addLongIdentifier( 635 "encryption-password-file", true); 636 encryptionPassphraseFileArg.setArgumentGroupName( 637 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 638 parser.addArgument(encryptionPassphraseFileArg); 639 640 rejectFileArg = new FileArgument('R', "rejectFile", true, 1, null, 641 INFO_PARALLEL_UPDATE_ARG_DESC_REJECT_FILE.get(), false, true, true, 642 false); 643 rejectFileArg.addLongIdentifier("reject-file", true); 644 rejectFileArg.addLongIdentifier("rejectsFile", true); 645 rejectFileArg.addLongIdentifier("rejects-file", true); 646 rejectFileArg.setArgumentGroupName( 647 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 648 parser.addArgument(rejectFileArg); 649 650 useFirstRejectResultCodeAsExitCodeArg = new BooleanArgument(null, 651 "useFirstRejectResultCodeAsExitCode", 1, 652 INFO_PARALLEL_UPDATE_USE_FIRST_REJECT_RC.get()); 653 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 654 "use-first-reject-result-code-as-exit-code", true); 655 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 656 "useFirstRejectResultCode", true); 657 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 658 "use-first-reject-result-code", true); 659 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 660 "useFirstRejectResult", true); 661 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 662 "use-first-reject-result", true); 663 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 664 "useRejectResultCodeAsExitCode", true); 665 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 666 "use-reject-result-code-as-exit-code", true); 667 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 668 "useRejectResultCode", true); 669 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 670 "use-reject-result-code", true); 671 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 672 "useRejectResult", true); 673 useFirstRejectResultCodeAsExitCodeArg.addLongIdentifier( 674 "use-reject-result", true); 675 useFirstRejectResultCodeAsExitCodeArg.setArgumentGroupName( 676 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 677 parser.addArgument(useFirstRejectResultCodeAsExitCodeArg); 678 679 neverRetryArg = new BooleanArgument('r', "neverRetry", 1, 680 INFO_PARALLEL_UPDATE_ARG_DESC_NEVER_RETRY.get()); 681 neverRetryArg.addLongIdentifier("never-retry", true); 682 neverRetryArg.setArgumentGroupName( 683 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 684 parser.addArgument(neverRetryArg); 685 686 logFileArg = new FileArgument('L', "logFile", false, 1, null, 687 INFO_PARALLEL_UPDATE_ARG_DESC_LOG_FILE.get(), false, true, true, 688 false); 689 logFileArg.addLongIdentifier("log-file", true); 690 logFileArg.setArgumentGroupName( 691 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 692 parser.addArgument(logFileArg); 693 694 defaultAddArg = new BooleanArgument('a', "defaultAdd", 1, 695 INFO_PARALLEL_UPDATE_ARG_DESC_DEFAULT_ADD.get()); 696 defaultAddArg.addLongIdentifier("default-add", true); 697 defaultAddArg.setArgumentGroupName( 698 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 699 parser.addArgument(defaultAddArg); 700 701 followReferralsArg = new BooleanArgument(null, "followReferrals", 1, 702 INFO_PARALLEL_UPDATE_ARG_DESC_FOLLOW_REFERRALS.get()); 703 followReferralsArg.addLongIdentifier("follow-referrals", true); 704 followReferralsArg.setArgumentGroupName( 705 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 706 parser.addArgument(followReferralsArg); 707 708 numThreadsArg = new IntegerArgument('t', "numThreads", true, 1, null, 709 INFO_PARALLEL_UPDATE_ARG_DESC_NUM_THREADS.get(), 1, Integer.MAX_VALUE, 710 8); 711 numThreadsArg.addLongIdentifier("num-threads", true); 712 numThreadsArg.addLongIdentifier("threads", true); 713 numThreadsArg.setArgumentGroupName( 714 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 715 parser.addArgument(numThreadsArg); 716 717 ratePerSecondArg = new IntegerArgument('s', "ratePerSecond", false, 1, 718 null, INFO_PARALLEL_UPDATE_ARG_DESC_RATE_PER_SECOND.get(), 1, 719 Integer.MAX_VALUE); 720 ratePerSecondArg.addLongIdentifier("rate-per-second", true); 721 ratePerSecondArg.addLongIdentifier("requestsPerSecond", true); 722 ratePerSecondArg.addLongIdentifier("requests-per-second", true); 723 ratePerSecondArg.addLongIdentifier("operationsPerSecond", true); 724 ratePerSecondArg.addLongIdentifier("operations-per-second", true); 725 ratePerSecondArg.addLongIdentifier("opsPerSecond", true); 726 ratePerSecondArg.addLongIdentifier("ops-per-second", true); 727 ratePerSecondArg.addLongIdentifier("rate", true); 728 ratePerSecondArg.setArgumentGroupName( 729 INFO_PARALLEL_UPDATE_ARG_GROUP_PROCESSING.get()); 730 parser.addArgument(ratePerSecondArg); 731 732 usePermissiveModifyArg = new BooleanArgument('M', "usePermissiveModify", 1, 733 INFO_PARALLEL_UPDATE_ARG_DESC_USE_PERMISSIVE_MODIFY.get()); 734 usePermissiveModifyArg.addLongIdentifier("use-permissive-modify", true); 735 usePermissiveModifyArg.addLongIdentifier("permissiveModify", true); 736 usePermissiveModifyArg.addLongIdentifier("permissive-modify", true); 737 usePermissiveModifyArg.addLongIdentifier("permissive", true); 738 usePermissiveModifyArg.setArgumentGroupName( 739 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 740 parser.addArgument(usePermissiveModifyArg); 741 742 ignoreNoUserModificationArg = new BooleanArgument(null, 743 "ignoreNoUserModification", 1, 744 INFO_PARALLEL_UPDATE_ARG_DESC_IGNORE_NO_USER_MOD.get()); 745 ignoreNoUserModificationArg.addLongIdentifier("ignore-no-user-modification", 746 true); 747 ignoreNoUserModificationArg.addLongIdentifier("ignoreNoUserMod", true); 748 ignoreNoUserModificationArg.addLongIdentifier("ignore-no-user-mod", true); 749 ignoreNoUserModificationArg.setArgumentGroupName( 750 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 751 parser.addArgument(ignoreNoUserModificationArg); 752 753 proxyAsArg = new StringArgument('Y', "proxyAs", false, 1, 754 INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_PROXY_AS.get(), 755 INFO_PARALLEL_UPDATE_ARG_DESC_PROXY_AS.get()); 756 proxyAsArg.addLongIdentifier("proxy-as", true); 757 proxyAsArg.addLongIdentifier("proxyV2As", true); 758 proxyAsArg.addLongIdentifier("proxy-v2-as", true); 759 proxyAsArg.setArgumentGroupName( 760 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 761 parser.addArgument(proxyAsArg); 762 763 proxyV1AsArg = new DNArgument(null, "proxyV1As", false, 1, null, 764 INFO_PARALLEL_UPDATE_ARG_DESC_PROXY_V1_AS.get()); 765 proxyV1AsArg.addLongIdentifier("proxy-v1-as", true); 766 proxyV1AsArg.setArgumentGroupName( 767 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 768 parser.addArgument(proxyV1AsArg); 769 770 passwordUpdateBehaviorArg = new StringArgument(null, 771 "passwordUpdateBehavior", false, 0, 772 INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_PW_UPDATE_BEHAVIOR.get(), 773 INFO_PARALLEL_UPDATE_ARG_DESC_PW_UPDATE_BEHAVIOR.get()); 774 passwordUpdateBehaviorArg.addLongIdentifier("password-update-behavior", 775 true); 776 passwordUpdateBehaviorArg.setArgumentGroupName( 777 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 778 parser.addArgument(passwordUpdateBehaviorArg); 779 780 operationPurposeArg = new StringArgument(null, "operationPurpose", false, 1, 781 INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_OPERATION_PURPOSE.get(), 782 INFO_PARALLEL_UPDATE_ARG_DESC_OPERATION_PURPOSE.get()); 783 operationPurposeArg.addLongIdentifier("operation-purpose", true); 784 operationPurposeArg.addLongIdentifier("purpose", true); 785 operationPurposeArg.setArgumentGroupName( 786 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 787 parser.addArgument(operationPurposeArg); 788 789 useManageDsaITArg = new BooleanArgument(null, "useManageDsaIT", 1, 790 INFO_PARALLEL_UPDATE_ARG_DESC_USE_MANAGE_DSA_IT.get()); 791 useManageDsaITArg.addLongIdentifier("use-manage-dsa-it", true); 792 useManageDsaITArg.addLongIdentifier("manageDsaIT", true); 793 useManageDsaITArg.addLongIdentifier("manage-dsa-it", true); 794 useManageDsaITArg.setArgumentGroupName( 795 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 796 parser.addArgument(useManageDsaITArg); 797 798 nameWithEntryUUIDArg = new BooleanArgument(null, "nameWithEntryUUID", 1, 799 INFO_LDAPMODIFY_ARG_DESCRIPTION_NAME_WITH_ENTRY_UUID.get()); 800 nameWithEntryUUIDArg.addLongIdentifier("name-with-entryuuid", true); 801 nameWithEntryUUIDArg.addLongIdentifier("name-with-entry-uuid", true); 802 nameWithEntryUUIDArg.setArgumentGroupName( 803 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 804 parser.addArgument(nameWithEntryUUIDArg); 805 806 softDeleteArg = new BooleanArgument(null, "useSoftDelete", 1, 807 INFO_PARALLEL_UPDATE_ARG_DESC_SOFT_DELETE.get()); 808 softDeleteArg.addLongIdentifier("use-soft-delete", true); 809 softDeleteArg.addLongIdentifier("softDelete", true); 810 softDeleteArg.addLongIdentifier("soft-delete", true); 811 softDeleteArg.setArgumentGroupName( 812 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 813 parser.addArgument(softDeleteArg); 814 815 hardDeleteArg = new BooleanArgument(null, "useHardDelete", 1, 816 INFO_PARALLEL_UPDATE_ARG_DESC_HARD_DELETE.get()); 817 hardDeleteArg.addLongIdentifier("use-hard-delete", true); 818 hardDeleteArg.addLongIdentifier("hardDelete", true); 819 hardDeleteArg.addLongIdentifier("hard-delete", true); 820 hardDeleteArg.setArgumentGroupName( 821 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 822 parser.addArgument(hardDeleteArg); 823 824 allowUndeleteArg = new BooleanArgument(null, "allowUndelete", 1, 825 INFO_PARALLEL_UPDATE_ARG_DESC_ALLOW_UNDELETE.get()); 826 allowUndeleteArg.addLongIdentifier("allow-undelete", true); 827 allowUndeleteArg.addLongIdentifier("undelete", true); 828 allowUndeleteArg.setArgumentGroupName( 829 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 830 parser.addArgument(allowUndeleteArg); 831 832 useAssuredReplicationArg = new BooleanArgument(null, 833 "useAssuredReplication", 1, 834 INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION.get()); 835 useAssuredReplicationArg.addLongIdentifier("use-assured-replication", true); 836 useAssuredReplicationArg.addLongIdentifier("assuredReplication", true); 837 useAssuredReplicationArg.addLongIdentifier("assured-replication", true); 838 useAssuredReplicationArg.setArgumentGroupName( 839 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 840 parser.addArgument(useAssuredReplicationArg); 841 842 assuredReplicationLocalLevelArg = new StringArgument(null, 843 "assuredReplicationLocalLevel", false, 1, 844 INFO_LDAPDELETE_ARG_PLACEHOLDER_ASSURED_REPLICATION_LOCAL_LEVEL.get(), 845 INFO_LDAPDELETE_ARG_DESC_ASSURED_REPLICATION_LOCAL_LEVEL.get(), 846 StaticUtils.setOf( 847 ASSURED_REPLICATION_LOCAL_LEVEL_NONE, 848 ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER, 849 ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS), 850 (String) null); 851 assuredReplicationLocalLevelArg.addLongIdentifier( 852 "assured-replication-local-level", true); 853 assuredReplicationLocalLevelArg.setArgumentGroupName( 854 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 855 parser.addArgument(assuredReplicationLocalLevelArg); 856 857 assuredReplicationRemoteLevelArg = new StringArgument(null, 858 "assuredReplicationRemoteLevel", false, 1, 859 INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_ASSURED_REPLICATION_REMOTE_LEVEL. 860 get(), 861 INFO_PARALLEL_UPDATE_ARG_DESC_ASSURED_REPLICATION_REMOTE_LEVEL.get(), 862 StaticUtils.setOf( 863 ASSURED_REPLICATION_REMOTE_LEVEL_NONE, 864 ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION, 865 ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS, 866 ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS), 867 (String) null); 868 assuredReplicationRemoteLevelArg.addLongIdentifier( 869 "assured-replication-remote-level", true); 870 assuredReplicationRemoteLevelArg.setArgumentGroupName( 871 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 872 parser.addArgument(assuredReplicationRemoteLevelArg); 873 874 assuredReplicationTimeoutArg = new DurationArgument(null, 875 "assuredReplicationTimeout", false, null, 876 INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION_TIMEOUT.get()); 877 assuredReplicationTimeoutArg.addLongIdentifier( 878 "assured-replication-timeout", true); 879 assuredReplicationTimeoutArg.setArgumentGroupName( 880 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 881 parser.addArgument(assuredReplicationTimeoutArg); 882 883 replicationRepairArg = new BooleanArgument(null, "replicationRepair", 1, 884 INFO_PARALLEL_UPDATE_ARG_DESC_USE_REPLICATION_REPAIR.get()); 885 replicationRepairArg.addLongIdentifier("replication-repair", true); 886 replicationRepairArg.setArgumentGroupName( 887 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 888 parser.addArgument(replicationRepairArg); 889 890 suppressOperationalAttributeUpdatesArg = new StringArgument(null, 891 "suppressOperationalAttributeUpdates", false, 0, 892 INFO_PARALLEL_UPDATE_ARG_PLACEHOLDER_SUPPRESS_OP_ATTR_UPDATES.get(), 893 INFO_PARALLEL_UPDATE_ARG_DESC_SUPPRESS_OP_ATTR_UPDATES.get(), 894 StaticUtils.setOf( 895 SUPPRESS_OP_ATTR_LAST_ACCESS_TIME, 896 SUPPRESS_OP_ATTR_LAST_LOGIN_TIME, 897 SUPPRESS_OP_ATTR_LAST_LOGIN_IP, 898 SUPPRESS_OP_ATTR_LASTMOD), 899 (String) null); 900 suppressOperationalAttributeUpdatesArg.addLongIdentifier( 901 "suppress-operational-attribute-updates", true); 902 suppressOperationalAttributeUpdatesArg.addLongIdentifier( 903 "suppressOperationalAttributeUpdate", true); 904 suppressOperationalAttributeUpdatesArg.addLongIdentifier( 905 "suppress-operational-attribute-update", true); 906 suppressOperationalAttributeUpdatesArg.setArgumentGroupName( 907 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 908 parser.addArgument(suppressOperationalAttributeUpdatesArg); 909 910 suppressReferentialIntegrityUpdatesArg = new BooleanArgument(null, 911 "suppressReferentialIntegrityUpdates", 1, 912 INFO_PARALLEL_UPDATE_ARG_DESC_SUPPRESS_REFERENTIAL_INTEGRITY_UPDATES. 913 get()); 914 suppressReferentialIntegrityUpdatesArg.addLongIdentifier( 915 "suppress-referential-integrity-updates", true); 916 suppressReferentialIntegrityUpdatesArg.addLongIdentifier( 917 "suppressReferentialIntegrityUpdate", true); 918 suppressReferentialIntegrityUpdatesArg.addLongIdentifier( 919 "suppress-referential-integrity-update", true); 920 suppressReferentialIntegrityUpdatesArg.setArgumentGroupName( 921 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 922 parser.addArgument(suppressReferentialIntegrityUpdatesArg); 923 924 addControlArg = new ControlArgument(null, "addControl", false, 0, null, 925 INFO_PARALLEL_UPDATE_ARG_DESC_ADD_CONTROL.get()); 926 addControlArg.addLongIdentifier("add-control", true); 927 addControlArg.setArgumentGroupName( 928 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 929 parser.addArgument(addControlArg); 930 931 bindControlArg = new ControlArgument(null, "bindControl", false, 0, null, 932 INFO_PARALLEL_UPDATE_ARG_DESC_BIND_CONTROL.get()); 933 bindControlArg.addLongIdentifier("bind-control", true); 934 bindControlArg.setArgumentGroupName( 935 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 936 parser.addArgument(bindControlArg); 937 938 deleteControlArg = new ControlArgument(null, "deleteControl", false, 0, 939 null, INFO_PARALLEL_UPDATE_ARG_DESC_DELETE_CONTROL.get()); 940 deleteControlArg.addLongIdentifier("delete-control", true); 941 deleteControlArg.setArgumentGroupName( 942 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 943 parser.addArgument(deleteControlArg); 944 945 modifyControlArg = new ControlArgument(null, "modifyControl", false, 0, 946 null, INFO_PARALLEL_UPDATE_ARG_DESC_MODIFY_CONTROL.get()); 947 modifyControlArg.addLongIdentifier("modify-control", true); 948 modifyControlArg.setArgumentGroupName( 949 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 950 parser.addArgument(modifyControlArg); 951 952 modifyDNControlArg = new ControlArgument(null, "modifyDNControl", false, 0, 953 null, INFO_PARALLEL_UPDATE_ARG_DESC_MODIFY_DN_CONTROL.get()); 954 modifyDNControlArg.addLongIdentifier("modify-dn-control", true); 955 modifyDNControlArg.setArgumentGroupName( 956 INFO_PARALLEL_UPDATE_ARG_GROUP_CONTROLS.get()); 957 parser.addArgument(modifyDNControlArg); 958 959 parser.addExclusiveArgumentSet(followReferralsArg, useManageDsaITArg); 960 961 parser.addExclusiveArgumentSet(proxyAsArg, proxyV1AsArg); 962 963 parser.addExclusiveArgumentSet(softDeleteArg, hardDeleteArg); 964 965 parser.addDependentArgumentSet(assuredReplicationLocalLevelArg, 966 useAssuredReplicationArg); 967 parser.addDependentArgumentSet(assuredReplicationRemoteLevelArg, 968 useAssuredReplicationArg); 969 parser.addDependentArgumentSet(assuredReplicationTimeoutArg, 970 useAssuredReplicationArg); 971 972 973 parser.addExclusiveArgumentSet(useAssuredReplicationArg, 974 replicationRepairArg); 975 } 976 977 978 979 /** 980 * {@inheritDoc} 981 */ 982 @Override() 983 public boolean supportsInteractiveMode() 984 { 985 return true; 986 } 987 988 989 990 /** 991 * {@inheritDoc} 992 */ 993 @Override() 994 public boolean defaultsToInteractiveMode() 995 { 996 return true; 997 } 998 999 1000 1001 /** 1002 * {@inheritDoc} 1003 */ 1004 @Override() 1005 public boolean supportsPropertiesFile() 1006 { 1007 return true; 1008 } 1009 1010 1011 1012 /** 1013 * {@inheritDoc} 1014 */ 1015 @Override() 1016 protected boolean supportsAuthentication() 1017 { 1018 return true; 1019 } 1020 1021 1022 1023 /** 1024 * {@inheritDoc} 1025 */ 1026 @Override() 1027 protected boolean defaultToPromptForBindPassword() 1028 { 1029 return true; 1030 } 1031 1032 1033 1034 /** 1035 * {@inheritDoc} 1036 */ 1037 @Override() 1038 protected boolean supportsSASLHelp() 1039 { 1040 return true; 1041 } 1042 1043 1044 1045 /** 1046 * {@inheritDoc} 1047 */ 1048 @Override() 1049 protected boolean includeAlternateLongIdentifiers() 1050 { 1051 return true; 1052 } 1053 1054 1055 1056 /** 1057 * {@inheritDoc} 1058 */ 1059 @Override() 1060 @NotNull() 1061 protected List<Control> getBindControls() 1062 { 1063 final List<Control> bindControls = new ArrayList<>(); 1064 1065 if ((bindControlArg != null) && bindControlArg.isPresent()) 1066 { 1067 bindControls.addAll(bindControlArg.getValues()); 1068 } 1069 1070 if ((suppressOperationalAttributeUpdatesArg != null) && 1071 suppressOperationalAttributeUpdatesArg.isPresent()) 1072 { 1073 final EnumSet<SuppressType> suppressTypes = 1074 EnumSet.noneOf(SuppressType.class); 1075 for (final String s : suppressOperationalAttributeUpdatesArg.getValues()) 1076 { 1077 if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_ACCESS_TIME)) 1078 { 1079 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 1080 } 1081 else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_TIME)) 1082 { 1083 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 1084 } 1085 else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_IP)) 1086 { 1087 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 1088 } 1089 } 1090 1091 bindControls.add(new SuppressOperationalAttributeUpdateRequestControl( 1092 true, suppressTypes)); 1093 } 1094 1095 return Collections.emptyList(); 1096 } 1097 1098 1099 1100 /** 1101 * {@inheritDoc} 1102 */ 1103 @Override() 1104 protected boolean supportsMultipleServers() 1105 { 1106 return true; 1107 } 1108 1109 1110 1111 /** 1112 * {@inheritDoc} 1113 */ 1114 @Override() 1115 protected boolean supportsSSLDebugging() 1116 { 1117 return true; 1118 } 1119 1120 1121 1122 /** 1123 * {@inheritDoc} 1124 */ 1125 @Override() 1126 @NotNull() 1127 public LDAPConnectionOptions getConnectionOptions() 1128 { 1129 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 1130 options.setUseSynchronousMode(true); 1131 options.setFollowReferrals( 1132 ((followReferralsArg != null) && followReferralsArg.isPresent())); 1133 options.setUnsolicitedNotificationHandler(this); 1134 options.setResponseTimeoutMillis(0L); 1135 return options; 1136 } 1137 1138 1139 1140 /** 1141 * {@inheritDoc} 1142 */ 1143 @Override() 1144 protected boolean logToolInvocationByDefault() 1145 { 1146 return true; 1147 } 1148 1149 1150 1151 /** 1152 * {@inheritDoc} 1153 */ 1154 @Override() 1155 @Nullable() 1156 public String getToolCompletionMessage() 1157 { 1158 return completionMessage.get(); 1159 } 1160 1161 1162 1163 /** 1164 * {@inheritDoc} 1165 */ 1166 @Override() 1167 @NotNull() 1168 public ResultCode doToolProcessing() 1169 { 1170 // Create the sets of controls to include in each type of request. 1171 final Control[] addControls; 1172 final Control[] deleteControls; 1173 final Control[] modifyControls; 1174 final Control[] modifyDNControls; 1175 try 1176 { 1177 final List<Control> addControlList = new ArrayList<>(); 1178 final List<Control> deleteControlList = new ArrayList<>(); 1179 final List<Control> modifyControlList = new ArrayList<>(); 1180 final List<Control> modifyDNControlList = new ArrayList<>(); 1181 1182 getOperationControls(addControlList, deleteControlList, 1183 modifyControlList, modifyDNControlList); 1184 1185 addControls = StaticUtils.toArray(addControlList, Control.class); 1186 deleteControls = StaticUtils.toArray(deleteControlList, Control.class); 1187 modifyControls = StaticUtils.toArray(modifyControlList, Control.class); 1188 modifyDNControls = 1189 StaticUtils.toArray(modifyDNControlList, Control.class); 1190 } 1191 catch (final LDAPException e) 1192 { 1193 Debug.debugException(e); 1194 logCompletionMessage(true, e.getMessage()); 1195 return e.getResultCode(); 1196 } 1197 1198 1199 // Get the connection pool to use to communicate with the directory 1200 // server(s). 1201 final LDAPConnectionPool connectionPool; 1202 final int numThreads = numThreadsArg.getValue(); 1203 try 1204 { 1205 connectionPool = getConnectionPool(numThreads, numThreads, 1, null, null, 1206 true, null); 1207 connectionPool.setConnectionPoolName("parallel-update"); 1208 connectionPool.setRetryFailedOperationsDueToInvalidConnections( 1209 (! neverRetryArg.isPresent())); 1210 } 1211 catch (final LDAPException e) 1212 { 1213 Debug.debugException(e); 1214 logCompletionMessage(true, 1215 ERR_PARALLEL_UPDATE_CANNOT_CREATE_POOL.get( 1216 StaticUtils.getExceptionMessage(e))); 1217 return e.getResultCode(); 1218 } 1219 1220 1221 // Create the LDIF reader that will read the changes to process. 1222 final LDIFReader ldifReader; 1223 final String encryptionPassphrase; 1224 try 1225 { 1226 final ObjectPair<LDIFReader,String> ldifReaderPair = createLDIFReader(); 1227 ldifReader = ldifReaderPair.getFirst(); 1228 encryptionPassphrase = ldifReaderPair.getSecond(); 1229 } 1230 catch (final LDAPException e) 1231 { 1232 Debug.debugException(e); 1233 logCompletionMessage(true, e.getMessage()); 1234 connectionPool.close(); 1235 return e.getResultCode(); 1236 } 1237 1238 final AtomicReference<ResultCode> resultCodeRef = 1239 new AtomicReference<>(ResultCode.SUCCESS); 1240 try 1241 { 1242 // If the LDIF file is encrypted, then get the ID of the encryption 1243 // settings definition (if any) used to generate the encryption key. 1244 final String encryptionSettingsDefinitionID; 1245 if (encryptionPassphrase == null) 1246 { 1247 encryptionSettingsDefinitionID = null; 1248 } 1249 else 1250 { 1251 encryptionSettingsDefinitionID = getEncryptionSettingsDefinitionID(); 1252 } 1253 1254 1255 // Create the LDIF writer that will be used to write rejects. 1256 try 1257 { 1258 rejectWriter = createRejectWriter(encryptionPassphrase, 1259 encryptionSettingsDefinitionID); 1260 } 1261 catch (final LDAPException e) 1262 { 1263 Debug.debugException(e); 1264 logCompletionMessage(true, e.getMessage()); 1265 return ResultCode.LOCAL_ERROR; 1266 } 1267 1268 1269 // If appropriate, create the log writer that will be used to provide a 1270 // log of the changes that are attempted. 1271 if (logFileArg.isPresent()) 1272 { 1273 try 1274 { 1275 logWriter = new PrintWriter(logFileArg.getValue()); 1276 } 1277 catch (final Exception e) 1278 { 1279 Debug.debugException(e); 1280 logCompletionMessage(true, 1281 ERR_PARALLEL_UPDATE_ERROR_CREATING_LOG_WRITER.get( 1282 logFileArg.getValue().getAbsolutePath(), 1283 StaticUtils.getExceptionMessage(e))); 1284 return ResultCode.LOCAL_ERROR; 1285 } 1286 } 1287 1288 1289 // Create The queue that will hold the operations to process. 1290 final ParallelUpdateOperationQueue operationQueue = 1291 new ParallelUpdateOperationQueue(this, numThreads, 1292 (2 * numThreads)); 1293 1294 1295 // Create the rate limiter, if appropriate. 1296 if (ratePerSecondArg.isPresent()) 1297 { 1298 rateLimiter = new FixedRateBarrier(1000L, ratePerSecondArg.getValue()); 1299 } 1300 else 1301 { 1302 rateLimiter = null; 1303 } 1304 1305 1306 // Create and start all of the threads that will be used to process 1307 // requests. 1308 final List<ParallelUpdateOperationThread> operationThreadList = 1309 new ArrayList<>(numThreads); 1310 for (int i=1; i <= numThreads; i++) 1311 { 1312 final ParallelUpdateOperationThread operationThread = 1313 new ParallelUpdateOperationThread(this, connectionPool, 1314 operationQueue, i, rateLimiter, addControls, deleteControls, 1315 modifyControls, modifyDNControls, 1316 allowUndeleteArg.isPresent()); 1317 operationThreadList.add(operationThread); 1318 operationThread.start(); 1319 } 1320 1321 1322 // Create a progress monitor that will be used to report periodic status 1323 // updates about the processing that has been performed. 1324 final ParallelUpdateProgressMonitor progressMonitor = 1325 new ParallelUpdateProgressMonitor(this); 1326 try 1327 { 1328 processingStartTimeMillis = System.currentTimeMillis(); 1329 progressMonitor.start(); 1330 1331 while (! shouldAbort.get()) 1332 { 1333 final LDIFChangeRecord changeRecord; 1334 try 1335 { 1336 changeRecord = ldifReader.readChangeRecord( 1337 defaultAddArg.isPresent()); 1338 } 1339 catch (final LDIFException e) 1340 { 1341 Debug.debugException(e); 1342 if (e.mayContinueReading()) 1343 { 1344 final String message = 1345 ERR_PARALLEL_UPDATE_RECOVERABLE_LDIF_EXCEPTION.get( 1346 ldifFileArg.getValue().getAbsolutePath(), 1347 e.getMessage()); 1348 logMessage(message); 1349 reject(null, 1350 new LDAPException(ResultCode.DECODING_ERROR, message, e)); 1351 opsAttempted.incrementAndGet(); 1352 continue; 1353 } 1354 else 1355 { 1356 shouldAbort.set(true); 1357 final String message = 1358 ERR_PARALLEL_UPDATE_UNRECOVERABLE_LDIF_EXCEPTION.get( 1359 ldifFileArg.getValue().getAbsolutePath(), 1360 StaticUtils.getExceptionMessage(e)); 1361 reject(null, 1362 new LDAPException(ResultCode.DECODING_ERROR, message, e)); 1363 logCompletionMessage(true, message); 1364 return ResultCode.DECODING_ERROR; 1365 } 1366 } 1367 catch (final Exception e) 1368 { 1369 Debug.debugException(e); 1370 shouldAbort.set(true); 1371 final String message = 1372 ERR_PARALLEL_UPDATE_ERROR_READING_LDIF_FILE.get( 1373 ldifFileArg.getValue().getAbsolutePath(), 1374 StaticUtils.getExceptionMessage(e)); 1375 reject(null, 1376 new LDAPException(ResultCode.LOCAL_ERROR, message, e)); 1377 logCompletionMessage(true, message); 1378 return ResultCode.LOCAL_ERROR; 1379 } 1380 1381 if (changeRecord == null) 1382 { 1383 // We've reached the end of the LDIF file. 1384 break; 1385 } 1386 else 1387 { 1388 try 1389 { 1390 operationQueue.addChangeRecord(changeRecord); 1391 } 1392 catch (final Exception e) 1393 { 1394 Debug.debugException(e); 1395 1396 // This indicates that the attempt to enqueue the change record 1397 // was interrupted. This shouldn't happen, but if it does, then 1398 // mark it to be retried. 1399 final LDAPException le = new LDAPException(ResultCode.LOCAL_ERROR, 1400 ERR_PARALLEL_UPDATE_ENQUEUE_FAILED.get( 1401 StaticUtils.getExceptionMessage(e)), 1402 e); 1403 retry(changeRecord, le); 1404 } 1405 } 1406 } 1407 1408 1409 // If a failure was encountered, then abort. 1410 if (shouldAbort.get()) 1411 { 1412 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1413 ResultCode.LOCAL_ERROR); 1414 return resultCodeRef.get(); 1415 } 1416 1417 1418 // Indicate that we've reached the end of the LDIF file. 1419 out(); 1420 wrapOut(0, WRAP_COLUMN, 1421 INFO_PARALLEL_UPDATE_END_OF_LDIF.get()); 1422 out(); 1423 1424 1425 // Wait for the operation queue to become idle so that we know there 1426 // are no more outstanding operations to be processed. 1427 operationQueue.waitUntilIdle(); 1428 initialAttempted = opsAttempted.get(); 1429 initialSucceeded = opsSucceeded.get(); 1430 1431 1432 // If there are any operations to retry, then do so now. 1433 Map<DN,List<ObjectPair<LDIFChangeRecord,LDAPException>>> retryQueueCopy; 1434 synchronized (retryQueue) 1435 { 1436 retryQueueCopy = new TreeMap<>(retryQueue); 1437 retryQueue.clear(); 1438 } 1439 1440 int lastRetryQueueSize = 0; 1441 while ((! retryQueueCopy.isEmpty()) && 1442 (retryQueueCopy.size() != lastRetryQueueSize)) 1443 { 1444 out(); 1445 wrapOut(0, WRAP_COLUMN, 1446 INFO_PARALLEL_UPDATE_BEGINNING_RETRY.get(retryQueueCopy.size())); 1447 out(); 1448 1449 1450 for (final 1451 Map.Entry<DN,List<ObjectPair<LDIFChangeRecord,LDAPException>>> 1452 e : retryQueueCopy.entrySet()) 1453 { 1454 for (final ObjectPair<LDIFChangeRecord,LDAPException> p : 1455 e.getValue()) 1456 { 1457 if (shouldAbort.get()) 1458 { 1459 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1460 ResultCode.LOCAL_ERROR); 1461 return resultCodeRef.get(); 1462 } 1463 1464 final LDIFChangeRecord changeRecord = p.getFirst(); 1465 try 1466 { 1467 operationQueue.addChangeRecord(changeRecord); 1468 retryQueueSize.decrementAndGet(); 1469 } 1470 catch (final Exception ex) 1471 { 1472 Debug.debugException(ex); 1473 1474 // This indicates that the attempt to enqueue the change record 1475 // was interrupted. This shouldn't happen, but if it does, then 1476 // mark it to be retried. 1477 final LDAPException le = new LDAPException( 1478 ResultCode.LOCAL_ERROR, 1479 ERR_PARALLEL_UPDATE_ENQUEUE_FAILED.get( 1480 StaticUtils.getExceptionMessage(ex)), 1481 ex); 1482 retry(changeRecord, le); 1483 } 1484 } 1485 } 1486 1487 1488 operationQueue.waitUntilIdle(); 1489 lastRetryQueueSize = retryQueueCopy.size(); 1490 1491 synchronized (retryQueue) 1492 { 1493 retryQueueCopy = new TreeMap<>(retryQueue); 1494 retryQueue.clear(); 1495 } 1496 } 1497 1498 1499 // If we've gotten here, then it means that either the retry queue 1500 // (NOTE: we actually need to use retryQueueCopy) is empty or none of 1501 // the retry attempts succeeded on the last pass. If it's the latter, 1502 // then reject any of the remaining operations. 1503 synchronized (retryQueue) 1504 { 1505 final int remainingToRetry = retryQueueCopy.size(); 1506 if (remainingToRetry > 0) 1507 { 1508 if (remainingToRetry == 1) 1509 { 1510 wrapErr(0, WRAP_COLUMN, 1511 ERR_PARALLEL_UPDATE_NO_PROGRESS_ONE.get()); 1512 } 1513 else 1514 { 1515 wrapErr(0, WRAP_COLUMN, 1516 ERR_PARALLEL_UPDATE_NO_PROGRESS_MULTIPLE.get( 1517 remainingToRetry)); 1518 } 1519 } 1520 1521 for (final 1522 Map.Entry<DN,List<ObjectPair<LDIFChangeRecord,LDAPException>>> 1523 e : retryQueueCopy.entrySet()) 1524 { 1525 for (final ObjectPair<LDIFChangeRecord,LDAPException> p : 1526 e.getValue()) 1527 { 1528 reject(p.getFirst(), p.getSecond()); 1529 retryQueueSize.decrementAndGet(); 1530 } 1531 } 1532 } 1533 } 1534 finally 1535 { 1536 operationQueue.setEndOfLDIF(); 1537 operationQueue.waitUntilIdle(); 1538 1539 for (final ParallelUpdateOperationThread operationThread : 1540 operationThreadList) 1541 { 1542 try 1543 { 1544 operationThread.join(); 1545 } 1546 catch (final Exception e) 1547 { 1548 Debug.debugException(e); 1549 logCompletionMessage(true, 1550 ERR_PARALLEL_UPDATE_CANNOT_JOIN_THREAD.get( 1551 operationThread.getName(), 1552 StaticUtils.getExceptionMessage(e))); 1553 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1554 ResultCode.LOCAL_ERROR); 1555 } 1556 } 1557 1558 try 1559 { 1560 progressMonitor.stopRunning(); 1561 progressMonitor.join(); 1562 } 1563 catch (final Exception e) 1564 { 1565 Debug.debugException(e); 1566 logCompletionMessage(true, 1567 ERR_PARALLEL_UPDATE_CANNOT_JOIN_PROGRESS_MONITOR.get( 1568 StaticUtils.getExceptionMessage(e))); 1569 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1570 ResultCode.LOCAL_ERROR); 1571 } 1572 } 1573 } 1574 finally 1575 { 1576 connectionPool.close(); 1577 1578 if (rejectWriter != null) 1579 { 1580 try 1581 { 1582 rejectWriter.close(); 1583 } 1584 catch (final Exception e) 1585 { 1586 Debug.debugException(e); 1587 logCompletionMessage(true, 1588 ERR_PARALLEL_UPDATE_ERROR_CLOSING_REJECT_WRITER.get( 1589 rejectFileArg.getValue().getAbsolutePath(), 1590 StaticUtils.getExceptionMessage(e))); 1591 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1592 ResultCode.LOCAL_ERROR); 1593 } 1594 } 1595 1596 if (logWriter != null) 1597 { 1598 try 1599 { 1600 logWriter.close(); 1601 } 1602 catch (final Exception e) 1603 { 1604 Debug.debugException(e); 1605 logCompletionMessage(true, 1606 ERR_PARALLEL_UPDATE_ERROR_CLOSING_LOG_WRITER.get( 1607 logFileArg.getValue().getAbsolutePath(), 1608 StaticUtils.getExceptionMessage(e))); 1609 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1610 ResultCode.LOCAL_ERROR); 1611 } 1612 } 1613 1614 try 1615 { 1616 ldifReader.close(); 1617 } 1618 catch (final Exception e) 1619 { 1620 Debug.debugException(e); 1621 logCompletionMessage(true, 1622 WARN_PARALLEL_UPDATE_ERROR_CLOSING_READER.get( 1623 ldifFileArg.getValue().getAbsolutePath(), 1624 StaticUtils.getExceptionMessage(e))); 1625 resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); 1626 } 1627 } 1628 1629 1630 // If we've gotten here, then processing has completed. Print some summary 1631 // messages and return an appropriate result code. 1632 final long processingDurationMillis = 1633 System.currentTimeMillis() - processingStartTimeMillis; 1634 final long numAttempts = opsAttempted.get(); 1635 final long numSuccesses = opsSucceeded.get(); 1636 final long numRejects = opsRejected.get(); 1637 1638 final long retryAttempts = numAttempts - initialAttempted; 1639 final long retrySuccesses = numSuccesses - initialSucceeded; 1640 1641 out(INFO_PARALLEL_UPDATE_DONE.get(getToolName())); 1642 out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_ATTEMPTED.get(numAttempts)); 1643 1644 if (retryAttempts > 0L) 1645 { 1646 out(INFO_PARALLEL_UPDATE_SUMMARY_INITIAL_ATTEMPTS.get(initialAttempted)); 1647 out(INFO_PARALLEL_UPDATE_SUMMARY_RETRY_ATTEMPTS.get(retryAttempts)); 1648 } 1649 1650 out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_SUCCEEDED.get(numSuccesses)); 1651 1652 if (retryAttempts > 0) 1653 { 1654 out(INFO_PARALLEL_UPDATED_OPS_SUCCEEDED_INITIAL.get(initialSucceeded)); 1655 out(INFO_PARALLEL_UPDATED_OPS_SUCCEEDED_RETRY.get(retrySuccesses)); 1656 } 1657 1658 out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_REJECTED.get(numRejects)); 1659 out(INFO_PARALLEL_UPDATE_SUMMARY_DURATION.get( 1660 StaticUtils.millisToHumanReadableDuration(processingDurationMillis))); 1661 1662 if ((numAttempts > 0L) && (processingDurationMillis > 0L)) 1663 { 1664 final double attemptsPerSecond = 1665 numAttempts * 1_000.0d / processingDurationMillis; 1666 final DecimalFormat decimalFormat = new DecimalFormat("0.000"); 1667 out(INFO_PARALLEL_UPDATE_SUMMARY_RATE.get( 1668 decimalFormat.format(attemptsPerSecond))); 1669 } 1670 1671 1672 if (numRejects == 0L) 1673 { 1674 completionMessage.compareAndSet(null, 1675 INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_ALL_SUCCEEDED.get( 1676 getToolName())); 1677 } 1678 else if (numRejects == 1L) 1679 { 1680 completionMessage.compareAndSet(null, 1681 INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_ONE_REJECTED.get( 1682 getToolName())); 1683 } 1684 else 1685 { 1686 completionMessage.compareAndSet(null, 1687 INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_MULTIPLE_REJECTED.get( 1688 getToolName(), numRejects)); 1689 } 1690 1691 1692 ResultCode finalResultCode = resultCodeRef.get(); 1693 if ((finalResultCode == ResultCode.SUCCESS) && 1694 useFirstRejectResultCodeAsExitCodeArg.isPresent() && 1695 (firstRejectResultCode.get() != null)) 1696 { 1697 finalResultCode = firstRejectResultCode.get(); 1698 } 1699 1700 return finalResultCode; 1701 } 1702 1703 1704 1705 /** 1706 * Updates the provided lists with the appropriate controls to include in 1707 * each type of request. 1708 * 1709 * @param addControls The list that should be updated with controls to 1710 * include in add requests. It must not be 1711 * {@code null} and must be updatable. 1712 * @param deleteControls The list that should be updated with controls to 1713 * include in delete requests. It must not be 1714 * {@code null} and must be updatable. 1715 * @param modifyControls The list that should be updated with controls to 1716 * include in modify requests. It must not be 1717 * {@code null} and must be updatable. 1718 * @param modifyDNControls The list that should be updated with controls to 1719 * include in modify DN requests. It must not be 1720 * {@code null} and must be updatable. 1721 * 1722 * @throws LDAPException If a problem is encountered while creating any of 1723 * the controls. 1724 */ 1725 private void getOperationControls( 1726 @NotNull final List<Control> addControls, 1727 @NotNull final List<Control> deleteControls, 1728 @NotNull final List<Control> modifyControls, 1729 @NotNull final List<Control> modifyDNControls) 1730 throws LDAPException 1731 { 1732 if (addControlArg.isPresent()) 1733 { 1734 addControls.addAll(addControlArg.getValues()); 1735 } 1736 1737 if (deleteControlArg.isPresent()) 1738 { 1739 deleteControls.addAll(deleteControlArg.getValues()); 1740 } 1741 1742 if (modifyControlArg.isPresent()) 1743 { 1744 modifyControls.addAll(modifyControlArg.getValues()); 1745 } 1746 1747 if (modifyDNControlArg.isPresent()) 1748 { 1749 modifyDNControls.addAll(modifyDNControlArg.getValues()); 1750 } 1751 1752 if (proxyAsArg.isPresent()) 1753 { 1754 final ProxiedAuthorizationV2RequestControl c = 1755 new ProxiedAuthorizationV2RequestControl(proxyAsArg.getValue()); 1756 addControls.add(c); 1757 deleteControls.add(c); 1758 modifyControls.add(c); 1759 modifyDNControls.add(c); 1760 } 1761 else if (proxyV1AsArg.isPresent()) 1762 { 1763 final ProxiedAuthorizationV1RequestControl c = 1764 new ProxiedAuthorizationV1RequestControl(proxyV1AsArg.getValue()); 1765 addControls.add(c); 1766 deleteControls.add(c); 1767 modifyControls.add(c); 1768 modifyDNControls.add(c); 1769 } 1770 1771 if (usePermissiveModifyArg.isPresent()) 1772 { 1773 modifyControls.add(new PermissiveModifyRequestControl(true)); 1774 } 1775 1776 if (ignoreNoUserModificationArg.isPresent()) 1777 { 1778 final IgnoreNoUserModificationRequestControl c = 1779 new IgnoreNoUserModificationRequestControl(); 1780 addControls.add(c); 1781 modifyControls.add(c); 1782 } 1783 1784 if (useManageDsaITArg.isPresent()) 1785 { 1786 final ManageDsaITRequestControl c = new ManageDsaITRequestControl(true); 1787 addControls.add(c); 1788 deleteControls.add(c); 1789 modifyControls.add(c); 1790 modifyDNControls.add(c); 1791 } 1792 1793 if (nameWithEntryUUIDArg.isPresent()) 1794 { 1795 addControls.add(new NameWithEntryUUIDRequestControl(true)); 1796 } 1797 1798 if (softDeleteArg.isPresent()) 1799 { 1800 deleteControls.add(new SoftDeleteRequestControl(true, true)); 1801 } 1802 else if (hardDeleteArg.isPresent()) 1803 { 1804 deleteControls.add(new HardDeleteRequestControl(true)); 1805 } 1806 1807 if (operationPurposeArg.isPresent()) 1808 { 1809 final OperationPurposeRequestControl c = 1810 new OperationPurposeRequestControl(false, "parallel-update", 1811 Version.NUMERIC_VERSION_STRING, 1812 ParallelUpdate.class.getName() + ".getOperationControls", 1813 operationPurposeArg.getValue()); 1814 addControls.add(c); 1815 deleteControls.add(c); 1816 modifyControls.add(c); 1817 modifyDNControls.add(c); 1818 } 1819 1820 if (replicationRepairArg.isPresent()) 1821 { 1822 final ReplicationRepairRequestControl c = 1823 new ReplicationRepairRequestControl(); 1824 addControls.add(c); 1825 deleteControls.add(c); 1826 modifyControls.add(c); 1827 modifyDNControls.add(c); 1828 } 1829 1830 if (suppressReferentialIntegrityUpdatesArg.isPresent()) 1831 { 1832 final SuppressReferentialIntegrityUpdatesRequestControl c = 1833 new SuppressReferentialIntegrityUpdatesRequestControl(true); 1834 deleteControls.add(c); 1835 modifyDNControls.add(c); 1836 } 1837 1838 1839 if (useAssuredReplicationArg.isPresent()) 1840 { 1841 final AssuredReplicationLocalLevel localLevel; 1842 if (assuredReplicationLocalLevelArg.isPresent()) 1843 { 1844 final String localLevelStr = StaticUtils.toLowerCase( 1845 assuredReplicationLocalLevelArg.getValue()); 1846 switch (localLevelStr) 1847 { 1848 case ASSURED_REPLICATION_LOCAL_LEVEL_NONE: 1849 localLevel = AssuredReplicationLocalLevel.NONE; 1850 break; 1851 case ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER: 1852 localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER; 1853 break; 1854 case ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS: 1855 localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS; 1856 break; 1857 default: 1858 // This should never happen. 1859 localLevel = null; 1860 break; 1861 } 1862 } 1863 else 1864 { 1865 localLevel = null; 1866 } 1867 1868 final AssuredReplicationRemoteLevel remoteLevel; 1869 if (assuredReplicationRemoteLevelArg.isPresent()) 1870 { 1871 final String remoteLevelStr = StaticUtils.toLowerCase( 1872 assuredReplicationRemoteLevelArg.getValue()); 1873 switch (remoteLevelStr) 1874 { 1875 case ASSURED_REPLICATION_REMOTE_LEVEL_NONE: 1876 remoteLevel = AssuredReplicationRemoteLevel.NONE; 1877 break; 1878 case ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION: 1879 remoteLevel = AssuredReplicationRemoteLevel. 1880 RECEIVED_ANY_REMOTE_LOCATION; 1881 break; 1882 case ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS: 1883 remoteLevel = AssuredReplicationRemoteLevel. 1884 RECEIVED_ALL_REMOTE_LOCATIONS; 1885 break; 1886 case ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS: 1887 remoteLevel = AssuredReplicationRemoteLevel. 1888 PROCESSED_ALL_REMOTE_SERVERS; 1889 break; 1890 default: 1891 // This should never happen. 1892 remoteLevel = null; 1893 break; 1894 } 1895 } 1896 else 1897 { 1898 remoteLevel = null; 1899 } 1900 1901 final Long timeoutMillis; 1902 if (assuredReplicationTimeoutArg.isPresent()) 1903 { 1904 timeoutMillis = assuredReplicationTimeoutArg.getValue( 1905 TimeUnit.MILLISECONDS); 1906 } 1907 else 1908 { 1909 timeoutMillis = null; 1910 } 1911 1912 final AssuredReplicationRequestControl c = 1913 new AssuredReplicationRequestControl(true, localLevel, localLevel, 1914 remoteLevel, remoteLevel, timeoutMillis, false); 1915 addControls.add(c); 1916 deleteControls.add(c); 1917 modifyControls.add(c); 1918 modifyDNControls.add(c); 1919 } 1920 1921 1922 if (passwordUpdateBehaviorArg.isPresent()) 1923 { 1924 final PasswordUpdateBehaviorRequestControlProperties properties = 1925 new PasswordUpdateBehaviorRequestControlProperties(); 1926 for (final String argValue : passwordUpdateBehaviorArg.getValues()) 1927 { 1928 final int equalPos = argValue.indexOf('='); 1929 if (equalPos < 0) 1930 { 1931 throw new LDAPException(ResultCode.PARAM_ERROR, 1932 ERR_PARALLEL_UPDATE_MALFORMED_PW_UPDATE_VALUE.get( 1933 argValue, passwordUpdateBehaviorArg.getIdentifierString())); 1934 } 1935 1936 final String propertyName = argValue.substring(0, equalPos).trim(); 1937 final String lowerName = StaticUtils.toLowerCase(propertyName); 1938 switch (lowerName) 1939 { 1940 case PW_UPDATE_BEHAVIOR_NAME_IS_SELF_CHANGE: 1941 properties.setIsSelfChange( 1942 getBooleanPWUpdateBehaviorValue(argValue)); 1943 break; 1944 case PW_UPDATE_BEHAVIOR_NAME_ALLOW_PRE_ENCODED_PW: 1945 properties.setAllowPreEncodedPassword( 1946 getBooleanPWUpdateBehaviorValue(argValue)); 1947 break; 1948 case PW_UPDATE_BEHAVIOR_NAME_SKIP_PW_VALIDATION: 1949 properties.setSkipPasswordValidation( 1950 getBooleanPWUpdateBehaviorValue(argValue)); 1951 break; 1952 case PW_UPDATE_BEHAVIOR_NAME_IGNORE_PW_HISTORY: 1953 properties.setIgnorePasswordHistory( 1954 getBooleanPWUpdateBehaviorValue(argValue)); 1955 break; 1956 case PW_UPDATE_BEHAVIOR_NAME_IGNORE_MIN_PW_AGE: 1957 properties.setIgnoreMinimumPasswordAge( 1958 getBooleanPWUpdateBehaviorValue(argValue)); 1959 break; 1960 case PW_UPDATE_BEHAVIOR_NAME_MUST_CHANGE_PW: 1961 properties.setMustChangePassword( 1962 getBooleanPWUpdateBehaviorValue(argValue)); 1963 break; 1964 case PW_UPDATE_BEHAVIOR_NAME_PW_STORAGE_SCHEME: 1965 final String propertyValue = argValue.substring(equalPos+1).trim(); 1966 properties.setPasswordStorageScheme(propertyValue); 1967 break; 1968 default: 1969 throw new LDAPException(ResultCode.PARAM_ERROR, 1970 ERR_PARALLEL_UPDATE_UNKNOWN_PW_UPDATE_PROP.get(argValue, 1971 passwordUpdateBehaviorArg.getIdentifierString(), 1972 PW_UPDATE_BEHAVIOR_NAME_IS_SELF_CHANGE, 1973 PW_UPDATE_BEHAVIOR_NAME_ALLOW_PRE_ENCODED_PW, 1974 PW_UPDATE_BEHAVIOR_NAME_SKIP_PW_VALIDATION, 1975 PW_UPDATE_BEHAVIOR_NAME_IGNORE_PW_HISTORY, 1976 PW_UPDATE_BEHAVIOR_NAME_IGNORE_MIN_PW_AGE, 1977 PW_UPDATE_BEHAVIOR_NAME_PW_STORAGE_SCHEME, 1978 PW_UPDATE_BEHAVIOR_NAME_MUST_CHANGE_PW)); 1979 } 1980 } 1981 1982 final PasswordUpdateBehaviorRequestControl c = 1983 new PasswordUpdateBehaviorRequestControl(properties, true); 1984 addControls.add(c); 1985 modifyControls.add(c); 1986 } 1987 1988 1989 if (suppressOperationalAttributeUpdatesArg.isPresent()) 1990 { 1991 final EnumSet<SuppressType> suppressTypes = 1992 EnumSet.noneOf(SuppressType.class); 1993 for (final String s : suppressOperationalAttributeUpdatesArg.getValues()) 1994 { 1995 if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_ACCESS_TIME)) 1996 { 1997 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 1998 } 1999 else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_TIME)) 2000 { 2001 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 2002 } 2003 else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_IP)) 2004 { 2005 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 2006 } 2007 } 2008 2009 final SuppressOperationalAttributeUpdateRequestControl c = 2010 new SuppressOperationalAttributeUpdateRequestControl(true, 2011 suppressTypes); 2012 addControls.add(c); 2013 deleteControls.add(c); 2014 modifyControls.add(c); 2015 modifyDNControls.add(c); 2016 } 2017 } 2018 2019 2020 2021 /** 2022 * Retrieves the value from the provided name-value pair and parses it as a 2023 * boolean. 2024 * 2025 * @param nameValuePair The name-value pair to be parsed. It must not be 2026 * {@code null} and it must contain an equal sign. 2027 * 2028 * @return The boolean value parsed from the provided name-value pair. 2029 * 2030 * @throws LDAPException If the value could not be parsed as a boolean. 2031 */ 2032 private boolean getBooleanPWUpdateBehaviorValue( 2033 @NotNull final String nameValuePair) 2034 throws LDAPException 2035 { 2036 final int equalPos = nameValuePair.indexOf('='); 2037 final String propertyValue = nameValuePair.substring(equalPos+1).trim(); 2038 final String lowerValue = StaticUtils.toLowerCase(propertyValue); 2039 switch (lowerValue) 2040 { 2041 case "true": 2042 case "t": 2043 case "yes": 2044 case "on": 2045 case "1": 2046 return true; 2047 case "false": 2048 case "f": 2049 case "no": 2050 case "off": 2051 case "0": 2052 return false; 2053 default: 2054 final String propertyName = nameValuePair.substring(0, equalPos).trim(); 2055 throw new LDAPException(ResultCode.PARAM_ERROR, 2056 ERR_PARALLEL_UPDATE_PW_UPDATE_VALUE_NOT_BOOLEAN.get(nameValuePair, 2057 passwordUpdateBehaviorArg.getIdentifierString(), 2058 propertyName)); 2059 } 2060 } 2061 2062 2063 2064 /** 2065 * Creates the LDIF reader to use to read the changes to process. 2066 * 2067 * @return An object pair in which the first element is the LDIF reader and 2068 * the second element is the passphrase used to encrypt the contents 2069 * of the LDIF file (or {@code null} if the LDIF file is not 2070 * encrypted). 2071 * 2072 * @throws LDAPException If a problem occurs while trying to create the LDIF 2073 * reader. 2074 */ 2075 @NotNull() 2076 private ObjectPair<LDIFReader,String> createLDIFReader() 2077 throws LDAPException 2078 { 2079 final File ldifFile = ldifFileArg.getValue(); 2080 2081 try 2082 { 2083 final String encryptionPassphraseFromFile; 2084 if (encryptionPassphraseFileArg.isPresent()) 2085 { 2086 final char[] pwChars = getPasswordFileReader().readPassword( 2087 encryptionPassphraseFileArg.getValue()); 2088 encryptionPassphraseFromFile = new String(pwChars); 2089 Arrays.fill(pwChars, '\u0000'); 2090 } 2091 else 2092 { 2093 encryptionPassphraseFromFile = null; 2094 } 2095 2096 final ObjectPair<InputStream,String> inputStreamPair = 2097 ToolUtils.getInputStreamForLDIFFiles( 2098 Collections.singletonList(ldifFile), 2099 encryptionPassphraseFromFile, getOut(), getErr()); 2100 2101 final LDIFReader ldifReader = new LDIFReader(inputStreamPair.getFirst()); 2102 final String encryptionPassphrase = inputStreamPair.getSecond(); 2103 return new ObjectPair<>(ldifReader, encryptionPassphrase); 2104 } 2105 catch (final Exception e) 2106 { 2107 Debug.debugException(e); 2108 throw new LDAPException(ResultCode.LOCAL_ERROR, 2109 ERR_PARALLEL_UPDATE_CANNOT_CREATE_LDIF_READER.get( 2110 ldifFile.getAbsolutePath(), 2111 StaticUtils.getExceptionMessage(e)), 2112 e); 2113 } 2114 } 2115 2116 2117 2118 /** 2119 * Attempts to retrieve the ID of the encryption settings definition used to 2120 * encrypt the LDIF file. This method should only be used if the LDIF file is 2121 * known to be encrypted. 2122 * 2123 * @return The ID of the encryption settings definition used to encrypt the 2124 * LDIF file, or {code null} if it was not encrypted with a 2125 * passphrase obtained from an encryption settings definition (or if 2126 * an error occurred while attempting to retrieve the ID). 2127 */ 2128 @Nullable() 2129 private String getEncryptionSettingsDefinitionID() 2130 { 2131 try (FileInputStream inputStream = 2132 new FileInputStream(ldifFileArg.getValue())) 2133 { 2134 final PassphraseEncryptedStreamHeader encryptionHeader = 2135 PassphraseEncryptedStreamHeader.readFrom(inputStream, null); 2136 return encryptionHeader.getKeyIdentifier(); 2137 } 2138 catch (final Exception e) 2139 { 2140 Debug.debugException(e); 2141 return null; 2142 } 2143 } 2144 2145 2146 2147 /** 2148 * Creates the LDIF writer that will be used to write information about 2149 * rejected entries. If the LDIF input file was encrypted, then the reject 2150 * file will be encrypted with the same settings, and it will also be 2151 * compressed (regardless of whether the input file was compressed). 2152 * 2153 * @param encryptionPassphrase The passphrase used to encrypt the 2154 * input file. This may be 2155 * {@code null} if the input file was 2156 * not encrypted. 2157 * @param encryptionSettingsDefinitionID The ID for the encryption settings 2158 * definition with which the 2159 * passphrase is associated. It may 2160 * be {@code null} if the input file 2161 * was not encrypted, or if the 2162 * encryption passphrase was not 2163 * obtained from an encryption 2164 * settings definition. 2165 * 2166 * @return The LDIF writer to which rejects should be written. 2167 * 2168 * @throws LDAPException If a problem occurs while creating the reject 2169 * writer. 2170 */ 2171 @NotNull() 2172 private LDIFWriter createRejectWriter( 2173 @Nullable final String encryptionPassphrase, 2174 @Nullable final String encryptionSettingsDefinitionID) 2175 throws LDAPException 2176 { 2177 final File rejectFile = rejectFileArg.getValue(); 2178 2179 OutputStream outputStream = null; 2180 try 2181 { 2182 outputStream = new FileOutputStream(rejectFile); 2183 2184 if (encryptionPassphrase != null) 2185 { 2186 outputStream = new PassphraseEncryptedOutputStream(encryptionPassphrase, 2187 outputStream, encryptionSettingsDefinitionID, true, true); 2188 outputStream = new GZIPOutputStream(outputStream); 2189 } 2190 2191 2192 final LDIFWriter ldifWriter = new LDIFWriter(outputStream); 2193 2194 // Set the wrap column to the maximum allowed value to ensure that 2195 // comments dont' get wrapped. 2196 ldifWriter.setWrapColumn(Integer.MAX_VALUE); 2197 2198 return ldifWriter; 2199 } 2200 catch (final Exception e) 2201 { 2202 Debug.debugException(e); 2203 2204 if (outputStream != null) 2205 { 2206 try 2207 { 2208 outputStream.close(); 2209 } 2210 catch (final Exception e2) 2211 { 2212 Debug.debugException(e2); 2213 } 2214 } 2215 2216 throw new LDAPException(ResultCode.LOCAL_ERROR, 2217 ERR_PARALLEL_UPDATE_ERROR_CREATING_REJECT_WRITER.get( 2218 rejectFile.getAbsolutePath(), 2219 StaticUtils.getExceptionMessage(e)), 2220 e); 2221 } 2222 } 2223 2224 2225 2226 /** 2227 * Writes the provided message to standard output or standard error and sets 2228 * it as the completion message if there isn't one already. 2229 * 2230 * @param isError Indicates whether the message represents an error. 2231 * @param message The message to log. 2232 */ 2233 private void logCompletionMessage(final boolean isError, 2234 @NotNull final String message) 2235 { 2236 completionMessage.compareAndSet(null, message); 2237 logMessage(message); 2238 if (isError) 2239 { 2240 wrapErr(0, WRAP_COLUMN, message); 2241 } 2242 else 2243 { 2244 wrapOut(0, WRAP_COLUMN, message); 2245 } 2246 } 2247 2248 2249 2250 /** 2251 * Logs the provided message if logging is enabled. 2252 * 2253 * @param messageElements The elements that make up the message to log. 2254 */ 2255 private void logMessage(@NotNull final Object... messageElements) 2256 { 2257 if (logWriter != null) 2258 { 2259 SimpleDateFormat timestampFormatter = timestampFormatters.get(); 2260 if (timestampFormatter == null) 2261 { 2262 timestampFormatter = 2263 new SimpleDateFormat("'['dd/MMM/yyyy:HH:mm:ss Z']'"); 2264 timestampFormatters.set(timestampFormatter); 2265 } 2266 2267 final String timestamp = timestampFormatter.format(new Date()); 2268 2269 final StringBuilder message = new StringBuilder(); 2270 message.append('['); 2271 message.append(timestamp); 2272 message.append("] "); 2273 for (final Object o : messageElements) 2274 { 2275 message.append(String.valueOf(o)); 2276 } 2277 2278 try 2279 { 2280 logWriter.println(message); 2281 } 2282 catch (final Exception e) 2283 { 2284 Debug.debugException(e); 2285 final String errorMessage = 2286 ERR_PARALLEL_UPDATE_CANNOT_WRITE_LOG_MESSAGE.get( 2287 message.toString(), logFileArg.getValue().getAbsolutePath(), 2288 StaticUtils.getExceptionMessage(e)); 2289 wrapErr(0, WRAP_COLUMN, errorMessage); 2290 completionMessage.compareAndSet(null, errorMessage); 2291 shouldAbort.set(true); 2292 } 2293 } 2294 } 2295 2296 2297 2298 /** 2299 * Indicates that processing for an operation completed successfully. 2300 * 2301 * @param changeRecord The LDIF change record for the operation. 2302 * It must not be {@code null}. 2303 * @param processingDurationMillis The length of time required to process 2304 * the operation, in milliseconds. 2305 */ 2306 void opCompletedSuccessfully(@NotNull final LDIFChangeRecord changeRecord, 2307 final long processingDurationMillis) 2308 { 2309 opsAttempted.incrementAndGet(); 2310 opsSucceeded.incrementAndGet(); 2311 totalOpDurationMillis.addAndGet(processingDurationMillis); 2312 logMessage(changeRecord.getDN(), " ", 2313 changeRecord.getChangeType().getName(), " SUCCESS 0 "); 2314 } 2315 2316 2317 2318 /** 2319 * Indicates that processing for an operation failed. Depending on the nature 2320 * of the failure, it either be added to the retry queue if it is potentially 2321 * an operation that could be retried (e.g., an operation that failed because 2322 * it depended on an entry that was added later in the LDIF), or will be added 2323 * to the reject file if it is determined that it is not a failure that may be 2324 * resolved by a later change. 2325 * 2326 * @param changeRecord The LDIF change record for the operation. 2327 * It must not be {@code null}. 2328 * @param ldapException The LDAP exception that was caught to 2329 * indicate that the operation failed. It 2330 * must not be {@code null}. 2331 * @param processingDurationMillis The length of time required to process 2332 * the operation, in milliseconds. 2333 */ 2334 void opFailed(@NotNull final LDIFChangeRecord changeRecord, 2335 @NotNull final LDAPException ldapException, 2336 final long processingDurationMillis) 2337 { 2338 opsAttempted.incrementAndGet(); 2339 totalOpDurationMillis.addAndGet(processingDurationMillis); 2340 2341 switch (ldapException.getResultCode().intValue()) 2342 { 2343 case ResultCode.NO_SUCH_OBJECT_INT_VALUE: 2344 case ResultCode.BUSY_INT_VALUE: 2345 case ResultCode.UNAVAILABLE_INT_VALUE: 2346 case ResultCode.NOT_ALLOWED_ON_NONLEAF_INT_VALUE: 2347 case ResultCode.SERVER_DOWN_INT_VALUE: 2348 case ResultCode.CONNECT_ERROR_INT_VALUE: 2349 retry(changeRecord, ldapException); 2350 break; 2351 default: 2352 reject(changeRecord, ldapException); 2353 break; 2354 } 2355 } 2356 2357 2358 2359 /** 2360 * Adds the provided operation to the retry queue. 2361 * 2362 * @param changeRecord The LDIF change record for the operation. 2363 * @param ldapException The LDAP exception that was caught to indicate that 2364 * the operation failed. 2365 */ 2366 private void retry(@NotNull final LDIFChangeRecord changeRecord, 2367 @NotNull final LDAPException ldapException) 2368 { 2369 if (neverRetryArg.isPresent()) 2370 { 2371 reject(changeRecord, ldapException); 2372 return; 2373 } 2374 2375 final DN parsedDN; 2376 try 2377 { 2378 parsedDN = changeRecord.getParsedDN(); 2379 } 2380 catch (final LDAPException e) 2381 { 2382 Debug.debugException(e); 2383 2384 // This should never happen, but if it does, then reject the change. 2385 reject(changeRecord, ldapException); 2386 return; 2387 } 2388 2389 logMessage(changeRecord.getDN(), " ", 2390 changeRecord.getChangeType().getName(), " RETRY ", 2391 ldapException.getResultCode(), " ", ldapException.getMessage()); 2392 2393 synchronized (retryQueue) 2394 { 2395 List<ObjectPair<LDIFChangeRecord,LDAPException>> changeList = 2396 retryQueue.get(parsedDN); 2397 2398 if (changeList == null) 2399 { 2400 changeList = new LinkedList<>(); 2401 retryQueue.put(parsedDN, changeList); 2402 } 2403 2404 changeList.add(new ObjectPair<>(changeRecord, ldapException)); 2405 retryQueueSize.incrementAndGet(); 2406 } 2407 } 2408 2409 2410 2411 /** 2412 * Adds the provided operation to the reject file. 2413 * 2414 * @param changeRecord The LDIF change record for the operation. 2415 * @param ldapException The LDAP exception that was caught to indicate that 2416 * the operation failed. 2417 */ 2418 void reject(@Nullable final LDIFChangeRecord changeRecord, 2419 @NotNull final LDAPException ldapException) 2420 { 2421 opsRejected.incrementAndGet(); 2422 2423 final ResultCode resultCode = ldapException.getResultCode(); 2424 if (resultCode != ResultCode.SUCCESS) 2425 { 2426 firstRejectResultCode.compareAndSet(null, resultCode); 2427 } 2428 2429 final StringBuilder commentBuffer = new StringBuilder(); 2430 for (final String line : 2431 ResultUtils.formatResult(ldapException, false, 0, 0)) 2432 { 2433 if (commentBuffer.length() > 0) 2434 { 2435 commentBuffer.append(StaticUtils.EOL); 2436 } 2437 commentBuffer.append(line); 2438 } 2439 final String comment = commentBuffer.toString(); 2440 2441 try 2442 { 2443 if (changeRecord != null) 2444 { 2445 logMessage(changeRecord.getDN(), " ", 2446 changeRecord.getChangeType().getName(), " REJECT ", resultCode, " ", 2447 ldapException.getMessage()); 2448 2449 synchronized (rejectWriter) 2450 { 2451 rejectWriter.writeChangeRecord(changeRecord, comment); 2452 } 2453 } 2454 else 2455 { 2456 synchronized (rejectWriter) 2457 { 2458 rejectWriter.writeComment(comment, true, true); 2459 } 2460 } 2461 } 2462 catch (final Exception e) 2463 { 2464 Debug.debugException(e); 2465 2466 final String errorMessage = ERR_PARALLEL_UPDATE_CANNOT_WRITE_REJECT.get( 2467 changeRecord.toString(), 2468 ERR_PARALLEL_UPDATE_REJECT_COMMENT.get(String.valueOf(resultCode), 2469 ldapException.getMessage()), 2470 StaticUtils.getExceptionMessage(e)); 2471 wrapErr(0, WRAP_COLUMN, errorMessage); 2472 completionMessage.compareAndSet(null, errorMessage); 2473 shouldAbort.set(true); 2474 } 2475 } 2476 2477 2478 2479 /** 2480 * Prints information about the processing performed by this program to 2481 * standard output. 2482 */ 2483 void printIntervalData() 2484 { 2485 final long currentAttempts = opsAttempted.get(); 2486 final long currentSuccesses = opsSucceeded.get(); 2487 final long currentReject = opsRejected.get(); 2488 final long currentRetry = retryQueueSize.get(); 2489 final long currentDurationMillis = totalOpDurationMillis.get(); 2490 final long currentTimeMillis = System.currentTimeMillis(); 2491 final long totalDurationMillis = 2492 currentTimeMillis - processingStartTimeMillis; 2493 2494 final long avgRate; 2495 if (totalDurationMillis == 0L) 2496 { 2497 avgRate = 0L; 2498 } 2499 else 2500 { 2501 avgRate = 1000L * currentAttempts / totalDurationMillis; 2502 } 2503 2504 final long avgDurationMillis; 2505 if (currentAttempts == 0L) 2506 { 2507 avgDurationMillis = 0L; 2508 } 2509 else 2510 { 2511 avgDurationMillis = currentDurationMillis / currentAttempts; 2512 } 2513 2514 final long recentDurationMillis; 2515 if (currentAttempts == lastOpsAttempted) 2516 { 2517 recentDurationMillis = 0L; 2518 } 2519 else 2520 { 2521 recentDurationMillis = (currentDurationMillis - lastTotalDurationMillis) / 2522 (currentAttempts - lastOpsAttempted); 2523 } 2524 2525 final long recentRate; 2526 if (lastOpsAttempted == 0) 2527 { 2528 out(" Attempts Successes Rejects ToRetry AvgOps/S " + 2529 " RctOps/S AvgDurMS RctDurMS"); 2530 out("--------- --------- --------- --------- --------- " + 2531 "--------- --------- ---------"); 2532 recentRate = avgRate; 2533 } 2534 else if (currentTimeMillis == lastUpdateTimeMillis) 2535 { 2536 recentRate = 0L; 2537 } 2538 else 2539 { 2540 recentRate = 1000L * (currentAttempts - lastOpsAttempted) / 2541 (currentTimeMillis - lastUpdateTimeMillis); 2542 } 2543 2544 final StringBuilder buffer = new StringBuilder(80); 2545 appendJustified(currentAttempts, buffer, true); 2546 appendJustified(currentSuccesses, buffer, true); 2547 appendJustified(currentReject, buffer, true); 2548 appendJustified(currentRetry, buffer, true); 2549 appendJustified(avgRate, buffer, true); 2550 appendJustified(recentRate, buffer, true); 2551 appendJustified(avgDurationMillis, buffer, true); 2552 appendJustified(recentDurationMillis, buffer, false); 2553 2554 out(buffer.toString()); 2555 2556 lastOpsAttempted = currentAttempts; 2557 lastTotalDurationMillis = currentDurationMillis; 2558 lastUpdateTimeMillis = currentTimeMillis; 2559 } 2560 2561 2562 2563 /** 2564 * Appends the provided number to the buffer, right justified in nine columns. 2565 * 2566 * @param value The value to be appended to the buffer. 2567 * @param buffer The buffer to which the value should be appended. 2568 * @param addSpace Indicates whether to append a space after the number. 2569 */ 2570 static void appendJustified(final long value, 2571 @NotNull final StringBuilder buffer, 2572 final boolean addSpace) 2573 { 2574 final String valueStr = String.valueOf(value); 2575 switch (valueStr.length()) 2576 { 2577 case 1: 2578 buffer.append(" "); 2579 break; 2580 case 2: 2581 buffer.append(" "); 2582 break; 2583 case 3: 2584 buffer.append(" "); 2585 break; 2586 case 4: 2587 buffer.append(" "); 2588 break; 2589 case 5: 2590 buffer.append(" "); 2591 break; 2592 case 6: 2593 buffer.append(" "); 2594 break; 2595 case 7: 2596 buffer.append(" "); 2597 break; 2598 case 8: 2599 buffer.append(' '); 2600 break; 2601 } 2602 2603 buffer.append(value); 2604 2605 if (addSpace) 2606 { 2607 buffer.append(' '); 2608 } 2609 } 2610 2611 2612 2613 /** 2614 * Retrieves the total number of operations attempted. This should only be 2615 * called after all tool processing has completed. 2616 * 2617 * @return The total number of operations attempted. 2618 */ 2619 public long getTotalAttemptCount() 2620 { 2621 return opsAttempted.get(); 2622 } 2623 2624 2625 2626 /** 2627 * Retrieves the number of operations attempted on the initial pass through 2628 * the LDIF file (that is, operations for which no retry attempts was made). 2629 * This should only be called after all tool processing has completed. 2630 * 2631 * @return The number of operations attempted on the initial pass through the 2632 * LDIF file. 2633 */ 2634 public long getInitialAttemptCount() 2635 { 2636 return initialAttempted; 2637 } 2638 2639 2640 2641 /** 2642 * Retrieves the number of retry attempts made for operations that did not 2643 * complete successfully on their first attempt. This should only be called 2644 * after all tool processing has completed. 2645 * 2646 * @return The number of retry attempts made for operations that did not 2647 * complete successfully on their first attempt. 2648 */ 2649 public long getRetryAttemptCount() 2650 { 2651 return opsAttempted.get() - initialAttempted; 2652 } 2653 2654 2655 2656 /** 2657 * Retrieves the total number of operations that completed successfully. This 2658 * should only be called after all tool processing has completed. 2659 * 2660 * @return The total number of operations that completed successfully. 2661 */ 2662 public long getTotalSuccessCount() 2663 { 2664 return opsSucceeded.get(); 2665 } 2666 2667 2668 2669 /** 2670 * Retrieves the number of operations that completed successfully on their 2671 * first attempt. This should only be called after all tool processing has 2672 * completed. 2673 * 2674 * @return The total number of operations that completed successfully on 2675 * their first attempt. 2676 */ 2677 public long getInitialSuccessCount() 2678 { 2679 return initialSucceeded; 2680 } 2681 2682 2683 2684 /** 2685 * Retrieves the number of operations that did not complete completed 2686 * successfully on their initial attempt but did succeed on a retry attempt. 2687 * This should only be called after all tool processing has completed. 2688 * 2689 * @return The number of operations that completed successfully on a retry 2690 * attempt. 2691 */ 2692 public long getRetrySuccessCount() 2693 { 2694 return opsSucceeded.get() - initialSucceeded; 2695 } 2696 2697 2698 2699 /** 2700 * Retrieves the number of operations that were rejected and did not complete 2701 * successfully during any of the attempts. This should only be called after 2702 * all tool processing has completed. 2703 * 2704 * @return The number of operations that were rejected. 2705 */ 2706 public long getRejectCount() 2707 { 2708 return opsRejected.get(); 2709 } 2710 2711 2712 2713 /** 2714 * Retrieves the total length of time, in milliseconds, spent processing 2715 * operations. This should only be called after all tool processing has 2716 * completed. Note that when running with multiple threads, this can exceed 2717 * the length of time spent running the tool because multiple operations can 2718 * be processed in parallel. 2719 * 2720 * @return The total length of time, in milliseconds, spent processing 2721 * operations. 2722 */ 2723 public long getTotalOpDurationMillis() 2724 { 2725 return totalOpDurationMillis.get(); 2726 } 2727 2728 2729 2730 /** 2731 * {@inheritDoc} 2732 */ 2733 @Override() 2734 protected boolean registerShutdownHook() 2735 { 2736 return true; 2737 } 2738 2739 2740 2741 /** 2742 * {@inheritDoc} 2743 */ 2744 @Override() 2745 protected void doShutdownHookProcessing(@Nullable final ResultCode resultCode) 2746 { 2747 shouldAbort.set(true); 2748 2749 final FixedRateBarrier b = rateLimiter; 2750 if (b != null) 2751 { 2752 b.shutdownRequested(); 2753 } 2754 } 2755 2756 2757 2758 /** 2759 * {@inheritDoc} 2760 */ 2761 @Override() 2762 public void handleUnsolicitedNotification( 2763 @NotNull final LDAPConnection connection, 2764 @NotNull final ExtendedResult notification) 2765 { 2766 final String message; 2767 if (notification.getDiagnosticMessage() == null) 2768 { 2769 message = INFO_PARALLEL_UPDATE_UNSOLICITED_NOTIFICATION_NO_MESSAGE.get( 2770 getToolName(), String.valueOf(notification.getResultCode()), 2771 notification.getOID()); 2772 } 2773 else 2774 { 2775 message = INFO_PARALLEL_UPDATE_UNSOLICITED_NOTIFICATION_NO_MESSAGE.get( 2776 getToolName(), String.valueOf(notification.getResultCode()), 2777 notification.getOID(), notification.getDiagnosticMessage()); 2778 } 2779 2780 out(); 2781 wrapOut(0, WRAP_COLUMN, message); 2782 out(); 2783 } 2784 2785 2786 2787 /** 2788 * {@inheritDoc} 2789 */ 2790 @Override() 2791 @NotNull() 2792 public LinkedHashMap<String[],String> getExampleUsages() 2793 { 2794 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 2795 2796 examples.put( 2797 new String[] 2798 { 2799 "--hostname", "server.example.com", 2800 "--port", "636", 2801 "--useSSL", 2802 "--bindDN", "uid=admin,dc=example,dc=com", 2803 "--promptForBindPassword", 2804 "--ldifFile", "changes.ldif", 2805 "--rejectFile", "rejects.ldif", 2806 "--defaultAdd", 2807 "--numThreads", "10", 2808 "--ratePerSecond", "5000" 2809 }, 2810 INFO_PARALLEL_UPDATE_EXAMPLE_DESC.get()); 2811 2812 return examples; 2813 } 2814}