001/* 002 * Copyright 2016-2023 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2016-2023 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-2023 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 1580 1581 /** 1582 * {@inheritDoc} 1583 */ 1584 @Override() 1585 @NotNull() 1586 protected List<Control> getBindControls() 1587 { 1588 final ArrayList<Control> bindControls = new ArrayList<>(10); 1589 1590 if (bindControl.isPresent()) 1591 { 1592 bindControls.addAll(bindControl.getValues()); 1593 } 1594 1595 if (authorizationIdentity.isPresent()) 1596 { 1597 bindControls.add(new AuthorizationIdentityRequestControl(false)); 1598 } 1599 1600 if (generateAccessToken.isPresent()) 1601 { 1602 bindControls.add(new GenerateAccessTokenRequestControl()); 1603 } 1604 1605 if (getAuthorizationEntryAttribute.isPresent()) 1606 { 1607 bindControls.add(new GetAuthorizationEntryRequestControl(true, true, 1608 getAuthorizationEntryAttribute.getValues())); 1609 } 1610 1611 if (getRecentLoginHistory.isPresent()) 1612 { 1613 bindControls.add(new GetRecentLoginHistoryRequestControl()); 1614 } 1615 1616 if (getUserResourceLimits.isPresent()) 1617 { 1618 bindControls.add(new GetUserResourceLimitsRequestControl()); 1619 } 1620 1621 if (usePasswordPolicyControl.isPresent()) 1622 { 1623 bindControls.add(new PasswordPolicyRequestControl()); 1624 } 1625 1626 if (suppressOperationalAttributeUpdates.isPresent()) 1627 { 1628 final EnumSet<SuppressType> suppressTypes = 1629 EnumSet.noneOf(SuppressType.class); 1630 for (final String s : suppressOperationalAttributeUpdates.getValues()) 1631 { 1632 if (s.equalsIgnoreCase("last-access-time")) 1633 { 1634 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 1635 } 1636 else if (s.equalsIgnoreCase("last-login-time")) 1637 { 1638 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 1639 } 1640 else if (s.equalsIgnoreCase("last-login-ip")) 1641 { 1642 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 1643 } 1644 } 1645 1646 bindControls.add(new SuppressOperationalAttributeUpdateRequestControl( 1647 suppressTypes)); 1648 } 1649 1650 if (useJSONFormattedRequestControls.isPresent()) 1651 { 1652 final JSONFormattedRequestControl jsonFormattedRequestControl = 1653 JSONFormattedRequestControl.createWithControls(true, bindControls); 1654 bindControls.clear(); 1655 bindControls.add(jsonFormattedRequestControl); 1656 } 1657 1658 return bindControls; 1659 } 1660 1661 1662 1663 /** 1664 * {@inheritDoc} 1665 */ 1666 @Override() 1667 protected boolean supportsMultipleServers() 1668 { 1669 // We will support providing information about multiple servers. This tool 1670 // will not communicate with multiple servers concurrently, but it can 1671 // accept information about multiple servers in the event that a large set 1672 // of changes is to be processed and a server goes down in the middle of 1673 // those changes. In this case, we can resume processing on a newly-created 1674 // connection, possibly to a different server. 1675 return true; 1676 } 1677 1678 1679 1680 /** 1681 * {@inheritDoc} 1682 */ 1683 @Override() 1684 @NotNull() 1685 public LDAPConnectionOptions getConnectionOptions() 1686 { 1687 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 1688 1689 options.setUseSynchronousMode(true); 1690 options.setFollowReferrals(followReferrals.isPresent()); 1691 options.setUnsolicitedNotificationHandler(this); 1692 options.setResponseTimeoutMillis(0L); 1693 1694 return options; 1695 } 1696 1697 1698 1699 /** 1700 * {@inheritDoc} 1701 */ 1702 @Override() 1703 @NotNull() 1704 public ResultCode doToolProcessing() 1705 { 1706 // Examine the arguments to determine the sets of controls to use for each 1707 // type of request. 1708 final ArrayList<Control> addControls = new ArrayList<>(10); 1709 final ArrayList<Control> deleteControls = new ArrayList<>(10); 1710 final ArrayList<Control> modifyControls = new ArrayList<>(10); 1711 final ArrayList<Control> modifyDNControls = new ArrayList<>(10); 1712 final ArrayList<Control> searchControls = new ArrayList<>(10); 1713 try 1714 { 1715 createRequestControls(addControls, deleteControls, modifyControls, 1716 modifyDNControls, searchControls); 1717 } 1718 catch (final LDAPException le) 1719 { 1720 Debug.debugException(le); 1721 for (final String line : 1722 ResultUtils.formatResult(le, true, 0, WRAP_COLUMN)) 1723 { 1724 err(line); 1725 } 1726 return le.getResultCode(); 1727 } 1728 1729 1730 // If an encryption passphrase file was specified, then read its value. 1731 String encryptionPassphrase = null; 1732 if (encryptionPassphraseFile.isPresent()) 1733 { 1734 try 1735 { 1736 encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile( 1737 encryptionPassphraseFile.getValue()); 1738 } 1739 catch (final LDAPException e) 1740 { 1741 Debug.debugException(e); 1742 wrapErr(0, WRAP_COLUMN, e.getMessage()); 1743 return e.getResultCode(); 1744 } 1745 } 1746 1747 1748 LDAPConnectionPool connectionPool = null; 1749 LDIFReader ldifReader = null; 1750 LDIFWriter rejectWriter = null; 1751 try 1752 { 1753 // Create a connection pool that will be used to communicate with the 1754 // directory server. If we should use an administrative session, then 1755 // create a connect processor that will be used to start the session 1756 // before performing the bind. 1757 try 1758 { 1759 final StartAdministrativeSessionPostConnectProcessor p; 1760 if (useAdministrativeSession.isPresent()) 1761 { 1762 p = new StartAdministrativeSessionPostConnectProcessor( 1763 new StartAdministrativeSessionExtendedRequest(getToolName(), 1764 true)); 1765 } 1766 else 1767 { 1768 p = null; 1769 } 1770 1771 if (! dryRun.isPresent()) 1772 { 1773 connectionPool = getConnectionPool(1, 2, 0, p, null, true, 1774 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 1775 verbose.isPresent())); 1776 } 1777 } 1778 catch (final LDAPException le) 1779 { 1780 Debug.debugException(le); 1781 1782 // Unable to create the connection pool, which means that either the 1783 // connection could not be established or the attempt to authenticate 1784 // the connection failed. If the bind failed, then the report bind 1785 // result health check should have already reported the bind failure. 1786 // If the failure was something else, then display that failure result. 1787 if (le.getResultCode() != ResultCode.INVALID_CREDENTIALS) 1788 { 1789 for (final String line : 1790 ResultUtils.formatResult(le, true, 0, WRAP_COLUMN)) 1791 { 1792 err(line); 1793 } 1794 } 1795 return le.getResultCode(); 1796 } 1797 1798 if (connectionPool != null) 1799 { 1800 connectionPool.setRetryFailedOperationsDueToInvalidConnections( 1801 (! neverRetry.isPresent())); 1802 } 1803 1804 1805 // Report that the connection was successfully established. 1806 if (connectionPool != null) 1807 { 1808 try 1809 { 1810 final LDAPConnection connection = connectionPool.getConnection(); 1811 final String hostPort = connection.getHostPort(); 1812 connectionPool.releaseConnection(connection); 1813 commentToOut(INFO_LDAPMODIFY_CONNECTION_ESTABLISHED.get(hostPort)); 1814 out(); 1815 } 1816 catch (final LDAPException le) 1817 { 1818 Debug.debugException(le); 1819 // This should never happen. 1820 } 1821 } 1822 1823 1824 // If we should process the operations in a transaction, then start that 1825 // now. 1826 final ASN1OctetString txnID; 1827 if (useTransaction.isPresent()) 1828 { 1829 final Control[] startTxnControls; 1830 if (proxyAs.isPresent()) 1831 { 1832 // In a transaction, the proxied authorization control must only be 1833 // used in the start transaction request and not in any of the 1834 // subsequent operation requests. 1835 startTxnControls = new Control[] 1836 { 1837 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()) 1838 }; 1839 } 1840 else if (proxyV1As.isPresent()) 1841 { 1842 // In a transaction, the proxied authorization control must only be 1843 // used in the start transaction request and not in any of the 1844 // subsequent operation requests. 1845 startTxnControls = new Control[] 1846 { 1847 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()) 1848 }; 1849 } 1850 else 1851 { 1852 startTxnControls = StaticUtils.NO_CONTROLS; 1853 } 1854 1855 try 1856 { 1857 final StartTransactionExtendedResult startTxnResult = 1858 (StartTransactionExtendedResult) 1859 connectionPool.processExtendedOperation( 1860 new StartTransactionExtendedRequest(startTxnControls)); 1861 if (startTxnResult.getResultCode() == ResultCode.SUCCESS) 1862 { 1863 txnID = startTxnResult.getTransactionID(); 1864 1865 final TransactionSpecificationRequestControl c = 1866 new TransactionSpecificationRequestControl(txnID); 1867 addControls.add(c); 1868 deleteControls.add(c); 1869 modifyControls.add(c); 1870 modifyDNControls.add(c); 1871 1872 final String txnIDString; 1873 if (StaticUtils.isPrintableString(txnID.getValue())) 1874 { 1875 txnIDString = txnID.stringValue(); 1876 } 1877 else 1878 { 1879 final StringBuilder hexBuffer = new StringBuilder(); 1880 StaticUtils.toHex(txnID.getValue(), ":", hexBuffer); 1881 txnIDString = hexBuffer.toString(); 1882 } 1883 1884 commentToOut(INFO_LDAPMODIFY_STARTED_TXN.get(txnIDString)); 1885 } 1886 else 1887 { 1888 commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get( 1889 startTxnResult.getResultString())); 1890 return startTxnResult.getResultCode(); 1891 } 1892 } 1893 catch (final LDAPException le) 1894 { 1895 Debug.debugException(le); 1896 commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get( 1897 StaticUtils.getExceptionMessage(le))); 1898 return le.getResultCode(); 1899 } 1900 } 1901 else 1902 { 1903 txnID = null; 1904 } 1905 1906 1907 // Create an LDIF reader that will be used to read the changes to process. 1908 try 1909 { 1910 final InputStream ldifInputStream; 1911 if (ldifFile.isPresent()) 1912 { 1913 ldifInputStream = ToolUtils.getInputStreamForLDIFFiles( 1914 ldifFile.getValues(), encryptionPassphrase, getOut(), 1915 getErr()).getFirst(); 1916 } 1917 else 1918 { 1919 ldifInputStream = in; 1920 } 1921 1922 ldifReader = new LDIFReader(ldifInputStream, 0, null, null, 1923 characterSet.getValue()); 1924 } 1925 catch (final Exception e) 1926 { 1927 commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_LDIF_READER.get( 1928 StaticUtils.getExceptionMessage(e))); 1929 return ResultCode.LOCAL_ERROR; 1930 } 1931 1932 if (stripTrailingSpaces.isPresent()) 1933 { 1934 ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.STRIP); 1935 } 1936 1937 1938 // If appropriate, create a reject writer. 1939 if (rejectFile.isPresent()) 1940 { 1941 try 1942 { 1943 rejectWriter = new LDIFWriter(rejectFile.getValue()); 1944 1945 // Set the maximum allowed wrap column. This is better than setting a 1946 // wrap column of zero because it will ensure that comments don't get 1947 // wrapped either. 1948 rejectWriter.setWrapColumn(Integer.MAX_VALUE); 1949 } 1950 catch (final Exception e) 1951 { 1952 Debug.debugException(e); 1953 commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_REJECT_WRITER.get( 1954 rejectFile.getValue().getAbsolutePath(), 1955 StaticUtils.getExceptionMessage(e))); 1956 return ResultCode.LOCAL_ERROR; 1957 } 1958 } 1959 1960 1961 // If appropriate, create a rate limiter. 1962 final FixedRateBarrier rateLimiter; 1963 if (ratePerSecond.isPresent()) 1964 { 1965 rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue()); 1966 } 1967 else 1968 { 1969 rateLimiter = null; 1970 } 1971 1972 1973 // Iterate through the set of changes to process. 1974 boolean commitTransaction = true; 1975 ResultCode resultCode = null; 1976 final ArrayList<LDAPRequest> multiUpdateRequests = 1977 new ArrayList<>(10); 1978 final boolean isBulkModify = modifyEntriesMatchingFilter.isPresent() || 1979 modifyEntriesMatchingFiltersFromFile.isPresent() || 1980 modifyEntryWithDN.isPresent() || 1981 modifyEntriesWithDNsFromFile.isPresent(); 1982readChangeRecordLoop: 1983 while (true) 1984 { 1985 // If there is a rate limiter, then use it to sleep if necessary. 1986 if ((rateLimiter != null) && (! isBulkModify)) 1987 { 1988 rateLimiter.await(); 1989 } 1990 1991 1992 // Read the next LDIF change record. If we get an error then handle it 1993 // and abort if appropriate. 1994 final LDIFChangeRecord changeRecord; 1995 try 1996 { 1997 changeRecord = ldifReader.readChangeRecord(defaultAdd.isPresent()); 1998 } 1999 catch (final IOException ioe) 2000 { 2001 Debug.debugException(ioe); 2002 2003 final String message = ERR_LDAPMODIFY_IO_ERROR_READING_CHANGE.get( 2004 StaticUtils.getExceptionMessage(ioe)); 2005 commentToErr(message); 2006 writeRejectedChange(rejectWriter, message, null); 2007 commitTransaction = false; 2008 resultCode = ResultCode.LOCAL_ERROR; 2009 break; 2010 } 2011 catch (final LDIFException le) 2012 { 2013 Debug.debugException(le); 2014 2015 final StringBuilder buffer = new StringBuilder(); 2016 if (le.mayContinueReading() && (! useTransaction.isPresent())) 2017 { 2018 buffer.append( 2019 ERR_LDAPMODIFY_RECOVERABLE_LDIF_ERROR_READING_CHANGE.get( 2020 le.getLineNumber(), StaticUtils.getExceptionMessage(le))); 2021 } 2022 else 2023 { 2024 buffer.append( 2025 ERR_LDAPMODIFY_UNRECOVERABLE_LDIF_ERROR_READING_CHANGE.get( 2026 le.getLineNumber(), StaticUtils.getExceptionMessage(le))); 2027 } 2028 2029 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS)) 2030 { 2031 resultCode = ResultCode.LOCAL_ERROR; 2032 } 2033 2034 if ((le.getDataLines() != null) && (! le.getDataLines().isEmpty())) 2035 { 2036 buffer.append(StaticUtils.EOL); 2037 buffer.append(StaticUtils.EOL); 2038 buffer.append(ERR_LDAPMODIFY_INVALID_LINES.get()); 2039 buffer.append(StaticUtils.EOL); 2040 for (final String s : le.getDataLines()) 2041 { 2042 buffer.append(s); 2043 buffer.append(StaticUtils.EOL); 2044 } 2045 } 2046 2047 final String message = buffer.toString(); 2048 commentToErr(message); 2049 writeRejectedChange(rejectWriter, message, null); 2050 2051 if (le.mayContinueReading() && (! useTransaction.isPresent())) 2052 { 2053 continue; 2054 } 2055 else 2056 { 2057 commitTransaction = false; 2058 resultCode = ResultCode.LOCAL_ERROR; 2059 break; 2060 } 2061 } 2062 2063 2064 // If we read a null change record, then there are no more changes to 2065 // process. Otherwise, treat it appropriately based on the operation 2066 // type. 2067 if (changeRecord == null) 2068 { 2069 break; 2070 } 2071 2072 2073 // If we should modify entries matching a specified filter, then convert 2074 // the change record into a set of modifications. 2075 if (modifyEntriesMatchingFilter.isPresent()) 2076 { 2077 for (final Filter filter : modifyEntriesMatchingFilter.getValues()) 2078 { 2079 final ResultCode rc = handleModifyMatchingFilter(connectionPool, 2080 changeRecord, 2081 modifyEntriesMatchingFilter.getIdentifierString(), 2082 filter, searchControls, modifyControls, rateLimiter, 2083 rejectWriter); 2084 if (rc != ResultCode.SUCCESS) 2085 { 2086 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 2087 (resultCode == ResultCode.NO_OPERATION)) 2088 { 2089 resultCode = rc; 2090 } 2091 } 2092 } 2093 } 2094 2095 if (modifyEntriesMatchingFiltersFromFile.isPresent()) 2096 { 2097 for (final File f : modifyEntriesMatchingFiltersFromFile.getValues()) 2098 { 2099 final FilterFileReader filterReader; 2100 try 2101 { 2102 filterReader = new FilterFileReader(f); 2103 } 2104 catch (final Exception e) 2105 { 2106 Debug.debugException(e); 2107 commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_FILTER_FILE.get( 2108 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 2109 return ResultCode.LOCAL_ERROR; 2110 } 2111 2112 try 2113 { 2114 while (true) 2115 { 2116 final Filter filter; 2117 try 2118 { 2119 filter = filterReader.readFilter(); 2120 } 2121 catch (final IOException ioe) 2122 { 2123 Debug.debugException(ioe); 2124 commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_FILTER_FILE.get( 2125 f.getAbsolutePath(), 2126 StaticUtils.getExceptionMessage(ioe))); 2127 return ResultCode.LOCAL_ERROR; 2128 } 2129 catch (final LDAPException le) 2130 { 2131 Debug.debugException(le); 2132 commentToErr(le.getMessage()); 2133 if (continueOnError.isPresent()) 2134 { 2135 if ((resultCode == null) || 2136 (resultCode == ResultCode.SUCCESS) || 2137 (resultCode == ResultCode.NO_OPERATION)) 2138 { 2139 resultCode = le.getResultCode(); 2140 } 2141 continue; 2142 } 2143 else 2144 { 2145 return le.getResultCode(); 2146 } 2147 } 2148 2149 if (filter == null) 2150 { 2151 break; 2152 } 2153 2154 final ResultCode rc = handleModifyMatchingFilter(connectionPool, 2155 changeRecord, 2156 modifyEntriesMatchingFiltersFromFile.getIdentifierString(), 2157 filter, searchControls, modifyControls, rateLimiter, 2158 rejectWriter); 2159 if (rc != ResultCode.SUCCESS) 2160 { 2161 if ((resultCode == null) || 2162 (resultCode == ResultCode.SUCCESS) || 2163 (resultCode == ResultCode.NO_OPERATION)) 2164 { 2165 resultCode = rc; 2166 } 2167 } 2168 } 2169 } 2170 finally 2171 { 2172 try 2173 { 2174 filterReader.close(); 2175 } 2176 catch (final Exception e) 2177 { 2178 Debug.debugException(e); 2179 } 2180 } 2181 } 2182 } 2183 2184 if (modifyEntryWithDN.isPresent()) 2185 { 2186 for (final DN dn : modifyEntryWithDN.getValues()) 2187 { 2188 final ResultCode rc = handleModifyWithDN(connectionPool, 2189 changeRecord, modifyEntryWithDN.getIdentifierString(), dn, 2190 modifyControls, rateLimiter, rejectWriter); 2191 if (rc != ResultCode.SUCCESS) 2192 { 2193 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 2194 (resultCode == ResultCode.NO_OPERATION)) 2195 { 2196 resultCode = rc; 2197 } 2198 } 2199 } 2200 } 2201 2202 if (modifyEntriesWithDNsFromFile.isPresent()) 2203 { 2204 for (final File f : modifyEntriesWithDNsFromFile.getValues()) 2205 { 2206 final DNFileReader dnReader; 2207 try 2208 { 2209 dnReader = new DNFileReader(f); 2210 } 2211 catch (final Exception e) 2212 { 2213 Debug.debugException(e); 2214 commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_DN_FILE.get( 2215 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 2216 return ResultCode.LOCAL_ERROR; 2217 } 2218 2219 try 2220 { 2221 while (true) 2222 { 2223 final DN dn; 2224 try 2225 { 2226 dn = dnReader.readDN(); 2227 } 2228 catch (final IOException ioe) 2229 { 2230 Debug.debugException(ioe); 2231 commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_DN_FILE.get( 2232 f.getAbsolutePath(), 2233 StaticUtils.getExceptionMessage(ioe))); 2234 return ResultCode.LOCAL_ERROR; 2235 } 2236 catch (final LDAPException le) 2237 { 2238 Debug.debugException(le); 2239 commentToErr(le.getMessage()); 2240 if (continueOnError.isPresent()) 2241 { 2242 if ((resultCode == null) || 2243 (resultCode == ResultCode.SUCCESS) || 2244 (resultCode == ResultCode.NO_OPERATION)) 2245 { 2246 resultCode = le.getResultCode(); 2247 } 2248 continue; 2249 } 2250 else 2251 { 2252 return le.getResultCode(); 2253 } 2254 } 2255 2256 if (dn == null) 2257 { 2258 break; 2259 } 2260 2261 final ResultCode rc = handleModifyWithDN(connectionPool, 2262 changeRecord, 2263 modifyEntriesWithDNsFromFile.getIdentifierString(), dn, 2264 modifyControls, rateLimiter, rejectWriter); 2265 if (rc != ResultCode.SUCCESS) 2266 { 2267 if ((resultCode == null) || 2268 (resultCode == ResultCode.SUCCESS) || 2269 (resultCode == ResultCode.NO_OPERATION)) 2270 { 2271 resultCode = rc; 2272 } 2273 } 2274 } 2275 } 2276 finally 2277 { 2278 try 2279 { 2280 dnReader.close(); 2281 } 2282 catch (final Exception e) 2283 { 2284 Debug.debugException(e); 2285 } 2286 } 2287 } 2288 } 2289 2290 if (isBulkModify) 2291 { 2292 continue; 2293 } 2294 2295 try 2296 { 2297 final ResultCode rc; 2298 if (changeRecord instanceof LDIFAddChangeRecord) 2299 { 2300 rc = doAdd((LDIFAddChangeRecord) changeRecord, addControls, 2301 connectionPool, multiUpdateRequests, rejectWriter); 2302 } 2303 else if (changeRecord instanceof LDIFDeleteChangeRecord) 2304 { 2305 rc = doDelete((LDIFDeleteChangeRecord) changeRecord, deleteControls, 2306 connectionPool, multiUpdateRequests, rejectWriter); 2307 } 2308 else if (changeRecord instanceof LDIFModifyChangeRecord) 2309 { 2310 rc = doModify((LDIFModifyChangeRecord) changeRecord, modifyControls, 2311 connectionPool, multiUpdateRequests, rejectWriter); 2312 } 2313 else if (changeRecord instanceof LDIFModifyDNChangeRecord) 2314 { 2315 rc = doModifyDN((LDIFModifyDNChangeRecord) changeRecord, 2316 modifyDNControls, connectionPool, multiUpdateRequests, 2317 rejectWriter); 2318 } 2319 else 2320 { 2321 // This should never happen. 2322 commentToErr(ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get()); 2323 for (final String line : changeRecord.toLDIF()) 2324 { 2325 err("# " + line); 2326 } 2327 throw new LDAPException(ResultCode.PARAM_ERROR, 2328 ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get() + 2329 changeRecord.toString()); 2330 } 2331 2332 if ((resultCode == null) && (rc != ResultCode.SUCCESS)) 2333 { 2334 resultCode = rc; 2335 } 2336 } 2337 catch (final LDAPException le) 2338 { 2339 Debug.debugException(le); 2340 2341 commitTransaction = false; 2342 if (continueOnError.isPresent()) 2343 { 2344 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 2345 (resultCode == ResultCode.NO_OPERATION)) 2346 { 2347 resultCode = le.getResultCode(); 2348 } 2349 } 2350 else 2351 { 2352 resultCode = le.getResultCode(); 2353 break; 2354 } 2355 } 2356 } 2357 2358 2359 // If the operations are part of a transaction, then commit or abort that 2360 // transaction now. Otherwise, if they should be part of a multi-update 2361 // operation, then process that now. 2362 if (useTransaction.isPresent()) 2363 { 2364 LDAPResult endTxnResult; 2365 final EndTransactionExtendedRequest endTxnRequest = 2366 new EndTransactionExtendedRequest(txnID, commitTransaction); 2367 try 2368 { 2369 endTxnResult = connectionPool.processExtendedOperation(endTxnRequest); 2370 } 2371 catch (final LDAPException le) 2372 { 2373 endTxnResult = le.toLDAPResult(); 2374 } 2375 2376 displayResult(endTxnResult, false); 2377 if (((resultCode == null) || (resultCode == ResultCode.SUCCESS)) && 2378 (endTxnResult.getResultCode() != ResultCode.SUCCESS)) 2379 { 2380 resultCode = endTxnResult.getResultCode(); 2381 } 2382 } 2383 else if (multiUpdateErrorBehavior.isPresent()) 2384 { 2385 final MultiUpdateErrorBehavior errorBehavior; 2386 if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase("atomic")) 2387 { 2388 errorBehavior = MultiUpdateErrorBehavior.ATOMIC; 2389 } 2390 else if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase( 2391 "abort-on-error")) 2392 { 2393 errorBehavior = MultiUpdateErrorBehavior.ABORT_ON_ERROR; 2394 } 2395 else 2396 { 2397 errorBehavior = MultiUpdateErrorBehavior.CONTINUE_ON_ERROR; 2398 } 2399 2400 final Control[] multiUpdateControls; 2401 if (proxyAs.isPresent()) 2402 { 2403 multiUpdateControls = new Control[] 2404 { 2405 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()) 2406 }; 2407 } 2408 else if (proxyV1As.isPresent()) 2409 { 2410 multiUpdateControls = new Control[] 2411 { 2412 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()) 2413 }; 2414 } 2415 else 2416 { 2417 multiUpdateControls = StaticUtils.NO_CONTROLS; 2418 } 2419 2420 ExtendedResult multiUpdateResult; 2421 try 2422 { 2423 commentToOut(INFO_LDAPMODIFY_SENDING_MULTI_UPDATE_REQUEST.get()); 2424 final MultiUpdateExtendedRequest multiUpdateRequest = 2425 new MultiUpdateExtendedRequest(errorBehavior, 2426 multiUpdateRequests, multiUpdateControls); 2427 multiUpdateResult = 2428 connectionPool.processExtendedOperation(multiUpdateRequest); 2429 } 2430 catch (final LDAPException le) 2431 { 2432 multiUpdateResult = new ExtendedResult(le); 2433 } 2434 2435 displayResult(multiUpdateResult, false); 2436 resultCode = multiUpdateResult.getResultCode(); 2437 } 2438 2439 2440 if (resultCode == null) 2441 { 2442 return ResultCode.SUCCESS; 2443 } 2444 else 2445 { 2446 return resultCode; 2447 } 2448 } 2449 finally 2450 { 2451 if (rejectWriter != null) 2452 { 2453 try 2454 { 2455 rejectWriter.close(); 2456 } 2457 catch (final Exception e) 2458 { 2459 Debug.debugException(e); 2460 } 2461 } 2462 2463 if (ldifReader != null) 2464 { 2465 try 2466 { 2467 ldifReader.close(); 2468 } 2469 catch (final Exception e) 2470 { 2471 Debug.debugException(e); 2472 } 2473 } 2474 2475 if (connectionPool != null) 2476 { 2477 try 2478 { 2479 connectionPool.close(); 2480 } 2481 catch (final Exception e) 2482 { 2483 Debug.debugException(e); 2484 } 2485 } 2486 } 2487 } 2488 2489 2490 2491 /** 2492 * Handles the processing for a change record when the tool should modify 2493 * entries matching a given filter. 2494 * 2495 * @param connectionPool The connection pool to use to communicate with 2496 * the directory server. 2497 * @param changeRecord The LDIF change record to be processed. 2498 * @param argIdentifierString The identifier string for the argument used to 2499 * specify the filter to use to identify the 2500 * entries to modify. 2501 * @param filter The filter to use to identify the entries to 2502 * modify. 2503 * @param searchControls The set of controls to include in the search 2504 * request. 2505 * @param modifyControls The set of controls to include in the modify 2506 * requests. 2507 * @param rateLimiter The fixed-rate barrier to use for rate 2508 * limiting. It may be {@code null} if no rate 2509 * limiting is required. 2510 * @param rejectWriter The reject writer to use to record information 2511 * about any failed operations. 2512 * 2513 * @return A result code obtained from processing. 2514 */ 2515 @NotNull() 2516 private ResultCode handleModifyMatchingFilter( 2517 @NotNull final LDAPConnectionPool connectionPool, 2518 @NotNull final LDIFChangeRecord changeRecord, 2519 @NotNull final String argIdentifierString, 2520 @NotNull final Filter filter, 2521 @NotNull final List<Control> searchControls, 2522 @NotNull final List<Control> modifyControls, 2523 @Nullable final FixedRateBarrier rateLimiter, 2524 @Nullable final LDIFWriter rejectWriter) 2525 { 2526 // If the provided change record isn't a modify change record, then that's 2527 // an error. Reject it. 2528 if (! (changeRecord instanceof LDIFModifyChangeRecord)) 2529 { 2530 writeRejectedChange(rejectWriter, 2531 ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString), 2532 changeRecord); 2533 return ResultCode.PARAM_ERROR; 2534 } 2535 2536 final LDIFModifyChangeRecord modifyChangeRecord = 2537 (LDIFModifyChangeRecord) changeRecord; 2538 final HashSet<DN> processedDNs = 2539 new HashSet<>(StaticUtils.computeMapCapacity(100)); 2540 2541 2542 // If we need to use the simple paged results control, then we may have to 2543 // issue multiple searches. 2544 ASN1OctetString pagedResultsCookie = null; 2545 long entriesProcessed = 0L; 2546 ResultCode resultCode = ResultCode.SUCCESS; 2547 while (true) 2548 { 2549 // Construct the search request to send. 2550 final LDAPModifySearchListener listener = 2551 new LDAPModifySearchListener(this, modifyChangeRecord, filter, 2552 modifyControls, connectionPool, rateLimiter, rejectWriter, 2553 processedDNs); 2554 2555 final SearchRequest searchRequest = 2556 new SearchRequest(listener, modifyChangeRecord.getDN(), 2557 SearchScope.SUB, filter, SearchRequest.NO_ATTRIBUTES); 2558 searchRequest.setControls(searchControls); 2559 if (searchPageSize.isPresent()) 2560 { 2561 searchRequest.addControl(new SimplePagedResultsControl( 2562 searchPageSize.getValue(), pagedResultsCookie)); 2563 } 2564 2565 2566 // The connection pool's automatic retry feature can't work for searches 2567 // that return one or more entries before encountering a failure. To get 2568 // around that, we'll check a connection out of the pool and use it to 2569 // process the search. If an error occurs that indicates the connection 2570 // is no longer valid, we can replace it with a newly-established 2571 // connection and try again. The search result listener will ensure that 2572 // no entry gets updated twice. 2573 LDAPConnection connection; 2574 try 2575 { 2576 connection = connectionPool.getConnection(); 2577 } 2578 catch (final LDAPException le) 2579 { 2580 Debug.debugException(le); 2581 2582 writeRejectedChange(rejectWriter, 2583 ERR_LDAPMODIFY_CANNOT_GET_SEARCH_CONNECTION.get( 2584 modifyChangeRecord.getDN(), String.valueOf(filter), 2585 StaticUtils.getExceptionMessage(le)), 2586 modifyChangeRecord, le.toLDAPResult()); 2587 return le.getResultCode(); 2588 } 2589 2590 SearchResult searchResult; 2591 boolean connectionValid = false; 2592 try 2593 { 2594 try 2595 { 2596 searchResult = connection.search(searchRequest); 2597 } 2598 catch (final LDAPSearchException lse) 2599 { 2600 searchResult = lse.getSearchResult(); 2601 } 2602 2603 if (searchResult.getResultCode() == ResultCode.SUCCESS) 2604 { 2605 connectionValid = true; 2606 } 2607 else if (searchResult.getResultCode().isConnectionUsable()) 2608 { 2609 connectionValid = true; 2610 writeRejectedChange(rejectWriter, 2611 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2612 String.valueOf(filter)), 2613 modifyChangeRecord, searchResult); 2614 return searchResult.getResultCode(); 2615 } 2616 else if (! neverRetry.isPresent()) 2617 { 2618 try 2619 { 2620 connection = connectionPool.replaceDefunctConnection(connection); 2621 } 2622 catch (final LDAPException le) 2623 { 2624 Debug.debugException(le); 2625 writeRejectedChange(rejectWriter, 2626 ERR_LDAPMODIFY_SEARCH_FAILED_CANNOT_RECONNECT.get( 2627 modifyChangeRecord.getDN(), String.valueOf(filter)), 2628 modifyChangeRecord, searchResult); 2629 return searchResult.getResultCode(); 2630 } 2631 2632 try 2633 { 2634 searchResult = connection.search(searchRequest); 2635 } 2636 catch (final LDAPSearchException lse) 2637 { 2638 Debug.debugException(lse); 2639 searchResult = lse.getSearchResult(); 2640 } 2641 2642 if (searchResult.getResultCode() == ResultCode.SUCCESS) 2643 { 2644 connectionValid = true; 2645 } 2646 else 2647 { 2648 connectionValid = searchResult.getResultCode().isConnectionUsable(); 2649 writeRejectedChange(rejectWriter, 2650 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2651 String.valueOf(filter)), 2652 modifyChangeRecord, searchResult); 2653 return searchResult.getResultCode(); 2654 } 2655 } 2656 else 2657 { 2658 writeRejectedChange(rejectWriter, 2659 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2660 String.valueOf(filter)), 2661 modifyChangeRecord, searchResult); 2662 return searchResult.getResultCode(); 2663 } 2664 } 2665 finally 2666 { 2667 if (connectionValid) 2668 { 2669 connectionPool.releaseConnection(connection); 2670 } 2671 else 2672 { 2673 connectionPool.releaseDefunctConnection(connection); 2674 } 2675 } 2676 2677 searchResult = LDAPSearch.handleJSONEncodedResponseControls(searchResult); 2678 2679 2680 // If we've gotten here, then the search was successful. Check to see if 2681 // any of the modifications failed, and if so then update the result code 2682 // accordingly. 2683 if ((resultCode == ResultCode.SUCCESS) && 2684 (listener.getResultCode() != ResultCode.SUCCESS)) 2685 { 2686 resultCode = listener.getResultCode(); 2687 } 2688 2689 2690 // If the search used the simple paged results control then we may need to 2691 // repeat the search to get the next page. 2692 entriesProcessed += searchResult.getEntryCount(); 2693 if (searchPageSize.isPresent()) 2694 { 2695 final SimplePagedResultsControl responseControl; 2696 try 2697 { 2698 responseControl = SimplePagedResultsControl.get(searchResult); 2699 } 2700 catch (final LDAPException le) 2701 { 2702 Debug.debugException(le); 2703 writeRejectedChange(rejectWriter, 2704 ERR_LDAPMODIFY_CANNOT_DECODE_PAGED_RESULTS_CONTROL.get( 2705 modifyChangeRecord.getDN(), String.valueOf(filter)), 2706 modifyChangeRecord, le.toLDAPResult()); 2707 return le.getResultCode(); 2708 } 2709 2710 if (responseControl == null) 2711 { 2712 writeRejectedChange(rejectWriter, 2713 ERR_LDAPMODIFY_MISSING_PAGED_RESULTS_RESPONSE.get( 2714 modifyChangeRecord.getDN(), String.valueOf(filter)), 2715 modifyChangeRecord); 2716 return ResultCode.CONTROL_NOT_FOUND; 2717 } 2718 else 2719 { 2720 pagedResultsCookie = responseControl.getCookie(); 2721 if (responseControl.moreResultsToReturn()) 2722 { 2723 if (verbose.isPresent()) 2724 { 2725 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED_MORE_PAGES.get( 2726 modifyChangeRecord.getDN(), String.valueOf(filter), 2727 entriesProcessed)); 2728 for (final String resultLine : 2729 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2730 { 2731 out(resultLine); 2732 } 2733 out(); 2734 } 2735 } 2736 else 2737 { 2738 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get( 2739 entriesProcessed, modifyChangeRecord.getDN(), 2740 String.valueOf(filter))); 2741 if (verbose.isPresent()) 2742 { 2743 for (final String resultLine : 2744 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2745 { 2746 out(resultLine); 2747 } 2748 } 2749 2750 out(); 2751 return resultCode; 2752 } 2753 } 2754 } 2755 else 2756 { 2757 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get( 2758 entriesProcessed, modifyChangeRecord.getDN(), 2759 String.valueOf(filter))); 2760 if (verbose.isPresent()) 2761 { 2762 for (final String resultLine : 2763 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2764 { 2765 out(resultLine); 2766 } 2767 } 2768 2769 out(); 2770 return resultCode; 2771 } 2772 } 2773 } 2774 2775 2776 2777 /** 2778 * Handles the processing for a change record when the tool should modify an 2779 * entry with a given DN instead of the DN contained in the change record. 2780 * 2781 * @param connectionPool The connection pool to use to communicate with 2782 * the directory server. 2783 * @param changeRecord The LDIF change record to be processed. 2784 * @param argIdentifierString The identifier string for the argument used to 2785 * specify the DN of the entry to modify. 2786 * @param dn The DN of the entry to modify. 2787 * @param modifyControls The set of controls to include in the modify 2788 * requests. 2789 * @param rateLimiter The fixed-rate barrier to use for rate 2790 * limiting. It may be {@code null} if no rate 2791 * limiting is required. 2792 * @param rejectWriter The reject writer to use to record information 2793 * about any failed operations. 2794 * 2795 * @return A result code obtained from processing. 2796 */ 2797 @NotNull() 2798 private ResultCode handleModifyWithDN( 2799 @NotNull final LDAPConnectionPool connectionPool, 2800 @NotNull final LDIFChangeRecord changeRecord, 2801 @NotNull final String argIdentifierString, 2802 @NotNull final DN dn, 2803 @NotNull final List<Control> modifyControls, 2804 @Nullable final FixedRateBarrier rateLimiter, 2805 @Nullable final LDIFWriter rejectWriter) 2806 { 2807 // If the provided change record isn't a modify change record, then that's 2808 // an error. Reject it. 2809 if (! (changeRecord instanceof LDIFModifyChangeRecord)) 2810 { 2811 writeRejectedChange(rejectWriter, 2812 ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString), 2813 changeRecord); 2814 return ResultCode.PARAM_ERROR; 2815 } 2816 2817 2818 // Create a new modify change record with the provided DN instead of the 2819 // original DN. 2820 final LDIFModifyChangeRecord originalChangeRecord = 2821 (LDIFModifyChangeRecord) changeRecord; 2822 final LDIFModifyChangeRecord updatedChangeRecord = 2823 new LDIFModifyChangeRecord(dn.toString(), 2824 originalChangeRecord.getModifications(), 2825 originalChangeRecord.getControls()); 2826 2827 if (rateLimiter != null) 2828 { 2829 rateLimiter.await(); 2830 } 2831 2832 try 2833 { 2834 return doModify(updatedChangeRecord, modifyControls, connectionPool, null, 2835 rejectWriter); 2836 } 2837 catch (final LDAPException le) 2838 { 2839 Debug.debugException(le); 2840 return le.getResultCode(); 2841 } 2842 } 2843 2844 2845 2846 /** 2847 * Populates lists of request controls that should be included in requests 2848 * of various types. 2849 * 2850 * @param addControls The list of controls to include in add requests. 2851 * @param deleteControls The list of controls to include in delete 2852 * requests. 2853 * @param modifyControls The list of controls to include in modify 2854 * requests. 2855 * @param modifyDNControls The list of controls to include in modify DN 2856 * requests. 2857 * @param searchControls The list of controls to include in search 2858 * requests. 2859 * 2860 * @throws LDAPException If a problem is encountered while creating any of 2861 * the requested controls. 2862 */ 2863 private void createRequestControls( 2864 @NotNull final List<Control> addControls, 2865 @NotNull final List<Control> deleteControls, 2866 @NotNull final List<Control> modifyControls, 2867 @NotNull final List<Control> modifyDNControls, 2868 @NotNull final List<Control> searchControls) 2869 throws LDAPException 2870 { 2871 if (addControl.isPresent()) 2872 { 2873 addControls.addAll(addControl.getValues()); 2874 } 2875 2876 if (deleteControl.isPresent()) 2877 { 2878 deleteControls.addAll(deleteControl.getValues()); 2879 } 2880 2881 if (modifyControl.isPresent()) 2882 { 2883 modifyControls.addAll(modifyControl.getValues()); 2884 } 2885 2886 if (modifyDNControl.isPresent()) 2887 { 2888 modifyDNControls.addAll(modifyDNControl.getValues()); 2889 } 2890 2891 if (accessLogField.isPresent()) 2892 { 2893 final Map<String,JSONValue> fields = new LinkedHashMap<>(); 2894 for (final String nameValueStr : accessLogField.getValues()) 2895 { 2896 final int colonPos = nameValueStr.indexOf(':'); 2897 if (colonPos < 0) 2898 { 2899 throw new LDAPException(ResultCode.PARAM_ERROR, 2900 ERR_LDAPMODIFY_ACCESS_LOG_FIELD_NO_COLON.get( 2901 accessLogField.getIdentifierString(), nameValueStr)); 2902 } 2903 2904 final String fieldName = nameValueStr.substring(0, colonPos); 2905 if (fields.containsKey(fieldName)) 2906 { 2907 throw new LDAPException(ResultCode.PARAM_ERROR, 2908 ERR_LDAPMODIFY_ACCESS_LOG_FIELD_DUPLICATE_FIELD.get( 2909 accessLogField.getIdentifierString(), fieldName)); 2910 } 2911 2912 final String valueStr = nameValueStr.substring(colonPos + 1); 2913 if (valueStr.equalsIgnoreCase("true")) 2914 { 2915 fields.put(fieldName, JSONBoolean.TRUE); 2916 } 2917 else if (valueStr.equalsIgnoreCase("false")) 2918 { 2919 fields.put(fieldName, JSONBoolean.FALSE); 2920 } 2921 else 2922 { 2923 try 2924 { 2925 final BigDecimal d = new BigDecimal(valueStr); 2926 fields.put(fieldName, new JSONNumber(d)); 2927 } 2928 catch (final Exception e) 2929 { 2930 Debug.debugException(e); 2931 fields.put(fieldName, new JSONString(valueStr)); 2932 } 2933 } 2934 } 2935 2936 final AccessLogFieldRequestControl c = 2937 new AccessLogFieldRequestControl(false, new JSONObject(fields)); 2938 addControls.add(c); 2939 deleteControls.add(c); 2940 modifyControls.add(c); 2941 modifyDNControls.add(c); 2942 } 2943 2944 if (operationControl.isPresent()) 2945 { 2946 addControls.addAll(operationControl.getValues()); 2947 deleteControls.addAll(operationControl.getValues()); 2948 modifyControls.addAll(operationControl.getValues()); 2949 modifyDNControls.addAll(operationControl.getValues()); 2950 } 2951 2952 addControls.addAll(routeToBackendSetRequestControls); 2953 deleteControls.addAll(routeToBackendSetRequestControls); 2954 modifyControls.addAll(routeToBackendSetRequestControls); 2955 modifyDNControls.addAll(routeToBackendSetRequestControls); 2956 2957 if (noOperation.isPresent()) 2958 { 2959 final NoOpRequestControl c = new NoOpRequestControl(); 2960 addControls.add(c); 2961 deleteControls.add(c); 2962 modifyControls.add(c); 2963 modifyDNControls.add(c); 2964 } 2965 2966 if (generatePassword.isPresent()) 2967 { 2968 addControls.add(new GeneratePasswordRequestControl()); 2969 } 2970 2971 if (getBackendSetID.isPresent()) 2972 { 2973 final GetBackendSetIDRequestControl c = 2974 new GetBackendSetIDRequestControl(false); 2975 addControls.add(c); 2976 deleteControls.add(c); 2977 modifyControls.add(c); 2978 modifyDNControls.add(c); 2979 } 2980 2981 if (getServerID.isPresent()) 2982 { 2983 final GetServerIDRequestControl c = 2984 new GetServerIDRequestControl(false); 2985 addControls.add(c); 2986 deleteControls.add(c); 2987 modifyControls.add(c); 2988 modifyDNControls.add(c); 2989 } 2990 2991 if (ignoreNoUserModification.isPresent()) 2992 { 2993 addControls.add(new IgnoreNoUserModificationRequestControl(false)); 2994 modifyControls.add(new IgnoreNoUserModificationRequestControl(false)); 2995 } 2996 2997 if (nameWithEntryUUID.isPresent()) 2998 { 2999 addControls.add(new NameWithEntryUUIDRequestControl(true)); 3000 } 3001 3002 if (permissiveModify.isPresent()) 3003 { 3004 modifyControls.add(new PermissiveModifyRequestControl(false)); 3005 } 3006 3007 if (routeToServer.isPresent()) 3008 { 3009 final RouteToServerRequestControl c = 3010 new RouteToServerRequestControl(false, 3011 routeToServer.getValue(), false, false, false); 3012 addControls.add(c); 3013 deleteControls.add(c); 3014 modifyControls.add(c); 3015 modifyDNControls.add(c); 3016 } 3017 3018 if (suppressReferentialIntegrityUpdates.isPresent()) 3019 { 3020 final SuppressReferentialIntegrityUpdatesRequestControl c = 3021 new SuppressReferentialIntegrityUpdatesRequestControl(true); 3022 deleteControls.add(c); 3023 modifyDNControls.add(c); 3024 } 3025 3026 if (suppressOperationalAttributeUpdates.isPresent()) 3027 { 3028 final EnumSet<SuppressType> suppressTypes = 3029 EnumSet.noneOf(SuppressType.class); 3030 for (final String s : suppressOperationalAttributeUpdates.getValues()) 3031 { 3032 if (s.equalsIgnoreCase("last-access-time")) 3033 { 3034 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 3035 } 3036 else if (s.equalsIgnoreCase("last-login-time")) 3037 { 3038 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 3039 } 3040 else if (s.equalsIgnoreCase("last-login-ip")) 3041 { 3042 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 3043 } 3044 else if (s.equalsIgnoreCase("lastmod")) 3045 { 3046 suppressTypes.add(SuppressType.LASTMOD); 3047 } 3048 } 3049 3050 final SuppressOperationalAttributeUpdateRequestControl c = 3051 new SuppressOperationalAttributeUpdateRequestControl(suppressTypes); 3052 addControls.add(c); 3053 deleteControls.add(c); 3054 modifyControls.add(c); 3055 modifyDNControls.add(c); 3056 } 3057 3058 if (usePasswordPolicyControl.isPresent()) 3059 { 3060 final PasswordPolicyRequestControl c = new PasswordPolicyRequestControl(); 3061 addControls.add(c); 3062 modifyControls.add(c); 3063 } 3064 3065 if (assuredReplication.isPresent()) 3066 { 3067 AssuredReplicationLocalLevel localLevel = null; 3068 if (assuredReplicationLocalLevel.isPresent()) 3069 { 3070 final String level = assuredReplicationLocalLevel.getValue(); 3071 if (level.equalsIgnoreCase("none")) 3072 { 3073 localLevel = AssuredReplicationLocalLevel.NONE; 3074 } 3075 else if (level.equalsIgnoreCase("received-any-server")) 3076 { 3077 localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER; 3078 } 3079 else if (level.equalsIgnoreCase("processed-all-servers")) 3080 { 3081 localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS; 3082 } 3083 } 3084 3085 AssuredReplicationRemoteLevel remoteLevel = null; 3086 if (assuredReplicationRemoteLevel.isPresent()) 3087 { 3088 final String level = assuredReplicationRemoteLevel.getValue(); 3089 if (level.equalsIgnoreCase("none")) 3090 { 3091 remoteLevel = AssuredReplicationRemoteLevel.NONE; 3092 } 3093 else if (level.equalsIgnoreCase("received-any-remote-location")) 3094 { 3095 remoteLevel = 3096 AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION; 3097 } 3098 else if (level.equalsIgnoreCase("received-all-remote-locations")) 3099 { 3100 remoteLevel = 3101 AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS; 3102 } 3103 else if (level.equalsIgnoreCase("processed-all-remote-servers")) 3104 { 3105 remoteLevel = 3106 AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS; 3107 } 3108 } 3109 3110 Long timeoutMillis = null; 3111 if (assuredReplicationTimeout.isPresent()) 3112 { 3113 timeoutMillis = 3114 assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS); 3115 } 3116 3117 final AssuredReplicationRequestControl c = 3118 new AssuredReplicationRequestControl(true, localLevel, localLevel, 3119 remoteLevel, remoteLevel, timeoutMillis, false); 3120 addControls.add(c); 3121 deleteControls.add(c); 3122 modifyControls.add(c); 3123 modifyDNControls.add(c); 3124 } 3125 3126 if (hardDelete.isPresent() && (! clientSideSubtreeDelete.isPresent())) 3127 { 3128 deleteControls.add(new HardDeleteRequestControl(true)); 3129 } 3130 3131 if (replicationRepair.isPresent()) 3132 { 3133 final ReplicationRepairRequestControl c = 3134 new ReplicationRepairRequestControl(); 3135 addControls.add(c); 3136 deleteControls.add(c); 3137 modifyControls.add(c); 3138 modifyDNControls.add(c); 3139 } 3140 3141 if (softDelete.isPresent()) 3142 { 3143 deleteControls.add(new SoftDeleteRequestControl(true, true)); 3144 } 3145 3146 if (serverSideSubtreeDelete.isPresent()) 3147 { 3148 deleteControls.add(new SubtreeDeleteRequestControl()); 3149 } 3150 3151 if (assertionFilter.isPresent()) 3152 { 3153 final AssertionRequestControl c = new AssertionRequestControl( 3154 assertionFilter.getValue(), true); 3155 addControls.add(c); 3156 deleteControls.add(c); 3157 modifyControls.add(c); 3158 modifyDNControls.add(c); 3159 } 3160 3161 if (operationPurpose.isPresent()) 3162 { 3163 final OperationPurposeRequestControl c = 3164 new OperationPurposeRequestControl(false, "ldapmodify", 3165 Version.NUMERIC_VERSION_STRING, 3166 LDAPModify.class.getName() + ".createRequestControls", 3167 operationPurpose.getValue()); 3168 addControls.add(c); 3169 deleteControls.add(c); 3170 modifyControls.add(c); 3171 modifyDNControls.add(c); 3172 } 3173 3174 if (manageDsaIT.isPresent()) 3175 { 3176 final ManageDsaITRequestControl c = new ManageDsaITRequestControl(true); 3177 addControls.add(c); 3178 if (! clientSideSubtreeDelete.isPresent()) 3179 { 3180 deleteControls.add(c); 3181 } 3182 modifyControls.add(c); 3183 modifyDNControls.add(c); 3184 } 3185 3186 if (passwordUpdateBehavior.isPresent()) 3187 { 3188 final PasswordUpdateBehaviorRequestControl c = 3189 createPasswordUpdateBehaviorRequestControl( 3190 passwordUpdateBehavior.getIdentifierString(), 3191 passwordUpdateBehavior.getValues()); 3192 addControls.add(c); 3193 modifyControls.add(c); 3194 } 3195 3196 if (preReadAttribute.isPresent()) 3197 { 3198 final ArrayList<String> attrList = new ArrayList<>(10); 3199 for (final String value : preReadAttribute.getValues()) 3200 { 3201 final StringTokenizer tokenizer = new StringTokenizer(value, ", "); 3202 while (tokenizer.hasMoreTokens()) 3203 { 3204 attrList.add(tokenizer.nextToken()); 3205 } 3206 } 3207 3208 final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS); 3209 final PreReadRequestControl c = new PreReadRequestControl(attrArray); 3210 deleteControls.add(c); 3211 modifyControls.add(c); 3212 modifyDNControls.add(c); 3213 } 3214 3215 if (postReadAttribute.isPresent()) 3216 { 3217 final ArrayList<String> attrList = new ArrayList<>(10); 3218 for (final String value : postReadAttribute.getValues()) 3219 { 3220 final StringTokenizer tokenizer = new StringTokenizer(value, ", "); 3221 while (tokenizer.hasMoreTokens()) 3222 { 3223 attrList.add(tokenizer.nextToken()); 3224 } 3225 } 3226 3227 final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS); 3228 final PostReadRequestControl c = new PostReadRequestControl(attrArray); 3229 addControls.add(c); 3230 modifyControls.add(c); 3231 modifyDNControls.add(c); 3232 } 3233 3234 if (proxyAs.isPresent() && (! useTransaction.isPresent()) && 3235 (! multiUpdateErrorBehavior.isPresent())) 3236 { 3237 final ProxiedAuthorizationV2RequestControl c = 3238 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()); 3239 addControls.add(c); 3240 deleteControls.add(c); 3241 modifyControls.add(c); 3242 modifyDNControls.add(c); 3243 searchControls.add(c); 3244 } 3245 3246 if (proxyV1As.isPresent() && (! useTransaction.isPresent()) && 3247 (! multiUpdateErrorBehavior.isPresent())) 3248 { 3249 final ProxiedAuthorizationV1RequestControl c = 3250 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()); 3251 addControls.add(c); 3252 deleteControls.add(c); 3253 modifyControls.add(c); 3254 modifyDNControls.add(c); 3255 searchControls.add(c); 3256 } 3257 3258 if (uniquenessAttribute.isPresent() || uniquenessFilter.isPresent()) 3259 { 3260 final UniquenessRequestControlProperties uniquenessProperties; 3261 if (uniquenessAttribute.isPresent()) 3262 { 3263 uniquenessProperties = new UniquenessRequestControlProperties( 3264 uniquenessAttribute.getValues()); 3265 if (uniquenessFilter.isPresent()) 3266 { 3267 uniquenessProperties.setFilter(uniquenessFilter.getValue()); 3268 } 3269 } 3270 else 3271 { 3272 uniquenessProperties = new UniquenessRequestControlProperties( 3273 uniquenessFilter.getValue()); 3274 } 3275 3276 if (uniquenessBaseDN.isPresent()) 3277 { 3278 uniquenessProperties.setBaseDN(uniquenessBaseDN.getStringValue()); 3279 } 3280 3281 if (uniquenessMultipleAttributeBehavior.isPresent()) 3282 { 3283 final String value = 3284 uniquenessMultipleAttributeBehavior.getValue().toLowerCase(); 3285 switch (value) 3286 { 3287 case "unique-within-each-attribute": 3288 uniquenessProperties.setMultipleAttributeBehavior( 3289 UniquenessMultipleAttributeBehavior. 3290 UNIQUE_WITHIN_EACH_ATTRIBUTE); 3291 break; 3292 case "unique-across-all-attributes-including-in-same-entry": 3293 uniquenessProperties.setMultipleAttributeBehavior( 3294 UniquenessMultipleAttributeBehavior. 3295 UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_IN_SAME_ENTRY); 3296 break; 3297 case "unique-across-all-attributes-except-in-same-entry": 3298 uniquenessProperties.setMultipleAttributeBehavior( 3299 UniquenessMultipleAttributeBehavior. 3300 UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_IN_SAME_ENTRY); 3301 break; 3302 case "unique-in-combination": 3303 uniquenessProperties.setMultipleAttributeBehavior( 3304 UniquenessMultipleAttributeBehavior.UNIQUE_IN_COMBINATION); 3305 break; 3306 } 3307 } 3308 3309 if (uniquenessPreCommitValidationLevel.isPresent()) 3310 { 3311 final String value = 3312 uniquenessPreCommitValidationLevel.getValue().toLowerCase(); 3313 switch (value) 3314 { 3315 case "none": 3316 uniquenessProperties.setPreCommitValidationLevel( 3317 UniquenessValidationLevel.NONE); 3318 break; 3319 case "all-subtree-views": 3320 uniquenessProperties.setPreCommitValidationLevel( 3321 UniquenessValidationLevel.ALL_SUBTREE_VIEWS); 3322 break; 3323 case "all-backend-sets": 3324 uniquenessProperties.setPreCommitValidationLevel( 3325 UniquenessValidationLevel.ALL_BACKEND_SETS); 3326 break; 3327 case "all-available-backend-servers": 3328 uniquenessProperties.setPreCommitValidationLevel( 3329 UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS); 3330 break; 3331 } 3332 } 3333 3334 if (uniquenessPostCommitValidationLevel.isPresent()) 3335 { 3336 final String value = 3337 uniquenessPostCommitValidationLevel.getValue().toLowerCase(); 3338 switch (value) 3339 { 3340 case "none": 3341 uniquenessProperties.setPostCommitValidationLevel( 3342 UniquenessValidationLevel.NONE); 3343 break; 3344 case "all-subtree-views": 3345 uniquenessProperties.setPostCommitValidationLevel( 3346 UniquenessValidationLevel.ALL_SUBTREE_VIEWS); 3347 break; 3348 case "all-backend-sets": 3349 uniquenessProperties.setPostCommitValidationLevel( 3350 UniquenessValidationLevel.ALL_BACKEND_SETS); 3351 break; 3352 case "all-available-backend-servers": 3353 uniquenessProperties.setPostCommitValidationLevel( 3354 UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS); 3355 break; 3356 } 3357 } 3358 3359 final UniquenessRequestControl c = 3360 new UniquenessRequestControl(true, null, uniquenessProperties); 3361 addControls.add(c); 3362 modifyControls.add(c); 3363 modifyDNControls.add(c); 3364 } 3365 3366 3367 if (useJSONFormattedRequestControls.isPresent()) 3368 { 3369 final JSONFormattedRequestControl jsonFormattedAddRequestControl = 3370 JSONFormattedRequestControl.createWithControls(true, addControls); 3371 addControls.clear(); 3372 addControls.add(jsonFormattedAddRequestControl); 3373 3374 final JSONFormattedRequestControl jsonFormattedDeleteRequestControl = 3375 JSONFormattedRequestControl.createWithControls(true, deleteControls); 3376 deleteControls.clear(); 3377 deleteControls.add(jsonFormattedDeleteRequestControl); 3378 3379 final JSONFormattedRequestControl jsonFormattedModifyRequestControl = 3380 JSONFormattedRequestControl.createWithControls(true, modifyControls); 3381 modifyControls.clear(); 3382 modifyControls.add(jsonFormattedModifyRequestControl); 3383 3384 final JSONFormattedRequestControl jsonFormattedModifyDNRequestControl = 3385 JSONFormattedRequestControl.createWithControls(true, 3386 modifyDNControls); 3387 modifyDNControls.clear(); 3388 modifyDNControls.add(jsonFormattedModifyDNRequestControl); 3389 3390 final JSONFormattedRequestControl jsonFormattedSearchRequestControl = 3391 JSONFormattedRequestControl.createWithControls(true, searchControls); 3392 searchControls.clear(); 3393 searchControls.add(jsonFormattedSearchRequestControl); 3394 } 3395 } 3396 3397 3398 3399 /** 3400 * Creates the password update behavior request control that should be 3401 * included in add and modify requests. 3402 * 3403 * @param argIdentifier The identifier string for the argument used to 3404 * configure the password update behavior request 3405 * control. 3406 * @param argValues The set of values for the password update behavior 3407 * request control. 3408 * 3409 * @return The password update behavior request control that was created. 3410 * 3411 * @throws LDAPException If a problem is encountered while creating the 3412 * control. 3413 */ 3414 @NotNull() 3415 static PasswordUpdateBehaviorRequestControl 3416 createPasswordUpdateBehaviorRequestControl( 3417 @NotNull final String argIdentifier, 3418 @NotNull final List<String> argValues) 3419 throws LDAPException 3420 { 3421 final PasswordUpdateBehaviorRequestControlProperties properties = 3422 new PasswordUpdateBehaviorRequestControlProperties(); 3423 3424 for (final String argValue : argValues) 3425 { 3426 int delimiterPos = argValue.indexOf('='); 3427 if (delimiterPos < 0) 3428 { 3429 delimiterPos = argValue.indexOf(':'); 3430 } 3431 3432 if ((delimiterPos <= 0) || (delimiterPos >= (argValue.length() - 1))) 3433 { 3434 throw new LDAPException(ResultCode.PARAM_ERROR, 3435 ERR_LDAPMODIFY_MALFORMED_PW_UPDATE_BEHAVIOR.get(argValue, 3436 argIdentifier)); 3437 } 3438 3439 final String name = argValue.substring(0, delimiterPos).trim(); 3440 final String value = argValue.substring(delimiterPos+1).trim(); 3441 if (name.equalsIgnoreCase("is-self-change") || 3442 name.equalsIgnoreCase("self-change") || 3443 name.equalsIgnoreCase("isSelfChange") || 3444 name.equalsIgnoreCase("selfChange")) 3445 { 3446 properties.setIsSelfChange(parseBooleanValue(name, value)); 3447 } 3448 else if (name.equalsIgnoreCase("allow-pre-encoded-password") || 3449 name.equalsIgnoreCase("allow-pre-encoded-passwords") || 3450 name.equalsIgnoreCase("allow-pre-encoded") || 3451 name.equalsIgnoreCase("allowPreEncodedPassword") || 3452 name.equalsIgnoreCase("allowPreEncodedPasswords") || 3453 name.equalsIgnoreCase("allowPreEncoded")) 3454 { 3455 properties.setAllowPreEncodedPassword(parseBooleanValue(name, value)); 3456 } 3457 else if (name.equalsIgnoreCase("skip-password-validation") || 3458 name.equalsIgnoreCase("skip-password-validators") || 3459 name.equalsIgnoreCase("skip-validation") || 3460 name.equalsIgnoreCase("skip-validators") || 3461 name.equalsIgnoreCase("skipPasswordValidation") || 3462 name.equalsIgnoreCase("skipPasswordValidators") || 3463 name.equalsIgnoreCase("skipValidation") || 3464 name.equalsIgnoreCase("skipValidators")) 3465 { 3466 properties.setSkipPasswordValidation(parseBooleanValue(name, value)); 3467 } 3468 else if (name.equalsIgnoreCase("ignore-password-history") || 3469 name.equalsIgnoreCase("skip-password-history") || 3470 name.equalsIgnoreCase("ignore-history") || 3471 name.equalsIgnoreCase("skip-history") || 3472 name.equalsIgnoreCase("ignorePasswordHistory") || 3473 name.equalsIgnoreCase("skipPasswordHistory") || 3474 name.equalsIgnoreCase("ignoreHistory") || 3475 name.equalsIgnoreCase("skipHistory")) 3476 { 3477 properties.setIgnorePasswordHistory(parseBooleanValue(name, value)); 3478 } 3479 else if (name.equalsIgnoreCase("ignore-minimum-password-age") || 3480 name.equalsIgnoreCase("ignore-min-password-age") || 3481 name.equalsIgnoreCase("ignore-password-age") || 3482 name.equalsIgnoreCase("skip-minimum-password-age") || 3483 name.equalsIgnoreCase("skip-min-password-age") || 3484 name.equalsIgnoreCase("skip-password-age") || 3485 name.equalsIgnoreCase("ignoreMinimumPasswordAge") || 3486 name.equalsIgnoreCase("ignoreMinPasswordAge") || 3487 name.equalsIgnoreCase("ignorePasswordAge") || 3488 name.equalsIgnoreCase("skipMinimumPasswordAge") || 3489 name.equalsIgnoreCase("skipMinPasswordAge") || 3490 name.equalsIgnoreCase("skipPasswordAge")) 3491 { 3492 properties.setIgnoreMinimumPasswordAge(parseBooleanValue(name, value)); 3493 } 3494 else if (name.equalsIgnoreCase("password-storage-scheme") || 3495 name.equalsIgnoreCase("password-scheme") || 3496 name.equalsIgnoreCase("storage-scheme") || 3497 name.equalsIgnoreCase("scheme") || 3498 name.equalsIgnoreCase("passwordStorageScheme") || 3499 name.equalsIgnoreCase("passwordScheme") || 3500 name.equalsIgnoreCase("storageScheme")) 3501 { 3502 properties.setPasswordStorageScheme(value); 3503 } 3504 else if (name.equalsIgnoreCase("must-change-password") || 3505 name.equalsIgnoreCase("mustChangePassword")) 3506 { 3507 properties.setMustChangePassword(parseBooleanValue(name, value)); 3508 } 3509 } 3510 3511 return new PasswordUpdateBehaviorRequestControl(properties, true); 3512 } 3513 3514 3515 3516 /** 3517 * Parses the provided value as the Boolean value for a password update 3518 * behavior property. 3519 * 3520 * @param name The name of the password update behavior property being 3521 * parsed. 3522 * @param value The value to be parsed. 3523 * 3524 * @return The Boolean value that was parsed. 3525 * 3526 * @throws LDAPException If the provided value cannot be parsed as a 3527 * Boolean value. 3528 */ 3529 private static boolean parseBooleanValue(@NotNull final String name, 3530 @NotNull final String value) 3531 throws LDAPException 3532 { 3533 if (value.equalsIgnoreCase("true") || 3534 value.equalsIgnoreCase("t") || 3535 value.equalsIgnoreCase("yes") || 3536 value.equalsIgnoreCase("y") || 3537 value.equalsIgnoreCase("1")) 3538 { 3539 return true; 3540 } 3541 else if (value.equalsIgnoreCase("false") || 3542 value.equalsIgnoreCase("f") || 3543 value.equalsIgnoreCase("no") || 3544 value.equalsIgnoreCase("n") || 3545 value.equalsIgnoreCase("0")) 3546 { 3547 return false; 3548 } 3549 else 3550 { 3551 throw new LDAPException(ResultCode.PARAM_ERROR, 3552 ERR_LDAPMODIFY_INVALID_PW_UPDATE_BOOLEAN_VALUE.get(value, name)); 3553 } 3554 } 3555 3556 3557 3558 /** 3559 * Performs the appropriate processing for an LDIF add change record. 3560 * 3561 * @param changeRecord The LDIF add change record to process. 3562 * @param controls The set of controls to include in the request. 3563 * @param pool The connection pool to use to communicate with 3564 * the directory server. 3565 * @param multiUpdateRequests The list to which the request should be added 3566 * if it is to be processed as part of a 3567 * multi-update operation. It may be 3568 * {@code null} if the operation should not be 3569 * processed via the multi-update operation. 3570 * @param rejectWriter The LDIF writer to use for recording 3571 * information about rejected changes. It may be 3572 * {@code null} if no reject writer is 3573 * configured. 3574 * 3575 * @return The result code obtained from processing. 3576 * 3577 * @throws LDAPException If the operation did not complete successfully 3578 * and processing should not continue. 3579 */ 3580 @NotNull() 3581 private ResultCode doAdd(@NotNull final LDIFAddChangeRecord changeRecord, 3582 @NotNull final List<Control> controls, 3583 @NotNull final LDAPConnectionPool pool, 3584 @Nullable final List<LDAPRequest> multiUpdateRequests, 3585 @Nullable final LDIFWriter rejectWriter) 3586 throws LDAPException 3587 { 3588 // Create the add request to process. 3589 final AddRequest addRequest = changeRecord.toAddRequest(true); 3590 for (final Control c : controls) 3591 { 3592 addRequest.addControl(c); 3593 } 3594 3595 3596 // If we should provide support for undelete operations and the entry 3597 // includes the ds-undelete-from-dn attribute, then add the undelete request 3598 // control. 3599 if (allowUndelete.isPresent() && 3600 addRequest.hasAttribute(ATTR_UNDELETE_FROM_DN)) 3601 { 3602 addRequest.addControl(new UndeleteRequestControl()); 3603 } 3604 3605 3606 // If the entry to add includes a password, then add a password validation 3607 // details request control if appropriate. 3608 if (passwordValidationDetails.isPresent()) 3609 { 3610 final Entry entryToAdd = addRequest.toEntry(); 3611 if ((! entryToAdd.getAttributesWithOptions(ATTR_USER_PASSWORD, 3612 null).isEmpty()) || 3613 (! entryToAdd.getAttributesWithOptions(ATTR_AUTH_PASSWORD, 3614 null).isEmpty())) 3615 { 3616 addRequest.addControl(new PasswordValidationDetailsRequestControl()); 3617 } 3618 } 3619 3620 3621 // If the operation should be processed in a multi-update operation, then 3622 // just add the request to the list and return without doing anything else. 3623 if (multiUpdateErrorBehavior.isPresent()) 3624 { 3625 multiUpdateRequests.add(addRequest); 3626 commentToOut(INFO_LDAPMODIFY_ADD_ADDED_TO_MULTI_UPDATE.get( 3627 addRequest.getDN())); 3628 return ResultCode.SUCCESS; 3629 } 3630 3631 3632 // If the --dryRun argument was provided, then we'll stop here. 3633 if (dryRun.isPresent()) 3634 { 3635 commentToOut(INFO_LDAPMODIFY_DRY_RUN_ADD.get(addRequest.getDN(), 3636 dryRun.getIdentifierString())); 3637 return ResultCode.SUCCESS; 3638 } 3639 3640 3641 // Process the add operation and get the result. 3642 commentToOut(INFO_LDAPMODIFY_ADDING_ENTRY.get(addRequest.getDN())); 3643 if (verbose.isPresent()) 3644 { 3645 for (final String ldifLine : 3646 addRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 3647 { 3648 out(ldifLine); 3649 } 3650 out(); 3651 } 3652 3653 LDAPResult addResult; 3654 try 3655 { 3656 addResult = pool.add(addRequest); 3657 } 3658 catch (final LDAPException le) 3659 { 3660 Debug.debugException(le); 3661 addResult = le.toLDAPResult(); 3662 } 3663 3664 addResult = handleJSONEncodedResponseControls(addResult); 3665 3666 3667 // Display information about the result. 3668 displayResult(addResult, useTransaction.isPresent()); 3669 3670 3671 // See if the add operation succeeded or failed. If it failed, and we 3672 // should end all processing, then throw an exception. 3673 switch (addResult.getResultCode().intValue()) 3674 { 3675 case ResultCode.SUCCESS_INT_VALUE: 3676 case ResultCode.NO_OPERATION_INT_VALUE: 3677 break; 3678 3679 case ResultCode.ASSERTION_FAILED_INT_VALUE: 3680 writeRejectedChange(rejectWriter, 3681 INFO_LDAPMODIFY_ASSERTION_FAILED.get(addRequest.getDN(), 3682 String.valueOf(assertionFilter.getValue())), 3683 addRequest.toLDIFChangeRecord(), addResult); 3684 throw new LDAPException(addResult); 3685 3686 default: 3687 writeRejectedChange(rejectWriter, null, addRequest.toLDIFChangeRecord(), 3688 addResult); 3689 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 3690 { 3691 throw new LDAPException(addResult); 3692 } 3693 break; 3694 } 3695 3696 return addResult.getResultCode(); 3697 } 3698 3699 3700 3701 /** 3702 * Performs the appropriate processing for an LDIF delete change record. 3703 * 3704 * @param changeRecord The LDIF delete change record to process. 3705 * @param controls The set of controls to include in the request. 3706 * @param pool The connection pool to use to communicate with 3707 * the directory server. 3708 * @param multiUpdateRequests The list to which the request should be added 3709 * if it is to be processed as part of a 3710 * multi-update operation. It may be 3711 * {@code null} if the operation should not be 3712 * processed via the multi-update operation. 3713 * @param rejectWriter The LDIF writer to use for recording 3714 * information about rejected changes. It may be 3715 * {@code null} if no reject writer is 3716 * configured. 3717 * 3718 * @return The result code obtained from processing. 3719 * 3720 * @throws LDAPException If the operation did not complete successfully 3721 * and processing should not continue. 3722 */ 3723 @NotNull() 3724 private ResultCode doDelete( 3725 @NotNull final LDIFDeleteChangeRecord changeRecord, 3726 @NotNull final List<Control> controls, 3727 @NotNull final LDAPConnectionPool pool, 3728 @Nullable final List<LDAPRequest> multiUpdateRequests, 3729 @Nullable final LDIFWriter rejectWriter) 3730 throws LDAPException 3731 { 3732 // If we should perform a client-side subtree delete, then do that 3733 // differently. 3734 if (clientSideSubtreeDelete.isPresent()) 3735 { 3736 return doClientSideSubtreeDelete(changeRecord, controls, pool, 3737 rejectWriter); 3738 } 3739 3740 3741 // Create the delete request to process. 3742 final DeleteRequest deleteRequest = changeRecord.toDeleteRequest(true); 3743 for (final Control c : controls) 3744 { 3745 deleteRequest.addControl(c); 3746 } 3747 3748 3749 // If the operation should be processed in a multi-update operation, then 3750 // just add the request to the list and return without doing anything else. 3751 if (multiUpdateErrorBehavior.isPresent()) 3752 { 3753 multiUpdateRequests.add(deleteRequest); 3754 commentToOut(INFO_LDAPMODIFY_DELETE_ADDED_TO_MULTI_UPDATE.get( 3755 deleteRequest.getDN())); 3756 return ResultCode.SUCCESS; 3757 } 3758 3759 3760 // If the --dryRun argument was provided, then we'll stop here. 3761 if (dryRun.isPresent()) 3762 { 3763 commentToOut(INFO_LDAPMODIFY_DRY_RUN_DELETE.get(deleteRequest.getDN(), 3764 dryRun.getIdentifierString())); 3765 return ResultCode.SUCCESS; 3766 } 3767 3768 3769 // Process the delete operation and get the result. 3770 commentToOut(INFO_LDAPMODIFY_DELETING_ENTRY.get(deleteRequest.getDN())); 3771 if (verbose.isPresent()) 3772 { 3773 for (final String ldifLine : 3774 deleteRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 3775 { 3776 out(ldifLine); 3777 } 3778 out(); 3779 } 3780 3781 3782 LDAPResult deleteResult; 3783 try 3784 { 3785 deleteResult = pool.delete(deleteRequest); 3786 } 3787 catch (final LDAPException le) 3788 { 3789 Debug.debugException(le); 3790 deleteResult = le.toLDAPResult(); 3791 } 3792 3793 deleteResult = handleJSONEncodedResponseControls(deleteResult); 3794 3795 3796 // Display information about the result. 3797 displayResult(deleteResult, useTransaction.isPresent()); 3798 3799 3800 // See if the delete operation succeeded or failed. If it failed, and we 3801 // should end all processing, then throw an exception. 3802 switch (deleteResult.getResultCode().intValue()) 3803 { 3804 case ResultCode.SUCCESS_INT_VALUE: 3805 case ResultCode.NO_OPERATION_INT_VALUE: 3806 break; 3807 3808 case ResultCode.ASSERTION_FAILED_INT_VALUE: 3809 writeRejectedChange(rejectWriter, 3810 INFO_LDAPMODIFY_ASSERTION_FAILED.get(deleteRequest.getDN(), 3811 String.valueOf(assertionFilter.getValue())), 3812 deleteRequest.toLDIFChangeRecord(), deleteResult); 3813 throw new LDAPException(deleteResult); 3814 3815 default: 3816 writeRejectedChange(rejectWriter, null, 3817 deleteRequest.toLDIFChangeRecord(), deleteResult); 3818 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 3819 { 3820 throw new LDAPException(deleteResult); 3821 } 3822 break; 3823 } 3824 3825 return deleteResult.getResultCode(); 3826 } 3827 3828 3829 3830 /** 3831 * Performs the appropriate processing for an LDIF delete change record. 3832 * 3833 * @param changeRecord The LDIF delete change record to process. 3834 * @param controls The set of controls to include in the request. 3835 * @param pool The connection pool to use to communicate with the 3836 * directory server. 3837 * @param rejectWriter The LDIF writer to use for recording information 3838 * about rejected changes. It may be {@code null} if no 3839 * reject writer is configured. 3840 * 3841 * @return The result code obtained from processing. 3842 * 3843 * @throws LDAPException If the operation did not complete successfully 3844 * and processing should not continue. 3845 */ 3846 @NotNull() 3847 private ResultCode doClientSideSubtreeDelete( 3848 @NotNull final LDIFChangeRecord changeRecord, 3849 @NotNull final List<Control> controls, 3850 @NotNull final LDAPConnectionPool pool, 3851 @Nullable final LDIFWriter rejectWriter) 3852 throws LDAPException 3853 { 3854 // Create the subtree deleter with the provided set of controls. Make sure 3855 // to include any controls in the delete change record itself. 3856 final List<Control> additionalControls; 3857 if (changeRecord.getControls().isEmpty()) 3858 { 3859 additionalControls = controls; 3860 } 3861 else 3862 { 3863 additionalControls = new ArrayList<>(controls.size() + 3864 changeRecord.getControls().size()); 3865 additionalControls.addAll(changeRecord.getControls()); 3866 additionalControls.addAll(controls); 3867 } 3868 3869 final SubtreeDeleter subtreeDeleter = new SubtreeDeleter(); 3870 subtreeDeleter.setAdditionalDeleteControls(additionalControls); 3871 3872 3873 // Perform the subtree delete. 3874 commentToOut(INFO_LDAPMODIFY_CLIENT_SIDE_DELETING_SUBTREE.get( 3875 changeRecord.getDN())); 3876 final SubtreeDeleterResult subtreeDeleterResult = 3877 subtreeDeleter.delete(pool, changeRecord.getDN()); 3878 3879 3880 // Evaluate the result of the subtree delete. 3881 LDAPResult finalResult; 3882 if (subtreeDeleterResult.completelySuccessful()) 3883 { 3884 final long entriesDeleted = subtreeDeleterResult.getEntriesDeleted(); 3885 if (entriesDeleted == 0L) 3886 { 3887 // This means that the base entry did not exist. Even though the 3888 // subtree deleter returned a successful result, we'll use a final 3889 // result of "no such object". 3890 finalResult = new LDAPResult(-1, ResultCode.NO_SUCH_OBJECT, 3891 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_0_ENTRIES.get( 3892 changeRecord.getDN()), 3893 null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 3894 } 3895 else if (entriesDeleted == 1L) 3896 { 3897 // This means the base entry existed (and we deleted it successfully), 3898 // but did not have any subordinates. 3899 finalResult = new LDAPResult(-1, ResultCode.SUCCESS, 3900 INFO_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_1_ENTRY.get( 3901 changeRecord.getDN()), 3902 null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 3903 } 3904 else 3905 { 3906 // This means that the base entry existed and had subordinates, and we 3907 // deleted all of them successfully. 3908 finalResult = new LDAPResult(-1, ResultCode.SUCCESS, 3909 INFO_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_ENTRIES.get( 3910 subtreeDeleterResult.getEntriesDeleted(), 3911 changeRecord.getDN()), 3912 null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 3913 } 3914 } 3915 else 3916 { 3917 // If there was a search error, then display information about it. 3918 final SearchResult searchError = subtreeDeleterResult.getSearchError(); 3919 if (searchError != null) 3920 { 3921 commentToErr(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SEARCH_ERROR.get()); 3922 displayResult(searchError, false); 3923 err("#"); 3924 } 3925 3926 final SortedMap<DN,LDAPResult> deleteErrors = 3927 subtreeDeleterResult.getDeleteErrorsDescendingMap(); 3928 for (final Map.Entry<DN,LDAPResult> deleteError : deleteErrors.entrySet()) 3929 { 3930 commentToErr(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_ERROR.get( 3931 String.valueOf(deleteError.getKey()))); 3932 displayResult(deleteError.getValue(), false); 3933 err("#"); 3934 } 3935 3936 ResultCode resultCode = ResultCode.OTHER; 3937 final StringBuilder buffer = new StringBuilder(); 3938 buffer.append(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_ERR_BASE.get()); 3939 if (searchError != null) 3940 { 3941 resultCode = searchError.getResultCode(); 3942 buffer.append(" "); 3943 buffer.append( 3944 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_SEARCH_ERR.get()); 3945 } 3946 3947 if (! deleteErrors.isEmpty()) 3948 { 3949 resultCode = deleteErrors.values().iterator().next().getResultCode(); 3950 buffer.append(" "); 3951 final int numDeleteErrors = deleteErrors.size(); 3952 if (numDeleteErrors == 1) 3953 { 3954 buffer.append( 3955 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_ERR_COUNT_1.get()); 3956 } 3957 else 3958 { 3959 buffer.append( 3960 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_ERR_COUNT.get( 3961 numDeleteErrors)); 3962 } 3963 } 3964 3965 buffer.append(" "); 3966 final long deletedCount = subtreeDeleterResult.getEntriesDeleted(); 3967 if (deletedCount == 1L) 3968 { 3969 buffer.append( 3970 ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_COUNT_1.get()); 3971 } 3972 else 3973 { 3974 buffer.append(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_COUNT.get( 3975 deletedCount)); 3976 } 3977 3978 finalResult = new LDAPResult(-1, resultCode, buffer.toString(), null, 3979 StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 3980 } 3981 3982 finalResult = handleJSONEncodedResponseControls(finalResult); 3983 3984 3985 // Display information about the final result. 3986 displayResult(finalResult, useTransaction.isPresent()); 3987 3988 3989 // See if the delete operation succeeded or failed. If it failed, and we 3990 // should end all processing, then throw an exception. 3991 switch (finalResult.getResultCode().intValue()) 3992 { 3993 case ResultCode.SUCCESS_INT_VALUE: 3994 case ResultCode.NO_OPERATION_INT_VALUE: 3995 break; 3996 3997 default: 3998 writeRejectedChange(rejectWriter, null, changeRecord, finalResult); 3999 if (! continueOnError.isPresent()) 4000 { 4001 throw new LDAPException(finalResult); 4002 } 4003 break; 4004 } 4005 4006 return finalResult.getResultCode(); 4007 } 4008 4009 4010 4011 /** 4012 * Performs the appropriate processing for an LDIF modify change record. 4013 * 4014 * @param changeRecord The LDIF modify change record to process. 4015 * @param controls The set of controls to include in the request. 4016 * @param pool The connection pool to use to communicate with 4017 * the directory server. 4018 * @param multiUpdateRequests The list to which the request should be added 4019 * if it is to be processed as part of a 4020 * multi-update operation. It may be 4021 * {@code null} if the operation should not be 4022 * processed via the multi-update operation. 4023 * @param rejectWriter The LDIF writer to use for recording 4024 * information about rejected changes. It may be 4025 * {@code null} if no reject writer is 4026 * configured. 4027 * 4028 * @return The result code obtained from processing. 4029 * 4030 * @throws LDAPException If the operation did not complete successfully 4031 * and processing should not continue. 4032 */ 4033 @NotNull() 4034 ResultCode doModify(@NotNull final LDIFModifyChangeRecord changeRecord, 4035 @NotNull final List<Control> controls, 4036 @NotNull final LDAPConnectionPool pool, 4037 @Nullable final List<LDAPRequest> multiUpdateRequests, 4038 @Nullable final LDIFWriter rejectWriter) 4039 throws LDAPException 4040 { 4041 // Create the modify request to process. 4042 final ModifyRequest modifyRequest = changeRecord.toModifyRequest(true); 4043 for (final Control c : controls) 4044 { 4045 modifyRequest.addControl(c); 4046 } 4047 4048 4049 // If the modify request includes a password change, then add any controls 4050 // that are specific to that. 4051 if (retireCurrentPassword.isPresent() || purgeCurrentPassword.isPresent() || 4052 passwordValidationDetails.isPresent()) 4053 { 4054 for (final Modification m : modifyRequest.getModifications()) 4055 { 4056 final String baseName = m.getAttribute().getBaseName(); 4057 if (baseName.equalsIgnoreCase(ATTR_USER_PASSWORD) || 4058 baseName.equalsIgnoreCase(ATTR_AUTH_PASSWORD)) 4059 { 4060 if (retireCurrentPassword.isPresent()) 4061 { 4062 modifyRequest.addControl(new RetirePasswordRequestControl(false)); 4063 } 4064 else if (purgeCurrentPassword.isPresent()) 4065 { 4066 modifyRequest.addControl(new PurgePasswordRequestControl(false)); 4067 } 4068 4069 if (passwordValidationDetails.isPresent()) 4070 { 4071 modifyRequest.addControl( 4072 new PasswordValidationDetailsRequestControl()); 4073 } 4074 4075 break; 4076 } 4077 } 4078 } 4079 4080 4081 // If the operation should be processed in a multi-update operation, then 4082 // just add the request to the list and return without doing anything else. 4083 if (multiUpdateErrorBehavior.isPresent()) 4084 { 4085 multiUpdateRequests.add(modifyRequest); 4086 commentToOut(INFO_LDAPMODIFY_MODIFY_ADDED_TO_MULTI_UPDATE.get( 4087 modifyRequest.getDN())); 4088 return ResultCode.SUCCESS; 4089 } 4090 4091 4092 // If the --dryRun argument was provided, then we'll stop here. 4093 if (dryRun.isPresent()) 4094 { 4095 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MODIFY.get(modifyRequest.getDN(), 4096 dryRun.getIdentifierString())); 4097 return ResultCode.SUCCESS; 4098 } 4099 4100 4101 // Process the modify operation and get the result. 4102 commentToOut(INFO_LDAPMODIFY_MODIFYING_ENTRY.get(modifyRequest.getDN())); 4103 if (verbose.isPresent()) 4104 { 4105 for (final String ldifLine : 4106 modifyRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 4107 { 4108 out(ldifLine); 4109 } 4110 out(); 4111 } 4112 4113 4114 LDAPResult modifyResult; 4115 try 4116 { 4117 modifyResult = pool.modify(modifyRequest); 4118 } 4119 catch (final LDAPException le) 4120 { 4121 Debug.debugException(le); 4122 modifyResult = le.toLDAPResult(); 4123 } 4124 4125 modifyResult = handleJSONEncodedResponseControls(modifyResult); 4126 4127 4128 // Display information about the result. 4129 displayResult(modifyResult, useTransaction.isPresent()); 4130 4131 4132 // See if the modify operation succeeded or failed. If it failed, and we 4133 // should end all processing, then throw an exception. 4134 switch (modifyResult.getResultCode().intValue()) 4135 { 4136 case ResultCode.SUCCESS_INT_VALUE: 4137 case ResultCode.NO_OPERATION_INT_VALUE: 4138 break; 4139 4140 case ResultCode.ASSERTION_FAILED_INT_VALUE: 4141 writeRejectedChange(rejectWriter, 4142 INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyRequest.getDN(), 4143 String.valueOf(assertionFilter.getValue())), 4144 modifyRequest.toLDIFChangeRecord(), modifyResult); 4145 throw new LDAPException(modifyResult); 4146 4147 default: 4148 writeRejectedChange(rejectWriter, null, 4149 modifyRequest.toLDIFChangeRecord(), modifyResult); 4150 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 4151 { 4152 throw new LDAPException(modifyResult); 4153 } 4154 break; 4155 } 4156 4157 return modifyResult.getResultCode(); 4158 } 4159 4160 4161 4162 /** 4163 * Performs the appropriate processing for an LDIF modify DN change record. 4164 * 4165 * @param changeRecord The LDIF modify DN change record to process. 4166 * @param controls The set of controls to include in the request. 4167 * @param pool The connection pool to use to communicate with 4168 * the directory server. 4169 * @param multiUpdateRequests The list to which the request should be added 4170 * if it is to be processed as part of a 4171 * multi-update operation. It may be 4172 * {@code null} if the operation should not be 4173 * processed via the multi-update operation. 4174 * @param rejectWriter The LDIF writer to use for recording 4175 * information about rejected changes. It may be 4176 * {@code null} if no reject writer is 4177 * configured. 4178 * 4179 * @return The result code obtained from processing. 4180 * 4181 * @throws LDAPException If the operation did not complete successfully 4182 * and processing should not continue. 4183 */ 4184 @NotNull() 4185 private ResultCode doModifyDN( 4186 @NotNull final LDIFModifyDNChangeRecord changeRecord, 4187 @NotNull final List<Control> controls, 4188 @NotNull final LDAPConnectionPool pool, 4189 @Nullable final List<LDAPRequest> multiUpdateRequests, 4190 @Nullable final LDIFWriter rejectWriter) 4191 throws LDAPException 4192 { 4193 // Create the modify DN request to process. 4194 final ModifyDNRequest modifyDNRequest = 4195 changeRecord.toModifyDNRequest(true); 4196 for (final Control c : controls) 4197 { 4198 modifyDNRequest.addControl(c); 4199 } 4200 4201 4202 // If the operation should be processed in a multi-update operation, then 4203 // just add the request to the list and return without doing anything else. 4204 if (multiUpdateErrorBehavior.isPresent()) 4205 { 4206 multiUpdateRequests.add(modifyDNRequest); 4207 commentToOut(INFO_LDAPMODIFY_MODIFY_DN_ADDED_TO_MULTI_UPDATE.get( 4208 modifyDNRequest.getDN())); 4209 return ResultCode.SUCCESS; 4210 } 4211 4212 4213 // Try to determine the new DN that the entry will have after the operation. 4214 DN newDN = null; 4215 try 4216 { 4217 newDN = changeRecord.getNewDN(); 4218 } 4219 catch (final Exception e) 4220 { 4221 Debug.debugException(e); 4222 4223 // This should only happen if the provided DN, new RDN, or new superior DN 4224 // was malformed. Although we could reject the operation now, we'll go 4225 // ahead and send the request to the server in case it has some special 4226 // handling for the DN. 4227 } 4228 4229 4230 // If the --dryRun argument was provided, then we'll stop here. 4231 if (dryRun.isPresent()) 4232 { 4233 if (modifyDNRequest.getNewSuperiorDN() == null) 4234 { 4235 if (newDN == null) 4236 { 4237 commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME.get( 4238 modifyDNRequest.getDN(), dryRun.getIdentifierString())); 4239 } 4240 else 4241 { 4242 commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME_TO.get( 4243 modifyDNRequest.getDN(), newDN.toString(), 4244 dryRun.getIdentifierString())); 4245 } 4246 } 4247 else 4248 { 4249 if (newDN == null) 4250 { 4251 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE.get( 4252 modifyDNRequest.getDN(), dryRun.getIdentifierString())); 4253 } 4254 else 4255 { 4256 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE_TO.get( 4257 modifyDNRequest.getDN(), newDN.toString(), 4258 dryRun.getIdentifierString())); 4259 } 4260 } 4261 return ResultCode.SUCCESS; 4262 } 4263 4264 4265 // Process the modify DN operation and get the result. 4266 final String currentDN = modifyDNRequest.getDN(); 4267 if (modifyDNRequest.getNewSuperiorDN() == null) 4268 { 4269 if (newDN == null) 4270 { 4271 commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY.get(currentDN)); 4272 } 4273 else 4274 { 4275 commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY_TO.get(currentDN, 4276 newDN.toString())); 4277 } 4278 } 4279 else 4280 { 4281 if (newDN == null) 4282 { 4283 commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY.get(currentDN)); 4284 } 4285 else 4286 { 4287 commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY_TO.get(currentDN, 4288 newDN.toString())); 4289 } 4290 } 4291 4292 if (verbose.isPresent()) 4293 { 4294 for (final String ldifLine : 4295 modifyDNRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 4296 { 4297 out(ldifLine); 4298 } 4299 out(); 4300 } 4301 4302 4303 LDAPResult modifyDNResult; 4304 try 4305 { 4306 modifyDNResult = pool.modifyDN(modifyDNRequest); 4307 } 4308 catch (final LDAPException le) 4309 { 4310 Debug.debugException(le); 4311 modifyDNResult = le.toLDAPResult(); 4312 } 4313 4314 modifyDNResult = handleJSONEncodedResponseControls(modifyDNResult); 4315 4316 4317 // Display information about the result. 4318 displayResult(modifyDNResult, useTransaction.isPresent()); 4319 4320 4321 // See if the modify DN operation succeeded or failed. If it failed, and we 4322 // should end all processing, then throw an exception. 4323 switch (modifyDNResult.getResultCode().intValue()) 4324 { 4325 case ResultCode.SUCCESS_INT_VALUE: 4326 case ResultCode.NO_OPERATION_INT_VALUE: 4327 break; 4328 4329 case ResultCode.ASSERTION_FAILED_INT_VALUE: 4330 writeRejectedChange(rejectWriter, 4331 INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyDNRequest.getDN(), 4332 String.valueOf(assertionFilter.getValue())), 4333 modifyDNRequest.toLDIFChangeRecord(), modifyDNResult); 4334 throw new LDAPException(modifyDNResult); 4335 4336 default: 4337 writeRejectedChange(rejectWriter, null, 4338 modifyDNRequest.toLDIFChangeRecord(), modifyDNResult); 4339 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 4340 { 4341 throw new LDAPException(modifyDNResult); 4342 } 4343 break; 4344 } 4345 4346 return modifyDNResult.getResultCode(); 4347 } 4348 4349 4350 4351 /** 4352 * Displays information about the provided result, including special 4353 * processing for a number of supported response controls. 4354 * 4355 * @param result The result to examine. 4356 * @param inTransaction Indicates whether the operation is part of a 4357 * transaction. 4358 */ 4359 private void displayResult(@NotNull final LDAPResult result, 4360 final boolean inTransaction) 4361 { 4362 final ArrayList<String> resultLines = new ArrayList<>(10); 4363 ResultUtils.formatResult(resultLines, result, true, inTransaction, 0, 4364 WRAP_COLUMN); 4365 4366 if (result.getResultCode() == ResultCode.SUCCESS) 4367 { 4368 for (final String line : resultLines) 4369 { 4370 out(line); 4371 } 4372 out(); 4373 } 4374 else 4375 { 4376 for (final String line : resultLines) 4377 { 4378 err(line); 4379 } 4380 err(); 4381 } 4382 } 4383 4384 4385 4386 /** 4387 * Writes a line-wrapped, commented version of the provided message to 4388 * standard output. 4389 * 4390 * @param message The message to be written. 4391 */ 4392 private void commentToOut(@NotNull final String message) 4393 { 4394 for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) 4395 { 4396 out("# ", line); 4397 } 4398 } 4399 4400 4401 4402 /** 4403 * Writes a line-wrapped, commented version of the provided message to 4404 * standard error. 4405 * 4406 * @param message The message to be written. 4407 */ 4408 private void commentToErr(@NotNull final String message) 4409 { 4410 for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) 4411 { 4412 err("# ", line); 4413 } 4414 } 4415 4416 4417 4418 /** 4419 * Writes information about the rejected change to the reject writer. 4420 * 4421 * @param writer The LDIF writer to which the information should be 4422 * written. It may be {@code null} if no reject file is 4423 * configured. 4424 * @param comment The comment to include before the change record, in 4425 * addition to the comment generated from the provided 4426 * LDAP result. It may be {@code null} if no additional 4427 * comment should be included. 4428 * @param changeRecord The LDIF change record to be written. It must not 4429 * be {@code null}. 4430 * @param ldapResult The LDAP result for the failed operation. It must 4431 * not be {@code null}. 4432 */ 4433 private void writeRejectedChange(@Nullable final LDIFWriter writer, 4434 @Nullable final String comment, 4435 @NotNull final LDIFChangeRecord changeRecord, 4436 @NotNull final LDAPResult ldapResult) 4437 { 4438 if (writer == null) 4439 { 4440 return; 4441 } 4442 4443 4444 final StringBuilder buffer = new StringBuilder(); 4445 if (comment != null) 4446 { 4447 buffer.append(comment); 4448 buffer.append(StaticUtils.EOL); 4449 buffer.append(StaticUtils.EOL); 4450 } 4451 4452 final ArrayList<String> resultLines = new ArrayList<>(10); 4453 ResultUtils.formatResult(resultLines, ldapResult, false, false, 0, 0); 4454 for (final String resultLine : resultLines) 4455 { 4456 buffer.append(resultLine); 4457 buffer.append(StaticUtils.EOL); 4458 } 4459 4460 writeRejectedChange(writer, buffer.toString(), changeRecord); 4461 } 4462 4463 4464 4465 /** 4466 * Writes information about the rejected change to the reject writer. 4467 * 4468 * @param writer The LDIF writer to which the information should be 4469 * written. It may be {@code null} if no reject file is 4470 * configured. 4471 * @param comment The comment to include before the change record. It 4472 * may be {@code null} if no comment should be included. 4473 * @param changeRecord The LDIF change record to be written. It may be 4474 * {@code null} if only a comment should be written. 4475 */ 4476 void writeRejectedChange(@Nullable final LDIFWriter writer, 4477 @Nullable final String comment, 4478 @Nullable final LDIFChangeRecord changeRecord) 4479 { 4480 if (writer == null) 4481 { 4482 return; 4483 } 4484 4485 if (rejectWritten.compareAndSet(false, true)) 4486 { 4487 try 4488 { 4489 writer.writeVersionHeader(); 4490 } 4491 catch (final Exception e) 4492 { 4493 Debug.debugException(e); 4494 } 4495 } 4496 4497 try 4498 { 4499 if (comment != null) 4500 { 4501 writer.writeComment(comment, true, false); 4502 } 4503 4504 if (changeRecord != null) 4505 { 4506 writer.writeChangeRecord(changeRecord); 4507 } 4508 } 4509 catch (final Exception e) 4510 { 4511 Debug.debugException(e); 4512 4513 commentToErr(ERR_LDAPMODIFY_UNABLE_TO_WRITE_REJECTED_CHANGE.get( 4514 rejectFile.getValue().getAbsolutePath(), 4515 StaticUtils.getExceptionMessage(e))); 4516 } 4517 } 4518 4519 4520 4521 /** 4522 * {@inheritDoc} 4523 */ 4524 @Override() 4525 public void handleUnsolicitedNotification( 4526 @NotNull final LDAPConnection connection, 4527 @NotNull final ExtendedResult notification) 4528 { 4529 final ArrayList<String> lines = new ArrayList<>(10); 4530 ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0, 4531 WRAP_COLUMN); 4532 for (final String line : lines) 4533 { 4534 err(line); 4535 } 4536 err(); 4537 } 4538 4539 4540 4541 /** 4542 * Examines the provided LDAP result to see if it includes a JSONf-formatted 4543 * response control. If so, then its embedded controls will be extracted and 4544 * a new LDAP result will be returned with those extracted controls instead 4545 * of the JSON-formatted response control. Otherwise, the provided LDAP 4546 * result will be returned. 4547 * 4548 * @param ldapResult The LDAP result to be handled. It must not be 4549 * {@code null}. 4550 * 4551 * @return A new LDAP result with the controls extracted from a 4552 * JSON-formatted response control, or the original LDAP result if 4553 * it did not include a JSON-formatted response control. 4554 */ 4555 @NotNull() 4556 static LDAPResult handleJSONEncodedResponseControls( 4557 @NotNull final LDAPResult ldapResult) 4558 { 4559 try 4560 { 4561 final JSONFormattedResponseControl jsonFormattedResponseControl = 4562 JSONFormattedResponseControl.get(ldapResult); 4563 if (jsonFormattedResponseControl == null) 4564 { 4565 return ldapResult; 4566 } 4567 4568 final JSONFormattedControlDecodeBehavior decodeBehavior = 4569 new JSONFormattedControlDecodeBehavior(); 4570 decodeBehavior.setThrowOnUnparsableObject(false); 4571 decodeBehavior.setThrowOnInvalidCriticalControl(false); 4572 decodeBehavior.setThrowOnInvalidNonCriticalControl(false); 4573 decodeBehavior.setThrowOnInvalidNonCriticalControl(false); 4574 decodeBehavior.setAllowEmbeddedJSONFormattedControl(true); 4575 decodeBehavior.setStrict(false); 4576 4577 final List<Control> decodedControls = 4578 jsonFormattedResponseControl.decodeEmbeddedControls( 4579 decodeBehavior, null); 4580 4581 return new LDAPResult(ldapResult.getMessageID(), 4582 ldapResult.getResultCode(), 4583 ldapResult.getDiagnosticMessage(), 4584 ldapResult.getMatchedDN(), 4585 ldapResult.getReferralURLs(), 4586 StaticUtils.toArray(decodedControls, Control.class)); 4587 } 4588 catch (final LDAPException e) 4589 { 4590 Debug.debugException(e); 4591 return ldapResult; 4592 } 4593 } 4594 4595 4596 4597 /** 4598 * {@inheritDoc} 4599 */ 4600 @Override() 4601 @NotNull() 4602 public LinkedHashMap<String[],String> getExampleUsages() 4603 { 4604 final LinkedHashMap<String[],String> examples = 4605 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 4606 4607 final String[] args1 = 4608 { 4609 "--hostname", "ldap.example.com", 4610 "--port", "389", 4611 "--bindDN", "uid=admin,dc=example,dc=com", 4612 "--bindPassword", "password", 4613 "--defaultAdd" 4614 }; 4615 examples.put(args1, INFO_LDAPMODIFY_EXAMPLE_1.get()); 4616 4617 final String[] args2 = 4618 { 4619 "--hostname", "ds1.example.com", 4620 "--port", "636", 4621 "--hostname", "ds2.example.com", 4622 "--port", "636", 4623 "--useSSL", 4624 "--bindDN", "uid=admin,dc=example,dc=com", 4625 "--bindPassword", "password", 4626 "--ldifFile", "changes.ldif", 4627 "--modifyEntriesMatchingFilter", "(objectClass=person)", 4628 "--searchPageSize", "100" 4629 }; 4630 examples.put(args2, INFO_LDAPMODIFY_EXAMPLE_2.get()); 4631 4632 return examples; 4633 } 4634}