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 supportsDebugLogging() 1017 { 1018 return true; 1019 } 1020 1021 1022 1023 /** 1024 * {@inheritDoc} 1025 */ 1026 @Override() 1027 protected boolean supportsAuthentication() 1028 { 1029 return true; 1030 } 1031 1032 1033 1034 /** 1035 * {@inheritDoc} 1036 */ 1037 @Override() 1038 protected boolean defaultToPromptForBindPassword() 1039 { 1040 return true; 1041 } 1042 1043 1044 1045 /** 1046 * {@inheritDoc} 1047 */ 1048 @Override() 1049 protected boolean supportsSASLHelp() 1050 { 1051 return true; 1052 } 1053 1054 1055 1056 /** 1057 * {@inheritDoc} 1058 */ 1059 @Override() 1060 protected boolean includeAlternateLongIdentifiers() 1061 { 1062 return true; 1063 } 1064 1065 1066 1067 /** 1068 * {@inheritDoc} 1069 */ 1070 @Override() 1071 @NotNull() 1072 protected List<Control> getBindControls() 1073 { 1074 final List<Control> bindControls = new ArrayList<>(); 1075 1076 if ((bindControlArg != null) && bindControlArg.isPresent()) 1077 { 1078 bindControls.addAll(bindControlArg.getValues()); 1079 } 1080 1081 if ((suppressOperationalAttributeUpdatesArg != null) && 1082 suppressOperationalAttributeUpdatesArg.isPresent()) 1083 { 1084 final EnumSet<SuppressType> suppressTypes = 1085 EnumSet.noneOf(SuppressType.class); 1086 for (final String s : suppressOperationalAttributeUpdatesArg.getValues()) 1087 { 1088 if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_ACCESS_TIME)) 1089 { 1090 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 1091 } 1092 else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_TIME)) 1093 { 1094 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 1095 } 1096 else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_IP)) 1097 { 1098 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 1099 } 1100 } 1101 1102 bindControls.add(new SuppressOperationalAttributeUpdateRequestControl( 1103 true, suppressTypes)); 1104 } 1105 1106 return Collections.emptyList(); 1107 } 1108 1109 1110 1111 /** 1112 * {@inheritDoc} 1113 */ 1114 @Override() 1115 protected boolean supportsMultipleServers() 1116 { 1117 return true; 1118 } 1119 1120 1121 1122 /** 1123 * {@inheritDoc} 1124 */ 1125 @Override() 1126 protected boolean supportsSSLDebugging() 1127 { 1128 return true; 1129 } 1130 1131 1132 1133 /** 1134 * {@inheritDoc} 1135 */ 1136 @Override() 1137 @NotNull() 1138 public LDAPConnectionOptions getConnectionOptions() 1139 { 1140 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 1141 options.setUseSynchronousMode(true); 1142 options.setFollowReferrals( 1143 ((followReferralsArg != null) && followReferralsArg.isPresent())); 1144 options.setUnsolicitedNotificationHandler(this); 1145 options.setResponseTimeoutMillis(0L); 1146 return options; 1147 } 1148 1149 1150 1151 /** 1152 * {@inheritDoc} 1153 */ 1154 @Override() 1155 protected boolean logToolInvocationByDefault() 1156 { 1157 return true; 1158 } 1159 1160 1161 1162 /** 1163 * {@inheritDoc} 1164 */ 1165 @Override() 1166 @Nullable() 1167 public String getToolCompletionMessage() 1168 { 1169 return completionMessage.get(); 1170 } 1171 1172 1173 1174 /** 1175 * {@inheritDoc} 1176 */ 1177 @Override() 1178 @NotNull() 1179 public ResultCode doToolProcessing() 1180 { 1181 // Create the sets of controls to include in each type of request. 1182 final Control[] addControls; 1183 final Control[] deleteControls; 1184 final Control[] modifyControls; 1185 final Control[] modifyDNControls; 1186 try 1187 { 1188 final List<Control> addControlList = new ArrayList<>(); 1189 final List<Control> deleteControlList = new ArrayList<>(); 1190 final List<Control> modifyControlList = new ArrayList<>(); 1191 final List<Control> modifyDNControlList = new ArrayList<>(); 1192 1193 getOperationControls(addControlList, deleteControlList, 1194 modifyControlList, modifyDNControlList); 1195 1196 addControls = StaticUtils.toArray(addControlList, Control.class); 1197 deleteControls = StaticUtils.toArray(deleteControlList, Control.class); 1198 modifyControls = StaticUtils.toArray(modifyControlList, Control.class); 1199 modifyDNControls = 1200 StaticUtils.toArray(modifyDNControlList, Control.class); 1201 } 1202 catch (final LDAPException e) 1203 { 1204 Debug.debugException(e); 1205 logCompletionMessage(true, e.getMessage()); 1206 return e.getResultCode(); 1207 } 1208 1209 1210 // Get the connection pool to use to communicate with the directory 1211 // server(s). 1212 final LDAPConnectionPool connectionPool; 1213 final int numThreads = numThreadsArg.getValue(); 1214 try 1215 { 1216 connectionPool = getConnectionPool(numThreads, numThreads, 1, null, null, 1217 true, null); 1218 connectionPool.setConnectionPoolName("parallel-update"); 1219 connectionPool.setRetryFailedOperationsDueToInvalidConnections( 1220 (! neverRetryArg.isPresent())); 1221 } 1222 catch (final LDAPException e) 1223 { 1224 Debug.debugException(e); 1225 logCompletionMessage(true, 1226 ERR_PARALLEL_UPDATE_CANNOT_CREATE_POOL.get( 1227 StaticUtils.getExceptionMessage(e))); 1228 return e.getResultCode(); 1229 } 1230 1231 1232 // Create the LDIF reader that will read the changes to process. 1233 final LDIFReader ldifReader; 1234 final String encryptionPassphrase; 1235 try 1236 { 1237 final ObjectPair<LDIFReader,String> ldifReaderPair = createLDIFReader(); 1238 ldifReader = ldifReaderPair.getFirst(); 1239 encryptionPassphrase = ldifReaderPair.getSecond(); 1240 } 1241 catch (final LDAPException e) 1242 { 1243 Debug.debugException(e); 1244 logCompletionMessage(true, e.getMessage()); 1245 connectionPool.close(); 1246 return e.getResultCode(); 1247 } 1248 1249 final AtomicReference<ResultCode> resultCodeRef = 1250 new AtomicReference<>(ResultCode.SUCCESS); 1251 try 1252 { 1253 // If the LDIF file is encrypted, then get the ID of the encryption 1254 // settings definition (if any) used to generate the encryption key. 1255 final String encryptionSettingsDefinitionID; 1256 if (encryptionPassphrase == null) 1257 { 1258 encryptionSettingsDefinitionID = null; 1259 } 1260 else 1261 { 1262 encryptionSettingsDefinitionID = getEncryptionSettingsDefinitionID(); 1263 } 1264 1265 1266 // Create the LDIF writer that will be used to write rejects. 1267 try 1268 { 1269 rejectWriter = createRejectWriter(encryptionPassphrase, 1270 encryptionSettingsDefinitionID); 1271 } 1272 catch (final LDAPException e) 1273 { 1274 Debug.debugException(e); 1275 logCompletionMessage(true, e.getMessage()); 1276 return ResultCode.LOCAL_ERROR; 1277 } 1278 1279 1280 // If appropriate, create the log writer that will be used to provide a 1281 // log of the changes that are attempted. 1282 if (logFileArg.isPresent()) 1283 { 1284 try 1285 { 1286 logWriter = new PrintWriter(logFileArg.getValue()); 1287 } 1288 catch (final Exception e) 1289 { 1290 Debug.debugException(e); 1291 logCompletionMessage(true, 1292 ERR_PARALLEL_UPDATE_ERROR_CREATING_LOG_WRITER.get( 1293 logFileArg.getValue().getAbsolutePath(), 1294 StaticUtils.getExceptionMessage(e))); 1295 return ResultCode.LOCAL_ERROR; 1296 } 1297 } 1298 1299 1300 // Create The queue that will hold the operations to process. 1301 final ParallelUpdateOperationQueue operationQueue = 1302 new ParallelUpdateOperationQueue(this, numThreads, 1303 (2 * numThreads)); 1304 1305 1306 // Create the rate limiter, if appropriate. 1307 if (ratePerSecondArg.isPresent()) 1308 { 1309 rateLimiter = new FixedRateBarrier(1000L, ratePerSecondArg.getValue()); 1310 } 1311 else 1312 { 1313 rateLimiter = null; 1314 } 1315 1316 1317 // Create and start all of the threads that will be used to process 1318 // requests. 1319 final List<ParallelUpdateOperationThread> operationThreadList = 1320 new ArrayList<>(numThreads); 1321 for (int i=1; i <= numThreads; i++) 1322 { 1323 final ParallelUpdateOperationThread operationThread = 1324 new ParallelUpdateOperationThread(this, connectionPool, 1325 operationQueue, i, rateLimiter, addControls, deleteControls, 1326 modifyControls, modifyDNControls, 1327 allowUndeleteArg.isPresent()); 1328 operationThreadList.add(operationThread); 1329 operationThread.start(); 1330 } 1331 1332 1333 // Create a progress monitor that will be used to report periodic status 1334 // updates about the processing that has been performed. 1335 final ParallelUpdateProgressMonitor progressMonitor = 1336 new ParallelUpdateProgressMonitor(this); 1337 try 1338 { 1339 processingStartTimeMillis = System.currentTimeMillis(); 1340 progressMonitor.start(); 1341 1342 while (! shouldAbort.get()) 1343 { 1344 final LDIFChangeRecord changeRecord; 1345 try 1346 { 1347 changeRecord = ldifReader.readChangeRecord( 1348 defaultAddArg.isPresent()); 1349 } 1350 catch (final LDIFException e) 1351 { 1352 Debug.debugException(e); 1353 if (e.mayContinueReading()) 1354 { 1355 final String message = 1356 ERR_PARALLEL_UPDATE_RECOVERABLE_LDIF_EXCEPTION.get( 1357 ldifFileArg.getValue().getAbsolutePath(), 1358 e.getMessage()); 1359 logMessage(message); 1360 reject(null, 1361 new LDAPException(ResultCode.DECODING_ERROR, message, e)); 1362 opsAttempted.incrementAndGet(); 1363 continue; 1364 } 1365 else 1366 { 1367 shouldAbort.set(true); 1368 final String message = 1369 ERR_PARALLEL_UPDATE_UNRECOVERABLE_LDIF_EXCEPTION.get( 1370 ldifFileArg.getValue().getAbsolutePath(), 1371 StaticUtils.getExceptionMessage(e)); 1372 reject(null, 1373 new LDAPException(ResultCode.DECODING_ERROR, message, e)); 1374 logCompletionMessage(true, message); 1375 return ResultCode.DECODING_ERROR; 1376 } 1377 } 1378 catch (final Exception e) 1379 { 1380 Debug.debugException(e); 1381 shouldAbort.set(true); 1382 final String message = 1383 ERR_PARALLEL_UPDATE_ERROR_READING_LDIF_FILE.get( 1384 ldifFileArg.getValue().getAbsolutePath(), 1385 StaticUtils.getExceptionMessage(e)); 1386 reject(null, 1387 new LDAPException(ResultCode.LOCAL_ERROR, message, e)); 1388 logCompletionMessage(true, message); 1389 return ResultCode.LOCAL_ERROR; 1390 } 1391 1392 if (changeRecord == null) 1393 { 1394 // We've reached the end of the LDIF file. 1395 break; 1396 } 1397 else 1398 { 1399 try 1400 { 1401 operationQueue.addChangeRecord(changeRecord); 1402 } 1403 catch (final Exception e) 1404 { 1405 Debug.debugException(e); 1406 1407 // This indicates that the attempt to enqueue the change record 1408 // was interrupted. This shouldn't happen, but if it does, then 1409 // mark it to be retried. 1410 final LDAPException le = new LDAPException(ResultCode.LOCAL_ERROR, 1411 ERR_PARALLEL_UPDATE_ENQUEUE_FAILED.get( 1412 StaticUtils.getExceptionMessage(e)), 1413 e); 1414 retry(changeRecord, le); 1415 } 1416 } 1417 } 1418 1419 1420 // If a failure was encountered, then abort. 1421 if (shouldAbort.get()) 1422 { 1423 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1424 ResultCode.LOCAL_ERROR); 1425 return resultCodeRef.get(); 1426 } 1427 1428 1429 // Indicate that we've reached the end of the LDIF file. 1430 out(); 1431 wrapOut(0, WRAP_COLUMN, 1432 INFO_PARALLEL_UPDATE_END_OF_LDIF.get()); 1433 out(); 1434 1435 1436 // Wait for the operation queue to become idle so that we know there 1437 // are no more outstanding operations to be processed. 1438 operationQueue.waitUntilIdle(); 1439 initialAttempted = opsAttempted.get(); 1440 initialSucceeded = opsSucceeded.get(); 1441 1442 1443 // If there are any operations to retry, then do so now. 1444 Map<DN,List<ObjectPair<LDIFChangeRecord,LDAPException>>> retryQueueCopy; 1445 synchronized (retryQueue) 1446 { 1447 retryQueueCopy = new TreeMap<>(retryQueue); 1448 retryQueue.clear(); 1449 } 1450 1451 int lastRetryQueueSize = 0; 1452 while ((! retryQueueCopy.isEmpty()) && 1453 (retryQueueCopy.size() != lastRetryQueueSize)) 1454 { 1455 out(); 1456 wrapOut(0, WRAP_COLUMN, 1457 INFO_PARALLEL_UPDATE_BEGINNING_RETRY.get(retryQueueCopy.size())); 1458 out(); 1459 1460 1461 for (final 1462 Map.Entry<DN,List<ObjectPair<LDIFChangeRecord,LDAPException>>> 1463 e : retryQueueCopy.entrySet()) 1464 { 1465 for (final ObjectPair<LDIFChangeRecord,LDAPException> p : 1466 e.getValue()) 1467 { 1468 if (shouldAbort.get()) 1469 { 1470 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1471 ResultCode.LOCAL_ERROR); 1472 return resultCodeRef.get(); 1473 } 1474 1475 final LDIFChangeRecord changeRecord = p.getFirst(); 1476 try 1477 { 1478 operationQueue.addChangeRecord(changeRecord); 1479 retryQueueSize.decrementAndGet(); 1480 } 1481 catch (final Exception ex) 1482 { 1483 Debug.debugException(ex); 1484 1485 // This indicates that the attempt to enqueue the change record 1486 // was interrupted. This shouldn't happen, but if it does, then 1487 // mark it to be retried. 1488 final LDAPException le = new LDAPException( 1489 ResultCode.LOCAL_ERROR, 1490 ERR_PARALLEL_UPDATE_ENQUEUE_FAILED.get( 1491 StaticUtils.getExceptionMessage(ex)), 1492 ex); 1493 retry(changeRecord, le); 1494 } 1495 } 1496 } 1497 1498 1499 operationQueue.waitUntilIdle(); 1500 lastRetryQueueSize = retryQueueCopy.size(); 1501 1502 synchronized (retryQueue) 1503 { 1504 retryQueueCopy = new TreeMap<>(retryQueue); 1505 retryQueue.clear(); 1506 } 1507 } 1508 1509 1510 // If we've gotten here, then it means that either the retry queue 1511 // (NOTE: we actually need to use retryQueueCopy) is empty or none of 1512 // the retry attempts succeeded on the last pass. If it's the latter, 1513 // then reject any of the remaining operations. 1514 synchronized (retryQueue) 1515 { 1516 final int remainingToRetry = retryQueueCopy.size(); 1517 if (remainingToRetry > 0) 1518 { 1519 if (remainingToRetry == 1) 1520 { 1521 wrapErr(0, WRAP_COLUMN, 1522 ERR_PARALLEL_UPDATE_NO_PROGRESS_ONE.get()); 1523 } 1524 else 1525 { 1526 wrapErr(0, WRAP_COLUMN, 1527 ERR_PARALLEL_UPDATE_NO_PROGRESS_MULTIPLE.get( 1528 remainingToRetry)); 1529 } 1530 } 1531 1532 for (final 1533 Map.Entry<DN,List<ObjectPair<LDIFChangeRecord,LDAPException>>> 1534 e : retryQueueCopy.entrySet()) 1535 { 1536 for (final ObjectPair<LDIFChangeRecord,LDAPException> p : 1537 e.getValue()) 1538 { 1539 reject(p.getFirst(), p.getSecond()); 1540 retryQueueSize.decrementAndGet(); 1541 } 1542 } 1543 } 1544 } 1545 finally 1546 { 1547 operationQueue.setEndOfLDIF(); 1548 operationQueue.waitUntilIdle(); 1549 1550 for (final ParallelUpdateOperationThread operationThread : 1551 operationThreadList) 1552 { 1553 try 1554 { 1555 operationThread.join(); 1556 } 1557 catch (final Exception e) 1558 { 1559 Debug.debugException(e); 1560 logCompletionMessage(true, 1561 ERR_PARALLEL_UPDATE_CANNOT_JOIN_THREAD.get( 1562 operationThread.getName(), 1563 StaticUtils.getExceptionMessage(e))); 1564 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1565 ResultCode.LOCAL_ERROR); 1566 } 1567 } 1568 1569 try 1570 { 1571 progressMonitor.stopRunning(); 1572 progressMonitor.join(); 1573 } 1574 catch (final Exception e) 1575 { 1576 Debug.debugException(e); 1577 logCompletionMessage(true, 1578 ERR_PARALLEL_UPDATE_CANNOT_JOIN_PROGRESS_MONITOR.get( 1579 StaticUtils.getExceptionMessage(e))); 1580 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1581 ResultCode.LOCAL_ERROR); 1582 } 1583 } 1584 } 1585 finally 1586 { 1587 connectionPool.close(); 1588 1589 if (rejectWriter != null) 1590 { 1591 try 1592 { 1593 rejectWriter.close(); 1594 } 1595 catch (final Exception e) 1596 { 1597 Debug.debugException(e); 1598 logCompletionMessage(true, 1599 ERR_PARALLEL_UPDATE_ERROR_CLOSING_REJECT_WRITER.get( 1600 rejectFileArg.getValue().getAbsolutePath(), 1601 StaticUtils.getExceptionMessage(e))); 1602 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1603 ResultCode.LOCAL_ERROR); 1604 } 1605 } 1606 1607 if (logWriter != null) 1608 { 1609 try 1610 { 1611 logWriter.close(); 1612 } 1613 catch (final Exception e) 1614 { 1615 Debug.debugException(e); 1616 logCompletionMessage(true, 1617 ERR_PARALLEL_UPDATE_ERROR_CLOSING_LOG_WRITER.get( 1618 logFileArg.getValue().getAbsolutePath(), 1619 StaticUtils.getExceptionMessage(e))); 1620 resultCodeRef.compareAndSet(ResultCode.SUCCESS, 1621 ResultCode.LOCAL_ERROR); 1622 } 1623 } 1624 1625 try 1626 { 1627 ldifReader.close(); 1628 } 1629 catch (final Exception e) 1630 { 1631 Debug.debugException(e); 1632 logCompletionMessage(true, 1633 WARN_PARALLEL_UPDATE_ERROR_CLOSING_READER.get( 1634 ldifFileArg.getValue().getAbsolutePath(), 1635 StaticUtils.getExceptionMessage(e))); 1636 resultCodeRef.compareAndSet(ResultCode.SUCCESS, ResultCode.LOCAL_ERROR); 1637 } 1638 } 1639 1640 1641 // If we've gotten here, then processing has completed. Print some summary 1642 // messages and return an appropriate result code. 1643 final long processingDurationMillis = 1644 System.currentTimeMillis() - processingStartTimeMillis; 1645 final long numAttempts = opsAttempted.get(); 1646 final long numSuccesses = opsSucceeded.get(); 1647 final long numRejects = opsRejected.get(); 1648 1649 final long retryAttempts = numAttempts - initialAttempted; 1650 final long retrySuccesses = numSuccesses - initialSucceeded; 1651 1652 out(INFO_PARALLEL_UPDATE_DONE.get(getToolName())); 1653 out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_ATTEMPTED.get(numAttempts)); 1654 1655 if (retryAttempts > 0L) 1656 { 1657 out(INFO_PARALLEL_UPDATE_SUMMARY_INITIAL_ATTEMPTS.get(initialAttempted)); 1658 out(INFO_PARALLEL_UPDATE_SUMMARY_RETRY_ATTEMPTS.get(retryAttempts)); 1659 } 1660 1661 out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_SUCCEEDED.get(numSuccesses)); 1662 1663 if (retryAttempts > 0) 1664 { 1665 out(INFO_PARALLEL_UPDATED_OPS_SUCCEEDED_INITIAL.get(initialSucceeded)); 1666 out(INFO_PARALLEL_UPDATED_OPS_SUCCEEDED_RETRY.get(retrySuccesses)); 1667 } 1668 1669 out(INFO_PARALLEL_UPDATE_SUMMARY_OPS_REJECTED.get(numRejects)); 1670 out(INFO_PARALLEL_UPDATE_SUMMARY_DURATION.get( 1671 StaticUtils.millisToHumanReadableDuration(processingDurationMillis))); 1672 1673 if ((numAttempts > 0L) && (processingDurationMillis > 0L)) 1674 { 1675 final double attemptsPerSecond = 1676 numAttempts * 1_000.0d / processingDurationMillis; 1677 final DecimalFormat decimalFormat = new DecimalFormat("0.000"); 1678 out(INFO_PARALLEL_UPDATE_SUMMARY_RATE.get( 1679 decimalFormat.format(attemptsPerSecond))); 1680 } 1681 1682 1683 if (numRejects == 0L) 1684 { 1685 completionMessage.compareAndSet(null, 1686 INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_ALL_SUCCEEDED.get( 1687 getToolName())); 1688 } 1689 else if (numRejects == 1L) 1690 { 1691 completionMessage.compareAndSet(null, 1692 INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_ONE_REJECTED.get( 1693 getToolName())); 1694 } 1695 else 1696 { 1697 completionMessage.compareAndSet(null, 1698 INFO_PARALLEL_UPDATE_COMPLETION_MESSAGE_MULTIPLE_REJECTED.get( 1699 getToolName(), numRejects)); 1700 } 1701 1702 1703 ResultCode finalResultCode = resultCodeRef.get(); 1704 if ((finalResultCode == ResultCode.SUCCESS) && 1705 useFirstRejectResultCodeAsExitCodeArg.isPresent() && 1706 (firstRejectResultCode.get() != null)) 1707 { 1708 finalResultCode = firstRejectResultCode.get(); 1709 } 1710 1711 return finalResultCode; 1712 } 1713 1714 1715 1716 /** 1717 * Updates the provided lists with the appropriate controls to include in 1718 * each type of request. 1719 * 1720 * @param addControls The list that should be updated with controls to 1721 * include in add requests. It must not be 1722 * {@code null} and must be updatable. 1723 * @param deleteControls The list that should be updated with controls to 1724 * include in delete requests. It must not be 1725 * {@code null} and must be updatable. 1726 * @param modifyControls The list that should be updated with controls to 1727 * include in modify requests. It must not be 1728 * {@code null} and must be updatable. 1729 * @param modifyDNControls The list that should be updated with controls to 1730 * include in modify DN requests. It must not be 1731 * {@code null} and must be updatable. 1732 * 1733 * @throws LDAPException If a problem is encountered while creating any of 1734 * the controls. 1735 */ 1736 private void getOperationControls( 1737 @NotNull final List<Control> addControls, 1738 @NotNull final List<Control> deleteControls, 1739 @NotNull final List<Control> modifyControls, 1740 @NotNull final List<Control> modifyDNControls) 1741 throws LDAPException 1742 { 1743 if (addControlArg.isPresent()) 1744 { 1745 addControls.addAll(addControlArg.getValues()); 1746 } 1747 1748 if (deleteControlArg.isPresent()) 1749 { 1750 deleteControls.addAll(deleteControlArg.getValues()); 1751 } 1752 1753 if (modifyControlArg.isPresent()) 1754 { 1755 modifyControls.addAll(modifyControlArg.getValues()); 1756 } 1757 1758 if (modifyDNControlArg.isPresent()) 1759 { 1760 modifyDNControls.addAll(modifyDNControlArg.getValues()); 1761 } 1762 1763 if (proxyAsArg.isPresent()) 1764 { 1765 final ProxiedAuthorizationV2RequestControl c = 1766 new ProxiedAuthorizationV2RequestControl(proxyAsArg.getValue()); 1767 addControls.add(c); 1768 deleteControls.add(c); 1769 modifyControls.add(c); 1770 modifyDNControls.add(c); 1771 } 1772 else if (proxyV1AsArg.isPresent()) 1773 { 1774 final ProxiedAuthorizationV1RequestControl c = 1775 new ProxiedAuthorizationV1RequestControl(proxyV1AsArg.getValue()); 1776 addControls.add(c); 1777 deleteControls.add(c); 1778 modifyControls.add(c); 1779 modifyDNControls.add(c); 1780 } 1781 1782 if (usePermissiveModifyArg.isPresent()) 1783 { 1784 modifyControls.add(new PermissiveModifyRequestControl(true)); 1785 } 1786 1787 if (ignoreNoUserModificationArg.isPresent()) 1788 { 1789 final IgnoreNoUserModificationRequestControl c = 1790 new IgnoreNoUserModificationRequestControl(); 1791 addControls.add(c); 1792 modifyControls.add(c); 1793 } 1794 1795 if (useManageDsaITArg.isPresent()) 1796 { 1797 final ManageDsaITRequestControl c = new ManageDsaITRequestControl(true); 1798 addControls.add(c); 1799 deleteControls.add(c); 1800 modifyControls.add(c); 1801 modifyDNControls.add(c); 1802 } 1803 1804 if (nameWithEntryUUIDArg.isPresent()) 1805 { 1806 addControls.add(new NameWithEntryUUIDRequestControl(true)); 1807 } 1808 1809 if (softDeleteArg.isPresent()) 1810 { 1811 deleteControls.add(new SoftDeleteRequestControl(true, true)); 1812 } 1813 else if (hardDeleteArg.isPresent()) 1814 { 1815 deleteControls.add(new HardDeleteRequestControl(true)); 1816 } 1817 1818 if (operationPurposeArg.isPresent()) 1819 { 1820 final OperationPurposeRequestControl c = 1821 new OperationPurposeRequestControl(false, "parallel-update", 1822 Version.NUMERIC_VERSION_STRING, 1823 ParallelUpdate.class.getName() + ".getOperationControls", 1824 operationPurposeArg.getValue()); 1825 addControls.add(c); 1826 deleteControls.add(c); 1827 modifyControls.add(c); 1828 modifyDNControls.add(c); 1829 } 1830 1831 if (replicationRepairArg.isPresent()) 1832 { 1833 final ReplicationRepairRequestControl c = 1834 new ReplicationRepairRequestControl(); 1835 addControls.add(c); 1836 deleteControls.add(c); 1837 modifyControls.add(c); 1838 modifyDNControls.add(c); 1839 } 1840 1841 if (suppressReferentialIntegrityUpdatesArg.isPresent()) 1842 { 1843 final SuppressReferentialIntegrityUpdatesRequestControl c = 1844 new SuppressReferentialIntegrityUpdatesRequestControl(true); 1845 deleteControls.add(c); 1846 modifyDNControls.add(c); 1847 } 1848 1849 1850 if (useAssuredReplicationArg.isPresent()) 1851 { 1852 final AssuredReplicationLocalLevel localLevel; 1853 if (assuredReplicationLocalLevelArg.isPresent()) 1854 { 1855 final String localLevelStr = StaticUtils.toLowerCase( 1856 assuredReplicationLocalLevelArg.getValue()); 1857 switch (localLevelStr) 1858 { 1859 case ASSURED_REPLICATION_LOCAL_LEVEL_NONE: 1860 localLevel = AssuredReplicationLocalLevel.NONE; 1861 break; 1862 case ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER: 1863 localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER; 1864 break; 1865 case ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS: 1866 localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS; 1867 break; 1868 default: 1869 // This should never happen. 1870 localLevel = null; 1871 break; 1872 } 1873 } 1874 else 1875 { 1876 localLevel = null; 1877 } 1878 1879 final AssuredReplicationRemoteLevel remoteLevel; 1880 if (assuredReplicationRemoteLevelArg.isPresent()) 1881 { 1882 final String remoteLevelStr = StaticUtils.toLowerCase( 1883 assuredReplicationRemoteLevelArg.getValue()); 1884 switch (remoteLevelStr) 1885 { 1886 case ASSURED_REPLICATION_REMOTE_LEVEL_NONE: 1887 remoteLevel = AssuredReplicationRemoteLevel.NONE; 1888 break; 1889 case ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION: 1890 remoteLevel = AssuredReplicationRemoteLevel. 1891 RECEIVED_ANY_REMOTE_LOCATION; 1892 break; 1893 case ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS: 1894 remoteLevel = AssuredReplicationRemoteLevel. 1895 RECEIVED_ALL_REMOTE_LOCATIONS; 1896 break; 1897 case ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS: 1898 remoteLevel = AssuredReplicationRemoteLevel. 1899 PROCESSED_ALL_REMOTE_SERVERS; 1900 break; 1901 default: 1902 // This should never happen. 1903 remoteLevel = null; 1904 break; 1905 } 1906 } 1907 else 1908 { 1909 remoteLevel = null; 1910 } 1911 1912 final Long timeoutMillis; 1913 if (assuredReplicationTimeoutArg.isPresent()) 1914 { 1915 timeoutMillis = assuredReplicationTimeoutArg.getValue( 1916 TimeUnit.MILLISECONDS); 1917 } 1918 else 1919 { 1920 timeoutMillis = null; 1921 } 1922 1923 final AssuredReplicationRequestControl c = 1924 new AssuredReplicationRequestControl(true, localLevel, localLevel, 1925 remoteLevel, remoteLevel, timeoutMillis, false); 1926 addControls.add(c); 1927 deleteControls.add(c); 1928 modifyControls.add(c); 1929 modifyDNControls.add(c); 1930 } 1931 1932 1933 if (passwordUpdateBehaviorArg.isPresent()) 1934 { 1935 final PasswordUpdateBehaviorRequestControlProperties properties = 1936 new PasswordUpdateBehaviorRequestControlProperties(); 1937 for (final String argValue : passwordUpdateBehaviorArg.getValues()) 1938 { 1939 final int equalPos = argValue.indexOf('='); 1940 if (equalPos < 0) 1941 { 1942 throw new LDAPException(ResultCode.PARAM_ERROR, 1943 ERR_PARALLEL_UPDATE_MALFORMED_PW_UPDATE_VALUE.get( 1944 argValue, passwordUpdateBehaviorArg.getIdentifierString())); 1945 } 1946 1947 final String propertyName = argValue.substring(0, equalPos).trim(); 1948 final String lowerName = StaticUtils.toLowerCase(propertyName); 1949 switch (lowerName) 1950 { 1951 case PW_UPDATE_BEHAVIOR_NAME_IS_SELF_CHANGE: 1952 properties.setIsSelfChange( 1953 getBooleanPWUpdateBehaviorValue(argValue)); 1954 break; 1955 case PW_UPDATE_BEHAVIOR_NAME_ALLOW_PRE_ENCODED_PW: 1956 properties.setAllowPreEncodedPassword( 1957 getBooleanPWUpdateBehaviorValue(argValue)); 1958 break; 1959 case PW_UPDATE_BEHAVIOR_NAME_SKIP_PW_VALIDATION: 1960 properties.setSkipPasswordValidation( 1961 getBooleanPWUpdateBehaviorValue(argValue)); 1962 break; 1963 case PW_UPDATE_BEHAVIOR_NAME_IGNORE_PW_HISTORY: 1964 properties.setIgnorePasswordHistory( 1965 getBooleanPWUpdateBehaviorValue(argValue)); 1966 break; 1967 case PW_UPDATE_BEHAVIOR_NAME_IGNORE_MIN_PW_AGE: 1968 properties.setIgnoreMinimumPasswordAge( 1969 getBooleanPWUpdateBehaviorValue(argValue)); 1970 break; 1971 case PW_UPDATE_BEHAVIOR_NAME_MUST_CHANGE_PW: 1972 properties.setMustChangePassword( 1973 getBooleanPWUpdateBehaviorValue(argValue)); 1974 break; 1975 case PW_UPDATE_BEHAVIOR_NAME_PW_STORAGE_SCHEME: 1976 final String propertyValue = argValue.substring(equalPos+1).trim(); 1977 properties.setPasswordStorageScheme(propertyValue); 1978 break; 1979 default: 1980 throw new LDAPException(ResultCode.PARAM_ERROR, 1981 ERR_PARALLEL_UPDATE_UNKNOWN_PW_UPDATE_PROP.get(argValue, 1982 passwordUpdateBehaviorArg.getIdentifierString(), 1983 PW_UPDATE_BEHAVIOR_NAME_IS_SELF_CHANGE, 1984 PW_UPDATE_BEHAVIOR_NAME_ALLOW_PRE_ENCODED_PW, 1985 PW_UPDATE_BEHAVIOR_NAME_SKIP_PW_VALIDATION, 1986 PW_UPDATE_BEHAVIOR_NAME_IGNORE_PW_HISTORY, 1987 PW_UPDATE_BEHAVIOR_NAME_IGNORE_MIN_PW_AGE, 1988 PW_UPDATE_BEHAVIOR_NAME_PW_STORAGE_SCHEME, 1989 PW_UPDATE_BEHAVIOR_NAME_MUST_CHANGE_PW)); 1990 } 1991 } 1992 1993 final PasswordUpdateBehaviorRequestControl c = 1994 new PasswordUpdateBehaviorRequestControl(properties, true); 1995 addControls.add(c); 1996 modifyControls.add(c); 1997 } 1998 1999 2000 if (suppressOperationalAttributeUpdatesArg.isPresent()) 2001 { 2002 final EnumSet<SuppressType> suppressTypes = 2003 EnumSet.noneOf(SuppressType.class); 2004 for (final String s : suppressOperationalAttributeUpdatesArg.getValues()) 2005 { 2006 if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_ACCESS_TIME)) 2007 { 2008 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 2009 } 2010 else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_TIME)) 2011 { 2012 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 2013 } 2014 else if (s.equalsIgnoreCase(SUPPRESS_OP_ATTR_LAST_LOGIN_IP)) 2015 { 2016 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 2017 } 2018 } 2019 2020 final SuppressOperationalAttributeUpdateRequestControl c = 2021 new SuppressOperationalAttributeUpdateRequestControl(true, 2022 suppressTypes); 2023 addControls.add(c); 2024 deleteControls.add(c); 2025 modifyControls.add(c); 2026 modifyDNControls.add(c); 2027 } 2028 } 2029 2030 2031 2032 /** 2033 * Retrieves the value from the provided name-value pair and parses it as a 2034 * boolean. 2035 * 2036 * @param nameValuePair The name-value pair to be parsed. It must not be 2037 * {@code null} and it must contain an equal sign. 2038 * 2039 * @return The boolean value parsed from the provided name-value pair. 2040 * 2041 * @throws LDAPException If the value could not be parsed as a boolean. 2042 */ 2043 private boolean getBooleanPWUpdateBehaviorValue( 2044 @NotNull final String nameValuePair) 2045 throws LDAPException 2046 { 2047 final int equalPos = nameValuePair.indexOf('='); 2048 final String propertyValue = nameValuePair.substring(equalPos+1).trim(); 2049 final String lowerValue = StaticUtils.toLowerCase(propertyValue); 2050 switch (lowerValue) 2051 { 2052 case "true": 2053 case "t": 2054 case "yes": 2055 case "on": 2056 case "1": 2057 return true; 2058 case "false": 2059 case "f": 2060 case "no": 2061 case "off": 2062 case "0": 2063 return false; 2064 default: 2065 final String propertyName = nameValuePair.substring(0, equalPos).trim(); 2066 throw new LDAPException(ResultCode.PARAM_ERROR, 2067 ERR_PARALLEL_UPDATE_PW_UPDATE_VALUE_NOT_BOOLEAN.get(nameValuePair, 2068 passwordUpdateBehaviorArg.getIdentifierString(), 2069 propertyName)); 2070 } 2071 } 2072 2073 2074 2075 /** 2076 * Creates the LDIF reader to use to read the changes to process. 2077 * 2078 * @return An object pair in which the first element is the LDIF reader and 2079 * the second element is the passphrase used to encrypt the contents 2080 * of the LDIF file (or {@code null} if the LDIF file is not 2081 * encrypted). 2082 * 2083 * @throws LDAPException If a problem occurs while trying to create the LDIF 2084 * reader. 2085 */ 2086 @NotNull() 2087 private ObjectPair<LDIFReader,String> createLDIFReader() 2088 throws LDAPException 2089 { 2090 final File ldifFile = ldifFileArg.getValue(); 2091 2092 try 2093 { 2094 final String encryptionPassphraseFromFile; 2095 if (encryptionPassphraseFileArg.isPresent()) 2096 { 2097 final char[] pwChars = getPasswordFileReader().readPassword( 2098 encryptionPassphraseFileArg.getValue()); 2099 encryptionPassphraseFromFile = new String(pwChars); 2100 Arrays.fill(pwChars, '\u0000'); 2101 } 2102 else 2103 { 2104 encryptionPassphraseFromFile = null; 2105 } 2106 2107 final ObjectPair<InputStream,String> inputStreamPair = 2108 ToolUtils.getInputStreamForLDIFFiles( 2109 Collections.singletonList(ldifFile), 2110 encryptionPassphraseFromFile, getOut(), getErr()); 2111 2112 final LDIFReader ldifReader = new LDIFReader(inputStreamPair.getFirst()); 2113 final String encryptionPassphrase = inputStreamPair.getSecond(); 2114 return new ObjectPair<>(ldifReader, encryptionPassphrase); 2115 } 2116 catch (final Exception e) 2117 { 2118 Debug.debugException(e); 2119 throw new LDAPException(ResultCode.LOCAL_ERROR, 2120 ERR_PARALLEL_UPDATE_CANNOT_CREATE_LDIF_READER.get( 2121 ldifFile.getAbsolutePath(), 2122 StaticUtils.getExceptionMessage(e)), 2123 e); 2124 } 2125 } 2126 2127 2128 2129 /** 2130 * Attempts to retrieve the ID of the encryption settings definition used to 2131 * encrypt the LDIF file. This method should only be used if the LDIF file is 2132 * known to be encrypted. 2133 * 2134 * @return The ID of the encryption settings definition used to encrypt the 2135 * LDIF file, or {code null} if it was not encrypted with a 2136 * passphrase obtained from an encryption settings definition (or if 2137 * an error occurred while attempting to retrieve the ID). 2138 */ 2139 @Nullable() 2140 private String getEncryptionSettingsDefinitionID() 2141 { 2142 try (FileInputStream inputStream = 2143 new FileInputStream(ldifFileArg.getValue())) 2144 { 2145 final PassphraseEncryptedStreamHeader encryptionHeader = 2146 PassphraseEncryptedStreamHeader.readFrom(inputStream, null); 2147 return encryptionHeader.getKeyIdentifier(); 2148 } 2149 catch (final Exception e) 2150 { 2151 Debug.debugException(e); 2152 return null; 2153 } 2154 } 2155 2156 2157 2158 /** 2159 * Creates the LDIF writer that will be used to write information about 2160 * rejected entries. If the LDIF input file was encrypted, then the reject 2161 * file will be encrypted with the same settings, and it will also be 2162 * compressed (regardless of whether the input file was compressed). 2163 * 2164 * @param encryptionPassphrase The passphrase used to encrypt the 2165 * input file. This may be 2166 * {@code null} if the input file was 2167 * not encrypted. 2168 * @param encryptionSettingsDefinitionID The ID for the encryption settings 2169 * definition with which the 2170 * passphrase is associated. It may 2171 * be {@code null} if the input file 2172 * was not encrypted, or if the 2173 * encryption passphrase was not 2174 * obtained from an encryption 2175 * settings definition. 2176 * 2177 * @return The LDIF writer to which rejects should be written. 2178 * 2179 * @throws LDAPException If a problem occurs while creating the reject 2180 * writer. 2181 */ 2182 @NotNull() 2183 private LDIFWriter createRejectWriter( 2184 @Nullable final String encryptionPassphrase, 2185 @Nullable final String encryptionSettingsDefinitionID) 2186 throws LDAPException 2187 { 2188 final File rejectFile = rejectFileArg.getValue(); 2189 2190 OutputStream outputStream = null; 2191 try 2192 { 2193 outputStream = new FileOutputStream(rejectFile); 2194 2195 if (encryptionPassphrase != null) 2196 { 2197 outputStream = new PassphraseEncryptedOutputStream(encryptionPassphrase, 2198 outputStream, encryptionSettingsDefinitionID, true, true); 2199 outputStream = new GZIPOutputStream(outputStream); 2200 } 2201 2202 2203 final LDIFWriter ldifWriter = new LDIFWriter(outputStream); 2204 2205 // Set the wrap column to the maximum allowed value to ensure that 2206 // comments dont' get wrapped. 2207 ldifWriter.setWrapColumn(Integer.MAX_VALUE); 2208 2209 return ldifWriter; 2210 } 2211 catch (final Exception e) 2212 { 2213 Debug.debugException(e); 2214 2215 if (outputStream != null) 2216 { 2217 try 2218 { 2219 outputStream.close(); 2220 } 2221 catch (final Exception e2) 2222 { 2223 Debug.debugException(e2); 2224 } 2225 } 2226 2227 throw new LDAPException(ResultCode.LOCAL_ERROR, 2228 ERR_PARALLEL_UPDATE_ERROR_CREATING_REJECT_WRITER.get( 2229 rejectFile.getAbsolutePath(), 2230 StaticUtils.getExceptionMessage(e)), 2231 e); 2232 } 2233 } 2234 2235 2236 2237 /** 2238 * Writes the provided message to standard output or standard error and sets 2239 * it as the completion message if there isn't one already. 2240 * 2241 * @param isError Indicates whether the message represents an error. 2242 * @param message The message to log. 2243 */ 2244 private void logCompletionMessage(final boolean isError, 2245 @NotNull final String message) 2246 { 2247 completionMessage.compareAndSet(null, message); 2248 logMessage(message); 2249 if (isError) 2250 { 2251 wrapErr(0, WRAP_COLUMN, message); 2252 } 2253 else 2254 { 2255 wrapOut(0, WRAP_COLUMN, message); 2256 } 2257 } 2258 2259 2260 2261 /** 2262 * Logs the provided message if logging is enabled. 2263 * 2264 * @param messageElements The elements that make up the message to log. 2265 */ 2266 private void logMessage(@NotNull final Object... messageElements) 2267 { 2268 if (logWriter != null) 2269 { 2270 SimpleDateFormat timestampFormatter = timestampFormatters.get(); 2271 if (timestampFormatter == null) 2272 { 2273 timestampFormatter = 2274 new SimpleDateFormat("'['dd/MMM/yyyy:HH:mm:ss Z']'"); 2275 timestampFormatters.set(timestampFormatter); 2276 } 2277 2278 final String timestamp = timestampFormatter.format(new Date()); 2279 2280 final StringBuilder message = new StringBuilder(); 2281 message.append('['); 2282 message.append(timestamp); 2283 message.append("] "); 2284 for (final Object o : messageElements) 2285 { 2286 message.append(String.valueOf(o)); 2287 } 2288 2289 try 2290 { 2291 logWriter.println(message); 2292 } 2293 catch (final Exception e) 2294 { 2295 Debug.debugException(e); 2296 final String errorMessage = 2297 ERR_PARALLEL_UPDATE_CANNOT_WRITE_LOG_MESSAGE.get( 2298 message.toString(), logFileArg.getValue().getAbsolutePath(), 2299 StaticUtils.getExceptionMessage(e)); 2300 wrapErr(0, WRAP_COLUMN, errorMessage); 2301 completionMessage.compareAndSet(null, errorMessage); 2302 shouldAbort.set(true); 2303 } 2304 } 2305 } 2306 2307 2308 2309 /** 2310 * Indicates that processing for an operation completed successfully. 2311 * 2312 * @param changeRecord The LDIF change record for the operation. 2313 * It must not be {@code null}. 2314 * @param processingDurationMillis The length of time required to process 2315 * the operation, in milliseconds. 2316 */ 2317 void opCompletedSuccessfully(@NotNull final LDIFChangeRecord changeRecord, 2318 final long processingDurationMillis) 2319 { 2320 opsAttempted.incrementAndGet(); 2321 opsSucceeded.incrementAndGet(); 2322 totalOpDurationMillis.addAndGet(processingDurationMillis); 2323 logMessage(changeRecord.getDN(), " ", 2324 changeRecord.getChangeType().getName(), " SUCCESS 0 "); 2325 } 2326 2327 2328 2329 /** 2330 * Indicates that processing for an operation failed. Depending on the nature 2331 * of the failure, it either be added to the retry queue if it is potentially 2332 * an operation that could be retried (e.g., an operation that failed because 2333 * it depended on an entry that was added later in the LDIF), or will be added 2334 * to the reject file if it is determined that it is not a failure that may be 2335 * resolved by a later change. 2336 * 2337 * @param changeRecord The LDIF change record for the operation. 2338 * It must not be {@code null}. 2339 * @param ldapException The LDAP exception that was caught to 2340 * indicate that the operation failed. It 2341 * must not be {@code null}. 2342 * @param processingDurationMillis The length of time required to process 2343 * the operation, in milliseconds. 2344 */ 2345 void opFailed(@NotNull final LDIFChangeRecord changeRecord, 2346 @NotNull final LDAPException ldapException, 2347 final long processingDurationMillis) 2348 { 2349 opsAttempted.incrementAndGet(); 2350 totalOpDurationMillis.addAndGet(processingDurationMillis); 2351 2352 switch (ldapException.getResultCode().intValue()) 2353 { 2354 case ResultCode.NO_SUCH_OBJECT_INT_VALUE: 2355 case ResultCode.BUSY_INT_VALUE: 2356 case ResultCode.UNAVAILABLE_INT_VALUE: 2357 case ResultCode.NOT_ALLOWED_ON_NONLEAF_INT_VALUE: 2358 case ResultCode.SERVER_DOWN_INT_VALUE: 2359 case ResultCode.CONNECT_ERROR_INT_VALUE: 2360 retry(changeRecord, ldapException); 2361 break; 2362 default: 2363 reject(changeRecord, ldapException); 2364 break; 2365 } 2366 } 2367 2368 2369 2370 /** 2371 * Adds the provided operation to the retry queue. 2372 * 2373 * @param changeRecord The LDIF change record for the operation. 2374 * @param ldapException The LDAP exception that was caught to indicate that 2375 * the operation failed. 2376 */ 2377 private void retry(@NotNull final LDIFChangeRecord changeRecord, 2378 @NotNull final LDAPException ldapException) 2379 { 2380 if (neverRetryArg.isPresent()) 2381 { 2382 reject(changeRecord, ldapException); 2383 return; 2384 } 2385 2386 final DN parsedDN; 2387 try 2388 { 2389 parsedDN = changeRecord.getParsedDN(); 2390 } 2391 catch (final LDAPException e) 2392 { 2393 Debug.debugException(e); 2394 2395 // This should never happen, but if it does, then reject the change. 2396 reject(changeRecord, ldapException); 2397 return; 2398 } 2399 2400 logMessage(changeRecord.getDN(), " ", 2401 changeRecord.getChangeType().getName(), " RETRY ", 2402 ldapException.getResultCode(), " ", ldapException.getMessage()); 2403 2404 synchronized (retryQueue) 2405 { 2406 List<ObjectPair<LDIFChangeRecord,LDAPException>> changeList = 2407 retryQueue.get(parsedDN); 2408 2409 if (changeList == null) 2410 { 2411 changeList = new LinkedList<>(); 2412 retryQueue.put(parsedDN, changeList); 2413 } 2414 2415 changeList.add(new ObjectPair<>(changeRecord, ldapException)); 2416 retryQueueSize.incrementAndGet(); 2417 } 2418 } 2419 2420 2421 2422 /** 2423 * Adds the provided operation to the reject file. 2424 * 2425 * @param changeRecord The LDIF change record for the operation. 2426 * @param ldapException The LDAP exception that was caught to indicate that 2427 * the operation failed. 2428 */ 2429 void reject(@Nullable final LDIFChangeRecord changeRecord, 2430 @NotNull final LDAPException ldapException) 2431 { 2432 opsRejected.incrementAndGet(); 2433 2434 final ResultCode resultCode = ldapException.getResultCode(); 2435 if (resultCode != ResultCode.SUCCESS) 2436 { 2437 firstRejectResultCode.compareAndSet(null, resultCode); 2438 } 2439 2440 final StringBuilder commentBuffer = new StringBuilder(); 2441 for (final String line : 2442 ResultUtils.formatResult(ldapException, false, 0, 0)) 2443 { 2444 if (commentBuffer.length() > 0) 2445 { 2446 commentBuffer.append(StaticUtils.EOL); 2447 } 2448 commentBuffer.append(line); 2449 } 2450 final String comment = commentBuffer.toString(); 2451 2452 try 2453 { 2454 if (changeRecord != null) 2455 { 2456 logMessage(changeRecord.getDN(), " ", 2457 changeRecord.getChangeType().getName(), " REJECT ", resultCode, " ", 2458 ldapException.getMessage()); 2459 2460 synchronized (rejectWriter) 2461 { 2462 rejectWriter.writeChangeRecord(changeRecord, comment); 2463 rejectWriter.flush(); 2464 } 2465 } 2466 else 2467 { 2468 synchronized (rejectWriter) 2469 { 2470 rejectWriter.writeComment(comment, true, true); 2471 rejectWriter.flush(); 2472 } 2473 } 2474 } 2475 catch (final Exception e) 2476 { 2477 Debug.debugException(e); 2478 2479 final String errorMessage = ERR_PARALLEL_UPDATE_CANNOT_WRITE_REJECT.get( 2480 changeRecord.toString(), 2481 ERR_PARALLEL_UPDATE_REJECT_COMMENT.get(String.valueOf(resultCode), 2482 ldapException.getMessage()), 2483 StaticUtils.getExceptionMessage(e)); 2484 wrapErr(0, WRAP_COLUMN, errorMessage); 2485 completionMessage.compareAndSet(null, errorMessage); 2486 shouldAbort.set(true); 2487 } 2488 } 2489 2490 2491 2492 /** 2493 * Prints information about the processing performed by this program to 2494 * standard output. 2495 */ 2496 void printIntervalData() 2497 { 2498 final long currentAttempts = opsAttempted.get(); 2499 final long currentSuccesses = opsSucceeded.get(); 2500 final long currentReject = opsRejected.get(); 2501 final long currentRetry = retryQueueSize.get(); 2502 final long currentDurationMillis = totalOpDurationMillis.get(); 2503 final long currentTimeMillis = System.currentTimeMillis(); 2504 final long totalDurationMillis = 2505 currentTimeMillis - processingStartTimeMillis; 2506 2507 final long avgRate; 2508 if (totalDurationMillis == 0L) 2509 { 2510 avgRate = 0L; 2511 } 2512 else 2513 { 2514 avgRate = 1000L * currentAttempts / totalDurationMillis; 2515 } 2516 2517 final long avgDurationMillis; 2518 if (currentAttempts == 0L) 2519 { 2520 avgDurationMillis = 0L; 2521 } 2522 else 2523 { 2524 avgDurationMillis = currentDurationMillis / currentAttempts; 2525 } 2526 2527 final long recentDurationMillis; 2528 if (currentAttempts == lastOpsAttempted) 2529 { 2530 recentDurationMillis = 0L; 2531 } 2532 else 2533 { 2534 recentDurationMillis = (currentDurationMillis - lastTotalDurationMillis) / 2535 (currentAttempts - lastOpsAttempted); 2536 } 2537 2538 final long recentRate; 2539 if (lastOpsAttempted == 0) 2540 { 2541 out(" Attempts Successes Rejects ToRetry AvgOps/S " + 2542 " RctOps/S AvgDurMS RctDurMS"); 2543 out("--------- --------- --------- --------- --------- " + 2544 "--------- --------- ---------"); 2545 recentRate = avgRate; 2546 } 2547 else if (currentTimeMillis == lastUpdateTimeMillis) 2548 { 2549 recentRate = 0L; 2550 } 2551 else 2552 { 2553 recentRate = 1000L * (currentAttempts - lastOpsAttempted) / 2554 (currentTimeMillis - lastUpdateTimeMillis); 2555 } 2556 2557 final StringBuilder buffer = new StringBuilder(80); 2558 appendJustified(currentAttempts, buffer, true); 2559 appendJustified(currentSuccesses, buffer, true); 2560 appendJustified(currentReject, buffer, true); 2561 appendJustified(currentRetry, buffer, true); 2562 appendJustified(avgRate, buffer, true); 2563 appendJustified(recentRate, buffer, true); 2564 appendJustified(avgDurationMillis, buffer, true); 2565 appendJustified(recentDurationMillis, buffer, false); 2566 2567 out(buffer.toString()); 2568 2569 lastOpsAttempted = currentAttempts; 2570 lastTotalDurationMillis = currentDurationMillis; 2571 lastUpdateTimeMillis = currentTimeMillis; 2572 } 2573 2574 2575 2576 /** 2577 * Appends the provided number to the buffer, right justified in nine columns. 2578 * 2579 * @param value The value to be appended to the buffer. 2580 * @param buffer The buffer to which the value should be appended. 2581 * @param addSpace Indicates whether to append a space after the number. 2582 */ 2583 static void appendJustified(final long value, 2584 @NotNull final StringBuilder buffer, 2585 final boolean addSpace) 2586 { 2587 final String valueStr = String.valueOf(value); 2588 switch (valueStr.length()) 2589 { 2590 case 1: 2591 buffer.append(" "); 2592 break; 2593 case 2: 2594 buffer.append(" "); 2595 break; 2596 case 3: 2597 buffer.append(" "); 2598 break; 2599 case 4: 2600 buffer.append(" "); 2601 break; 2602 case 5: 2603 buffer.append(" "); 2604 break; 2605 case 6: 2606 buffer.append(" "); 2607 break; 2608 case 7: 2609 buffer.append(" "); 2610 break; 2611 case 8: 2612 buffer.append(' '); 2613 break; 2614 } 2615 2616 buffer.append(value); 2617 2618 if (addSpace) 2619 { 2620 buffer.append(' '); 2621 } 2622 } 2623 2624 2625 2626 /** 2627 * Retrieves the total number of operations attempted. This should only be 2628 * called after all tool processing has completed. 2629 * 2630 * @return The total number of operations attempted. 2631 */ 2632 public long getTotalAttemptCount() 2633 { 2634 return opsAttempted.get(); 2635 } 2636 2637 2638 2639 /** 2640 * Retrieves the number of operations attempted on the initial pass through 2641 * the LDIF file (that is, operations for which no retry attempts was made). 2642 * This should only be called after all tool processing has completed. 2643 * 2644 * @return The number of operations attempted on the initial pass through the 2645 * LDIF file. 2646 */ 2647 public long getInitialAttemptCount() 2648 { 2649 return initialAttempted; 2650 } 2651 2652 2653 2654 /** 2655 * Retrieves the number of retry attempts made for operations that did not 2656 * complete successfully on their first attempt. This should only be called 2657 * after all tool processing has completed. 2658 * 2659 * @return The number of retry attempts made for operations that did not 2660 * complete successfully on their first attempt. 2661 */ 2662 public long getRetryAttemptCount() 2663 { 2664 return opsAttempted.get() - initialAttempted; 2665 } 2666 2667 2668 2669 /** 2670 * Retrieves the total number of operations that completed successfully. This 2671 * should only be called after all tool processing has completed. 2672 * 2673 * @return The total number of operations that completed successfully. 2674 */ 2675 public long getTotalSuccessCount() 2676 { 2677 return opsSucceeded.get(); 2678 } 2679 2680 2681 2682 /** 2683 * Retrieves the number of operations that completed successfully on their 2684 * first attempt. This should only be called after all tool processing has 2685 * completed. 2686 * 2687 * @return The total number of operations that completed successfully on 2688 * their first attempt. 2689 */ 2690 public long getInitialSuccessCount() 2691 { 2692 return initialSucceeded; 2693 } 2694 2695 2696 2697 /** 2698 * Retrieves the number of operations that did not complete completed 2699 * successfully on their initial attempt but did succeed on a retry attempt. 2700 * This should only be called after all tool processing has completed. 2701 * 2702 * @return The number of operations that completed successfully on a retry 2703 * attempt. 2704 */ 2705 public long getRetrySuccessCount() 2706 { 2707 return opsSucceeded.get() - initialSucceeded; 2708 } 2709 2710 2711 2712 /** 2713 * Retrieves the number of operations that were rejected and did not complete 2714 * successfully during any of the attempts. This should only be called after 2715 * all tool processing has completed. 2716 * 2717 * @return The number of operations that were rejected. 2718 */ 2719 public long getRejectCount() 2720 { 2721 return opsRejected.get(); 2722 } 2723 2724 2725 2726 /** 2727 * Retrieves the total length of time, in milliseconds, spent processing 2728 * operations. This should only be called after all tool processing has 2729 * completed. Note that when running with multiple threads, this can exceed 2730 * the length of time spent running the tool because multiple operations can 2731 * be processed in parallel. 2732 * 2733 * @return The total length of time, in milliseconds, spent processing 2734 * operations. 2735 */ 2736 public long getTotalOpDurationMillis() 2737 { 2738 return totalOpDurationMillis.get(); 2739 } 2740 2741 2742 2743 /** 2744 * {@inheritDoc} 2745 */ 2746 @Override() 2747 protected boolean registerShutdownHook() 2748 { 2749 return true; 2750 } 2751 2752 2753 2754 /** 2755 * {@inheritDoc} 2756 */ 2757 @Override() 2758 protected void doShutdownHookProcessing(@Nullable final ResultCode resultCode) 2759 { 2760 shouldAbort.set(true); 2761 2762 final FixedRateBarrier b = rateLimiter; 2763 if (b != null) 2764 { 2765 b.shutdownRequested(); 2766 } 2767 } 2768 2769 2770 2771 /** 2772 * {@inheritDoc} 2773 */ 2774 @Override() 2775 public void handleUnsolicitedNotification( 2776 @NotNull final LDAPConnection connection, 2777 @NotNull final ExtendedResult notification) 2778 { 2779 final String message; 2780 if (notification.getDiagnosticMessage() == null) 2781 { 2782 message = INFO_PARALLEL_UPDATE_UNSOLICITED_NOTIFICATION_NO_MESSAGE.get( 2783 getToolName(), String.valueOf(notification.getResultCode()), 2784 notification.getOID()); 2785 } 2786 else 2787 { 2788 message = INFO_PARALLEL_UPDATE_UNSOLICITED_NOTIFICATION_NO_MESSAGE.get( 2789 getToolName(), String.valueOf(notification.getResultCode()), 2790 notification.getOID(), notification.getDiagnosticMessage()); 2791 } 2792 2793 out(); 2794 wrapOut(0, WRAP_COLUMN, message); 2795 out(); 2796 } 2797 2798 2799 2800 /** 2801 * {@inheritDoc} 2802 */ 2803 @Override() 2804 @NotNull() 2805 public LinkedHashMap<String[],String> getExampleUsages() 2806 { 2807 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 2808 2809 examples.put( 2810 new String[] 2811 { 2812 "--hostname", "server.example.com", 2813 "--port", "636", 2814 "--useSSL", 2815 "--bindDN", "uid=admin,dc=example,dc=com", 2816 "--promptForBindPassword", 2817 "--ldifFile", "changes.ldif", 2818 "--rejectFile", "rejects.ldif", 2819 "--defaultAdd", 2820 "--numThreads", "10", 2821 "--ratePerSecond", "5000" 2822 }, 2823 INFO_PARALLEL_UPDATE_EXAMPLE_DESC.get()); 2824 2825 return examples; 2826 } 2827}