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