001/* 002 * Copyright 2016-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2016-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) 2016-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.ByteArrayInputStream; 041import java.io.File; 042import java.io.InputStream; 043import java.io.IOException; 044import java.io.OutputStream; 045import java.math.BigDecimal; 046import java.util.ArrayList; 047import java.util.EnumSet; 048import java.util.HashSet; 049import java.util.LinkedHashMap; 050import java.util.List; 051import java.util.Map; 052import java.util.Set; 053import java.util.SortedMap; 054import java.util.StringTokenizer; 055import java.util.concurrent.TimeUnit; 056import java.util.concurrent.atomic.AtomicBoolean; 057 058import com.unboundid.asn1.ASN1OctetString; 059import com.unboundid.ldap.sdk.AddRequest; 060import com.unboundid.ldap.sdk.Control; 061import com.unboundid.ldap.sdk.DeleteRequest; 062import com.unboundid.ldap.sdk.DN; 063import com.unboundid.ldap.sdk.Entry; 064import com.unboundid.ldap.sdk.ExtendedResult; 065import com.unboundid.ldap.sdk.Filter; 066import com.unboundid.ldap.sdk.LDAPConnectionOptions; 067import com.unboundid.ldap.sdk.LDAPConnection; 068import com.unboundid.ldap.sdk.LDAPConnectionPool; 069import com.unboundid.ldap.sdk.LDAPException; 070import com.unboundid.ldap.sdk.LDAPRequest; 071import com.unboundid.ldap.sdk.LDAPResult; 072import com.unboundid.ldap.sdk.LDAPSearchException; 073import com.unboundid.ldap.sdk.Modification; 074import com.unboundid.ldap.sdk.ModifyRequest; 075import com.unboundid.ldap.sdk.ModifyDNRequest; 076import com.unboundid.ldap.sdk.ResultCode; 077import com.unboundid.ldap.sdk.SearchRequest; 078import com.unboundid.ldap.sdk.SearchResult; 079import com.unboundid.ldap.sdk.SearchScope; 080import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler; 081import com.unboundid.ldap.sdk.Version; 082import com.unboundid.ldap.sdk.controls.AssertionRequestControl; 083import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; 084import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl; 085import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl; 086import com.unboundid.ldap.sdk.controls.PostReadRequestControl; 087import com.unboundid.ldap.sdk.controls.PreReadRequestControl; 088import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl; 089import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; 090import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl; 091import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl; 092import com.unboundid.ldap.sdk.controls.TransactionSpecificationRequestControl; 093import com.unboundid.ldap.sdk.extensions.StartTransactionExtendedRequest; 094import com.unboundid.ldap.sdk.extensions.StartTransactionExtendedResult; 095import com.unboundid.ldap.sdk.extensions.EndTransactionExtendedRequest; 096import com.unboundid.ldap.sdk.unboundidds.controls.AccessLogFieldRequestControl; 097import com.unboundid.ldap.sdk.unboundidds.controls.AssuredReplicationLocalLevel; 098import com.unboundid.ldap.sdk.unboundidds.controls. 099 AssuredReplicationRequestControl; 100import com.unboundid.ldap.sdk.unboundidds.controls. 101 AssuredReplicationRemoteLevel; 102import com.unboundid.ldap.sdk.unboundidds.controls. 103 GenerateAccessTokenRequestControl; 104import com.unboundid.ldap.sdk.unboundidds.controls. 105 GeneratePasswordRequestControl; 106import com.unboundid.ldap.sdk.unboundidds.controls. 107 GetAuthorizationEntryRequestControl; 108import com.unboundid.ldap.sdk.unboundidds.controls. 109 GetBackendSetIDRequestControl; 110import com.unboundid.ldap.sdk.unboundidds.controls. 111 GetRecentLoginHistoryRequestControl; 112import com.unboundid.ldap.sdk.unboundidds.controls. 113 GetUserResourceLimitsRequestControl; 114import com.unboundid.ldap.sdk.unboundidds.controls.GetServerIDRequestControl; 115import com.unboundid.ldap.sdk.unboundidds.controls.HardDeleteRequestControl; 116import com.unboundid.ldap.sdk.unboundidds.controls. 117 IgnoreNoUserModificationRequestControl; 118import com.unboundid.ldap.sdk.unboundidds.controls. 119 JSONFormattedControlDecodeBehavior; 120import com.unboundid.ldap.sdk.unboundidds.controls.JSONFormattedRequestControl; 121import com.unboundid.ldap.sdk.unboundidds.controls.JSONFormattedResponseControl; 122import com.unboundid.ldap.sdk.unboundidds.controls. 123 NameWithEntryUUIDRequestControl; 124import com.unboundid.ldap.sdk.unboundidds.controls.NoOpRequestControl; 125import com.unboundid.ldap.sdk.unboundidds.controls. 126 OperationPurposeRequestControl; 127import com.unboundid.ldap.sdk.unboundidds.controls.PasswordPolicyRequestControl; 128import com.unboundid.ldap.sdk.unboundidds.controls. 129 PasswordUpdateBehaviorRequestControl; 130import com.unboundid.ldap.sdk.unboundidds.controls. 131 PasswordUpdateBehaviorRequestControlProperties; 132import com.unboundid.ldap.sdk.unboundidds.controls. 133 PasswordValidationDetailsRequestControl; 134import com.unboundid.ldap.sdk.unboundidds.controls.PurgePasswordRequestControl; 135import com.unboundid.ldap.sdk.unboundidds.controls. 136 ReplicationRepairRequestControl; 137import com.unboundid.ldap.sdk.unboundidds.controls.RetirePasswordRequestControl; 138import com.unboundid.ldap.sdk.unboundidds.controls. 139 RouteToBackendSetRequestControl; 140import com.unboundid.ldap.sdk.unboundidds.controls.RouteToServerRequestControl; 141import com.unboundid.ldap.sdk.unboundidds.controls.SoftDeleteRequestControl; 142import com.unboundid.ldap.sdk.unboundidds.controls. 143 SuppressOperationalAttributeUpdateRequestControl; 144import com.unboundid.ldap.sdk.unboundidds.controls. 145 SuppressReferentialIntegrityUpdatesRequestControl; 146import com.unboundid.ldap.sdk.unboundidds.controls. 147 UniquenessMultipleAttributeBehavior; 148import com.unboundid.ldap.sdk.unboundidds.controls.UniquenessRequestControl; 149import com.unboundid.ldap.sdk.unboundidds.controls. 150 UniquenessRequestControlProperties; 151import com.unboundid.ldap.sdk.unboundidds.controls.SuppressType; 152import com.unboundid.ldap.sdk.unboundidds.controls.UndeleteRequestControl; 153import com.unboundid.ldap.sdk.unboundidds.controls.UniquenessValidationLevel; 154import com.unboundid.ldap.sdk.unboundidds.extensions.MultiUpdateErrorBehavior; 155import com.unboundid.ldap.sdk.unboundidds.extensions.MultiUpdateExtendedRequest; 156import com.unboundid.ldap.sdk.unboundidds.extensions. 157 StartAdministrativeSessionExtendedRequest; 158import com.unboundid.ldap.sdk.unboundidds.extensions. 159 StartAdministrativeSessionPostConnectProcessor; 160import com.unboundid.ldif.LDIFAddChangeRecord; 161import com.unboundid.ldif.LDIFChangeRecord; 162import com.unboundid.ldif.LDIFDeleteChangeRecord; 163import com.unboundid.ldif.LDIFException; 164import com.unboundid.ldif.LDIFModifyChangeRecord; 165import com.unboundid.ldif.LDIFModifyDNChangeRecord; 166import com.unboundid.ldif.LDIFReader; 167import com.unboundid.ldif.LDIFWriter; 168import com.unboundid.ldif.TrailingSpaceBehavior; 169import com.unboundid.util.Debug; 170import com.unboundid.util.DNFileReader; 171import com.unboundid.util.FilterFileReader; 172import com.unboundid.util.FixedRateBarrier; 173import com.unboundid.util.LDAPCommandLineTool; 174import com.unboundid.util.NotNull; 175import com.unboundid.util.Nullable; 176import com.unboundid.util.StaticUtils; 177import com.unboundid.util.SubtreeDeleter; 178import com.unboundid.util.SubtreeDeleterResult; 179import com.unboundid.util.ThreadSafety; 180import com.unboundid.util.ThreadSafetyLevel; 181import com.unboundid.util.args.ArgumentException; 182import com.unboundid.util.args.ArgumentParser; 183import com.unboundid.util.args.BooleanArgument; 184import com.unboundid.util.args.ControlArgument; 185import com.unboundid.util.args.DNArgument; 186import com.unboundid.util.args.DurationArgument; 187import com.unboundid.util.args.FileArgument; 188import com.unboundid.util.args.FilterArgument; 189import com.unboundid.util.args.IntegerArgument; 190import com.unboundid.util.args.StringArgument; 191import com.unboundid.util.json.JSONBoolean; 192import com.unboundid.util.json.JSONNumber; 193import com.unboundid.util.json.JSONObject; 194import com.unboundid.util.json.JSONString; 195import com.unboundid.util.json.JSONValue; 196 197import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 198 199 200 201/** 202 * This class provides an implementation of an LDAP command-line tool that may 203 * be used to apply changes to a directory server. The changes to apply (which 204 * may include add, delete, modify, and modify DN operations) will be read in 205 * LDIF form, either from standard input or a specified file or set of files. 206 * This is a much more full-featured tool than the 207 * {@link com.unboundid.ldap.sdk.examples.LDAPModify} tool 208 * <BR> 209 * <BLOCKQUOTE> 210 * <B>NOTE:</B> This class, and other classes within the 211 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 212 * supported for use against Ping Identity, UnboundID, and 213 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 214 * for proprietary functionality or for external specifications that are not 215 * considered stable or mature enough to be guaranteed to work in an 216 * interoperable way with other types of LDAP servers. 217 * </BLOCKQUOTE> 218 */ 219@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 220public final class LDAPModify 221 extends LDAPCommandLineTool 222 implements UnsolicitedNotificationHandler 223{ 224 /** 225 * The column at which output should be wrapped. 226 */ 227 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 228 229 230 231 /** 232 * The name of the attribute type used to specify a password in the 233 * authentication password syntax as described in RFC 3112. 234 */ 235 @NotNull private static final String ATTR_AUTH_PASSWORD = "authPassword"; 236 237 238 239 /** 240 * The name of the attribute type used to specify the DN of the soft-deleted 241 * entry to be restored via an undelete operation. 242 */ 243 @NotNull private static final String ATTR_UNDELETE_FROM_DN = 244 "ds-undelete-from-dn"; 245 246 247 248 /** 249 * The name of the attribute type used to specify a password in the 250 * userPassword syntax. 251 */ 252 @NotNull private static final String ATTR_USER_PASSWORD = "userPassword"; 253 254 255 256 /** 257 * The long identifier for the argument used to specify the desired assured 258 * replication local level. 259 */ 260 @NotNull private static final String ARG_ASSURED_REPLICATION_LOCAL_LEVEL = 261 "assuredReplicationLocalLevel"; 262 263 264 265 /** 266 * The long identifier for the argument used to specify the desired assured 267 * replication remote level. 268 */ 269 @NotNull private static final String ARG_ASSURED_REPLICATION_REMOTE_LEVEL = 270 "assuredReplicationRemoteLevel"; 271 272 273 274 /** 275 * The long identifier for the argument used to specify the desired assured 276 * timeout. 277 */ 278 @NotNull private static final String ARG_ASSURED_REPLICATION_TIMEOUT = 279 "assuredReplicationTimeout"; 280 281 282 283 /** 284 * The long identifier for the argument used to specify the path to an LDIF 285 * file containing changes to apply. 286 */ 287 @NotNull private static final String ARG_LDIF_FILE = "ldifFile"; 288 289 290 291 /** 292 * The long identifier for the argument used to specify the simple paged 293 * results page size to use when modifying entries that match a provided 294 * filter. 295 */ 296 @NotNull private static final String ARG_SEARCH_PAGE_SIZE = "searchPageSize"; 297 298 299 300 // The set of arguments supported by this program. 301 @Nullable private BooleanArgument allowUndelete = null; 302 @Nullable private BooleanArgument assuredReplication = null; 303 @Nullable private BooleanArgument authorizationIdentity = null; 304 @Nullable private BooleanArgument clientSideSubtreeDelete = null; 305 @Nullable private BooleanArgument continueOnError = null; 306 @Nullable private BooleanArgument defaultAdd = null; 307 @Nullable private BooleanArgument dryRun = null; 308 @Nullable private BooleanArgument followReferrals = null; 309 @Nullable private BooleanArgument generateAccessToken = null; 310 @Nullable private BooleanArgument generatePassword = null; 311 @Nullable private BooleanArgument getBackendSetID = null; 312 @Nullable private BooleanArgument getRecentLoginHistory = null; 313 @Nullable private BooleanArgument getServerID = null; 314 @Nullable private BooleanArgument getUserResourceLimits = null; 315 @Nullable private BooleanArgument hardDelete = null; 316 @Nullable private BooleanArgument ignoreNoUserModification = null; 317 @Nullable private BooleanArgument manageDsaIT = null; 318 @Nullable private BooleanArgument nameWithEntryUUID = null; 319 @Nullable private BooleanArgument neverRetry = null; 320 @Nullable private BooleanArgument noOperation = null; 321 @Nullable private BooleanArgument passwordValidationDetails = null; 322 @Nullable private BooleanArgument permissiveModify = null; 323 @Nullable private BooleanArgument purgeCurrentPassword = null; 324 @Nullable private BooleanArgument replicationRepair = null; 325 @Nullable private BooleanArgument retireCurrentPassword = null; 326 @Nullable private BooleanArgument retryFailedOperations = null; 327 @Nullable private BooleanArgument softDelete = null; 328 @Nullable private BooleanArgument stripTrailingSpaces = null; 329 @Nullable private BooleanArgument serverSideSubtreeDelete = null; 330 @Nullable private BooleanArgument suppressReferentialIntegrityUpdates = null; 331 @Nullable private BooleanArgument useAdministrativeSession = null; 332 @Nullable private BooleanArgument useJSONFormattedRequestControls = null; 333 @Nullable private BooleanArgument usePasswordPolicyControl = null; 334 @Nullable private BooleanArgument useTransaction = null; 335 @Nullable private BooleanArgument verbose = null; 336 @Nullable private ControlArgument addControl = null; 337 @Nullable private ControlArgument bindControl = null; 338 @Nullable private ControlArgument deleteControl = null; 339 @Nullable private ControlArgument modifyControl = null; 340 @Nullable private ControlArgument modifyDNControl = null; 341 @Nullable private ControlArgument operationControl = null; 342 @Nullable private DNArgument modifyEntryWithDN = null; 343 @Nullable private DNArgument proxyV1As = null; 344 @Nullable private DNArgument uniquenessBaseDN = null; 345 @Nullable private DurationArgument assuredReplicationTimeout = null; 346 @Nullable private FileArgument encryptionPassphraseFile = null; 347 @Nullable private FileArgument ldifFile = null; 348 @Nullable private FileArgument modifyEntriesMatchingFiltersFromFile = null; 349 @Nullable private FileArgument modifyEntriesWithDNsFromFile = null; 350 @Nullable private FileArgument rejectFile = null; 351 @Nullable private FilterArgument assertionFilter = null; 352 @Nullable private FilterArgument modifyEntriesMatchingFilter = null; 353 @Nullable private FilterArgument uniquenessFilter = null; 354 @Nullable private IntegerArgument ratePerSecond = null; 355 @Nullable private IntegerArgument searchPageSize = null; 356 @Nullable private StringArgument accessLogField = null; 357 @Nullable private StringArgument assuredReplicationLocalLevel = null; 358 @Nullable private StringArgument assuredReplicationRemoteLevel = null; 359 @Nullable private StringArgument characterSet = null; 360 @Nullable private StringArgument getAuthorizationEntryAttribute = null; 361 @Nullable private StringArgument multiUpdateErrorBehavior = null; 362 @Nullable private StringArgument operationPurpose = null; 363 @Nullable private StringArgument passwordUpdateBehavior = null; 364 @Nullable private StringArgument postReadAttribute = null; 365 @Nullable private StringArgument preReadAttribute = null; 366 @Nullable private StringArgument proxyAs = null; 367 @Nullable private StringArgument routeToBackendSet = null; 368 @Nullable private StringArgument routeToServer = null; 369 @Nullable private StringArgument suppressOperationalAttributeUpdates = null; 370 @Nullable private StringArgument uniquenessAttribute = null; 371 @Nullable private StringArgument uniquenessMultipleAttributeBehavior = null; 372 @Nullable private StringArgument uniquenessPostCommitValidationLevel = null; 373 @Nullable private StringArgument uniquenessPreCommitValidationLevel = null; 374 375 // Indicates whether we've written anything to the reject writer yet. 376 @NotNull private final AtomicBoolean rejectWritten; 377 378 // The input stream from to use for standard input. 379 @NotNull private final InputStream in; 380 381 // The route to backend set request controls to include in write requests. 382 @NotNull private final List<RouteToBackendSetRequestControl> 383 routeToBackendSetRequestControls = new ArrayList<>(10); 384 385 386 387 /** 388 * Runs this tool with the provided command-line arguments. It will use the 389 * JVM-default streams for standard input, output, and error. 390 * 391 * @param args The command-line arguments to provide to this program. 392 */ 393 public static void main(@NotNull final String... args) 394 { 395 final ResultCode resultCode = main(System.in, System.out, System.err, args); 396 if (resultCode != ResultCode.SUCCESS) 397 { 398 System.exit(Math.min(resultCode.intValue(), 255)); 399 } 400 } 401 402 403 404 /** 405 * Runs this tool with the provided streams and command-line arguments. 406 * 407 * @param in The input stream to use for standard input. If this is 408 * {@code null}, then no standard input will be used. 409 * @param out The output stream to use for standard output. If this is 410 * {@code null}, then standard output will be suppressed. 411 * @param err The output stream to use for standard error. If this is 412 * {@code null}, then standard error will be suppressed. 413 * @param args The command-line arguments provided to this program. 414 * 415 * @return The result code obtained when running the tool. Any result code 416 * other than {@link ResultCode#SUCCESS} indicates an error. 417 */ 418 @NotNull() 419 public static ResultCode main(@Nullable final InputStream in, 420 @Nullable final OutputStream out, 421 @Nullable final OutputStream err, 422 @NotNull final String... args) 423 { 424 final LDAPModify tool = new LDAPModify(in, out, err); 425 return tool.runTool(args); 426 } 427 428 429 430 /** 431 * Creates a new instance of this tool with the provided streams. Standard 432 * input will not be available. 433 * 434 * @param out The output stream to use for standard output. If this is 435 * {@code null}, then standard output will be suppressed. 436 * @param err The output stream to use for standard error. If this is 437 * {@code null}, then standard error will be suppressed. 438 */ 439 public LDAPModify(@Nullable final OutputStream out, 440 @Nullable final OutputStream err) 441 { 442 this(null, out, err); 443 } 444 445 446 447 /** 448 * Creates a new instance of this tool with the provided streams. 449 * 450 * @param in The input stream to use for standard input. If this is 451 * {@code null}, then no standard input will be used. 452 * @param out The output stream to use for standard output. If this is 453 * {@code null}, then standard output will be suppressed. 454 * @param err The output stream to use for standard error. If this is 455 * {@code null}, then standard error will be suppressed. 456 */ 457 public LDAPModify(@Nullable final InputStream in, 458 @Nullable final OutputStream out, 459 @Nullable final OutputStream err) 460 { 461 super(out, err); 462 463 if (in == null) 464 { 465 this.in = new ByteArrayInputStream(StaticUtils.NO_BYTES); 466 } 467 else 468 { 469 this.in = in; 470 } 471 472 473 rejectWritten = new AtomicBoolean(false); 474 } 475 476 477 478 /** 479 * {@inheritDoc} 480 */ 481 @Override() 482 @NotNull() 483 public String getToolName() 484 { 485 return "ldapmodify"; 486 } 487 488 489 490 /** 491 * {@inheritDoc} 492 */ 493 @Override() 494 @NotNull() 495 public String getToolDescription() 496 { 497 return INFO_LDAPMODIFY_TOOL_DESCRIPTION.get(ARG_LDIF_FILE); 498 } 499 500 501 502 /** 503 * {@inheritDoc} 504 */ 505 @Override() 506 @NotNull() 507 public String getToolVersion() 508 { 509 return Version.NUMERIC_VERSION_STRING; 510 } 511 512 513 514 /** 515 * {@inheritDoc} 516 */ 517 @Override() 518 public boolean supportsInteractiveMode() 519 { 520 return true; 521 } 522 523 524 525 /** 526 * {@inheritDoc} 527 */ 528 @Override() 529 public boolean defaultsToInteractiveMode() 530 { 531 return true; 532 } 533 534 535 536 /** 537 * {@inheritDoc} 538 */ 539 @Override() 540 public boolean supportsPropertiesFile() 541 { 542 return true; 543 } 544 545 546 547 /** 548 * {@inheritDoc} 549 */ 550 @Override() 551 public boolean supportsOutputFile() 552 { 553 return true; 554 } 555 556 557 558 /** 559 * {@inheritDoc} 560 */ 561 @Override() 562 protected boolean supportsDebugLogging() 563 { 564 return true; 565 } 566 567 568 569 /** 570 * {@inheritDoc} 571 */ 572 @Override() 573 protected boolean defaultToPromptForBindPassword() 574 { 575 return true; 576 } 577 578 579 580 /** 581 * {@inheritDoc} 582 */ 583 @Override() 584 protected boolean includeAlternateLongIdentifiers() 585 { 586 return true; 587 } 588 589 590 591 /** 592 * {@inheritDoc} 593 */ 594 @Override() 595 protected boolean supportsSSLDebugging() 596 { 597 return true; 598 } 599 600 601 602 /** 603 * {@inheritDoc} 604 */ 605 @Override() 606 protected boolean logToolInvocationByDefault() 607 { 608 return true; 609 } 610 611 612 613 /** 614 * {@inheritDoc} 615 */ 616 @Override() 617 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 618 throws ArgumentException 619 { 620 ldifFile = new FileArgument('f', ARG_LDIF_FILE, false, -1, null, 621 INFO_LDAPMODIFY_ARG_DESCRIPTION_LDIF_FILE.get(), true, true, true, 622 false); 623 ldifFile.addLongIdentifier("filename", true); 624 ldifFile.addLongIdentifier("ldif-file", true); 625 ldifFile.addLongIdentifier("file-name", true); 626 ldifFile.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 627 parser.addArgument(ldifFile); 628 629 630 encryptionPassphraseFile = new FileArgument(null, 631 "encryptionPassphraseFile", false, 1, null, 632 INFO_LDAPMODIFY_ARG_DESCRIPTION_ENCRYPTION_PW_FILE.get(), true, true, 633 true, false); 634 encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file", 635 true); 636 encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true); 637 encryptionPassphraseFile.addLongIdentifier("encryption-password-file", 638 true); 639 encryptionPassphraseFile.setArgumentGroupName( 640 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 641 parser.addArgument(encryptionPassphraseFile); 642 643 644 characterSet = new StringArgument('i', "characterSet", false, 1, 645 INFO_LDAPMODIFY_PLACEHOLDER_CHARSET.get(), 646 INFO_LDAPMODIFY_ARG_DESCRIPTION_CHARACTER_SET.get(), "UTF-8"); 647 characterSet.addLongIdentifier("encoding", true); 648 characterSet.addLongIdentifier("character-set", true); 649 characterSet.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 650 parser.addArgument(characterSet); 651 652 653 rejectFile = new FileArgument('R', "rejectFile", false, 1, null, 654 INFO_LDAPMODIFY_ARG_DESCRIPTION_REJECT_FILE.get(), false, true, true, 655 false); 656 rejectFile.addLongIdentifier("reject-file", true); 657 rejectFile.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 658 parser.addArgument(rejectFile); 659 660 661 verbose = new BooleanArgument('v', "verbose", 1, 662 INFO_LDAPMODIFY_ARG_DESCRIPTION_VERBOSE.get()); 663 verbose.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 664 parser.addArgument(verbose); 665 666 667 modifyEntriesMatchingFilter = new FilterArgument(null, 668 "modifyEntriesMatchingFilter", false, 0, null, 669 INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_ENTRIES_MATCHING_FILTER.get( 670 ARG_SEARCH_PAGE_SIZE)); 671 modifyEntriesMatchingFilter.addLongIdentifier( 672 "modify-entries-matching-filter", true); 673 modifyEntriesMatchingFilter.setArgumentGroupName( 674 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 675 parser.addArgument(modifyEntriesMatchingFilter); 676 677 678 modifyEntriesMatchingFiltersFromFile = new FileArgument(null, 679 "modifyEntriesMatchingFiltersFromFile", false, 0, null, 680 INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_FILTER_FILE.get( 681 ARG_SEARCH_PAGE_SIZE), true, false, true, false); 682 modifyEntriesMatchingFiltersFromFile.addLongIdentifier( 683 "modify-entries-matching-filters-from-file", true); 684 modifyEntriesMatchingFiltersFromFile.setArgumentGroupName( 685 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 686 parser.addArgument(modifyEntriesMatchingFiltersFromFile); 687 688 689 modifyEntryWithDN = new DNArgument(null, "modifyEntryWithDN", false, 0, 690 null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_ENTRY_DN.get()); 691 modifyEntryWithDN.addLongIdentifier("modify-entry-with-dn", true); 692 modifyEntryWithDN.setArgumentGroupName( 693 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 694 parser.addArgument(modifyEntryWithDN); 695 696 697 modifyEntriesWithDNsFromFile = new FileArgument(null, 698 "modifyEntriesWithDNsFromFile", false, 0, 699 null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_DN_FILE.get(), true, 700 false, true, false); 701 modifyEntriesWithDNsFromFile.addLongIdentifier( 702 "modify-entries-with-dns-from-file", true); 703 modifyEntriesWithDNsFromFile.setArgumentGroupName( 704 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 705 parser.addArgument(modifyEntriesWithDNsFromFile); 706 707 708 searchPageSize = new IntegerArgument(null, ARG_SEARCH_PAGE_SIZE, false, 1, 709 null, 710 INFO_LDAPMODIFY_ARG_DESCRIPTION_SEARCH_PAGE_SIZE.get( 711 modifyEntriesMatchingFilter.getIdentifierString(), 712 modifyEntriesMatchingFiltersFromFile.getIdentifierString()), 713 1, Integer.MAX_VALUE); 714 searchPageSize.addLongIdentifier("search-page-size", true); 715 searchPageSize.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 716 parser.addArgument(searchPageSize); 717 718 719 // NOTE: The retryFailedOperations argument is now hidden, as we will retry 720 // operations by default. The neverRetry argument can be used to disable 721 // this. 722 retryFailedOperations = new BooleanArgument(null, "retryFailedOperations", 723 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_RETRY_FAILED_OPERATIONS.get()); 724 retryFailedOperations.addLongIdentifier("retry-failed-operations", true); 725 retryFailedOperations.setArgumentGroupName( 726 INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 727 retryFailedOperations.setHidden(true); 728 parser.addArgument(retryFailedOperations); 729 730 731 neverRetry = new BooleanArgument(null, "neverRetry", 1, 732 INFO_LDAPMODIFY_ARG_DESC_NEVER_RETRY.get()); 733 neverRetry.addLongIdentifier("never-retry", true); 734 neverRetry.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 735 parser.addArgument(neverRetry); 736 737 738 dryRun = new BooleanArgument('n', "dryRun", 1, 739 INFO_LDAPMODIFY_ARG_DESCRIPTION_DRY_RUN.get()); 740 dryRun.addLongIdentifier("dry-run", true); 741 dryRun.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 742 parser.addArgument(dryRun); 743 744 745 defaultAdd = new BooleanArgument('a', "defaultAdd", 1, 746 INFO_LDAPMODIFY_ARG_DESCRIPTION_DEFAULT_ADD.get()); 747 defaultAdd.addLongIdentifier("default-add", true); 748 defaultAdd.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 749 parser.addArgument(defaultAdd); 750 751 752 continueOnError = new BooleanArgument('c', "continueOnError", 1, 753 INFO_LDAPMODIFY_ARG_DESCRIPTION_CONTINUE_ON_ERROR.get()); 754 continueOnError.addLongIdentifier("continue-on-error", true); 755 continueOnError.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 756 parser.addArgument(continueOnError); 757 758 759 stripTrailingSpaces = new BooleanArgument(null, "stripTrailingSpaces", 1, 760 INFO_LDAPMODIFY_ARG_DESCRIPTION_STRIP_TRAILING_SPACES.get()); 761 stripTrailingSpaces.addLongIdentifier("strip-trailing-spaces", true); 762 stripTrailingSpaces.setArgumentGroupName( 763 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 764 parser.addArgument(stripTrailingSpaces); 765 766 767 768 followReferrals = new BooleanArgument(null, "followReferrals", 1, 769 INFO_LDAPMODIFY_ARG_DESCRIPTION_FOLLOW_REFERRALS.get()); 770 followReferrals.addLongIdentifier("follow-referrals", true); 771 followReferrals.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 772 parser.addArgument(followReferrals); 773 774 775 proxyAs = new StringArgument('Y', "proxyAs", false, 1, 776 INFO_PLACEHOLDER_AUTHZID.get(), 777 INFO_LDAPMODIFY_ARG_DESCRIPTION_PROXY_AS.get()); 778 proxyAs.addLongIdentifier("proxyV2As", true); 779 proxyAs.addLongIdentifier("proxy-as", true); 780 proxyAs.addLongIdentifier("proxy-v2-as", true); 781 proxyAs.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 782 parser.addArgument(proxyAs); 783 784 proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null, 785 INFO_LDAPMODIFY_ARG_DESCRIPTION_PROXY_V1_AS.get()); 786 proxyV1As.addLongIdentifier("proxy-v1-as", true); 787 proxyV1As.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 788 parser.addArgument(proxyV1As); 789 790 791 useAdministrativeSession = new BooleanArgument(null, 792 "useAdministrativeSession", 1, 793 INFO_LDAPMODIFY_ARG_DESCRIPTION_USE_ADMIN_SESSION.get()); 794 useAdministrativeSession.addLongIdentifier("use-administrative-session", 795 true); 796 useAdministrativeSession.setArgumentGroupName( 797 INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 798 parser.addArgument(useAdministrativeSession); 799 800 801 accessLogField = new StringArgument(null, "accessLogField", false, 0, 802 INFO_LDAPMODIFY_ARG_PLACEHOLDER_NAME_VALUE.get(), 803 INFO_LDAMODIFY_ARG_DESCRIPTION_ACCESS_LOG_FIELD.get()); 804 accessLogField.addLongIdentifier("access-log-field", true); 805 accessLogField.setArgumentGroupName( 806 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 807 parser.addArgument(accessLogField); 808 809 810 operationPurpose = new StringArgument(null, "operationPurpose", false, 1, 811 INFO_PLACEHOLDER_PURPOSE.get(), 812 INFO_LDAPMODIFY_ARG_DESCRIPTION_OPERATION_PURPOSE.get()); 813 operationPurpose.addLongIdentifier("operation-purpose", true); 814 operationPurpose.setArgumentGroupName( 815 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 816 parser.addArgument(operationPurpose); 817 818 819 manageDsaIT = new BooleanArgument(null, "useManageDsaIT", 1, 820 INFO_LDAPMODIFY_ARG_DESCRIPTION_MANAGE_DSA_IT.get()); 821 manageDsaIT.addLongIdentifier("manageDsaIT", true); 822 manageDsaIT.addLongIdentifier("use-manage-dsa-it", true); 823 manageDsaIT.addLongIdentifier("manage-dsa-it", true); 824 manageDsaIT.setArgumentGroupName( 825 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 826 parser.addArgument(manageDsaIT); 827 828 829 useTransaction = new BooleanArgument(null, "useTransaction", 1, 830 INFO_LDAPMODIFY_ARG_DESCRIPTION_USE_TRANSACTION.get()); 831 useTransaction.addLongIdentifier("use-transaction", true); 832 useTransaction.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 833 parser.addArgument(useTransaction); 834 835 836 final Set<String> multiUpdateErrorBehaviorAllowedValues = 837 StaticUtils.setOf("atomic", "abort-on-error", "continue-on-error"); 838 multiUpdateErrorBehavior = new StringArgument(null, 839 "multiUpdateErrorBehavior", false, 1, 840 "{atomic|abort-on-error|continue-on-error}", 841 INFO_LDAPMODIFY_ARG_DESCRIPTION_MULTI_UPDATE_ERROR_BEHAVIOR.get(), 842 multiUpdateErrorBehaviorAllowedValues); 843 multiUpdateErrorBehavior.addLongIdentifier("multi-update-error-behavior", 844 true); 845 multiUpdateErrorBehavior.setArgumentGroupName( 846 INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 847 parser.addArgument(multiUpdateErrorBehavior); 848 849 850 assertionFilter = new FilterArgument(null, "assertionFilter", false, 1, 851 INFO_PLACEHOLDER_FILTER.get(), 852 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSERTION_FILTER.get()); 853 assertionFilter.addLongIdentifier("assertion-filter", true); 854 assertionFilter.setArgumentGroupName( 855 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 856 parser.addArgument(assertionFilter); 857 858 859 authorizationIdentity = new BooleanArgument('E', 860 "authorizationIdentity", 1, 861 INFO_LDAPMODIFY_ARG_DESCRIPTION_AUTHZ_IDENTITY.get()); 862 authorizationIdentity.addLongIdentifier("reportAuthzID", true); 863 authorizationIdentity.addLongIdentifier("authorization-identity", true); 864 authorizationIdentity.addLongIdentifier("report-authzID", true); 865 authorizationIdentity.addLongIdentifier("report-authz-id", true); 866 authorizationIdentity.setArgumentGroupName( 867 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 868 parser.addArgument(authorizationIdentity); 869 870 871 generateAccessToken = new BooleanArgument(null, "generateAccessToken", 1, 872 INFO_LDAPMODIFY_ARG_DESCRIPTION_GENERATE_ACCESS_TOKEN.get()); 873 generateAccessToken.addLongIdentifier("generate-access-token", true); 874 generateAccessToken.addLongIdentifier("requestAccessToken", true); 875 generateAccessToken.addLongIdentifier("request-access-token", true); 876 generateAccessToken.setArgumentGroupName( 877 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 878 parser.addArgument(generateAccessToken); 879 880 881 generatePassword = new BooleanArgument(null, "generatePassword", 1, 882 INFO_LDAPMODIFY_ARG_DESCRIPTION_GENERATE_PASSWORD.get()); 883 generatePassword.addLongIdentifier("generatePW", true); 884 generatePassword.addLongIdentifier("generate-password", true); 885 generatePassword.addLongIdentifier("generate-pw", true); 886 generatePassword.setArgumentGroupName( 887 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 888 parser.addArgument(generatePassword); 889 890 891 getAuthorizationEntryAttribute = new StringArgument(null, 892 "getAuthorizationEntryAttribute", false, 0, 893 INFO_PLACEHOLDER_ATTR.get(), 894 INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_AUTHZ_ENTRY_ATTR.get()); 895 getAuthorizationEntryAttribute.addLongIdentifier( 896 "get-authorization-entry-attribute", true); 897 getAuthorizationEntryAttribute.setArgumentGroupName( 898 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 899 parser.addArgument(getAuthorizationEntryAttribute); 900 901 902 getBackendSetID = new BooleanArgument(null, "getBackendSetID", 903 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_BACKEND_SET_ID.get()); 904 getBackendSetID.addLongIdentifier("get-backend-set-id", true); 905 getBackendSetID.setArgumentGroupName( 906 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 907 parser.addArgument(getBackendSetID); 908 909 910 getRecentLoginHistory = new BooleanArgument(null, "getRecentLoginHistory", 911 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_RECENT_LOGIN_HISTORY.get()); 912 getRecentLoginHistory.addLongIdentifier("get-recent-login-history", true); 913 getRecentLoginHistory.setArgumentGroupName( 914 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 915 parser.addArgument(getRecentLoginHistory); 916 917 918 getServerID = new BooleanArgument(null, "getServerID", 919 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_SERVER_ID.get()); 920 getServerID.addLongIdentifier("get-server-id", true); 921 getServerID.setArgumentGroupName( 922 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 923 parser.addArgument(getServerID); 924 925 926 getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits", 927 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_USER_RESOURCE_LIMITS.get()); 928 getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true); 929 getUserResourceLimits.setArgumentGroupName( 930 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 931 parser.addArgument(getUserResourceLimits); 932 933 934 ignoreNoUserModification = new BooleanArgument(null, 935 "ignoreNoUserModification", 1, 936 INFO_LDAPMODIFY_ARG_DESCRIPTION_IGNORE_NO_USER_MOD.get()); 937 ignoreNoUserModification.addLongIdentifier("ignore-no-user-modification", 938 true); 939 ignoreNoUserModification.setArgumentGroupName( 940 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 941 parser.addArgument(ignoreNoUserModification); 942 943 944 preReadAttribute = new StringArgument(null, "preReadAttribute", false, -1, 945 INFO_PLACEHOLDER_ATTR.get(), 946 INFO_LDAPMODIFY_ARG_DESCRIPTION_PRE_READ_ATTRIBUTE.get()); 947 preReadAttribute.addLongIdentifier("preReadAttributes", true); 948 preReadAttribute.addLongIdentifier("pre-read-attribute", true); 949 preReadAttribute.addLongIdentifier("pre-read-attributes", true); 950 preReadAttribute.setArgumentGroupName( 951 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 952 parser.addArgument(preReadAttribute); 953 954 955 postReadAttribute = new StringArgument(null, "postReadAttribute", false, 956 -1, INFO_PLACEHOLDER_ATTR.get(), 957 INFO_LDAPMODIFY_ARG_DESCRIPTION_POST_READ_ATTRIBUTE.get()); 958 postReadAttribute.addLongIdentifier("postReadAttributes", true); 959 postReadAttribute.addLongIdentifier("post-read-attribute", true); 960 postReadAttribute.addLongIdentifier("post-read-attributes", true); 961 postReadAttribute.setArgumentGroupName( 962 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 963 parser.addArgument(postReadAttribute); 964 965 966 routeToBackendSet = new StringArgument(null, "routeToBackendSet", 967 false, 0, 968 INFO_LDAPMODIFY_ARG_PLACEHOLDER_ROUTE_TO_BACKEND_SET.get(), 969 INFO_LDAPMODIFY_ARG_DESCRIPTION_ROUTE_TO_BACKEND_SET.get()); 970 routeToBackendSet.addLongIdentifier("route-to-backend-set", true); 971 routeToBackendSet.setArgumentGroupName( 972 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 973 parser.addArgument(routeToBackendSet); 974 975 976 routeToServer = new StringArgument(null, "routeToServer", false, 1, 977 INFO_LDAPMODIFY_ARG_PLACEHOLDER_ROUTE_TO_SERVER.get(), 978 INFO_LDAPMODIFY_ARG_DESCRIPTION_ROUTE_TO_SERVER.get()); 979 routeToServer.addLongIdentifier("route-to-server", true); 980 routeToServer.setArgumentGroupName( 981 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 982 parser.addArgument(routeToServer); 983 984 985 assuredReplication = new BooleanArgument(null, "useAssuredReplication", 1, 986 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPLICATION.get( 987 ARG_ASSURED_REPLICATION_LOCAL_LEVEL, 988 ARG_ASSURED_REPLICATION_REMOTE_LEVEL, 989 ARG_ASSURED_REPLICATION_TIMEOUT)); 990 assuredReplication.addLongIdentifier("assuredReplication", true); 991 assuredReplication.addLongIdentifier("use-assured-replication", true); 992 assuredReplication.addLongIdentifier("assured-replication", true); 993 assuredReplication.setArgumentGroupName( 994 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 995 parser.addArgument(assuredReplication); 996 997 998 final Set<String> assuredReplicationLocalLevelAllowedValues = 999 StaticUtils.setOf("none", "received-any-server", 1000 "processed-all-servers"); 1001 assuredReplicationLocalLevel = new StringArgument(null, 1002 ARG_ASSURED_REPLICATION_LOCAL_LEVEL, false, 1, 1003 INFO_PLACEHOLDER_LEVEL.get(), 1004 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_LOCAL_LEVEL.get( 1005 assuredReplication.getIdentifierString()), 1006 assuredReplicationLocalLevelAllowedValues); 1007 assuredReplicationLocalLevel.addLongIdentifier( 1008 "assured-replication-local-level", true); 1009 assuredReplicationLocalLevel.setArgumentGroupName( 1010 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1011 parser.addArgument(assuredReplicationLocalLevel); 1012 1013 1014 final Set<String> assuredReplicationRemoteLevelAllowedValues = 1015 StaticUtils.setOf("none", "received-any-remote-location", 1016 "received-all-remote-locations", "processed-all-remote-servers"); 1017 assuredReplicationRemoteLevel = new StringArgument(null, 1018 ARG_ASSURED_REPLICATION_REMOTE_LEVEL, false, 1, 1019 INFO_PLACEHOLDER_LEVEL.get(), 1020 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_REMOTE_LEVEL.get( 1021 assuredReplication.getIdentifierString()), 1022 assuredReplicationRemoteLevelAllowedValues); 1023 assuredReplicationRemoteLevel.addLongIdentifier( 1024 "assured-replication-remote-level", true); 1025 assuredReplicationRemoteLevel.setArgumentGroupName( 1026 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1027 parser.addArgument(assuredReplicationRemoteLevel); 1028 1029 1030 assuredReplicationTimeout = new DurationArgument(null, 1031 ARG_ASSURED_REPLICATION_TIMEOUT, false, INFO_PLACEHOLDER_TIMEOUT.get(), 1032 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_TIMEOUT.get( 1033 assuredReplication.getIdentifierString())); 1034 assuredReplicationTimeout.setArgumentGroupName( 1035 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1036 parser.addArgument(assuredReplicationTimeout); 1037 1038 1039 replicationRepair = new BooleanArgument(null, "replicationRepair", 1040 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_REPLICATION_REPAIR.get()); 1041 replicationRepair.addLongIdentifier("replication-repair", true); 1042 replicationRepair.setArgumentGroupName( 1043 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1044 parser.addArgument(replicationRepair); 1045 1046 1047 nameWithEntryUUID = new BooleanArgument(null, "nameWithEntryUUID", 1, 1048 INFO_LDAPMODIFY_ARG_DESCRIPTION_NAME_WITH_ENTRY_UUID.get()); 1049 nameWithEntryUUID.addLongIdentifier("name-with-entryUUID", true); 1050 nameWithEntryUUID.addLongIdentifier("name-with-entry-uuid", true); 1051 nameWithEntryUUID.setArgumentGroupName( 1052 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1053 parser.addArgument(nameWithEntryUUID); 1054 1055 1056 noOperation = new BooleanArgument(null, "noOperation", 1, 1057 INFO_LDAPMODIFY_ARG_DESCRIPTION_NO_OPERATION.get()); 1058 noOperation.addLongIdentifier("noOp", true); 1059 noOperation.addLongIdentifier("no-operation", true); 1060 noOperation.addLongIdentifier("no-op", true); 1061 noOperation.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1062 parser.addArgument(noOperation); 1063 1064 1065 passwordUpdateBehavior = new StringArgument(null, 1066 "passwordUpdateBehavior", false, 0, 1067 INFO_LDAPMODIFY_PLACEHOLDER_NAME_EQUALS_VALUE.get(), 1068 INFO_LDAPMODIFY_ARG_DESCRIPTION_PW_UPDATE_BEHAVIOR.get()); 1069 passwordUpdateBehavior.addLongIdentifier("password-update-behavior", true); 1070 passwordUpdateBehavior.setArgumentGroupName( 1071 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1072 parser.addArgument(passwordUpdateBehavior); 1073 1074 passwordValidationDetails = new BooleanArgument(null, 1075 "getPasswordValidationDetails", 1, 1076 INFO_LDAPMODIFY_ARG_DESCRIPTION_PASSWORD_VALIDATION_DETAILS.get( 1077 ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD)); 1078 passwordValidationDetails.addLongIdentifier("passwordValidationDetails", 1079 true); 1080 passwordValidationDetails.addLongIdentifier( 1081 "get-password-validation-details", true); 1082 passwordValidationDetails.addLongIdentifier("password-validation-details", 1083 true); 1084 passwordValidationDetails.setArgumentGroupName( 1085 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1086 parser.addArgument(passwordValidationDetails); 1087 1088 1089 permissiveModify = new BooleanArgument(null, "permissiveModify", 1090 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_PERMISSIVE_MODIFY.get()); 1091 permissiveModify.addLongIdentifier("permissive-modify", true); 1092 permissiveModify.setArgumentGroupName( 1093 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1094 parser.addArgument(permissiveModify); 1095 1096 1097 clientSideSubtreeDelete = new BooleanArgument(null, 1098 "clientSideSubtreeDelete", 1, 1099 INFO_LDAPMODIFY_ARG_DESCRIPTION_CLIENT_SIDE_SUBTREE_DELETE.get()); 1100 clientSideSubtreeDelete.addLongIdentifier("client-side-subtree-delete", 1101 true); 1102 clientSideSubtreeDelete.setArgumentGroupName( 1103 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1104 parser.addArgument(clientSideSubtreeDelete); 1105 1106 1107 serverSideSubtreeDelete = new BooleanArgument(null, 1108 "serverSideSubtreeDelete", 1, 1109 INFO_LDAPMODIFY_ARG_DESCRIPTION_SERVER_SIDE_SUBTREE_DELETE.get()); 1110 serverSideSubtreeDelete.addLongIdentifier("server-side-subtree-delete", 1111 true); 1112 serverSideSubtreeDelete.addLongIdentifier("subtreeDelete", true); 1113 serverSideSubtreeDelete.addLongIdentifier("subtree-delete", true); 1114 serverSideSubtreeDelete.addLongIdentifier("subtreeDeleteControl", true); 1115 serverSideSubtreeDelete.addLongIdentifier("subtree-delete-control", true); 1116 serverSideSubtreeDelete.addLongIdentifier("useSubtreeDeleteControl", true); 1117 serverSideSubtreeDelete.addLongIdentifier("use-subtree-delete-control", 1118 true); 1119 serverSideSubtreeDelete.setArgumentGroupName( 1120 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1121 parser.addArgument(serverSideSubtreeDelete); 1122 1123 1124 softDelete = new BooleanArgument('s', "softDelete", 1, 1125 INFO_LDAPMODIFY_ARG_DESCRIPTION_SOFT_DELETE.get()); 1126 softDelete.addLongIdentifier("useSoftDelete", true); 1127 softDelete.addLongIdentifier("soft-delete", true); 1128 softDelete.addLongIdentifier("use-soft-delete", true); 1129 softDelete.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1130 parser.addArgument(softDelete); 1131 1132 1133 hardDelete = new BooleanArgument(null, "hardDelete", 1, 1134 INFO_LDAPMODIFY_ARG_DESCRIPTION_HARD_DELETE.get()); 1135 hardDelete.addLongIdentifier("hard-delete", true); 1136 hardDelete.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1137 parser.addArgument(hardDelete); 1138 1139 1140 allowUndelete = new BooleanArgument(null, "allowUndelete", 1, 1141 INFO_LDAPMODIFY_ARG_DESCRIPTION_ALLOW_UNDELETE.get( 1142 ATTR_UNDELETE_FROM_DN)); 1143 allowUndelete.addLongIdentifier("allow-undelete", true); 1144 allowUndelete.setArgumentGroupName( 1145 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1146 parser.addArgument(allowUndelete); 1147 1148 1149 retireCurrentPassword = new BooleanArgument(null, "retireCurrentPassword", 1150 1, 1151 INFO_LDAPMODIFY_ARG_DESCRIPTION_RETIRE_CURRENT_PASSWORD.get( 1152 ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD)); 1153 retireCurrentPassword.addLongIdentifier("retire-current-password", true); 1154 retireCurrentPassword.setArgumentGroupName( 1155 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1156 parser.addArgument(retireCurrentPassword); 1157 1158 1159 purgeCurrentPassword = new BooleanArgument(null, "purgeCurrentPassword", 1, 1160 INFO_LDAPMODIFY_ARG_DESCRIPTION_PURGE_CURRENT_PASSWORD.get( 1161 ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD)); 1162 purgeCurrentPassword.addLongIdentifier("purge-current-password", true); 1163 purgeCurrentPassword.setArgumentGroupName( 1164 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1165 parser.addArgument(purgeCurrentPassword); 1166 1167 1168 final Set<String> suppressOperationalAttributeUpdatesAllowedValues = 1169 StaticUtils.setOf("last-access-time", "last-login-time", 1170 "last-login-ip", "lastmod"); 1171 suppressOperationalAttributeUpdates = new StringArgument(null, 1172 "suppressOperationalAttributeUpdates", false, -1, 1173 INFO_PLACEHOLDER_ATTR.get(), 1174 INFO_LDAPMODIFY_ARG_DESCRIPTION_SUPPRESS_OP_ATTR_UPDATES.get(), 1175 suppressOperationalAttributeUpdatesAllowedValues); 1176 suppressOperationalAttributeUpdates.addLongIdentifier( 1177 "suppress-operational-attribute-updates", true); 1178 suppressOperationalAttributeUpdates.setArgumentGroupName( 1179 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1180 parser.addArgument(suppressOperationalAttributeUpdates); 1181 1182 1183 suppressReferentialIntegrityUpdates = new BooleanArgument(null, 1184 "suppressReferentialIntegrityUpdates", 1, 1185 INFO_LDAPMODIFY_ARG_DESCRIPTION_SUPPRESS_REFERINT_UPDATES.get()); 1186 suppressReferentialIntegrityUpdates.addLongIdentifier( 1187 "suppress-referential-integrity-updates", true); 1188 suppressReferentialIntegrityUpdates.setArgumentGroupName( 1189 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1190 parser.addArgument(suppressReferentialIntegrityUpdates); 1191 1192 1193 usePasswordPolicyControl = new BooleanArgument(null, 1194 "usePasswordPolicyControl", 1, 1195 INFO_LDAPMODIFY_ARG_DESCRIPTION_PASSWORD_POLICY.get()); 1196 usePasswordPolicyControl.addLongIdentifier("use-password-policy-control", 1197 true); 1198 usePasswordPolicyControl.setArgumentGroupName( 1199 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1200 parser.addArgument(usePasswordPolicyControl); 1201 1202 1203 uniquenessAttribute = new StringArgument(null, "uniquenessAttribute", false, 1204 0, INFO_PLACEHOLDER_ATTR.get(), 1205 INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_ATTR.get()); 1206 uniquenessAttribute.addLongIdentifier("uniquenessAttributeType", true); 1207 uniquenessAttribute.addLongIdentifier("uniqueAttribute", true); 1208 uniquenessAttribute.addLongIdentifier("uniqueAttributeType", true); 1209 uniquenessAttribute.addLongIdentifier("uniqueness-attribute", true); 1210 uniquenessAttribute.addLongIdentifier("uniqueness-attribute-type", true); 1211 uniquenessAttribute.addLongIdentifier("unique-attribute", true); 1212 uniquenessAttribute.addLongIdentifier("unique-attribute-type", true); 1213 uniquenessAttribute.setArgumentGroupName( 1214 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1215 parser.addArgument(uniquenessAttribute); 1216 1217 1218 uniquenessFilter = new FilterArgument(null, "uniquenessFilter", false, 1, 1219 null, INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_FILTER.get()); 1220 uniquenessFilter.addLongIdentifier("uniqueness-filter", true); 1221 uniquenessFilter.setArgumentGroupName( 1222 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1223 parser.addArgument(uniquenessFilter); 1224 1225 1226 uniquenessBaseDN = new DNArgument(null, "uniquenessBaseDN", false, 1, null, 1227 INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_BASE_DN.get()); 1228 uniquenessBaseDN.addLongIdentifier("uniqueness-base-dn", true); 1229 uniquenessBaseDN.setArgumentGroupName( 1230 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1231 parser.addArgument(uniquenessBaseDN); 1232 parser.addDependentArgumentSet(uniquenessBaseDN, uniquenessAttribute, 1233 uniquenessFilter); 1234 1235 1236 final Set<String> mabValues = StaticUtils.setOf( 1237 "unique-within-each-attribute", 1238 "unique-across-all-attributes-including-in-same-entry", 1239 "unique-across-all-attributes-except-in-same-entry", 1240 "unique-in-combination"); 1241 uniquenessMultipleAttributeBehavior = new StringArgument(null, 1242 "uniquenessMultipleAttributeBehavior", false, 1, 1243 INFO_LDAPMODIFY_PLACEHOLDER_BEHAVIOR.get(), 1244 INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_MULTIPLE_ATTRIBUTE_BEHAVIOR. 1245 get(), 1246 mabValues); 1247 uniquenessMultipleAttributeBehavior.addLongIdentifier( 1248 "uniqueness-multiple-attribute-behavior", true); 1249 uniquenessMultipleAttributeBehavior.setArgumentGroupName( 1250 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1251 parser.addArgument(uniquenessMultipleAttributeBehavior); 1252 parser.addDependentArgumentSet(uniquenessMultipleAttributeBehavior, 1253 uniquenessAttribute); 1254 1255 1256 final Set<String> vlValues = StaticUtils.setOf("none", "all-subtree-views", 1257 "all-backend-sets", "all-available-backend-servers"); 1258 uniquenessPreCommitValidationLevel = new StringArgument(null, 1259 "uniquenessPreCommitValidationLevel", false, 1, 1260 INFO_LDAPMODIFY_PLACEHOLDER_LEVEL.get(), 1261 INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_PRE_COMMIT_LEVEL.get(), 1262 vlValues); 1263 uniquenessPreCommitValidationLevel.addLongIdentifier( 1264 "uniqueness-pre-commit-validation-level", true); 1265 uniquenessPreCommitValidationLevel.setArgumentGroupName( 1266 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1267 parser.addArgument(uniquenessPreCommitValidationLevel); 1268 parser.addDependentArgumentSet(uniquenessPreCommitValidationLevel, 1269 uniquenessAttribute, uniquenessFilter); 1270 1271 1272 uniquenessPostCommitValidationLevel = new StringArgument(null, 1273 "uniquenessPostCommitValidationLevel", false, 1, 1274 INFO_LDAPMODIFY_PLACEHOLDER_LEVEL.get(), 1275 INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_POST_COMMIT_LEVEL.get(), 1276 vlValues); 1277 uniquenessPostCommitValidationLevel.addLongIdentifier( 1278 "uniqueness-post-commit-validation-level", true); 1279 uniquenessPostCommitValidationLevel.setArgumentGroupName( 1280 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1281 parser.addArgument(uniquenessPostCommitValidationLevel); 1282 parser.addDependentArgumentSet(uniquenessPostCommitValidationLevel, 1283 uniquenessAttribute, uniquenessFilter); 1284 1285 operationControl = new ControlArgument('J', "control", false, 0, null, 1286 INFO_LDAPMODIFY_ARG_DESCRIPTION_OP_CONTROL.get()); 1287 operationControl.setArgumentGroupName( 1288 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1289 parser.addArgument(operationControl); 1290 1291 1292 addControl = new ControlArgument(null, "addControl", false, 0, null, 1293 INFO_LDAPMODIFY_ARG_DESCRIPTION_ADD_CONTROL.get()); 1294 addControl.addLongIdentifier("add-control", true); 1295 addControl.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1296 parser.addArgument(addControl); 1297 1298 1299 bindControl = new ControlArgument(null, "bindControl", false, 0, null, 1300 INFO_LDAPMODIFY_ARG_DESCRIPTION_BIND_CONTROL.get()); 1301 bindControl.addLongIdentifier("bind-control", true); 1302 bindControl.setArgumentGroupName( 1303 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1304 parser.addArgument(bindControl); 1305 1306 1307 deleteControl = new ControlArgument(null, "deleteControl", false, 0, null, 1308 INFO_LDAPMODIFY_ARG_DESCRIPTION_DELETE_CONTROL.get()); 1309 deleteControl.addLongIdentifier("delete-control", true); 1310 deleteControl.setArgumentGroupName( 1311 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1312 parser.addArgument(deleteControl); 1313 1314 1315 modifyControl = new ControlArgument(null, "modifyControl", false, 0, null, 1316 INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_CONTROL.get()); 1317 modifyControl.addLongIdentifier("modify-control", true); 1318 modifyControl.setArgumentGroupName( 1319 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1320 parser.addArgument(modifyControl); 1321 1322 1323 modifyDNControl = new ControlArgument(null, "modifyDNControl", false, 0, 1324 null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_DN_CONTROL.get()); 1325 modifyDNControl.addLongIdentifier("modify-dn-control", true); 1326 modifyDNControl.setArgumentGroupName( 1327 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1328 parser.addArgument(modifyDNControl); 1329 1330 1331 useJSONFormattedRequestControls = new BooleanArgument(null, 1332 "useJSONFormattedRequestControls", 1, 1333 INFO_LDAPMODIFY_ARG_DESCRIPTION_USE_JSON_FORMATTED_CONTROLS.get()); 1334 useJSONFormattedRequestControls.addLongIdentifier( 1335 "use-json-formatted-request-controls", true); 1336 useJSONFormattedRequestControls.addLongIdentifier( 1337 "useJSONFormattedControls", true); 1338 useJSONFormattedRequestControls.addLongIdentifier( 1339 "use-json-formatted-controls", true); 1340 useJSONFormattedRequestControls.setArgumentGroupName( 1341 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1342 parser.addArgument(useJSONFormattedRequestControls); 1343 1344 1345 ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1, 1346 INFO_PLACEHOLDER_NUM.get(), 1347 INFO_LDAPMODIFY_ARG_DESCRIPTION_RATE_PER_SECOND.get(), 1, 1348 Integer.MAX_VALUE); 1349 ratePerSecond.addLongIdentifier("rate-per-second", true); 1350 ratePerSecond.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 1351 parser.addArgument(ratePerSecond); 1352 1353 1354 // The "--scriptFriendly" argument is provided for compatibility with legacy 1355 // ldapmodify tools, but is not actually used by this tool. 1356 final BooleanArgument scriptFriendly = new BooleanArgument(null, 1357 "scriptFriendly", 1, 1358 INFO_LDAPMODIFY_ARG_DESCRIPTION_SCRIPT_FRIENDLY.get()); 1359 scriptFriendly.addLongIdentifier("script-friendly", true); 1360 scriptFriendly.setArgumentGroupName( 1361 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 1362 scriptFriendly.setHidden(true); 1363 parser.addArgument(scriptFriendly); 1364 1365 1366 // The "-V" / "--ldapVersion" argument is provided for compatibility with 1367 // legacy ldapmodify tools, but is not actually used by this tool. 1368 final IntegerArgument ldapVersion = new IntegerArgument('V', "ldapVersion", 1369 false, 1, null, INFO_LDAPMODIFY_ARG_DESCRIPTION_LDAP_VERSION.get()); 1370 ldapVersion.addLongIdentifier("ldap-version", true); 1371 ldapVersion.setHidden(true); 1372 parser.addArgument(ldapVersion); 1373 1374 1375 // A few assured replication arguments will only be allowed if assured 1376 // replication is to be used. 1377 parser.addDependentArgumentSet(assuredReplicationLocalLevel, 1378 assuredReplication); 1379 parser.addDependentArgumentSet(assuredReplicationRemoteLevel, 1380 assuredReplication); 1381 parser.addDependentArgumentSet(assuredReplicationTimeout, 1382 assuredReplication); 1383 1384 // Transactions will be incompatible with a lot of settings. 1385 parser.addExclusiveArgumentSet(useTransaction, multiUpdateErrorBehavior); 1386 parser.addExclusiveArgumentSet(useTransaction, rejectFile); 1387 parser.addExclusiveArgumentSet(useTransaction, retryFailedOperations); 1388 parser.addExclusiveArgumentSet(useTransaction, continueOnError); 1389 parser.addExclusiveArgumentSet(useTransaction, dryRun); 1390 parser.addExclusiveArgumentSet(useTransaction, followReferrals); 1391 parser.addExclusiveArgumentSet(useTransaction, nameWithEntryUUID); 1392 parser.addExclusiveArgumentSet(useTransaction, noOperation); 1393 parser.addExclusiveArgumentSet(useTransaction, modifyEntriesMatchingFilter); 1394 parser.addExclusiveArgumentSet(useTransaction, 1395 modifyEntriesMatchingFiltersFromFile); 1396 parser.addExclusiveArgumentSet(useTransaction, modifyEntryWithDN); 1397 parser.addExclusiveArgumentSet(useTransaction, 1398 modifyEntriesWithDNsFromFile); 1399 parser.addExclusiveArgumentSet(useTransaction, 1400 clientSideSubtreeDelete); 1401 1402 // Multi-update is incompatible with a lot of settings. 1403 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, ratePerSecond); 1404 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, rejectFile); 1405 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1406 retryFailedOperations); 1407 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, continueOnError); 1408 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, dryRun); 1409 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, followReferrals); 1410 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, nameWithEntryUUID); 1411 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, noOperation); 1412 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1413 modifyEntriesMatchingFilter); 1414 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1415 modifyEntriesMatchingFiltersFromFile); 1416 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, modifyEntryWithDN); 1417 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1418 modifyEntriesWithDNsFromFile); 1419 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1420 clientSideSubtreeDelete); 1421 1422 // Client-side and server-side subtree deletes cannot be used together. 1423 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, 1424 serverSideSubtreeDelete); 1425 1426 // Soft delete cannot be used with either hard delete or subtree delete. 1427 parser.addExclusiveArgumentSet(softDelete, hardDelete); 1428 parser.addExclusiveArgumentSet(softDelete, clientSideSubtreeDelete); 1429 parser.addExclusiveArgumentSet(softDelete, serverSideSubtreeDelete); 1430 1431 // Client-side subtree delete cannot be used in conjunction with a few 1432 // other settings. 1433 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, followReferrals); 1434 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, preReadAttribute); 1435 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, getBackendSetID); 1436 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, getServerID); 1437 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, noOperation); 1438 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, dryRun); 1439 1440 // Password retiring and purging can't be used together. 1441 parser.addExclusiveArgumentSet(retireCurrentPassword, purgeCurrentPassword); 1442 1443 // Referral following cannot be used in conjunction with the manageDsaIT 1444 // control. 1445 parser.addExclusiveArgumentSet(followReferrals, manageDsaIT); 1446 1447 // The proxyAs and proxyV1As arguments cannot be used together. 1448 parser.addExclusiveArgumentSet(proxyAs, proxyV1As); 1449 1450 // The modifyEntriesMatchingFilter argument is incompatible with a lot of 1451 // settings, since it can only be used for modify operations. 1452 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, allowUndelete); 1453 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, defaultAdd); 1454 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, dryRun); 1455 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, hardDelete); 1456 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1457 ignoreNoUserModification); 1458 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1459 nameWithEntryUUID); 1460 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, softDelete); 1461 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1462 clientSideSubtreeDelete); 1463 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1464 serverSideSubtreeDelete); 1465 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1466 suppressReferentialIntegrityUpdates); 1467 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, addControl); 1468 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, deleteControl); 1469 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1470 modifyDNControl); 1471 1472 // The modifyEntriesMatchingFilterFromFile argument is incompatible with a 1473 // lot of settings, since it can only be used for modify operations. 1474 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1475 allowUndelete); 1476 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1477 defaultAdd); 1478 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1479 dryRun); 1480 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1481 hardDelete); 1482 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1483 ignoreNoUserModification); 1484 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1485 nameWithEntryUUID); 1486 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1487 softDelete); 1488 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1489 clientSideSubtreeDelete); 1490 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1491 serverSideSubtreeDelete); 1492 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1493 suppressReferentialIntegrityUpdates); 1494 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1495 addControl); 1496 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1497 deleteControl); 1498 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1499 modifyDNControl); 1500 1501 // The modifyEntryWithDN argument is incompatible with a lot of 1502 // settings, since it can only be used for modify operations. 1503 parser.addExclusiveArgumentSet(modifyEntryWithDN, allowUndelete); 1504 parser.addExclusiveArgumentSet(modifyEntryWithDN, defaultAdd); 1505 parser.addExclusiveArgumentSet(modifyEntryWithDN, dryRun); 1506 parser.addExclusiveArgumentSet(modifyEntryWithDN, hardDelete); 1507 parser.addExclusiveArgumentSet(modifyEntryWithDN, ignoreNoUserModification); 1508 parser.addExclusiveArgumentSet(modifyEntryWithDN, nameWithEntryUUID); 1509 parser.addExclusiveArgumentSet(modifyEntryWithDN, softDelete); 1510 parser.addExclusiveArgumentSet(modifyEntryWithDN, clientSideSubtreeDelete); 1511 parser.addExclusiveArgumentSet(modifyEntryWithDN, serverSideSubtreeDelete); 1512 parser.addExclusiveArgumentSet(modifyEntryWithDN, 1513 suppressReferentialIntegrityUpdates); 1514 parser.addExclusiveArgumentSet(modifyEntryWithDN, addControl); 1515 parser.addExclusiveArgumentSet(modifyEntryWithDN, deleteControl); 1516 parser.addExclusiveArgumentSet(modifyEntryWithDN, modifyDNControl); 1517 1518 // The modifyEntriesWithDNsFromFile argument is incompatible with a lot of 1519 // settings, since it can only be used for modify operations. 1520 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, allowUndelete); 1521 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, defaultAdd); 1522 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, dryRun); 1523 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, hardDelete); 1524 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1525 ignoreNoUserModification); 1526 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1527 nameWithEntryUUID); 1528 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, softDelete); 1529 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1530 clientSideSubtreeDelete); 1531 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1532 serverSideSubtreeDelete); 1533 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1534 suppressReferentialIntegrityUpdates); 1535 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, addControl); 1536 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, deleteControl); 1537 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1538 modifyDNControl); 1539 } 1540 1541 1542 1543 /** 1544 * {@inheritDoc} 1545 */ 1546 @Override() 1547 public void doExtendedNonLDAPArgumentValidation() 1548 throws ArgumentException 1549 { 1550 // If we should use the route to backend set request control, then validate 1551 // and pre-create those controls. 1552 if (routeToBackendSet.isPresent()) 1553 { 1554 final List<String> values = routeToBackendSet.getValues(); 1555 final Map<String,List<String>> idsByRP = new LinkedHashMap<>( 1556 StaticUtils.computeMapCapacity(values.size())); 1557 for (final String value : values) 1558 { 1559 final int colonPos = value.indexOf(':'); 1560 if (colonPos <= 0) 1561 { 1562 throw new ArgumentException( 1563 ERR_LDAPMODIFY_ROUTE_TO_BACKEND_SET_INVALID_FORMAT.get(value, 1564 routeToBackendSet.getIdentifierString())); 1565 } 1566 1567 final String rpID = value.substring(0, colonPos); 1568 final String bsID = value.substring(colonPos+1); 1569 1570 List<String> idsForRP = idsByRP.get(rpID); 1571 if (idsForRP == null) 1572 { 1573 idsForRP = new ArrayList<>(values.size()); 1574 idsByRP.put(rpID, idsForRP); 1575 } 1576 idsForRP.add(bsID); 1577 } 1578 1579 for (final Map.Entry<String,List<String>> e : idsByRP.entrySet()) 1580 { 1581 final String rpID = e.getKey(); 1582 final List<String> bsIDs = e.getValue(); 1583 routeToBackendSetRequestControls.add( 1584 RouteToBackendSetRequestControl.createAbsoluteRoutingRequest(true, 1585 rpID, bsIDs)); 1586 } 1587 } 1588 1589 1590 // If the --proxyAs argument was provided, then make sure its value is 1591 // properly formatted. 1592 if (proxyAs.isPresent()) 1593 { 1594 final String proxyAsValue = proxyAs.getValue(); 1595 final String lowerProxyAsValue = StaticUtils.toLowerCase(proxyAsValue); 1596 if (lowerProxyAsValue.startsWith("dn:")) 1597 { 1598 final String dnString = proxyAsValue.substring(3); 1599 if (! DN.isValidDN(dnString)) 1600 { 1601 throw new ArgumentException(ERR_LDAPMODIFY_PROXY_AS_DN_NOT_DN.get( 1602 proxyAs.getIdentifierString(), dnString)); 1603 } 1604 } 1605 else if (! lowerProxyAsValue.startsWith("u:")) 1606 { 1607 throw new ArgumentException( 1608 ERR_LDAPMODIFY_PROXY_AS_VALUE_MISSING_PREFIX.get( 1609 proxyAs.getIdentifierString())); 1610 } 1611 } 1612 } 1613 1614 1615 1616 /** 1617 * {@inheritDoc} 1618 */ 1619 @Override() 1620 @NotNull() 1621 protected List<Control> getBindControls() 1622 { 1623 final ArrayList<Control> bindControls = new ArrayList<>(10); 1624 1625 if (bindControl.isPresent()) 1626 { 1627 bindControls.addAll(bindControl.getValues()); 1628 } 1629 1630 if (authorizationIdentity.isPresent()) 1631 { 1632 bindControls.add(new AuthorizationIdentityRequestControl(false)); 1633 } 1634 1635 if (generateAccessToken.isPresent()) 1636 { 1637 bindControls.add(new GenerateAccessTokenRequestControl()); 1638 } 1639 1640 if (getAuthorizationEntryAttribute.isPresent()) 1641 { 1642 bindControls.add(new GetAuthorizationEntryRequestControl(true, true, 1643 getAuthorizationEntryAttribute.getValues())); 1644 } 1645 1646 if (getRecentLoginHistory.isPresent()) 1647 { 1648 bindControls.add(new GetRecentLoginHistoryRequestControl()); 1649 } 1650 1651 if (getUserResourceLimits.isPresent()) 1652 { 1653 bindControls.add(new GetUserResourceLimitsRequestControl()); 1654 } 1655 1656 if (usePasswordPolicyControl.isPresent()) 1657 { 1658 bindControls.add(new PasswordPolicyRequestControl()); 1659 } 1660 1661 if (suppressOperationalAttributeUpdates.isPresent()) 1662 { 1663 final EnumSet<SuppressType> suppressTypes = 1664 EnumSet.noneOf(SuppressType.class); 1665 for (final String s : suppressOperationalAttributeUpdates.getValues()) 1666 { 1667 if (s.equalsIgnoreCase("last-access-time")) 1668 { 1669 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 1670 } 1671 else if (s.equalsIgnoreCase("last-login-time")) 1672 { 1673 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 1674 } 1675 else if (s.equalsIgnoreCase("last-login-ip")) 1676 { 1677 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 1678 } 1679 } 1680 1681 bindControls.add(new SuppressOperationalAttributeUpdateRequestControl( 1682 suppressTypes)); 1683 } 1684 1685 if (useJSONFormattedRequestControls.isPresent()) 1686 { 1687 final JSONFormattedRequestControl jsonFormattedRequestControl = 1688 JSONFormattedRequestControl.createWithControls(true, bindControls); 1689 bindControls.clear(); 1690 bindControls.add(jsonFormattedRequestControl); 1691 } 1692 1693 return bindControls; 1694 } 1695 1696 1697 1698 /** 1699 * {@inheritDoc} 1700 */ 1701 @Override() 1702 protected boolean supportsMultipleServers() 1703 { 1704 // We will support providing information about multiple servers. This tool 1705 // will not communicate with multiple servers concurrently, but it can 1706 // accept information about multiple servers in the event that a large set 1707 // of changes is to be processed and a server goes down in the middle of 1708 // those changes. In this case, we can resume processing on a newly-created 1709 // connection, possibly to a different server. 1710 return true; 1711 } 1712 1713 1714 1715 /** 1716 * {@inheritDoc} 1717 */ 1718 @Override() 1719 @NotNull() 1720 public LDAPConnectionOptions getConnectionOptions() 1721 { 1722 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 1723 1724 options.setUseSynchronousMode(true); 1725 options.setFollowReferrals(followReferrals.isPresent()); 1726 options.setUnsolicitedNotificationHandler(this); 1727 options.setResponseTimeoutMillis(0L); 1728 1729 return options; 1730 } 1731 1732 1733 1734 /** 1735 * {@inheritDoc} 1736 */ 1737 @Override() 1738 @NotNull() 1739 public ResultCode doToolProcessing() 1740 { 1741 // Examine the arguments to determine the sets of controls to use for each 1742 // type of request. 1743 final ArrayList<Control> addControls = new ArrayList<>(10); 1744 final ArrayList<Control> deleteControls = new ArrayList<>(10); 1745 final ArrayList<Control> modifyControls = new ArrayList<>(10); 1746 final ArrayList<Control> modifyDNControls = new ArrayList<>(10); 1747 final ArrayList<Control> searchControls = new ArrayList<>(10); 1748 try 1749 { 1750 createRequestControls(addControls, deleteControls, modifyControls, 1751 modifyDNControls, searchControls); 1752 } 1753 catch (final LDAPException le) 1754 { 1755 Debug.debugException(le); 1756 for (final String line : 1757 ResultUtils.formatResult(le, true, 0, WRAP_COLUMN)) 1758 { 1759 err(line); 1760 } 1761 return le.getResultCode(); 1762 } 1763 1764 1765 // If an encryption passphrase file was specified, then read its value. 1766 String encryptionPassphrase = null; 1767 if (encryptionPassphraseFile.isPresent()) 1768 { 1769 try 1770 { 1771 encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile( 1772 encryptionPassphraseFile.getValue()); 1773 } 1774 catch (final LDAPException e) 1775 { 1776 Debug.debugException(e); 1777 wrapErr(0, WRAP_COLUMN, e.getMessage()); 1778 return e.getResultCode(); 1779 } 1780 } 1781 1782 1783 LDAPConnectionPool connectionPool = null; 1784 LDIFReader ldifReader = null; 1785 LDIFWriter rejectWriter = null; 1786 try 1787 { 1788 // Create a connection pool that will be used to communicate with the 1789 // directory server. If we should use an administrative session, then 1790 // create a connect processor that will be used to start the session 1791 // before performing the bind. 1792 try 1793 { 1794 final StartAdministrativeSessionPostConnectProcessor p; 1795 if (useAdministrativeSession.isPresent()) 1796 { 1797 p = new StartAdministrativeSessionPostConnectProcessor( 1798 new StartAdministrativeSessionExtendedRequest(getToolName(), 1799 true)); 1800 } 1801 else 1802 { 1803 p = null; 1804 } 1805 1806 if (! dryRun.isPresent()) 1807 { 1808 connectionPool = getConnectionPool(1, 2, 0, p, null, true, 1809 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 1810 verbose.isPresent())); 1811 } 1812 } 1813 catch (final LDAPException le) 1814 { 1815 Debug.debugException(le); 1816 1817 // Unable to create the connection pool, which means that either the 1818 // connection could not be established or the attempt to authenticate 1819 // the connection failed. If the bind failed, then the report bind 1820 // result health check should have already reported the bind failure. 1821 // If the failure was something else, then display that failure result. 1822 if (le.getResultCode() != ResultCode.INVALID_CREDENTIALS) 1823 { 1824 for (final String line : 1825 ResultUtils.formatResult(le, true, 0, WRAP_COLUMN)) 1826 { 1827 err(line); 1828 } 1829 } 1830 return le.getResultCode(); 1831 } 1832 1833 if (connectionPool != null) 1834 { 1835 connectionPool.setRetryFailedOperationsDueToInvalidConnections( 1836 (! neverRetry.isPresent())); 1837 } 1838 1839 1840 // Report that the connection was successfully established. 1841 if (connectionPool != null) 1842 { 1843 try 1844 { 1845 final LDAPConnection connection = connectionPool.getConnection(); 1846 final String hostPort = connection.getHostPort(); 1847 connectionPool.releaseConnection(connection); 1848 commentToOut(INFO_LDAPMODIFY_CONNECTION_ESTABLISHED.get(hostPort)); 1849 out(); 1850 } 1851 catch (final LDAPException le) 1852 { 1853 Debug.debugException(le); 1854 // This should never happen. 1855 } 1856 } 1857 1858 1859 // If we should process the operations in a transaction, then start that 1860 // now. 1861 final ASN1OctetString txnID; 1862 if (useTransaction.isPresent()) 1863 { 1864 final Control[] startTxnControls; 1865 if (proxyAs.isPresent()) 1866 { 1867 // In a transaction, the proxied authorization control must only be 1868 // used in the start transaction request and not in any of the 1869 // subsequent operation requests. 1870 startTxnControls = new Control[] 1871 { 1872 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()) 1873 }; 1874 } 1875 else if (proxyV1As.isPresent()) 1876 { 1877 // In a transaction, the proxied authorization control must only be 1878 // used in the start transaction request and not in any of the 1879 // subsequent operation requests. 1880 startTxnControls = new Control[] 1881 { 1882 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()) 1883 }; 1884 } 1885 else 1886 { 1887 startTxnControls = StaticUtils.NO_CONTROLS; 1888 } 1889 1890 try 1891 { 1892 final StartTransactionExtendedResult startTxnResult = 1893 (StartTransactionExtendedResult) 1894 connectionPool.processExtendedOperation( 1895 new StartTransactionExtendedRequest(startTxnControls)); 1896 if (startTxnResult.getResultCode() == ResultCode.SUCCESS) 1897 { 1898 txnID = startTxnResult.getTransactionID(); 1899 1900 final TransactionSpecificationRequestControl c = 1901 new TransactionSpecificationRequestControl(txnID); 1902 addControls.add(c); 1903 deleteControls.add(c); 1904 modifyControls.add(c); 1905 modifyDNControls.add(c); 1906 1907 final String txnIDString; 1908 if (StaticUtils.isPrintableString(txnID.getValue())) 1909 { 1910 txnIDString = txnID.stringValue(); 1911 } 1912 else 1913 { 1914 final StringBuilder hexBuffer = new StringBuilder(); 1915 StaticUtils.toHex(txnID.getValue(), ":", hexBuffer); 1916 txnIDString = hexBuffer.toString(); 1917 } 1918 1919 commentToOut(INFO_LDAPMODIFY_STARTED_TXN.get(txnIDString)); 1920 } 1921 else 1922 { 1923 commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get( 1924 startTxnResult.getResultString())); 1925 return startTxnResult.getResultCode(); 1926 } 1927 } 1928 catch (final LDAPException le) 1929 { 1930 Debug.debugException(le); 1931 commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get( 1932 StaticUtils.getExceptionMessage(le))); 1933 return le.getResultCode(); 1934 } 1935 } 1936 else 1937 { 1938 txnID = null; 1939 } 1940 1941 1942 // Create an LDIF reader that will be used to read the changes to process. 1943 try 1944 { 1945 final InputStream ldifInputStream; 1946 if (ldifFile.isPresent()) 1947 { 1948 ldifInputStream = ToolUtils.getInputStreamForLDIFFiles( 1949 ldifFile.getValues(), encryptionPassphrase, getOut(), 1950 getErr()).getFirst(); 1951 } 1952 else 1953 { 1954 ldifInputStream = in; 1955 } 1956 1957 ldifReader = new LDIFReader(ldifInputStream, 0, null, null, 1958 characterSet.getValue()); 1959 } 1960 catch (final Exception e) 1961 { 1962 commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_LDIF_READER.get( 1963 StaticUtils.getExceptionMessage(e))); 1964 return ResultCode.LOCAL_ERROR; 1965 } 1966 1967 if (stripTrailingSpaces.isPresent()) 1968 { 1969 ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.STRIP); 1970 } 1971 1972 1973 // If appropriate, create a reject writer. 1974 if (rejectFile.isPresent()) 1975 { 1976 try 1977 { 1978 rejectWriter = new LDIFWriter(rejectFile.getValue()); 1979 1980 // Set the maximum allowed wrap column. This is better than setting a 1981 // wrap column of zero because it will ensure that comments don't get 1982 // wrapped either. 1983 rejectWriter.setWrapColumn(Integer.MAX_VALUE); 1984 } 1985 catch (final Exception e) 1986 { 1987 Debug.debugException(e); 1988 commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_REJECT_WRITER.get( 1989 rejectFile.getValue().getAbsolutePath(), 1990 StaticUtils.getExceptionMessage(e))); 1991 return ResultCode.LOCAL_ERROR; 1992 } 1993 } 1994 1995 1996 // If appropriate, create a rate limiter. 1997 final FixedRateBarrier rateLimiter; 1998 if (ratePerSecond.isPresent()) 1999 { 2000 rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue()); 2001 } 2002 else 2003 { 2004 rateLimiter = null; 2005 } 2006 2007 2008 // Iterate through the set of changes to process. 2009 boolean commitTransaction = true; 2010 ResultCode resultCode = null; 2011 final ArrayList<LDAPRequest> multiUpdateRequests = 2012 new ArrayList<>(10); 2013 final boolean isBulkModify = modifyEntriesMatchingFilter.isPresent() || 2014 modifyEntriesMatchingFiltersFromFile.isPresent() || 2015 modifyEntryWithDN.isPresent() || 2016 modifyEntriesWithDNsFromFile.isPresent(); 2017readChangeRecordLoop: 2018 while (true) 2019 { 2020 // If there is a rate limiter, then use it to sleep if necessary. 2021 if ((rateLimiter != null) && (! isBulkModify)) 2022 { 2023 rateLimiter.await(); 2024 } 2025 2026 2027 // Read the next LDIF change record. If we get an error then handle it 2028 // and abort if appropriate. 2029 final LDIFChangeRecord changeRecord; 2030 try 2031 { 2032 changeRecord = ldifReader.readChangeRecord(defaultAdd.isPresent()); 2033 } 2034 catch (final IOException ioe) 2035 { 2036 Debug.debugException(ioe); 2037 2038 final String message = ERR_LDAPMODIFY_IO_ERROR_READING_CHANGE.get( 2039 StaticUtils.getExceptionMessage(ioe)); 2040 commentToErr(message); 2041 writeRejectedChange(rejectWriter, message, null); 2042 commitTransaction = false; 2043 resultCode = ResultCode.LOCAL_ERROR; 2044 break; 2045 } 2046 catch (final LDIFException le) 2047 { 2048 Debug.debugException(le); 2049 2050 final StringBuilder buffer = new StringBuilder(); 2051 if (le.mayContinueReading() && (! useTransaction.isPresent())) 2052 { 2053 buffer.append( 2054 ERR_LDAPMODIFY_RECOVERABLE_LDIF_ERROR_READING_CHANGE.get( 2055 le.getLineNumber(), StaticUtils.getExceptionMessage(le))); 2056 } 2057 else 2058 { 2059 buffer.append( 2060 ERR_LDAPMODIFY_UNRECOVERABLE_LDIF_ERROR_READING_CHANGE.get( 2061 le.getLineNumber(), StaticUtils.getExceptionMessage(le))); 2062 } 2063 2064 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS)) 2065 { 2066 resultCode = ResultCode.LOCAL_ERROR; 2067 } 2068 2069 if ((le.getDataLines() != null) && (! le.getDataLines().isEmpty())) 2070 { 2071 buffer.append(StaticUtils.EOL); 2072 buffer.append(StaticUtils.EOL); 2073 buffer.append(ERR_LDAPMODIFY_INVALID_LINES.get()); 2074 buffer.append(StaticUtils.EOL); 2075 for (final String s : le.getDataLines()) 2076 { 2077 buffer.append(s); 2078 buffer.append(StaticUtils.EOL); 2079 } 2080 } 2081 2082 final String message = buffer.toString(); 2083 commentToErr(message); 2084 writeRejectedChange(rejectWriter, message, null); 2085 2086 if (le.mayContinueReading() && (! useTransaction.isPresent())) 2087 { 2088 continue; 2089 } 2090 else 2091 { 2092 commitTransaction = false; 2093 resultCode = ResultCode.LOCAL_ERROR; 2094 break; 2095 } 2096 } 2097 2098 2099 // If we read a null change record, then there are no more changes to 2100 // process. Otherwise, treat it appropriately based on the operation 2101 // type. 2102 if (changeRecord == null) 2103 { 2104 break; 2105 } 2106 2107 2108 // If we should modify entries matching a specified filter, then convert 2109 // the change record into a set of modifications. 2110 if (modifyEntriesMatchingFilter.isPresent()) 2111 { 2112 for (final Filter filter : modifyEntriesMatchingFilter.getValues()) 2113 { 2114 final ResultCode rc = handleModifyMatchingFilter(connectionPool, 2115 changeRecord, 2116 modifyEntriesMatchingFilter.getIdentifierString(), 2117 filter, searchControls, modifyControls, rateLimiter, 2118 rejectWriter); 2119 if (rc != ResultCode.SUCCESS) 2120 { 2121 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 2122 (resultCode == ResultCode.NO_OPERATION)) 2123 { 2124 resultCode = rc; 2125 } 2126 } 2127 } 2128 } 2129 2130 if (modifyEntriesMatchingFiltersFromFile.isPresent()) 2131 { 2132 for (final File f : modifyEntriesMatchingFiltersFromFile.getValues()) 2133 { 2134 final FilterFileReader filterReader; 2135 try 2136 { 2137 filterReader = new FilterFileReader(f); 2138 } 2139 catch (final Exception e) 2140 { 2141 Debug.debugException(e); 2142 commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_FILTER_FILE.get( 2143 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 2144 return ResultCode.LOCAL_ERROR; 2145 } 2146 2147 try 2148 { 2149 while (true) 2150 { 2151 final Filter filter; 2152 try 2153 { 2154 filter = filterReader.readFilter(); 2155 } 2156 catch (final IOException ioe) 2157 { 2158 Debug.debugException(ioe); 2159 commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_FILTER_FILE.get( 2160 f.getAbsolutePath(), 2161 StaticUtils.getExceptionMessage(ioe))); 2162 return ResultCode.LOCAL_ERROR; 2163 } 2164 catch (final LDAPException le) 2165 { 2166 Debug.debugException(le); 2167 commentToErr(le.getMessage()); 2168 if (continueOnError.isPresent()) 2169 { 2170 if ((resultCode == null) || 2171 (resultCode == ResultCode.SUCCESS) || 2172 (resultCode == ResultCode.NO_OPERATION)) 2173 { 2174 resultCode = le.getResultCode(); 2175 } 2176 continue; 2177 } 2178 else 2179 { 2180 return le.getResultCode(); 2181 } 2182 } 2183 2184 if (filter == null) 2185 { 2186 break; 2187 } 2188 2189 final ResultCode rc = handleModifyMatchingFilter(connectionPool, 2190 changeRecord, 2191 modifyEntriesMatchingFiltersFromFile.getIdentifierString(), 2192 filter, searchControls, modifyControls, rateLimiter, 2193 rejectWriter); 2194 if (rc != ResultCode.SUCCESS) 2195 { 2196 if ((resultCode == null) || 2197 (resultCode == ResultCode.SUCCESS) || 2198 (resultCode == ResultCode.NO_OPERATION)) 2199 { 2200 resultCode = rc; 2201 } 2202 } 2203 } 2204 } 2205 finally 2206 { 2207 try 2208 { 2209 filterReader.close(); 2210 } 2211 catch (final Exception e) 2212 { 2213 Debug.debugException(e); 2214 } 2215 } 2216 } 2217 } 2218 2219 if (modifyEntryWithDN.isPresent()) 2220 { 2221 for (final DN dn : modifyEntryWithDN.getValues()) 2222 { 2223 final ResultCode rc = handleModifyWithDN(connectionPool, 2224 changeRecord, modifyEntryWithDN.getIdentifierString(), dn, 2225 modifyControls, rateLimiter, rejectWriter); 2226 if (rc != ResultCode.SUCCESS) 2227 { 2228 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 2229 (resultCode == ResultCode.NO_OPERATION)) 2230 { 2231 resultCode = rc; 2232 } 2233 } 2234 } 2235 } 2236 2237 if (modifyEntriesWithDNsFromFile.isPresent()) 2238 { 2239 for (final File f : modifyEntriesWithDNsFromFile.getValues()) 2240 { 2241 final DNFileReader dnReader; 2242 try 2243 { 2244 dnReader = new DNFileReader(f); 2245 } 2246 catch (final Exception e) 2247 { 2248 Debug.debugException(e); 2249 commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_DN_FILE.get( 2250 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 2251 return ResultCode.LOCAL_ERROR; 2252 } 2253 2254 try 2255 { 2256 while (true) 2257 { 2258 final DN dn; 2259 try 2260 { 2261 dn = dnReader.readDN(); 2262 } 2263 catch (final IOException ioe) 2264 { 2265 Debug.debugException(ioe); 2266 commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_DN_FILE.get( 2267 f.getAbsolutePath(), 2268 StaticUtils.getExceptionMessage(ioe))); 2269 return ResultCode.LOCAL_ERROR; 2270 } 2271 catch (final LDAPException le) 2272 { 2273 Debug.debugException(le); 2274 commentToErr(le.getMessage()); 2275 if (continueOnError.isPresent()) 2276 { 2277 if ((resultCode == null) || 2278 (resultCode == ResultCode.SUCCESS) || 2279 (resultCode == ResultCode.NO_OPERATION)) 2280 { 2281 resultCode = le.getResultCode(); 2282 } 2283 continue; 2284 } 2285 else 2286 { 2287 return le.getResultCode(); 2288 } 2289 } 2290 2291 if (dn == null) 2292 { 2293 break; 2294 } 2295 2296 final ResultCode rc = handleModifyWithDN(connectionPool, 2297 changeRecord, 2298 modifyEntriesWithDNsFromFile.getIdentifierString(), dn, 2299 modifyControls, rateLimiter, rejectWriter); 2300 if (rc != ResultCode.SUCCESS) 2301 { 2302 if ((resultCode == null) || 2303 (resultCode == ResultCode.SUCCESS) || 2304 (resultCode == ResultCode.NO_OPERATION)) 2305 { 2306 resultCode = rc; 2307 } 2308 } 2309 } 2310 } 2311 finally 2312 { 2313 try 2314 { 2315 dnReader.close(); 2316 } 2317 catch (final Exception e) 2318 { 2319 Debug.debugException(e); 2320 } 2321 } 2322 } 2323 } 2324 2325 if (isBulkModify) 2326 { 2327 continue; 2328 } 2329 2330 try 2331 { 2332 final ResultCode rc; 2333 if (changeRecord instanceof LDIFAddChangeRecord) 2334 { 2335 rc = doAdd((LDIFAddChangeRecord) changeRecord, addControls, 2336 connectionPool, multiUpdateRequests, rejectWriter); 2337 } 2338 else if (changeRecord instanceof LDIFDeleteChangeRecord) 2339 { 2340 rc = doDelete((LDIFDeleteChangeRecord) changeRecord, deleteControls, 2341 connectionPool, multiUpdateRequests, rejectWriter); 2342 } 2343 else if (changeRecord instanceof LDIFModifyChangeRecord) 2344 { 2345 rc = doModify((LDIFModifyChangeRecord) changeRecord, modifyControls, 2346 connectionPool, multiUpdateRequests, rejectWriter); 2347 } 2348 else if (changeRecord instanceof LDIFModifyDNChangeRecord) 2349 { 2350 rc = doModifyDN((LDIFModifyDNChangeRecord) changeRecord, 2351 modifyDNControls, connectionPool, multiUpdateRequests, 2352 rejectWriter); 2353 } 2354 else 2355 { 2356 // This should never happen. 2357 commentToErr(ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get()); 2358 for (final String line : changeRecord.toLDIF()) 2359 { 2360 err("# " + line); 2361 } 2362 throw new LDAPException(ResultCode.PARAM_ERROR, 2363 ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get() + 2364 changeRecord.toString()); 2365 } 2366 2367 if ((resultCode == null) && (rc != ResultCode.SUCCESS)) 2368 { 2369 resultCode = rc; 2370 } 2371 } 2372 catch (final LDAPException le) 2373 { 2374 Debug.debugException(le); 2375 2376 commitTransaction = false; 2377 if (continueOnError.isPresent()) 2378 { 2379 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 2380 (resultCode == ResultCode.NO_OPERATION)) 2381 { 2382 resultCode = le.getResultCode(); 2383 } 2384 } 2385 else 2386 { 2387 resultCode = le.getResultCode(); 2388 break; 2389 } 2390 } 2391 } 2392 2393 2394 // If the operations are part of a transaction, then commit or abort that 2395 // transaction now. Otherwise, if they should be part of a multi-update 2396 // operation, then process that now. 2397 if (useTransaction.isPresent()) 2398 { 2399 LDAPResult endTxnResult; 2400 final EndTransactionExtendedRequest endTxnRequest = 2401 new EndTransactionExtendedRequest(txnID, commitTransaction); 2402 try 2403 { 2404 endTxnResult = connectionPool.processExtendedOperation(endTxnRequest); 2405 } 2406 catch (final LDAPException le) 2407 { 2408 endTxnResult = le.toLDAPResult(); 2409 } 2410 2411 displayResult(endTxnResult, false); 2412 if (((resultCode == null) || (resultCode == ResultCode.SUCCESS)) && 2413 (endTxnResult.getResultCode() != ResultCode.SUCCESS)) 2414 { 2415 resultCode = endTxnResult.getResultCode(); 2416 } 2417 } 2418 else if (multiUpdateErrorBehavior.isPresent()) 2419 { 2420 final MultiUpdateErrorBehavior errorBehavior; 2421 if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase("atomic")) 2422 { 2423 errorBehavior = MultiUpdateErrorBehavior.ATOMIC; 2424 } 2425 else if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase( 2426 "abort-on-error")) 2427 { 2428 errorBehavior = MultiUpdateErrorBehavior.ABORT_ON_ERROR; 2429 } 2430 else 2431 { 2432 errorBehavior = MultiUpdateErrorBehavior.CONTINUE_ON_ERROR; 2433 } 2434 2435 final Control[] multiUpdateControls; 2436 if (proxyAs.isPresent()) 2437 { 2438 multiUpdateControls = new Control[] 2439 { 2440 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()) 2441 }; 2442 } 2443 else if (proxyV1As.isPresent()) 2444 { 2445 multiUpdateControls = new Control[] 2446 { 2447 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()) 2448 }; 2449 } 2450 else 2451 { 2452 multiUpdateControls = StaticUtils.NO_CONTROLS; 2453 } 2454 2455 ExtendedResult multiUpdateResult; 2456 try 2457 { 2458 commentToOut(INFO_LDAPMODIFY_SENDING_MULTI_UPDATE_REQUEST.get()); 2459 final MultiUpdateExtendedRequest multiUpdateRequest = 2460 new MultiUpdateExtendedRequest(errorBehavior, 2461 multiUpdateRequests, multiUpdateControls); 2462 multiUpdateResult = 2463 connectionPool.processExtendedOperation(multiUpdateRequest); 2464 } 2465 catch (final LDAPException le) 2466 { 2467 multiUpdateResult = new ExtendedResult(le); 2468 } 2469 2470 displayResult(multiUpdateResult, false); 2471 resultCode = multiUpdateResult.getResultCode(); 2472 } 2473 2474 2475 if (resultCode == null) 2476 { 2477 return ResultCode.SUCCESS; 2478 } 2479 else 2480 { 2481 return resultCode; 2482 } 2483 } 2484 finally 2485 { 2486 if (rejectWriter != null) 2487 { 2488 try 2489 { 2490 rejectWriter.close(); 2491 } 2492 catch (final Exception e) 2493 { 2494 Debug.debugException(e); 2495 } 2496 } 2497 2498 if (ldifReader != null) 2499 { 2500 try 2501 { 2502 ldifReader.close(); 2503 } 2504 catch (final Exception e) 2505 { 2506 Debug.debugException(e); 2507 } 2508 } 2509 2510 if (connectionPool != null) 2511 { 2512 try 2513 { 2514 connectionPool.close(); 2515 } 2516 catch (final Exception e) 2517 { 2518 Debug.debugException(e); 2519 } 2520 } 2521 } 2522 } 2523 2524 2525 2526 /** 2527 * Handles the processing for a change record when the tool should modify 2528 * entries matching a given filter. 2529 * 2530 * @param connectionPool The connection pool to use to communicate with 2531 * the directory server. 2532 * @param changeRecord The LDIF change record to be processed. 2533 * @param argIdentifierString The identifier string for the argument used to 2534 * specify the filter to use to identify the 2535 * entries to modify. 2536 * @param filter The filter to use to identify the entries to 2537 * modify. 2538 * @param searchControls The set of controls to include in the search 2539 * request. 2540 * @param modifyControls The set of controls to include in the modify 2541 * requests. 2542 * @param rateLimiter The fixed-rate barrier to use for rate 2543 * limiting. It may be {@code null} if no rate 2544 * limiting is required. 2545 * @param rejectWriter The reject writer to use to record information 2546 * about any failed operations. 2547 * 2548 * @return A result code obtained from processing. 2549 */ 2550 @NotNull() 2551 private ResultCode handleModifyMatchingFilter( 2552 @NotNull final LDAPConnectionPool connectionPool, 2553 @NotNull final LDIFChangeRecord changeRecord, 2554 @NotNull final String argIdentifierString, 2555 @NotNull final Filter filter, 2556 @NotNull final List<Control> searchControls, 2557 @NotNull final List<Control> modifyControls, 2558 @Nullable final FixedRateBarrier rateLimiter, 2559 @Nullable final LDIFWriter rejectWriter) 2560 { 2561 // If the provided change record isn't a modify change record, then that's 2562 // an error. Reject it. 2563 if (! (changeRecord instanceof LDIFModifyChangeRecord)) 2564 { 2565 writeRejectedChange(rejectWriter, 2566 ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString), 2567 changeRecord); 2568 return ResultCode.PARAM_ERROR; 2569 } 2570 2571 final LDIFModifyChangeRecord modifyChangeRecord = 2572 (LDIFModifyChangeRecord) changeRecord; 2573 final HashSet<DN> processedDNs = 2574 new HashSet<>(StaticUtils.computeMapCapacity(100)); 2575 2576 2577 // If we need to use the simple paged results control, then we may have to 2578 // issue multiple searches. 2579 ASN1OctetString pagedResultsCookie = null; 2580 long entriesProcessed = 0L; 2581 ResultCode resultCode = ResultCode.SUCCESS; 2582 while (true) 2583 { 2584 // Construct the search request to send. 2585 final LDAPModifySearchListener listener = 2586 new LDAPModifySearchListener(this, modifyChangeRecord, filter, 2587 modifyControls, connectionPool, rateLimiter, rejectWriter, 2588 processedDNs); 2589 2590 final SearchRequest searchRequest = 2591 new SearchRequest(listener, modifyChangeRecord.getDN(), 2592 SearchScope.SUB, filter, SearchRequest.NO_ATTRIBUTES); 2593 searchRequest.setControls(searchControls); 2594 if (searchPageSize.isPresent()) 2595 { 2596 searchRequest.addControl(new SimplePagedResultsControl( 2597 searchPageSize.getValue(), pagedResultsCookie)); 2598 } 2599 2600 2601 // The connection pool's automatic retry feature can't work for searches 2602 // that return one or more entries before encountering a failure. To get 2603 // around that, we'll check a connection out of the pool and use it to 2604 // process the search. If an error occurs that indicates the connection 2605 // is no longer valid, we can replace it with a newly-established 2606 // connection and try again. The search result listener will ensure that 2607 // no entry gets updated twice. 2608 LDAPConnection connection; 2609 try 2610 { 2611 connection = connectionPool.getConnection(); 2612 } 2613 catch (final LDAPException le) 2614 { 2615 Debug.debugException(le); 2616 2617 writeRejectedChange(rejectWriter, 2618 ERR_LDAPMODIFY_CANNOT_GET_SEARCH_CONNECTION.get( 2619 modifyChangeRecord.getDN(), String.valueOf(filter), 2620 StaticUtils.getExceptionMessage(le)), 2621 modifyChangeRecord, le.toLDAPResult()); 2622 return le.getResultCode(); 2623 } 2624 2625 SearchResult searchResult; 2626 boolean connectionValid = false; 2627 try 2628 { 2629 try 2630 { 2631 searchResult = connection.search(searchRequest); 2632 } 2633 catch (final LDAPSearchException lse) 2634 { 2635 searchResult = lse.getSearchResult(); 2636 } 2637 2638 if (searchResult.getResultCode() == ResultCode.SUCCESS) 2639 { 2640 connectionValid = true; 2641 } 2642 else if (searchResult.getResultCode().isConnectionUsable()) 2643 { 2644 connectionValid = true; 2645 writeRejectedChange(rejectWriter, 2646 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2647 String.valueOf(filter)), 2648 modifyChangeRecord, searchResult); 2649 return searchResult.getResultCode(); 2650 } 2651 else if (! neverRetry.isPresent()) 2652 { 2653 try 2654 { 2655 connection = connectionPool.replaceDefunctConnection(connection); 2656 } 2657 catch (final LDAPException le) 2658 { 2659 Debug.debugException(le); 2660 writeRejectedChange(rejectWriter, 2661 ERR_LDAPMODIFY_SEARCH_FAILED_CANNOT_RECONNECT.get( 2662 modifyChangeRecord.getDN(), String.valueOf(filter)), 2663 modifyChangeRecord, searchResult); 2664 return searchResult.getResultCode(); 2665 } 2666 2667 try 2668 { 2669 searchResult = connection.search(searchRequest); 2670 } 2671 catch (final LDAPSearchException lse) 2672 { 2673 Debug.debugException(lse); 2674 searchResult = lse.getSearchResult(); 2675 } 2676 2677 if (searchResult.getResultCode() == ResultCode.SUCCESS) 2678 { 2679 connectionValid = true; 2680 } 2681 else 2682 { 2683 connectionValid = searchResult.getResultCode().isConnectionUsable(); 2684 writeRejectedChange(rejectWriter, 2685 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2686 String.valueOf(filter)), 2687 modifyChangeRecord, searchResult); 2688 return searchResult.getResultCode(); 2689 } 2690 } 2691 else 2692 { 2693 writeRejectedChange(rejectWriter, 2694 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2695 String.valueOf(filter)), 2696 modifyChangeRecord, searchResult); 2697 return searchResult.getResultCode(); 2698 } 2699 } 2700 finally 2701 { 2702 if (connectionValid) 2703 { 2704 connectionPool.releaseConnection(connection); 2705 } 2706 else 2707 { 2708 connectionPool.releaseDefunctConnection(connection); 2709 } 2710 } 2711 2712 searchResult = LDAPSearch.handleJSONEncodedResponseControls(searchResult); 2713 2714 2715 // If we've gotten here, then the search was successful. Check to see if 2716 // any of the modifications failed, and if so then update the result code 2717 // accordingly. 2718 if ((resultCode == ResultCode.SUCCESS) && 2719 (listener.getResultCode() != ResultCode.SUCCESS)) 2720 { 2721 resultCode = listener.getResultCode(); 2722 } 2723 2724 2725 // If the search used the simple paged results control then we may need to 2726 // repeat the search to get the next page. 2727 entriesProcessed += searchResult.getEntryCount(); 2728 if (searchPageSize.isPresent()) 2729 { 2730 final SimplePagedResultsControl responseControl; 2731 try 2732 { 2733 responseControl = SimplePagedResultsControl.get(searchResult); 2734 } 2735 catch (final LDAPException le) 2736 { 2737 Debug.debugException(le); 2738 writeRejectedChange(rejectWriter, 2739 ERR_LDAPMODIFY_CANNOT_DECODE_PAGED_RESULTS_CONTROL.get( 2740 modifyChangeRecord.getDN(), String.valueOf(filter)), 2741 modifyChangeRecord, le.toLDAPResult()); 2742 return le.getResultCode(); 2743 } 2744 2745 if (responseControl == null) 2746 { 2747 writeRejectedChange(rejectWriter, 2748 ERR_LDAPMODIFY_MISSING_PAGED_RESULTS_RESPONSE.get( 2749 modifyChangeRecord.getDN(), String.valueOf(filter)), 2750 modifyChangeRecord); 2751 return ResultCode.CONTROL_NOT_FOUND; 2752 } 2753 else 2754 { 2755 pagedResultsCookie = responseControl.getCookie(); 2756 if (responseControl.moreResultsToReturn()) 2757 { 2758 if (verbose.isPresent()) 2759 { 2760 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED_MORE_PAGES.get( 2761 modifyChangeRecord.getDN(), String.valueOf(filter), 2762 entriesProcessed)); 2763 for (final String resultLine : 2764 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2765 { 2766 out(resultLine); 2767 } 2768 out(); 2769 } 2770 } 2771 else 2772 { 2773 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get( 2774 entriesProcessed, modifyChangeRecord.getDN(), 2775 String.valueOf(filter))); 2776 if (verbose.isPresent()) 2777 { 2778 for (final String resultLine : 2779 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2780 { 2781 out(resultLine); 2782 } 2783 } 2784 2785 out(); 2786 return resultCode; 2787 } 2788 } 2789 } 2790 else 2791 { 2792 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get( 2793 entriesProcessed, modifyChangeRecord.getDN(), 2794 String.valueOf(filter))); 2795 if (verbose.isPresent()) 2796 { 2797 for (final String resultLine : 2798 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2799 { 2800 out(resultLine); 2801 } 2802 } 2803 2804 out(); 2805 return resultCode; 2806 } 2807 } 2808 } 2809 2810 2811 2812 /** 2813 * Handles the processing for a change record when the tool should modify an 2814 * entry with a given DN instead of the DN contained in the change record. 2815 * 2816 * @param connectionPool The connection pool to use to communicate with 2817 * the directory server. 2818 * @param changeRecord The LDIF change record to be processed. 2819 * @param argIdentifierString The identifier string for the argument used to 2820 * specify the DN of the entry to modify. 2821 * @param dn The DN of the entry to modify. 2822 * @param modifyControls The set of controls to include in the modify 2823 * requests. 2824 * @param rateLimiter The fixed-rate barrier to use for rate 2825 * limiting. It may be {@code null} if no rate 2826 * limiting is required. 2827 * @param rejectWriter The reject writer to use to record information 2828 * about any failed operations. 2829 * 2830 * @return A result code obtained from processing. 2831 */ 2832 @NotNull() 2833 private ResultCode handleModifyWithDN( 2834 @NotNull final LDAPConnectionPool connectionPool, 2835 @NotNull final LDIFChangeRecord changeRecord, 2836 @NotNull final String argIdentifierString, 2837 @NotNull final DN dn, 2838 @NotNull final List<Control> modifyControls, 2839 @Nullable final FixedRateBarrier rateLimiter, 2840 @Nullable final LDIFWriter rejectWriter) 2841 { 2842 // If the provided change record isn't a modify change record, then that's 2843 // an error. Reject it. 2844 if (! (changeRecord instanceof LDIFModifyChangeRecord)) 2845 { 2846 writeRejectedChange(rejectWriter, 2847 ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString), 2848 changeRecord); 2849 return ResultCode.PARAM_ERROR; 2850 } 2851 2852 2853 // Create a new modify change record with the provided DN instead of the 2854 // original DN. 2855 final LDIFModifyChangeRecord originalChangeRecord = 2856 (LDIFModifyChangeRecord) changeRecord; 2857 final LDIFModifyChangeRecord updatedChangeRecord = 2858 new LDIFModifyChangeRecord(dn.toString(), 2859 originalChangeRecord.getModifications(), 2860 originalChangeRecord.getControls()); 2861 2862 if (rateLimiter != null) 2863 { 2864 rateLimiter.await(); 2865 } 2866 2867 try 2868 { 2869 return doModify(updatedChangeRecord, modifyControls, connectionPool, null, 2870 rejectWriter); 2871 } 2872 catch (final LDAPException le) 2873 { 2874 Debug.debugException(le); 2875 return le.getResultCode(); 2876 } 2877 } 2878 2879 2880 2881 /** 2882 * Populates lists of request controls that should be included in requests 2883 * of various types. 2884 * 2885 * @param addControls The list of controls to include in add requests. 2886 * @param deleteControls The list of controls to include in delete 2887 * requests. 2888 * @param modifyControls The list of controls to include in modify 2889 * requests. 2890 * @param modifyDNControls The list of controls to include in modify DN 2891 * requests. 2892 * @param searchControls The list of controls to include in search 2893 * requests. 2894 * 2895 * @throws LDAPException If a problem is encountered while creating any of 2896 * the requested controls. 2897 */ 2898 private void createRequestControls( 2899 @NotNull final List<Control> addControls, 2900 @NotNull final List<Control> deleteControls, 2901 @NotNull final List<Control> modifyControls, 2902 @NotNull final List<Control> modifyDNControls, 2903 @NotNull final List<Control> searchControls) 2904 throws LDAPException 2905 { 2906 if (addControl.isPresent()) 2907 { 2908 addControls.addAll(addControl.getValues()); 2909 } 2910 2911 if (deleteControl.isPresent()) 2912 { 2913 deleteControls.addAll(deleteControl.getValues()); 2914 } 2915 2916 if (modifyControl.isPresent()) 2917 { 2918 modifyControls.addAll(modifyControl.getValues()); 2919 } 2920 2921 if (modifyDNControl.isPresent()) 2922 { 2923 modifyDNControls.addAll(modifyDNControl.getValues()); 2924 } 2925 2926 if (accessLogField.isPresent()) 2927 { 2928 final Map<String,JSONValue> fields = new LinkedHashMap<>(); 2929 for (final String nameValueStr : accessLogField.getValues()) 2930 { 2931 final int colonPos = nameValueStr.indexOf(':'); 2932 if (colonPos < 0) 2933 { 2934 throw new LDAPException(ResultCode.PARAM_ERROR, 2935 ERR_LDAPMODIFY_ACCESS_LOG_FIELD_NO_COLON.get( 2936 accessLogField.getIdentifierString(), nameValueStr)); 2937 } 2938 2939 final String fieldName = nameValueStr.substring(0, colonPos); 2940 if (fields.containsKey(fieldName)) 2941 { 2942 throw new LDAPException(ResultCode.PARAM_ERROR, 2943 ERR_LDAPMODIFY_ACCESS_LOG_FIELD_DUPLICATE_FIELD.get( 2944 accessLogField.getIdentifierString(), fieldName)); 2945 } 2946 2947 final String valueStr = nameValueStr.substring(colonPos + 1); 2948 if (valueStr.equalsIgnoreCase("true")) 2949 { 2950 fields.put(fieldName, JSONBoolean.TRUE); 2951 } 2952 else if (valueStr.equalsIgnoreCase("false")) 2953 { 2954 fields.put(fieldName, JSONBoolean.FALSE); 2955 } 2956 else 2957 { 2958 try 2959 { 2960 final BigDecimal d = new BigDecimal(valueStr); 2961 fields.put(fieldName, new JSONNumber(d)); 2962 } 2963 catch (final Exception e) 2964 { 2965 Debug.debugException(e); 2966 fields.put(fieldName, new JSONString(valueStr)); 2967 } 2968 } 2969 } 2970 2971 final AccessLogFieldRequestControl c = 2972 new AccessLogFieldRequestControl(false, new JSONObject(fields)); 2973 addControls.add(c); 2974 deleteControls.add(c); 2975 modifyControls.add(c); 2976 modifyDNControls.add(c); 2977 } 2978 2979 if (operationControl.isPresent()) 2980 { 2981 addControls.addAll(operationControl.getValues()); 2982 deleteControls.addAll(operationControl.getValues()); 2983 modifyControls.addAll(operationControl.getValues()); 2984 modifyDNControls.addAll(operationControl.getValues()); 2985 } 2986 2987 addControls.addAll(routeToBackendSetRequestControls); 2988 deleteControls.addAll(routeToBackendSetRequestControls); 2989 modifyControls.addAll(routeToBackendSetRequestControls); 2990 modifyDNControls.addAll(routeToBackendSetRequestControls); 2991 2992 if (noOperation.isPresent()) 2993 { 2994 final NoOpRequestControl c = new NoOpRequestControl(); 2995 addControls.add(c); 2996 deleteControls.add(c); 2997 modifyControls.add(c); 2998 modifyDNControls.add(c); 2999 } 3000 3001 if (generatePassword.isPresent()) 3002 { 3003 addControls.add(new GeneratePasswordRequestControl()); 3004 } 3005 3006 if (getBackendSetID.isPresent()) 3007 { 3008 final GetBackendSetIDRequestControl c = 3009 new GetBackendSetIDRequestControl(false); 3010 addControls.add(c); 3011 deleteControls.add(c); 3012 modifyControls.add(c); 3013 modifyDNControls.add(c); 3014 } 3015 3016 if (getServerID.isPresent()) 3017 { 3018 final GetServerIDRequestControl c = 3019 new GetServerIDRequestControl(false); 3020 addControls.add(c); 3021 deleteControls.add(c); 3022 modifyControls.add(c); 3023 modifyDNControls.add(c); 3024 } 3025 3026 if (ignoreNoUserModification.isPresent()) 3027 { 3028 addControls.add(new IgnoreNoUserModificationRequestControl(false)); 3029 modifyControls.add(new IgnoreNoUserModificationRequestControl(false)); 3030 } 3031 3032 if (nameWithEntryUUID.isPresent()) 3033 { 3034 addControls.add(new NameWithEntryUUIDRequestControl(true)); 3035 } 3036 3037 if (permissiveModify.isPresent()) 3038 { 3039 modifyControls.add(new PermissiveModifyRequestControl(false)); 3040 } 3041 3042 if (routeToServer.isPresent()) 3043 { 3044 final RouteToServerRequestControl c = 3045 new RouteToServerRequestControl(false, 3046 routeToServer.getValue(), false, false, false); 3047 addControls.add(c); 3048 deleteControls.add(c); 3049 modifyControls.add(c); 3050 modifyDNControls.add(c); 3051 } 3052 3053 if (suppressReferentialIntegrityUpdates.isPresent()) 3054 { 3055 final SuppressReferentialIntegrityUpdatesRequestControl c = 3056 new SuppressReferentialIntegrityUpdatesRequestControl(true); 3057 deleteControls.add(c); 3058 modifyDNControls.add(c); 3059 } 3060 3061 if (suppressOperationalAttributeUpdates.isPresent()) 3062 { 3063 final EnumSet<SuppressType> suppressTypes = 3064 EnumSet.noneOf(SuppressType.class); 3065 for (final String s : suppressOperationalAttributeUpdates.getValues()) 3066 { 3067 if (s.equalsIgnoreCase("last-access-time")) 3068 { 3069 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 3070 } 3071 else if (s.equalsIgnoreCase("last-login-time")) 3072 { 3073 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 3074 } 3075 else if (s.equalsIgnoreCase("last-login-ip")) 3076 { 3077 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 3078 } 3079 else if (s.equalsIgnoreCase("lastmod")) 3080 { 3081 suppressTypes.add(SuppressType.LASTMOD); 3082 } 3083 } 3084 3085 final SuppressOperationalAttributeUpdateRequestControl c = 3086 new SuppressOperationalAttributeUpdateRequestControl(suppressTypes); 3087 addControls.add(c); 3088 deleteControls.add(c); 3089 modifyControls.add(c); 3090 modifyDNControls.add(c); 3091 } 3092 3093 if (usePasswordPolicyControl.isPresent()) 3094 { 3095 final PasswordPolicyRequestControl c = new PasswordPolicyRequestControl(); 3096 addControls.add(c); 3097 modifyControls.add(c); 3098 } 3099 3100 if (assuredReplication.isPresent()) 3101 { 3102 AssuredReplicationLocalLevel localLevel = null; 3103 if (assuredReplicationLocalLevel.isPresent()) 3104 { 3105 final String level = assuredReplicationLocalLevel.getValue(); 3106 if (level.equalsIgnoreCase("none")) 3107 { 3108 localLevel = AssuredReplicationLocalLevel.NONE; 3109 } 3110 else if (level.equalsIgnoreCase("received-any-server")) 3111 { 3112 localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER; 3113 } 3114 else if (level.equalsIgnoreCase("processed-all-servers")) 3115 { 3116 localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS; 3117 } 3118 } 3119 3120 AssuredReplicationRemoteLevel remoteLevel = null; 3121 if (assuredReplicationRemoteLevel.isPresent()) 3122 { 3123 final String level = assuredReplicationRemoteLevel.getValue(); 3124 if (level.equalsIgnoreCase("none")) 3125 { 3126 remoteLevel = AssuredReplicationRemoteLevel.NONE; 3127 } 3128 else if (level.equalsIgnoreCase("received-any-remote-location")) 3129 { 3130 remoteLevel = 3131 AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION; 3132 } 3133 else if (level.equalsIgnoreCase("received-all-remote-locations")) 3134 { 3135 remoteLevel = 3136 AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS; 3137 } 3138 else if (level.equalsIgnoreCase("processed-all-remote-servers")) 3139 { 3140 remoteLevel = 3141 AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS; 3142 } 3143 } 3144 3145 Long timeoutMillis = null; 3146 if (assuredReplicationTimeout.isPresent()) 3147 { 3148 timeoutMillis = 3149 assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS); 3150 } 3151 3152 final AssuredReplicationRequestControl c = 3153 new AssuredReplicationRequestControl(true, localLevel, localLevel, 3154 remoteLevel, remoteLevel, timeoutMillis, false); 3155 addControls.add(c); 3156 deleteControls.add(c); 3157 modifyControls.add(c); 3158 modifyDNControls.add(c); 3159 } 3160 3161 if (hardDelete.isPresent() && (! clientSideSubtreeDelete.isPresent())) 3162 { 3163 deleteControls.add(new HardDeleteRequestControl(true)); 3164 } 3165 3166 if (replicationRepair.isPresent()) 3167 { 3168 final ReplicationRepairRequestControl c = 3169 new ReplicationRepairRequestControl(); 3170 addControls.add(c); 3171 deleteControls.add(c); 3172 modifyControls.add(c); 3173 modifyDNControls.add(c); 3174 } 3175 3176 if (softDelete.isPresent()) 3177 { 3178 deleteControls.add(new SoftDeleteRequestControl(true, true)); 3179 } 3180 3181 if (serverSideSubtreeDelete.isPresent()) 3182 { 3183 deleteControls.add(new SubtreeDeleteRequestControl()); 3184 } 3185 3186 if (assertionFilter.isPresent()) 3187 { 3188 final AssertionRequestControl c = new AssertionRequestControl( 3189 assertionFilter.getValue(), true); 3190 addControls.add(c); 3191 deleteControls.add(c); 3192 modifyControls.add(c); 3193 modifyDNControls.add(c); 3194 } 3195 3196 if (operationPurpose.isPresent()) 3197 { 3198 final OperationPurposeRequestControl c = 3199 new OperationPurposeRequestControl(false, "ldapmodify", 3200 Version.NUMERIC_VERSION_STRING, 3201 LDAPModify.class.getName() + ".createRequestControls", 3202 operationPurpose.getValue()); 3203 addControls.add(c); 3204 deleteControls.add(c); 3205 modifyControls.add(c); 3206 modifyDNControls.add(c); 3207 } 3208 3209 if (manageDsaIT.isPresent()) 3210 { 3211 final ManageDsaITRequestControl c = new ManageDsaITRequestControl(true); 3212 addControls.add(c); 3213 if (! clientSideSubtreeDelete.isPresent()) 3214 { 3215 deleteControls.add(c); 3216 } 3217 modifyControls.add(c); 3218 modifyDNControls.add(c); 3219 } 3220 3221 if (passwordUpdateBehavior.isPresent()) 3222 { 3223 final PasswordUpdateBehaviorRequestControl c = 3224 createPasswordUpdateBehaviorRequestControl( 3225 passwordUpdateBehavior.getIdentifierString(), 3226 passwordUpdateBehavior.getValues()); 3227 addControls.add(c); 3228 modifyControls.add(c); 3229 } 3230 3231 if (preReadAttribute.isPresent()) 3232 { 3233 final ArrayList<String> attrList = new ArrayList<>(10); 3234 for (final String value : preReadAttribute.getValues()) 3235 { 3236 final StringTokenizer tokenizer = new StringTokenizer(value, ", "); 3237 while (tokenizer.hasMoreTokens()) 3238 { 3239 attrList.add(tokenizer.nextToken()); 3240 } 3241 } 3242 3243 final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS); 3244 final PreReadRequestControl c = new PreReadRequestControl(attrArray); 3245 deleteControls.add(c); 3246 modifyControls.add(c); 3247 modifyDNControls.add(c); 3248 } 3249 3250 if (postReadAttribute.isPresent()) 3251 { 3252 final ArrayList<String> attrList = new ArrayList<>(10); 3253 for (final String value : postReadAttribute.getValues()) 3254 { 3255 final StringTokenizer tokenizer = new StringTokenizer(value, ", "); 3256 while (tokenizer.hasMoreTokens()) 3257 { 3258 attrList.add(tokenizer.nextToken()); 3259 } 3260 } 3261 3262 final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS); 3263 final PostReadRequestControl c = new PostReadRequestControl(attrArray); 3264 addControls.add(c); 3265 modifyControls.add(c); 3266 modifyDNControls.add(c); 3267 } 3268 3269 if (proxyAs.isPresent() && (! useTransaction.isPresent()) && 3270 (! multiUpdateErrorBehavior.isPresent())) 3271 { 3272 final ProxiedAuthorizationV2RequestControl c = 3273 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()); 3274 addControls.add(c); 3275 deleteControls.add(c); 3276 modifyControls.add(c); 3277 modifyDNControls.add(c); 3278 searchControls.add(c); 3279 } 3280 3281 if (proxyV1As.isPresent() && (! useTransaction.isPresent()) && 3282 (! multiUpdateErrorBehavior.isPresent())) 3283 { 3284 final ProxiedAuthorizationV1RequestControl c = 3285 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()); 3286 addControls.add(c); 3287 deleteControls.add(c); 3288 modifyControls.add(c); 3289 modifyDNControls.add(c); 3290 searchControls.add(c); 3291 } 3292 3293 if (uniquenessAttribute.isPresent() || uniquenessFilter.isPresent()) 3294 { 3295 final UniquenessRequestControlProperties uniquenessProperties; 3296 if (uniquenessAttribute.isPresent()) 3297 { 3298 uniquenessProperties = new UniquenessRequestControlProperties( 3299 uniquenessAttribute.getValues()); 3300 if (uniquenessFilter.isPresent()) 3301 { 3302 uniquenessProperties.setFilter(uniquenessFilter.getValue()); 3303 } 3304 } 3305 else 3306 { 3307 uniquenessProperties = new UniquenessRequestControlProperties( 3308 uniquenessFilter.getValue()); 3309 } 3310 3311 if (uniquenessBaseDN.isPresent()) 3312 { 3313 uniquenessProperties.setBaseDN(uniquenessBaseDN.getStringValue()); 3314 } 3315 3316 if (uniquenessMultipleAttributeBehavior.isPresent()) 3317 { 3318 final String value = 3319 uniquenessMultipleAttributeBehavior.getValue().toLowerCase(); 3320 switch (value) 3321 { 3322 case "unique-within-each-attribute": 3323 uniquenessProperties.setMultipleAttributeBehavior( 3324 UniquenessMultipleAttributeBehavior. 3325 UNIQUE_WITHIN_EACH_ATTRIBUTE); 3326 break; 3327 case "unique-across-all-attributes-including-in-same-entry": 3328 uniquenessProperties.setMultipleAttributeBehavior( 3329 UniquenessMultipleAttributeBehavior. 3330 UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_IN_SAME_ENTRY); 3331 break; 3332 case "unique-across-all-attributes-except-in-same-entry": 3333 uniquenessProperties.setMultipleAttributeBehavior( 3334 UniquenessMultipleAttributeBehavior. 3335 UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_IN_SAME_ENTRY); 3336 break; 3337 case "unique-in-combination": 3338 uniquenessProperties.setMultipleAttributeBehavior( 3339 UniquenessMultipleAttributeBehavior.UNIQUE_IN_COMBINATION); 3340 break; 3341 } 3342 } 3343 3344 if (uniquenessPreCommitValidationLevel.isPresent()) 3345 { 3346 final String value = 3347 uniquenessPreCommitValidationLevel.getValue().toLowerCase(); 3348 switch (value) 3349 { 3350 case "none": 3351 uniquenessProperties.setPreCommitValidationLevel( 3352 UniquenessValidationLevel.NONE); 3353 break; 3354 case "all-subtree-views": 3355 uniquenessProperties.setPreCommitValidationLevel( 3356 UniquenessValidationLevel.ALL_SUBTREE_VIEWS); 3357 break; 3358 case "all-backend-sets": 3359 uniquenessProperties.setPreCommitValidationLevel( 3360 UniquenessValidationLevel.ALL_BACKEND_SETS); 3361 break; 3362 case "all-available-backend-servers": 3363 uniquenessProperties.setPreCommitValidationLevel( 3364 UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS); 3365 break; 3366 } 3367 } 3368 3369 if (uniquenessPostCommitValidationLevel.isPresent()) 3370 { 3371 final String value = 3372 uniquenessPostCommitValidationLevel.getValue().toLowerCase(); 3373 switch (value) 3374 { 3375 case "none": 3376 uniquenessProperties.setPostCommitValidationLevel( 3377 UniquenessValidationLevel.NONE); 3378 break; 3379 case "all-subtree-views": 3380 uniquenessProperties.setPostCommitValidationLevel( 3381 UniquenessValidationLevel.ALL_SUBTREE_VIEWS); 3382 break; 3383 case "all-backend-sets": 3384 uniquenessProperties.setPostCommitValidationLevel( 3385 UniquenessValidationLevel.ALL_BACKEND_SETS); 3386 break; 3387 case "all-available-backend-servers": 3388 uniquenessProperties.setPostCommitValidationLevel( 3389 UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS); 3390 break; 3391 } 3392 } 3393 3394 final UniquenessRequestControl c = 3395 new UniquenessRequestControl(true, null, uniquenessProperties); 3396 addControls.add(c); 3397 modifyControls.add(c); 3398 modifyDNControls.add(c); 3399 } 3400 3401 3402 if (useJSONFormattedRequestControls.isPresent()) 3403 { 3404 final JSONFormattedRequestControl jsonFormattedAddRequestControl = 3405 JSONFormattedRequestControl.createWithControls(true, addControls); 3406 addControls.clear(); 3407 addControls.add(jsonFormattedAddRequestControl); 3408 3409 final JSONFormattedRequestControl jsonFormattedDeleteRequestControl = 3410 JSONFormattedRequestControl.createWithControls(true, deleteControls); 3411 deleteControls.clear(); 3412 deleteControls.add(jsonFormattedDeleteRequestControl); 3413 3414 final JSONFormattedRequestControl jsonFormattedModifyRequestControl = 3415 JSONFormattedRequestControl.createWithControls(true, modifyControls); 3416 modifyControls.clear(); 3417 modifyControls.add(jsonFormattedModifyRequestControl); 3418 3419 final JSONFormattedRequestControl jsonFormattedModifyDNRequestControl = 3420 JSONFormattedRequestControl.createWithControls(true, 3421 modifyDNControls); 3422 modifyDNControls.clear(); 3423 modifyDNControls.add(jsonFormattedModifyDNRequestControl); 3424 3425 final JSONFormattedRequestControl jsonFormattedSearchRequestControl = 3426 JSONFormattedRequestControl.createWithControls(true, searchControls); 3427 searchControls.clear(); 3428 searchControls.add(jsonFormattedSearchRequestControl); 3429 } 3430 } 3431 3432 3433 3434 /** 3435 * Creates the password update behavior request control that should be 3436 * included in add and modify requests. 3437 * 3438 * @param argIdentifier The identifier string for the argument used to 3439 * configure the password update behavior request 3440 * control. 3441 * @param argValues The set of values for the password update behavior 3442 * request control. 3443 * 3444 * @return The password update behavior request control that was created. 3445 * 3446 * @throws LDAPException If a problem is encountered while creating the 3447 * control. 3448 */ 3449 @NotNull() 3450 static PasswordUpdateBehaviorRequestControl 3451 createPasswordUpdateBehaviorRequestControl( 3452 @NotNull final String argIdentifier, 3453 @NotNull final List<String> argValues) 3454 throws LDAPException 3455 { 3456 final PasswordUpdateBehaviorRequestControlProperties properties = 3457 new PasswordUpdateBehaviorRequestControlProperties(); 3458 3459 for (final String argValue : argValues) 3460 { 3461 int delimiterPos = argValue.indexOf('='); 3462 if (delimiterPos < 0) 3463 { 3464 delimiterPos = argValue.indexOf(':'); 3465 } 3466 3467 if ((delimiterPos <= 0) || (delimiterPos >= (argValue.length() - 1))) 3468 { 3469 throw new LDAPException(ResultCode.PARAM_ERROR, 3470 ERR_LDAPMODIFY_MALFORMED_PW_UPDATE_BEHAVIOR.get(argValue, 3471 argIdentifier)); 3472 } 3473 3474 final String name = argValue.substring(0, delimiterPos).trim(); 3475 final String value = argValue.substring(delimiterPos+1).trim(); 3476 if (name.equalsIgnoreCase("is-self-change") || 3477 name.equalsIgnoreCase("self-change") || 3478 name.equalsIgnoreCase("isSelfChange") || 3479 name.equalsIgnoreCase("selfChange")) 3480 { 3481 properties.setIsSelfChange(parseBooleanValue(name, value)); 3482 } 3483 else if (name.equalsIgnoreCase("allow-pre-encoded-password") || 3484 name.equalsIgnoreCase("allow-pre-encoded-passwords") || 3485 name.equalsIgnoreCase("allow-pre-encoded") || 3486 name.equalsIgnoreCase("allowPreEncodedPassword") || 3487 name.equalsIgnoreCase("allowPreEncodedPasswords") || 3488 name.equalsIgnoreCase("allowPreEncoded")) 3489 { 3490 properties.setAllowPreEncodedPassword(parseBooleanValue(name, value)); 3491 } 3492 else if (name.equalsIgnoreCase("skip-password-validation") || 3493 name.equalsIgnoreCase("skip-password-validators") || 3494 name.equalsIgnoreCase("skip-validation") || 3495 name.equalsIgnoreCase("skip-validators") || 3496 name.equalsIgnoreCase("skipPasswordValidation") || 3497 name.equalsIgnoreCase("skipPasswordValidators") || 3498 name.equalsIgnoreCase("skipValidation") || 3499 name.equalsIgnoreCase("skipValidators")) 3500 { 3501 properties.setSkipPasswordValidation(parseBooleanValue(name, value)); 3502 } 3503 else if (name.equalsIgnoreCase("ignore-password-history") || 3504 name.equalsIgnoreCase("skip-password-history") || 3505 name.equalsIgnoreCase("ignore-history") || 3506 name.equalsIgnoreCase("skip-history") || 3507 name.equalsIgnoreCase("ignorePasswordHistory") || 3508 name.equalsIgnoreCase("skipPasswordHistory") || 3509 name.equalsIgnoreCase("ignoreHistory") || 3510 name.equalsIgnoreCase("skipHistory")) 3511 { 3512 properties.setIgnorePasswordHistory(parseBooleanValue(name, value)); 3513 } 3514 else if (name.equalsIgnoreCase("ignore-minimum-password-age") || 3515 name.equalsIgnoreCase("ignore-min-password-age") || 3516 name.equalsIgnoreCase("ignore-password-age") || 3517 name.equalsIgnoreCase("skip-minimum-password-age") || 3518 name.equalsIgnoreCase("skip-min-password-age") || 3519 name.equalsIgnoreCase("skip-password-age") || 3520 name.equalsIgnoreCase("ignoreMinimumPasswordAge") || 3521 name.equalsIgnoreCase("ignoreMinPasswordAge") || 3522 name.equalsIgnoreCase("ignorePasswordAge") || 3523 name.equalsIgnoreCase("skipMinimumPasswordAge") || 3524 name.equalsIgnoreCase("skipMinPasswordAge") || 3525 name.equalsIgnoreCase("skipPasswordAge")) 3526 { 3527 properties.setIgnoreMinimumPasswordAge(parseBooleanValue(name, value)); 3528 } 3529 else if (name.equalsIgnoreCase("password-storage-scheme") || 3530 name.equalsIgnoreCase("password-scheme") || 3531 name.equalsIgnoreCase("storage-scheme") || 3532 name.equalsIgnoreCase("scheme") || 3533 name.equalsIgnoreCase("passwordStorageScheme") || 3534 name.equalsIgnoreCase("passwordScheme") || 3535 name.equalsIgnoreCase("storageScheme")) 3536 { 3537 properties.setPasswordStorageScheme(value); 3538 } 3539 else if (name.equalsIgnoreCase("must-change-password") || 3540 name.equalsIgnoreCase("mustChangePassword")) 3541 { 3542 properties.setMustChangePassword(parseBooleanValue(name, value)); 3543 } 3544 } 3545 3546 return new PasswordUpdateBehaviorRequestControl(properties, true); 3547 } 3548 3549 3550 3551 /** 3552 * Parses the provided value as the Boolean value for a password update 3553 * behavior property. 3554 * 3555 * @param name The name of the password update behavior property being 3556 * parsed. 3557 * @param value The value to be parsed. 3558 * 3559 * @return The Boolean value that was parsed. 3560 * 3561 * @throws LDAPException If the provided value cannot be parsed as a 3562 * Boolean value. 3563 */ 3564 private static boolean parseBooleanValue(@NotNull final String name, 3565 @NotNull final String value) 3566 throws LDAPException 3567 { 3568 if (value.equalsIgnoreCase("true") || 3569 value.equalsIgnoreCase("t") || 3570 value.equalsIgnoreCase("yes") || 3571 value.equalsIgnoreCase("y") || 3572 value.equalsIgnoreCase("1")) 3573 { 3574 return true; 3575 } 3576 else if (value.equalsIgnoreCase("false") || 3577 value.equalsIgnoreCase("f") || 3578 value.equalsIgnoreCase("no") || 3579 value.equalsIgnoreCase("n") || 3580 value.equalsIgnoreCase("0")) 3581 { 3582 return false; 3583 } 3584 else 3585 { 3586 throw new LDAPException(ResultCode.PARAM_ERROR, 3587 ERR_LDAPMODIFY_INVALID_PW_UPDATE_BOOLEAN_VALUE.get(value, name)); 3588 } 3589 } 3590 3591 3592 3593 /** 3594 * Performs the appropriate processing for an LDIF add change record. 3595 * 3596 * @param changeRecord The LDIF add change record to process. 3597 * @param controls The set of controls to include in the request. 3598 * @param pool The connection pool to use to communicate with 3599 * the directory server. 3600 * @param multiUpdateRequests The list to which the request should be added 3601 * if it is to be processed as part of a 3602 * multi-update operation. It may be 3603 * {@code null} if the operation should not be 3604 * processed via the multi-update operation. 3605 * @param rejectWriter The LDIF writer to use for recording 3606 * information about rejected changes. It may be 3607 * {@code null} if no reject writer is 3608 * configured. 3609 * 3610 * @return The result code obtained from processing. 3611 * 3612 * @throws LDAPException If the operation did not complete successfully 3613 * and processing should not continue. 3614 */ 3615 @NotNull() 3616 private ResultCode doAdd(@NotNull final LDIFAddChangeRecord changeRecord, 3617 @NotNull final List<Control> controls, 3618 @NotNull final LDAPConnectionPool pool, 3619 @Nullable final List<LDAPRequest> multiUpdateRequests, 3620 @Nullable final LDIFWriter rejectWriter) 3621 throws LDAPException 3622 { 3623 // Create the add request to process. 3624 final AddRequest addRequest = changeRecord.toAddRequest(true); 3625 for (final Control c : controls) 3626 { 3627 addRequest.addControl(c); 3628 } 3629 3630 3631 // If we should provide support for undelete operations and the entry 3632 // includes the ds-undelete-from-dn attribute, then add the undelete request 3633 // control. 3634 if (allowUndelete.isPresent() && 3635 addRequest.hasAttribute(ATTR_UNDELETE_FROM_DN)) 3636 { 3637 addRequest.addControl(new UndeleteRequestControl()); 3638 } 3639 3640 3641 // If the entry to add includes a password, then add a password validation 3642 // details request control if appropriate. 3643 if (passwordValidationDetails.isPresent()) 3644 { 3645 final Entry entryToAdd = addRequest.toEntry(); 3646 if ((! entryToAdd.getAttributesWithOptions(ATTR_USER_PASSWORD, 3647 null).isEmpty()) || 3648 (! entryToAdd.getAttributesWithOptions(ATTR_AUTH_PASSWORD, 3649 null).isEmpty())) 3650 { 3651 addRequest.addControl(new PasswordValidationDetailsRequestControl()); 3652 } 3653 } 3654 3655 3656 // If the operation should be processed in a multi-update operation, then 3657 // just add the request to the list and return without doing anything else. 3658 if (multiUpdateErrorBehavior.isPresent()) 3659 { 3660 multiUpdateRequests.add(addRequest); 3661 commentToOut(INFO_LDAPMODIFY_ADD_ADDED_TO_MULTI_UPDATE.get( 3662 addRequest.getDN())); 3663 return ResultCode.SUCCESS; 3664 } 3665 3666 3667 // If the --dryRun argument was provided, then we'll stop here. 3668 if (dryRun.isPresent()) 3669 { 3670 commentToOut(INFO_LDAPMODIFY_DRY_RUN_ADD.get(addRequest.getDN(), 3671 dryRun.getIdentifierString())); 3672 return ResultCode.SUCCESS; 3673 } 3674 3675 3676 // Process the add operation and get the result. 3677 commentToOut(INFO_LDAPMODIFY_ADDING_ENTRY.get(addRequest.getDN())); 3678 if (verbose.isPresent()) 3679 { 3680 for (final String ldifLine : 3681 addRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 3682 { 3683 out(ldifLine); 3684 } 3685 out(); 3686 } 3687 3688 LDAPResult addResult; 3689 try 3690 { 3691 addResult = pool.add(addRequest); 3692 } 3693 catch (final LDAPException le) 3694 { 3695 Debug.debugException(le); 3696 addResult = le.toLDAPResult(); 3697 } 3698 3699 addResult = handleJSONEncodedResponseControls(addResult); 3700 3701 3702 // Display information about the result. 3703 displayResult(addResult, useTransaction.isPresent()); 3704 3705 3706 // See if the add operation succeeded or failed. If it failed, and we 3707 // should end all processing, then throw an exception. 3708 switch (addResult.getResultCode().intValue()) 3709 { 3710 case ResultCode.SUCCESS_INT_VALUE: 3711 case ResultCode.NO_OPERATION_INT_VALUE: 3712 break; 3713 3714 case ResultCode.ASSERTION_FAILED_INT_VALUE: 3715 writeRejectedChange(rejectWriter, 3716 INFO_LDAPMODIFY_ASSERTION_FAILED.get(addRequest.getDN(), 3717 String.valueOf(assertionFilter.getValue())), 3718 addRequest.toLDIFChangeRecord(), addResult); 3719 throw new LDAPException(addResult); 3720 3721 default: 3722 writeRejectedChange(rejectWriter, null, addRequest.toLDIFChangeRecord(), 3723 addResult); 3724 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 3725 { 3726 throw new LDAPException(addResult); 3727 } 3728 break; 3729 } 3730 3731 return addResult.getResultCode(); 3732 } 3733 3734 3735 3736 /** 3737 * Performs the appropriate processing for an LDIF delete change record. 3738 * 3739 * @param changeRecord The LDIF delete change record to process. 3740 * @param controls The set of controls to include in the request. 3741 * @param pool The connection pool to use to communicate with 3742 * the directory server. 3743 * @param multiUpdateRequests The list to which the request should be added 3744 * if it is to be processed as part of a 3745 * multi-update operation. It may be 3746 * {@code null} if the operation should not be 3747 * processed via the multi-update operation. 3748 * @param rejectWriter The LDIF writer to use for recording 3749 * information about rejected changes. It may be 3750 * {@code null} if no reject writer is 3751 * configured. 3752 * 3753 * @return The result code obtained from processing. 3754 * 3755 * @throws LDAPException If the operation did not complete successfully 3756 * and processing should not continue. 3757 */ 3758 @NotNull() 3759 private ResultCode doDelete( 3760 @NotNull final LDIFDeleteChangeRecord changeRecord, 3761 @NotNull final List<Control> controls, 3762 @NotNull final LDAPConnectionPool pool, 3763 @Nullable final List<LDAPRequest> multiUpdateRequests, 3764 @Nullable final LDIFWriter rejectWriter) 3765 throws LDAPException 3766 { 3767 // If we should perform a client-side subtree delete, then do that 3768 // differently. 3769 if (clientSideSubtreeDelete.isPresent()) 3770 { 3771 return doClientSideSubtreeDelete(changeRecord, controls, pool, 3772 rejectWriter); 3773 } 3774 3775 3776 // Create the delete request to process. 3777 final DeleteRequest deleteRequest = changeRecord.toDeleteRequest(true); 3778 for (final Control c : controls) 3779 { 3780 deleteRequest.addControl(c); 3781 } 3782 3783 3784 // If the operation should be processed in a multi-update operation, then 3785 // just add the request to the list and return without doing anything else. 3786 if (multiUpdateErrorBehavior.isPresent()) 3787 { 3788 multiUpdateRequests.add(deleteRequest); 3789 commentToOut(INFO_LDAPMODIFY_DELETE_ADDED_TO_MULTI_UPDATE.get( 3790 deleteRequest.getDN())); 3791 return ResultCode.SUCCESS; 3792 } 3793 3794 3795 // If the --dryRun argument was provided, then we'll stop here. 3796 if (dryRun.isPresent()) 3797 { 3798 commentToOut(INFO_LDAPMODIFY_DRY_RUN_DELETE.get(deleteRequest.getDN(), 3799 dryRun.getIdentifierString())); 3800 return ResultCode.SUCCESS; 3801 } 3802 3803 3804 // Process the delete operation and get the result. 3805 commentToOut(INFO_LDAPMODIFY_DELETING_ENTRY.get(deleteRequest.getDN())); 3806 if (verbose.isPresent()) 3807 { 3808 for (final String ldifLine : 3809 deleteRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 3810 { 3811 out(ldifLine); 3812 } 3813 out(); 3814 } 3815 3816 3817 LDAPResult deleteResult; 3818 try 3819 { 3820 deleteResult = pool.delete(deleteRequest); 3821 } 3822 catch (final LDAPException le) 3823 { 3824 Debug.debugException(le); 3825 deleteResult = le.toLDAPResult(); 3826 } 3827 3828 deleteResult = handleJSONEncodedResponseControls(deleteResult); 3829 3830 3831 // Display information about the result. 3832 displayResult(deleteResult, useTransaction.isPresent()); 3833 3834 3835 // See if the delete operation succeeded or failed. If it failed, and we 3836 // should end all processing, then throw an exception. 3837 switch (deleteResult.getResultCode().intValue()) 3838 { 3839 case ResultCode.SUCCESS_INT_VALUE: 3840 case ResultCode.NO_OPERATION_INT_VALUE: 3841 break; 3842 3843 case ResultCode.ASSERTION_FAILED_INT_VALUE: 3844 writeRejectedChange(rejectWriter, 3845 INFO_LDAPMODIFY_ASSERTION_FAILED.get(deleteRequest.getDN(), 3846 String.valueOf(assertionFilter.getValue())), 3847 deleteRequest.toLDIFChangeRecord(), deleteResult); 3848 throw new LDAPException(deleteResult); 3849 3850 default: 3851 writeRejectedChange(rejectWriter, null, 3852 deleteRequest.toLDIFChangeRecord(), deleteResult); 3853 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 3854 { 3855 throw new LDAPException(deleteResult); 3856 } 3857 break; 3858 } 3859 3860 return deleteResult.getResultCode(); 3861 } 3862 3863 3864 3865 /** 3866 * Performs the appropriate processing for an LDIF delete change record. 3867 * 3868 * @param changeRecord The LDIF delete change record to process. 3869 * @param controls The set of controls to include in the request. 3870 * @param pool The connection pool to use to communicate with the 3871 * directory server. 3872 * @param rejectWriter The LDIF writer to use for recording information 3873 * about rejected changes. It may be {@code null} if no 3874 * reject writer is configured. 3875 * 3876 * @return The result code obtained from processing. 3877 * 3878 * @throws LDAPException If the operation did not complete successfully 3879 * and processing should not continue. 3880 */ 3881 @NotNull() 3882 private ResultCode doClientSideSubtreeDelete( 3883 @NotNull final LDIFChangeRecord changeRecord, 3884 @NotNull final List<Control> controls, 3885 @NotNull final LDAPConnectionPool pool, 3886 @Nullable final LDIFWriter rejectWriter) 3887 throws LDAPException 3888 { 3889 // Create the subtree deleter with the provided set of controls. Make sure 3890 // to include any controls in the delete change record itself. 3891 final List<Control> additionalControls; 3892 if (changeRecord.getControls().isEmpty()) 3893 { 3894 additionalControls = controls; 3895 } 3896 else 3897 { 3898 additionalControls = new ArrayList<>(controls.size() + 3899 changeRecord.getControls().size()); 3900 additionalControls.addAll(changeRecord.getControls()); 3901 additionalControls.addAll(controls); 3902 } 3903 3904 final SubtreeDeleter subtreeDeleter = new SubtreeDeleter(); 3905 subtreeDeleter.setAdditionalDeleteControls(additionalControls); 3906 3907 3908 // Perform the subtree delete. 3909 commentToOut(INFO_LDAPMODIFY_CLIENT_SIDE_DELETING_SUBTREE.get( 3910 changeRecord.getDN())); 3911 final SubtreeDeleterResult subtreeDeleterResult = 3912 subtreeDeleter.delete(pool, changeRecord.getDN()); 3913 3914 3915 // Evaluate the result of the subtree delete. 3916 LDAPResult finalResult; 3917 if (subtreeDeleterResult.completelySuccessful()) 3918 { 3919 final long entriesDeleted = subtreeDeleterResult.getEntriesDeleted(); 3920 if (entriesDeleted == 0L) 3921 { 3922 // This means that the base entry did not exist. Even though the 3923 // subtree deleter returned a successful result, we'll use a final 3924 // result of "no such object". 3925 finalResult = new LDAPResult(-1, ResultCode.NO_SUCH_OBJECT, 3926 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_0_ENTRIES.get( 3927 changeRecord.getDN()), 3928 null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 3929 } 3930 else if (entriesDeleted == 1L) 3931 { 3932 // This means the base entry existed (and we deleted it successfully), 3933 // but did not have any subordinates. 3934 finalResult = new LDAPResult(-1, ResultCode.SUCCESS, 3935 INFO_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_1_ENTRY.get( 3936 changeRecord.getDN()), 3937 null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 3938 } 3939 else 3940 { 3941 // This means that the base entry existed and had subordinates, and we 3942 // deleted all of them successfully. 3943 finalResult = new LDAPResult(-1, ResultCode.SUCCESS, 3944 INFO_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_ENTRIES.get( 3945 subtreeDeleterResult.getEntriesDeleted(), 3946 changeRecord.getDN()), 3947 null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 3948 } 3949 } 3950 else 3951 { 3952 // If there was a search error, then display information about it. 3953 final SearchResult searchError = subtreeDeleterResult.getSearchError(); 3954 if (searchError != null) 3955 { 3956 commentToErr(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SEARCH_ERROR.get()); 3957 displayResult(searchError, false); 3958 err("#"); 3959 } 3960 3961 final SortedMap<DN,LDAPResult> deleteErrors = 3962 subtreeDeleterResult.getDeleteErrorsDescendingMap(); 3963 for (final Map.Entry<DN,LDAPResult> deleteError : deleteErrors.entrySet()) 3964 { 3965 commentToErr(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_ERROR.get( 3966 String.valueOf(deleteError.getKey()))); 3967 displayResult(deleteError.getValue(), false); 3968 err("#"); 3969 } 3970 3971 ResultCode resultCode = ResultCode.OTHER; 3972 final StringBuilder buffer = new StringBuilder(); 3973 buffer.append(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_ERR_BASE.get()); 3974 if (searchError != null) 3975 { 3976 resultCode = searchError.getResultCode(); 3977 buffer.append(" "); 3978 buffer.append( 3979 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_SEARCH_ERR.get()); 3980 } 3981 3982 if (! deleteErrors.isEmpty()) 3983 { 3984 resultCode = deleteErrors.values().iterator().next().getResultCode(); 3985 buffer.append(" "); 3986 final int numDeleteErrors = deleteErrors.size(); 3987 if (numDeleteErrors == 1) 3988 { 3989 buffer.append( 3990 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_ERR_COUNT_1.get()); 3991 } 3992 else 3993 { 3994 buffer.append( 3995 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_ERR_COUNT.get( 3996 numDeleteErrors)); 3997 } 3998 } 3999 4000 buffer.append(" "); 4001 final long deletedCount = subtreeDeleterResult.getEntriesDeleted(); 4002 if (deletedCount == 1L) 4003 { 4004 buffer.append( 4005 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_COUNT_1.get()); 4006 } 4007 else 4008 { 4009 buffer.append(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_COUNT.get( 4010 deletedCount)); 4011 } 4012 4013 finalResult = new LDAPResult(-1, resultCode, buffer.toString(), null, 4014 StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 4015 } 4016 4017 finalResult = handleJSONEncodedResponseControls(finalResult); 4018 4019 4020 // Display information about the final result. 4021 displayResult(finalResult, useTransaction.isPresent()); 4022 4023 4024 // See if the delete operation succeeded or failed. If it failed, and we 4025 // should end all processing, then throw an exception. 4026 switch (finalResult.getResultCode().intValue()) 4027 { 4028 case ResultCode.SUCCESS_INT_VALUE: 4029 case ResultCode.NO_OPERATION_INT_VALUE: 4030 break; 4031 4032 default: 4033 writeRejectedChange(rejectWriter, null, changeRecord, finalResult); 4034 if (! continueOnError.isPresent()) 4035 { 4036 throw new LDAPException(finalResult); 4037 } 4038 break; 4039 } 4040 4041 return finalResult.getResultCode(); 4042 } 4043 4044 4045 4046 /** 4047 * Performs the appropriate processing for an LDIF modify change record. 4048 * 4049 * @param changeRecord The LDIF modify change record to process. 4050 * @param controls The set of controls to include in the request. 4051 * @param pool The connection pool to use to communicate with 4052 * the directory server. 4053 * @param multiUpdateRequests The list to which the request should be added 4054 * if it is to be processed as part of a 4055 * multi-update operation. It may be 4056 * {@code null} if the operation should not be 4057 * processed via the multi-update operation. 4058 * @param rejectWriter The LDIF writer to use for recording 4059 * information about rejected changes. It may be 4060 * {@code null} if no reject writer is 4061 * configured. 4062 * 4063 * @return The result code obtained from processing. 4064 * 4065 * @throws LDAPException If the operation did not complete successfully 4066 * and processing should not continue. 4067 */ 4068 @NotNull() 4069 ResultCode doModify(@NotNull final LDIFModifyChangeRecord changeRecord, 4070 @NotNull final List<Control> controls, 4071 @NotNull final LDAPConnectionPool pool, 4072 @Nullable final List<LDAPRequest> multiUpdateRequests, 4073 @Nullable final LDIFWriter rejectWriter) 4074 throws LDAPException 4075 { 4076 // Create the modify request to process. 4077 final ModifyRequest modifyRequest = changeRecord.toModifyRequest(true); 4078 for (final Control c : controls) 4079 { 4080 modifyRequest.addControl(c); 4081 } 4082 4083 4084 // If the modify request includes a password change, then add any controls 4085 // that are specific to that. 4086 if (retireCurrentPassword.isPresent() || purgeCurrentPassword.isPresent() || 4087 passwordValidationDetails.isPresent()) 4088 { 4089 for (final Modification m : modifyRequest.getModifications()) 4090 { 4091 final String baseName = m.getAttribute().getBaseName(); 4092 if (baseName.equalsIgnoreCase(ATTR_USER_PASSWORD) || 4093 baseName.equalsIgnoreCase(ATTR_AUTH_PASSWORD)) 4094 { 4095 if (retireCurrentPassword.isPresent()) 4096 { 4097 modifyRequest.addControl(new RetirePasswordRequestControl(false)); 4098 } 4099 else if (purgeCurrentPassword.isPresent()) 4100 { 4101 modifyRequest.addControl(new PurgePasswordRequestControl(false)); 4102 } 4103 4104 if (passwordValidationDetails.isPresent()) 4105 { 4106 modifyRequest.addControl( 4107 new PasswordValidationDetailsRequestControl()); 4108 } 4109 4110 break; 4111 } 4112 } 4113 } 4114 4115 4116 // If the operation should be processed in a multi-update operation, then 4117 // just add the request to the list and return without doing anything else. 4118 if (multiUpdateErrorBehavior.isPresent()) 4119 { 4120 multiUpdateRequests.add(modifyRequest); 4121 commentToOut(INFO_LDAPMODIFY_MODIFY_ADDED_TO_MULTI_UPDATE.get( 4122 modifyRequest.getDN())); 4123 return ResultCode.SUCCESS; 4124 } 4125 4126 4127 // If the --dryRun argument was provided, then we'll stop here. 4128 if (dryRun.isPresent()) 4129 { 4130 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MODIFY.get(modifyRequest.getDN(), 4131 dryRun.getIdentifierString())); 4132 return ResultCode.SUCCESS; 4133 } 4134 4135 4136 // Process the modify operation and get the result. 4137 commentToOut(INFO_LDAPMODIFY_MODIFYING_ENTRY.get(modifyRequest.getDN())); 4138 if (verbose.isPresent()) 4139 { 4140 for (final String ldifLine : 4141 modifyRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 4142 { 4143 out(ldifLine); 4144 } 4145 out(); 4146 } 4147 4148 4149 LDAPResult modifyResult; 4150 try 4151 { 4152 modifyResult = pool.modify(modifyRequest); 4153 } 4154 catch (final LDAPException le) 4155 { 4156 Debug.debugException(le); 4157 modifyResult = le.toLDAPResult(); 4158 } 4159 4160 modifyResult = handleJSONEncodedResponseControls(modifyResult); 4161 4162 4163 // Display information about the result. 4164 displayResult(modifyResult, useTransaction.isPresent()); 4165 4166 4167 // See if the modify operation succeeded or failed. If it failed, and we 4168 // should end all processing, then throw an exception. 4169 switch (modifyResult.getResultCode().intValue()) 4170 { 4171 case ResultCode.SUCCESS_INT_VALUE: 4172 case ResultCode.NO_OPERATION_INT_VALUE: 4173 break; 4174 4175 case ResultCode.ASSERTION_FAILED_INT_VALUE: 4176 writeRejectedChange(rejectWriter, 4177 INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyRequest.getDN(), 4178 String.valueOf(assertionFilter.getValue())), 4179 modifyRequest.toLDIFChangeRecord(), modifyResult); 4180 throw new LDAPException(modifyResult); 4181 4182 default: 4183 writeRejectedChange(rejectWriter, null, 4184 modifyRequest.toLDIFChangeRecord(), modifyResult); 4185 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 4186 { 4187 throw new LDAPException(modifyResult); 4188 } 4189 break; 4190 } 4191 4192 return modifyResult.getResultCode(); 4193 } 4194 4195 4196 4197 /** 4198 * Performs the appropriate processing for an LDIF modify DN change record. 4199 * 4200 * @param changeRecord The LDIF modify DN change record to process. 4201 * @param controls The set of controls to include in the request. 4202 * @param pool The connection pool to use to communicate with 4203 * the directory server. 4204 * @param multiUpdateRequests The list to which the request should be added 4205 * if it is to be processed as part of a 4206 * multi-update operation. It may be 4207 * {@code null} if the operation should not be 4208 * processed via the multi-update operation. 4209 * @param rejectWriter The LDIF writer to use for recording 4210 * information about rejected changes. It may be 4211 * {@code null} if no reject writer is 4212 * configured. 4213 * 4214 * @return The result code obtained from processing. 4215 * 4216 * @throws LDAPException If the operation did not complete successfully 4217 * and processing should not continue. 4218 */ 4219 @NotNull() 4220 private ResultCode doModifyDN( 4221 @NotNull final LDIFModifyDNChangeRecord changeRecord, 4222 @NotNull final List<Control> controls, 4223 @NotNull final LDAPConnectionPool pool, 4224 @Nullable final List<LDAPRequest> multiUpdateRequests, 4225 @Nullable final LDIFWriter rejectWriter) 4226 throws LDAPException 4227 { 4228 // Create the modify DN request to process. 4229 final ModifyDNRequest modifyDNRequest = 4230 changeRecord.toModifyDNRequest(true); 4231 for (final Control c : controls) 4232 { 4233 modifyDNRequest.addControl(c); 4234 } 4235 4236 4237 // If the operation should be processed in a multi-update operation, then 4238 // just add the request to the list and return without doing anything else. 4239 if (multiUpdateErrorBehavior.isPresent()) 4240 { 4241 multiUpdateRequests.add(modifyDNRequest); 4242 commentToOut(INFO_LDAPMODIFY_MODIFY_DN_ADDED_TO_MULTI_UPDATE.get( 4243 modifyDNRequest.getDN())); 4244 return ResultCode.SUCCESS; 4245 } 4246 4247 4248 // Try to determine the new DN that the entry will have after the operation. 4249 DN newDN = null; 4250 try 4251 { 4252 newDN = changeRecord.getNewDN(); 4253 } 4254 catch (final Exception e) 4255 { 4256 Debug.debugException(e); 4257 4258 // This should only happen if the provided DN, new RDN, or new superior DN 4259 // was malformed. Although we could reject the operation now, we'll go 4260 // ahead and send the request to the server in case it has some special 4261 // handling for the DN. 4262 } 4263 4264 4265 // If the --dryRun argument was provided, then we'll stop here. 4266 if (dryRun.isPresent()) 4267 { 4268 if (modifyDNRequest.getNewSuperiorDN() == null) 4269 { 4270 if (newDN == null) 4271 { 4272 commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME.get( 4273 modifyDNRequest.getDN(), dryRun.getIdentifierString())); 4274 } 4275 else 4276 { 4277 commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME_TO.get( 4278 modifyDNRequest.getDN(), newDN.toString(), 4279 dryRun.getIdentifierString())); 4280 } 4281 } 4282 else 4283 { 4284 if (newDN == null) 4285 { 4286 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE.get( 4287 modifyDNRequest.getDN(), dryRun.getIdentifierString())); 4288 } 4289 else 4290 { 4291 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE_TO.get( 4292 modifyDNRequest.getDN(), newDN.toString(), 4293 dryRun.getIdentifierString())); 4294 } 4295 } 4296 return ResultCode.SUCCESS; 4297 } 4298 4299 4300 // Process the modify DN operation and get the result. 4301 final String currentDN = modifyDNRequest.getDN(); 4302 if (modifyDNRequest.getNewSuperiorDN() == null) 4303 { 4304 if (newDN == null) 4305 { 4306 commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY.get(currentDN)); 4307 } 4308 else 4309 { 4310 commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY_TO.get(currentDN, 4311 newDN.toString())); 4312 } 4313 } 4314 else 4315 { 4316 if (newDN == null) 4317 { 4318 commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY.get(currentDN)); 4319 } 4320 else 4321 { 4322 commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY_TO.get(currentDN, 4323 newDN.toString())); 4324 } 4325 } 4326 4327 if (verbose.isPresent()) 4328 { 4329 for (final String ldifLine : 4330 modifyDNRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 4331 { 4332 out(ldifLine); 4333 } 4334 out(); 4335 } 4336 4337 4338 LDAPResult modifyDNResult; 4339 try 4340 { 4341 modifyDNResult = pool.modifyDN(modifyDNRequest); 4342 } 4343 catch (final LDAPException le) 4344 { 4345 Debug.debugException(le); 4346 modifyDNResult = le.toLDAPResult(); 4347 } 4348 4349 modifyDNResult = handleJSONEncodedResponseControls(modifyDNResult); 4350 4351 4352 // Display information about the result. 4353 displayResult(modifyDNResult, useTransaction.isPresent()); 4354 4355 4356 // See if the modify DN operation succeeded or failed. If it failed, and we 4357 // should end all processing, then throw an exception. 4358 switch (modifyDNResult.getResultCode().intValue()) 4359 { 4360 case ResultCode.SUCCESS_INT_VALUE: 4361 case ResultCode.NO_OPERATION_INT_VALUE: 4362 break; 4363 4364 case ResultCode.ASSERTION_FAILED_INT_VALUE: 4365 writeRejectedChange(rejectWriter, 4366 INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyDNRequest.getDN(), 4367 String.valueOf(assertionFilter.getValue())), 4368 modifyDNRequest.toLDIFChangeRecord(), modifyDNResult); 4369 throw new LDAPException(modifyDNResult); 4370 4371 default: 4372 writeRejectedChange(rejectWriter, null, 4373 modifyDNRequest.toLDIFChangeRecord(), modifyDNResult); 4374 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 4375 { 4376 throw new LDAPException(modifyDNResult); 4377 } 4378 break; 4379 } 4380 4381 return modifyDNResult.getResultCode(); 4382 } 4383 4384 4385 4386 /** 4387 * Displays information about the provided result, including special 4388 * processing for a number of supported response controls. 4389 * 4390 * @param result The result to examine. 4391 * @param inTransaction Indicates whether the operation is part of a 4392 * transaction. 4393 */ 4394 private void displayResult(@NotNull final LDAPResult result, 4395 final boolean inTransaction) 4396 { 4397 final ArrayList<String> resultLines = new ArrayList<>(10); 4398 ResultUtils.formatResult(resultLines, result, true, inTransaction, 0, 4399 WRAP_COLUMN); 4400 4401 if (result.getResultCode() == ResultCode.SUCCESS) 4402 { 4403 for (final String line : resultLines) 4404 { 4405 out(line); 4406 } 4407 out(); 4408 } 4409 else 4410 { 4411 for (final String line : resultLines) 4412 { 4413 err(line); 4414 } 4415 err(); 4416 } 4417 } 4418 4419 4420 4421 /** 4422 * Writes a line-wrapped, commented version of the provided message to 4423 * standard output. 4424 * 4425 * @param message The message to be written. 4426 */ 4427 private void commentToOut(@NotNull final String message) 4428 { 4429 for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) 4430 { 4431 out("# ", line); 4432 } 4433 } 4434 4435 4436 4437 /** 4438 * Writes a line-wrapped, commented version of the provided message to 4439 * standard error. 4440 * 4441 * @param message The message to be written. 4442 */ 4443 private void commentToErr(@NotNull final String message) 4444 { 4445 for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) 4446 { 4447 err("# ", line); 4448 } 4449 } 4450 4451 4452 4453 /** 4454 * Writes information about the rejected change to the reject writer. 4455 * 4456 * @param writer The LDIF writer to which the information should be 4457 * written. It may be {@code null} if no reject file is 4458 * configured. 4459 * @param comment The comment to include before the change record, in 4460 * addition to the comment generated from the provided 4461 * LDAP result. It may be {@code null} if no additional 4462 * comment should be included. 4463 * @param changeRecord The LDIF change record to be written. It must not 4464 * be {@code null}. 4465 * @param ldapResult The LDAP result for the failed operation. It must 4466 * not be {@code null}. 4467 */ 4468 private void writeRejectedChange(@Nullable final LDIFWriter writer, 4469 @Nullable final String comment, 4470 @NotNull final LDIFChangeRecord changeRecord, 4471 @NotNull final LDAPResult ldapResult) 4472 { 4473 if (writer == null) 4474 { 4475 return; 4476 } 4477 4478 4479 final StringBuilder buffer = new StringBuilder(); 4480 if (comment != null) 4481 { 4482 buffer.append(comment); 4483 buffer.append(StaticUtils.EOL); 4484 buffer.append(StaticUtils.EOL); 4485 } 4486 4487 final ArrayList<String> resultLines = new ArrayList<>(10); 4488 ResultUtils.formatResult(resultLines, ldapResult, false, false, 0, 0); 4489 for (final String resultLine : resultLines) 4490 { 4491 buffer.append(resultLine); 4492 buffer.append(StaticUtils.EOL); 4493 } 4494 4495 writeRejectedChange(writer, buffer.toString(), changeRecord); 4496 } 4497 4498 4499 4500 /** 4501 * Writes information about the rejected change to the reject writer. 4502 * 4503 * @param writer The LDIF writer to which the information should be 4504 * written. It may be {@code null} if no reject file is 4505 * configured. 4506 * @param comment The comment to include before the change record. It 4507 * may be {@code null} if no comment should be included. 4508 * @param changeRecord The LDIF change record to be written. It may be 4509 * {@code null} if only a comment should be written. 4510 */ 4511 void writeRejectedChange(@Nullable final LDIFWriter writer, 4512 @Nullable final String comment, 4513 @Nullable final LDIFChangeRecord changeRecord) 4514 { 4515 if (writer == null) 4516 { 4517 return; 4518 } 4519 4520 if (rejectWritten.compareAndSet(false, true)) 4521 { 4522 try 4523 { 4524 writer.writeVersionHeader(); 4525 } 4526 catch (final Exception e) 4527 { 4528 Debug.debugException(e); 4529 } 4530 } 4531 4532 try 4533 { 4534 if (comment != null) 4535 { 4536 writer.writeComment(comment, true, false); 4537 } 4538 4539 if (changeRecord != null) 4540 { 4541 writer.writeChangeRecord(changeRecord); 4542 } 4543 } 4544 catch (final Exception e) 4545 { 4546 Debug.debugException(e); 4547 4548 commentToErr(ERR_LDAPMODIFY_UNABLE_TO_WRITE_REJECTED_CHANGE.get( 4549 rejectFile.getValue().getAbsolutePath(), 4550 StaticUtils.getExceptionMessage(e))); 4551 } 4552 } 4553 4554 4555 4556 /** 4557 * {@inheritDoc} 4558 */ 4559 @Override() 4560 public void handleUnsolicitedNotification( 4561 @NotNull final LDAPConnection connection, 4562 @NotNull final ExtendedResult notification) 4563 { 4564 final ArrayList<String> lines = new ArrayList<>(10); 4565 ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0, 4566 WRAP_COLUMN); 4567 for (final String line : lines) 4568 { 4569 err(line); 4570 } 4571 err(); 4572 } 4573 4574 4575 4576 /** 4577 * Examines the provided LDAP result to see if it includes a JSONf-formatted 4578 * response control. If so, then its embedded controls will be extracted and 4579 * a new LDAP result will be returned with those extracted controls instead 4580 * of the JSON-formatted response control. Otherwise, the provided LDAP 4581 * result will be returned. 4582 * 4583 * @param ldapResult The LDAP result to be handled. It must not be 4584 * {@code null}. 4585 * 4586 * @return A new LDAP result with the controls extracted from a 4587 * JSON-formatted response control, or the original LDAP result if 4588 * it did not include a JSON-formatted response control. 4589 */ 4590 @NotNull() 4591 static LDAPResult handleJSONEncodedResponseControls( 4592 @NotNull final LDAPResult ldapResult) 4593 { 4594 try 4595 { 4596 final JSONFormattedResponseControl jsonFormattedResponseControl = 4597 JSONFormattedResponseControl.get(ldapResult); 4598 if (jsonFormattedResponseControl == null) 4599 { 4600 return ldapResult; 4601 } 4602 4603 final JSONFormattedControlDecodeBehavior decodeBehavior = 4604 new JSONFormattedControlDecodeBehavior(); 4605 decodeBehavior.setThrowOnUnparsableObject(false); 4606 decodeBehavior.setThrowOnInvalidCriticalControl(false); 4607 decodeBehavior.setThrowOnInvalidNonCriticalControl(false); 4608 decodeBehavior.setThrowOnInvalidNonCriticalControl(false); 4609 decodeBehavior.setAllowEmbeddedJSONFormattedControl(true); 4610 decodeBehavior.setStrict(false); 4611 4612 final List<Control> decodedControls = 4613 jsonFormattedResponseControl.decodeEmbeddedControls( 4614 decodeBehavior, null); 4615 4616 return new LDAPResult(ldapResult.getMessageID(), 4617 ldapResult.getResultCode(), 4618 ldapResult.getDiagnosticMessage(), 4619 ldapResult.getMatchedDN(), 4620 ldapResult.getReferralURLs(), 4621 StaticUtils.toArray(decodedControls, Control.class)); 4622 } 4623 catch (final LDAPException e) 4624 { 4625 Debug.debugException(e); 4626 return ldapResult; 4627 } 4628 } 4629 4630 4631 4632 /** 4633 * {@inheritDoc} 4634 */ 4635 @Override() 4636 @NotNull() 4637 public LinkedHashMap<String[],String> getExampleUsages() 4638 { 4639 final LinkedHashMap<String[],String> examples = 4640 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 4641 4642 final String[] args1 = 4643 { 4644 "--hostname", "ldap.example.com", 4645 "--port", "389", 4646 "--bindDN", "uid=admin,dc=example,dc=com", 4647 "--bindPassword", "password", 4648 "--defaultAdd" 4649 }; 4650 examples.put(args1, INFO_LDAPMODIFY_EXAMPLE_1.get()); 4651 4652 final String[] args2 = 4653 { 4654 "--hostname", "ds1.example.com", 4655 "--port", "636", 4656 "--hostname", "ds2.example.com", 4657 "--port", "636", 4658 "--useSSL", 4659 "--bindDN", "uid=admin,dc=example,dc=com", 4660 "--bindPassword", "password", 4661 "--ldifFile", "changes.ldif", 4662 "--modifyEntriesMatchingFilter", "(objectClass=person)", 4663 "--searchPageSize", "100" 4664 }; 4665 examples.put(args2, INFO_LDAPMODIFY_EXAMPLE_2.get()); 4666 4667 return examples; 4668 } 4669}