001/* 002 * Copyright 2016-2017 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2017 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.unboundidds.tools; 022 023 024 025import java.io.ByteArrayInputStream; 026import java.io.File; 027import java.io.InputStream; 028import java.io.IOException; 029import java.io.OutputStream; 030import java.util.ArrayList; 031import java.util.EnumSet; 032import java.util.HashSet; 033import java.util.LinkedHashMap; 034import java.util.LinkedHashSet; 035import java.util.List; 036import java.util.StringTokenizer; 037import java.util.concurrent.TimeUnit; 038import java.util.concurrent.atomic.AtomicBoolean; 039 040import com.unboundid.asn1.ASN1OctetString; 041import com.unboundid.ldap.sdk.AddRequest; 042import com.unboundid.ldap.sdk.Control; 043import com.unboundid.ldap.sdk.DeleteRequest; 044import com.unboundid.ldap.sdk.DN; 045import com.unboundid.ldap.sdk.Entry; 046import com.unboundid.ldap.sdk.ExtendedResult; 047import com.unboundid.ldap.sdk.Filter; 048import com.unboundid.ldap.sdk.LDAPConnectionOptions; 049import com.unboundid.ldap.sdk.LDAPConnection; 050import com.unboundid.ldap.sdk.LDAPConnectionPool; 051import com.unboundid.ldap.sdk.LDAPException; 052import com.unboundid.ldap.sdk.LDAPRequest; 053import com.unboundid.ldap.sdk.LDAPResult; 054import com.unboundid.ldap.sdk.LDAPSearchException; 055import com.unboundid.ldap.sdk.Modification; 056import com.unboundid.ldap.sdk.ModifyRequest; 057import com.unboundid.ldap.sdk.ModifyDNRequest; 058import com.unboundid.ldap.sdk.ResultCode; 059import com.unboundid.ldap.sdk.SearchRequest; 060import com.unboundid.ldap.sdk.SearchResult; 061import com.unboundid.ldap.sdk.SearchScope; 062import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler; 063import com.unboundid.ldap.sdk.Version; 064import com.unboundid.ldap.sdk.controls.AssertionRequestControl; 065import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; 066import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl; 067import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl; 068import com.unboundid.ldap.sdk.controls.PostReadRequestControl; 069import com.unboundid.ldap.sdk.controls.PreReadRequestControl; 070import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl; 071import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; 072import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl; 073import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl; 074import com.unboundid.ldap.sdk.controls.TransactionSpecificationRequestControl; 075import com.unboundid.ldap.sdk.extensions.StartTransactionExtendedRequest; 076import com.unboundid.ldap.sdk.extensions.StartTransactionExtendedResult; 077import com.unboundid.ldap.sdk.extensions.EndTransactionExtendedRequest; 078import com.unboundid.ldap.sdk.unboundidds.controls.AssuredReplicationLocalLevel; 079import com.unboundid.ldap.sdk.unboundidds.controls. 080 AssuredReplicationRequestControl; 081import com.unboundid.ldap.sdk.unboundidds.controls. 082 AssuredReplicationRemoteLevel; 083import com.unboundid.ldap.sdk.unboundidds.controls. 084 GetAuthorizationEntryRequestControl; 085import com.unboundid.ldap.sdk.unboundidds.controls. 086 GetUserResourceLimitsRequestControl; 087import com.unboundid.ldap.sdk.unboundidds.controls.HardDeleteRequestControl; 088import com.unboundid.ldap.sdk.unboundidds.controls. 089 IgnoreNoUserModificationRequestControl; 090import com.unboundid.ldap.sdk.unboundidds.controls. 091 NameWithEntryUUIDRequestControl; 092import com.unboundid.ldap.sdk.unboundidds.controls.NoOpRequestControl; 093import com.unboundid.ldap.sdk.unboundidds.controls. 094 OperationPurposeRequestControl; 095import com.unboundid.ldap.sdk.unboundidds.controls.PasswordPolicyRequestControl; 096import com.unboundid.ldap.sdk.unboundidds.controls. 097 PasswordUpdateBehaviorRequestControl; 098import com.unboundid.ldap.sdk.unboundidds.controls. 099 PasswordUpdateBehaviorRequestControlProperties; 100import com.unboundid.ldap.sdk.unboundidds.controls. 101 PasswordValidationDetailsRequestControl; 102import com.unboundid.ldap.sdk.unboundidds.controls.PurgePasswordRequestControl; 103import com.unboundid.ldap.sdk.unboundidds.controls. 104 ReplicationRepairRequestControl; 105import com.unboundid.ldap.sdk.unboundidds.controls.RetirePasswordRequestControl; 106import com.unboundid.ldap.sdk.unboundidds.controls.SoftDeleteRequestControl; 107import com.unboundid.ldap.sdk.unboundidds.controls. 108 SuppressOperationalAttributeUpdateRequestControl; 109import com.unboundid.ldap.sdk.unboundidds.controls. 110 SuppressReferentialIntegrityUpdatesRequestControl; 111import com.unboundid.ldap.sdk.unboundidds.controls.SuppressType; 112import com.unboundid.ldap.sdk.unboundidds.controls.UndeleteRequestControl; 113import com.unboundid.ldap.sdk.unboundidds.extensions.MultiUpdateErrorBehavior; 114import com.unboundid.ldap.sdk.unboundidds.extensions.MultiUpdateExtendedRequest; 115import com.unboundid.ldap.sdk.unboundidds.extensions. 116 StartAdministrativeSessionExtendedRequest; 117import com.unboundid.ldap.sdk.unboundidds.extensions. 118 StartAdministrativeSessionPostConnectProcessor; 119import com.unboundid.ldif.LDIFAddChangeRecord; 120import com.unboundid.ldif.LDIFChangeRecord; 121import com.unboundid.ldif.LDIFDeleteChangeRecord; 122import com.unboundid.ldif.LDIFException; 123import com.unboundid.ldif.LDIFModifyChangeRecord; 124import com.unboundid.ldif.LDIFModifyDNChangeRecord; 125import com.unboundid.ldif.LDIFReader; 126import com.unboundid.ldif.LDIFWriter; 127import com.unboundid.ldif.TrailingSpaceBehavior; 128import com.unboundid.util.Debug; 129import com.unboundid.util.DNFileReader; 130import com.unboundid.util.FilterFileReader; 131import com.unboundid.util.FixedRateBarrier; 132import com.unboundid.util.LDAPCommandLineTool; 133import com.unboundid.util.StaticUtils; 134import com.unboundid.util.ThreadSafety; 135import com.unboundid.util.ThreadSafetyLevel; 136import com.unboundid.util.args.ArgumentException; 137import com.unboundid.util.args.ArgumentParser; 138import com.unboundid.util.args.BooleanArgument; 139import com.unboundid.util.args.ControlArgument; 140import com.unboundid.util.args.DNArgument; 141import com.unboundid.util.args.DurationArgument; 142import com.unboundid.util.args.FileArgument; 143import com.unboundid.util.args.FilterArgument; 144import com.unboundid.util.args.IntegerArgument; 145import com.unboundid.util.args.StringArgument; 146 147import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 148 149 150 151/** 152 * This class provides an implementation of an LDAP command-line tool that may 153 * be used to apply changes to a directory server. The changes to apply (which 154 * may include add, delete, modify, and modify DN operations) will be read in 155 * LDIF form, either from standard input or a specified file or set of files. 156 * This is a much more full-featured tool than the 157 * {@link com.unboundid.ldap.sdk.examples.LDAPModify} tool 158 * <BR> 159 * <BLOCKQUOTE> 160 * <B>NOTE:</B> This class, and other classes within the 161 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 162 * supported for use against Ping Identity, UnboundID, and Alcatel-Lucent 8661 163 * server products. These classes provide support for proprietary 164 * functionality or for external specifications that are not considered stable 165 * or mature enough to be guaranteed to work in an interoperable way with 166 * other types of LDAP servers. 167 * </BLOCKQUOTE> 168 */ 169@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 170public final class LDAPModify 171 extends LDAPCommandLineTool 172 implements UnsolicitedNotificationHandler 173{ 174 /** 175 * The column at which output should be wrapped. 176 */ 177 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 178 179 180 181 /** 182 * The name of the attribute type used to specify a password in the 183 * authentication password syntax as described in RFC 3112. 184 */ 185 private static final String ATTR_AUTH_PASSWORD = "authPassword"; 186 187 188 189 /** 190 * The name of the attribute type used to specify the DN of the soft-deleted 191 * entry to be restored via an undelete operation. 192 */ 193 private static final String ATTR_UNDELETE_FROM_DN = "ds-undelete-from-dn"; 194 195 196 197 /** 198 * The name of the attribute type used to specify a password in the 199 * userPassword syntax. 200 */ 201 private static final String ATTR_USER_PASSWORD = "userPassword"; 202 203 204 205 /** 206 * The long identifier for the argument used to specify the desired assured 207 * replication local level. 208 */ 209 private static final String ARG_ASSURED_REPLICATION_LOCAL_LEVEL = 210 "assuredReplicationLocalLevel"; 211 212 213 214 /** 215 * The long identifier for the argument used to specify the desired assured 216 * replication remote level. 217 */ 218 private static final String ARG_ASSURED_REPLICATION_REMOTE_LEVEL = 219 "assuredReplicationRemoteLevel"; 220 221 222 223 /** 224 * The long identifier for the argument used to specify the desired assured 225 * timeout. 226 */ 227 private static final String ARG_ASSURED_REPLICATION_TIMEOUT = 228 "assuredReplicationTimeout"; 229 230 231 232 /** 233 * The long identifier for the argument used to specify the path to an LDIF 234 * file containing changes to apply. 235 */ 236 private static final String ARG_LDIF_FILE = "ldifFile"; 237 238 239 240 /** 241 * The long identifier for the argument used to specify the simple paged 242 * results page size to use when modifying entries that match a provided 243 * filter. 244 */ 245 private static final String ARG_SEARCH_PAGE_SIZE = "searchPageSize"; 246 247 248 249 // The set of arguments supported by this program. 250 private BooleanArgument allowUndelete = null; 251 private BooleanArgument assuredReplication = null; 252 private BooleanArgument authorizationIdentity = null; 253 private BooleanArgument continueOnError = null; 254 private BooleanArgument defaultAdd = null; 255 private BooleanArgument dryRun = null; 256 private BooleanArgument followReferrals = null; 257 private BooleanArgument getUserResourceLimits = null; 258 private BooleanArgument hardDelete = null; 259 private BooleanArgument ignoreNoUserModification = null; 260 private BooleanArgument manageDsaIT = null; 261 private BooleanArgument nameWithEntryUUID = null; 262 private BooleanArgument noOperation = null; 263 private BooleanArgument passwordValidationDetails = null; 264 private BooleanArgument permissiveModify = null; 265 private BooleanArgument purgeCurrentPassword = null; 266 private BooleanArgument replicationRepair = null; 267 private BooleanArgument retireCurrentPassword = null; 268 private BooleanArgument retryFailedOperations = null; 269 private BooleanArgument softDelete = null; 270 private BooleanArgument stripTrailingSpaces = null; 271 private BooleanArgument subtreeDelete = null; 272 private BooleanArgument suppressReferentialIntegrityUpdates = null; 273 private BooleanArgument useAdministrativeSession = null; 274 private BooleanArgument usePasswordPolicyControl = null; 275 private BooleanArgument useTransaction = null; 276 private BooleanArgument verbose = null; 277 private ControlArgument addControl = null; 278 private ControlArgument bindControl = null; 279 private ControlArgument deleteControl = null; 280 private ControlArgument modifyControl = null; 281 private ControlArgument modifyDNControl = null; 282 private ControlArgument operationControl = null; 283 private DNArgument modifyEntryWithDN = null; 284 private DNArgument proxyV1As = null; 285 private DurationArgument assuredReplicationTimeout = null; 286 private FileArgument ldifFile = null; 287 private FileArgument modifyEntriesMatchingFiltersFromFile = null; 288 private FileArgument modifyEntriesWithDNsFromFile = null; 289 private FileArgument rejectFile = null; 290 private FilterArgument assertionFilter = null; 291 private FilterArgument modifyEntriesMatchingFilter = null; 292 private IntegerArgument ratePerSecond = null; 293 private IntegerArgument searchPageSize = null; 294 private StringArgument assuredReplicationLocalLevel = null; 295 private StringArgument assuredReplicationRemoteLevel = null; 296 private StringArgument characterSet = null; 297 private StringArgument getAuthorizationEntryAttribute = null; 298 private StringArgument multiUpdateErrorBehavior = null; 299 private StringArgument operationPurpose = null; 300 private StringArgument passwordUpdateBehavior = null; 301 private StringArgument postReadAttribute = null; 302 private StringArgument preReadAttribute = null; 303 private StringArgument proxyAs = null; 304 private StringArgument suppressOperationalAttributeUpdates = null; 305 306 // Indicates whether we've written anything to the reject writer yet. 307 private final AtomicBoolean rejectWritten; 308 309 // The input stream from to use for standard input. 310 private final InputStream in; 311 312 313 314 /** 315 * Runs this tool with the provided command-line arguments. It will use the 316 * JVM-default streams for standard input, output, and error. 317 * 318 * @param args The command-line arguments to provide to this program. 319 */ 320 public static void main(final String... args) 321 { 322 final ResultCode resultCode = main(System.in, System.out, System.err, args); 323 if (resultCode != ResultCode.SUCCESS) 324 { 325 System.exit(Math.min(resultCode.intValue(), 255)); 326 } 327 } 328 329 330 331 /** 332 * Runs this tool with the provided streams and command-line arguments. 333 * 334 * @param in The input stream to use for standard input. If this is 335 * {@code null}, then no standard input will be used. 336 * @param out The output stream to use for standard output. If this is 337 * {@code null}, then standard output will be suppressed. 338 * @param err The output stream to use for standard error. If this is 339 * {@code null}, then standard error will be suppressed. 340 * @param args The command-line arguments provided to this program. 341 * 342 * @return The result code obtained when running the tool. Any result code 343 * other than {@link ResultCode#SUCCESS} indicates an error. 344 */ 345 public static ResultCode main(final InputStream in, final OutputStream out, 346 final OutputStream err, final String... args) 347 { 348 final LDAPModify tool = new LDAPModify(in, out, err); 349 return tool.runTool(args); 350 } 351 352 353 354 /** 355 * Creates a new instance of this tool with the provided streams. 356 * 357 * @param in The input stream to use for standard input. If this is 358 * {@code null}, then no standard input will be used. 359 * @param out The output stream to use for standard output. If this is 360 * {@code null}, then standard output will be suppressed. 361 * @param err The output stream to use for standard error. If this is 362 * {@code null}, then standard error will be suppressed. 363 */ 364 public LDAPModify(final InputStream in, final OutputStream out, 365 final OutputStream err) 366 { 367 super(out, err); 368 369 if (in == null) 370 { 371 this.in = new ByteArrayInputStream(StaticUtils.NO_BYTES); 372 } 373 else 374 { 375 this.in = in; 376 } 377 378 379 rejectWritten = new AtomicBoolean(false); 380 } 381 382 383 384 /** 385 * {@inheritDoc} 386 */ 387 @Override() 388 public String getToolName() 389 { 390 return "ldapmodify"; 391 } 392 393 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override() 399 public String getToolDescription() 400 { 401 return INFO_LDAPMODIFY_TOOL_DESCRIPTION.get(ARG_LDIF_FILE); 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 public String getToolVersion() 411 { 412 return Version.NUMERIC_VERSION_STRING; 413 } 414 415 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override() 421 public boolean supportsInteractiveMode() 422 { 423 return true; 424 } 425 426 427 428 /** 429 * {@inheritDoc} 430 */ 431 @Override() 432 public boolean defaultsToInteractiveMode() 433 { 434 return true; 435 } 436 437 438 439 /** 440 * {@inheritDoc} 441 */ 442 @Override() 443 public boolean supportsPropertiesFile() 444 { 445 return true; 446 } 447 448 449 450 /** 451 * {@inheritDoc} 452 */ 453 @Override() 454 public boolean supportsOutputFile() 455 { 456 return true; 457 } 458 459 460 461 /** 462 * {@inheritDoc} 463 */ 464 @Override() 465 protected boolean defaultToPromptForBindPassword() 466 { 467 return true; 468 } 469 470 471 472 /** 473 * {@inheritDoc} 474 */ 475 @Override() 476 protected boolean includeAlternateLongIdentifiers() 477 { 478 return true; 479 } 480 481 482 483 /** 484 * {@inheritDoc} 485 */ 486 @Override() 487 protected boolean logToolInvocationByDefault() 488 { 489 return true; 490 } 491 492 493 494 /** 495 * {@inheritDoc} 496 */ 497 @Override() 498 public void addNonLDAPArguments(final ArgumentParser parser) 499 throws ArgumentException 500 { 501 ldifFile = new FileArgument('f', ARG_LDIF_FILE, false, -1, null, 502 INFO_LDAPMODIFY_ARG_DESCRIPTION_LDIF_FILE.get(), true, true, true, 503 false); 504 ldifFile.addLongIdentifier("filename"); 505 ldifFile.addLongIdentifier("ldif-file"); 506 ldifFile.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 507 parser.addArgument(ldifFile); 508 509 510 characterSet = new StringArgument('i', "characterSet", false, 1, 511 INFO_LDAPMODIFY_PLACEHOLDER_CHARSET.get(), 512 INFO_LDAPMODIFY_ARG_DESCRIPTION_CHARACTER_SET.get(), "UTF-8"); 513 characterSet.addLongIdentifier("encoding"); 514 characterSet.addLongIdentifier("character-set"); 515 characterSet.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 516 parser.addArgument(characterSet); 517 518 519 rejectFile = new FileArgument('R', "rejectFile", false, 1, null, 520 INFO_LDAPMODIFY_ARG_DESCRIPTION_REJECT_FILE.get(), false, true, true, 521 false); 522 rejectFile.addLongIdentifier("reject-file"); 523 rejectFile.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 524 parser.addArgument(rejectFile); 525 526 527 verbose = new BooleanArgument('v', "verbose", 1, 528 INFO_LDAPMODIFY_ARG_DESCRIPTION_VERBOSE.get()); 529 verbose.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 530 parser.addArgument(verbose); 531 532 533 modifyEntriesMatchingFilter = new FilterArgument(null, 534 "modifyEntriesMatchingFilter", false, 0, null, 535 INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_ENTRIES_MATCHING_FILTER.get( 536 ARG_SEARCH_PAGE_SIZE)); 537 modifyEntriesMatchingFilter.addLongIdentifier( 538 "modify-entries-matching-filter"); 539 modifyEntriesMatchingFilter.setArgumentGroupName( 540 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 541 parser.addArgument(modifyEntriesMatchingFilter); 542 543 544 modifyEntriesMatchingFiltersFromFile = new FileArgument(null, 545 "modifyEntriesMatchingFiltersFromFile", false, 0, null, 546 INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_FILTER_FILE.get( 547 ARG_SEARCH_PAGE_SIZE), true, false, true, false); 548 modifyEntriesMatchingFiltersFromFile.addLongIdentifier( 549 "modify-entries-matching-filters-from-file"); 550 modifyEntriesMatchingFiltersFromFile.setArgumentGroupName( 551 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 552 parser.addArgument(modifyEntriesMatchingFiltersFromFile); 553 554 555 modifyEntryWithDN = new DNArgument(null, "modifyEntryWithDN", false, 0, 556 null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_ENTRY_DN.get()); 557 modifyEntryWithDN.addLongIdentifier("modify-entry-with-dn"); 558 modifyEntryWithDN.setArgumentGroupName( 559 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 560 parser.addArgument(modifyEntryWithDN); 561 562 563 modifyEntriesWithDNsFromFile = new FileArgument(null, 564 "modifyEntriesWithDNsFromFile", false, 0, 565 null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_DN_FILE.get(), true, 566 false, true, false); 567 modifyEntriesWithDNsFromFile.addLongIdentifier( 568 "modify-entries-with-dns-from-file"); 569 modifyEntriesWithDNsFromFile.setArgumentGroupName( 570 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 571 parser.addArgument(modifyEntriesWithDNsFromFile); 572 573 574 searchPageSize = new IntegerArgument(null, ARG_SEARCH_PAGE_SIZE, false, 1, 575 null, 576 INFO_LDAPMODIFY_ARG_DESCRIPTION_SEARCH_PAGE_SIZE.get( 577 modifyEntriesMatchingFilter.getIdentifierString(), 578 modifyEntriesMatchingFiltersFromFile.getIdentifierString()), 579 1, Integer.MAX_VALUE); 580 searchPageSize.addLongIdentifier("search-page-size"); 581 searchPageSize.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 582 parser.addArgument(searchPageSize); 583 584 585 retryFailedOperations = new BooleanArgument(null, "retryFailedOperations", 586 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_RETRY_FAILED_OPERATIONS.get()); 587 retryFailedOperations.addLongIdentifier("retry-failed-operations"); 588 retryFailedOperations.setArgumentGroupName( 589 INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 590 parser.addArgument(retryFailedOperations); 591 592 593 dryRun = new BooleanArgument('n', "dryRun", 1, 594 INFO_LDAPMODIFY_ARG_DESCRIPTION_DRY_RUN.get()); 595 dryRun.addLongIdentifier("dry-run"); 596 dryRun.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 597 parser.addArgument(dryRun); 598 599 600 defaultAdd = new BooleanArgument('a', "defaultAdd", 1, 601 INFO_LDAPMODIFY_ARG_DESCRIPTION_DEFAULT_ADD.get()); 602 defaultAdd.addLongIdentifier("default-add"); 603 defaultAdd.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 604 parser.addArgument(defaultAdd); 605 606 607 continueOnError = new BooleanArgument('c', "continueOnError", 1, 608 INFO_LDAPMODIFY_ARG_DESCRIPTION_CONTINUE_ON_ERROR.get()); 609 continueOnError.addLongIdentifier("continue-on-error"); 610 continueOnError.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 611 parser.addArgument(continueOnError); 612 613 614 stripTrailingSpaces = new BooleanArgument(null, "stripTrailingSpaces", 1, 615 INFO_LDAPMODIFY_ARG_DESCRIPTION_STRIP_TRAILING_SPACES.get()); 616 stripTrailingSpaces.addLongIdentifier("strip-trailing-spaces"); 617 stripTrailingSpaces.setArgumentGroupName( 618 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 619 parser.addArgument(stripTrailingSpaces); 620 621 622 623 followReferrals = new BooleanArgument(null, "followReferrals", 1, 624 INFO_LDAPMODIFY_ARG_DESCRIPTION_FOLLOW_REFERRALS.get()); 625 followReferrals.addLongIdentifier("follow-referrals"); 626 followReferrals.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 627 parser.addArgument(followReferrals); 628 629 630 proxyAs = new StringArgument('Y', "proxyAs", false, 1, 631 INFO_PLACEHOLDER_AUTHZID.get(), 632 INFO_LDAPMODIFY_ARG_DESCRIPTION_PROXY_AS.get()); 633 proxyAs.addLongIdentifier("proxyV2As"); 634 proxyAs.addLongIdentifier("proxy-as"); 635 proxyAs.addLongIdentifier("proxy-v2-as"); 636 proxyAs.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 637 parser.addArgument(proxyAs); 638 639 proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null, 640 INFO_LDAPMODIFY_ARG_DESCRIPTION_PROXY_V1_AS.get()); 641 proxyV1As.addLongIdentifier("proxy-v1-as"); 642 proxyV1As.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 643 parser.addArgument(proxyV1As); 644 645 646 useAdministrativeSession = new BooleanArgument(null, 647 "useAdministrativeSession", 1, 648 INFO_LDAPMODIFY_ARG_DESCRIPTION_USE_ADMIN_SESSION.get()); 649 useAdministrativeSession.addLongIdentifier("use-administrative-session"); 650 useAdministrativeSession.setArgumentGroupName( 651 INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 652 parser.addArgument(useAdministrativeSession); 653 654 655 operationPurpose = new StringArgument(null, "operationPurpose", false, 1, 656 INFO_PLACEHOLDER_PURPOSE.get(), 657 INFO_LDAPMODIFY_ARG_DESCRIPTION_OPERATION_PURPOSE.get()); 658 operationPurpose.addLongIdentifier("operation-purpose"); 659 operationPurpose.setArgumentGroupName( 660 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 661 parser.addArgument(operationPurpose); 662 663 664 manageDsaIT = new BooleanArgument(null, "useManageDsaIT", 1, 665 INFO_LDAPMODIFY_ARG_DESCRIPTION_MANAGE_DSA_IT.get()); 666 manageDsaIT.addLongIdentifier("manageDsaIT"); 667 manageDsaIT.addLongIdentifier("use-manage-dsa-it"); 668 manageDsaIT.addLongIdentifier("manage-dsa-it"); 669 manageDsaIT.setArgumentGroupName( 670 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 671 parser.addArgument(manageDsaIT); 672 673 674 useTransaction = new BooleanArgument(null, "useTransaction", 1, 675 INFO_LDAPMODIFY_ARG_DESCRIPTION_USE_TRANSACTION.get()); 676 useTransaction.addLongIdentifier("use-transaction"); 677 useTransaction.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 678 parser.addArgument(useTransaction); 679 680 681 final LinkedHashSet<String> multiUpdateErrorBehaviorAllowedValues = 682 new LinkedHashSet<String>(3); 683 multiUpdateErrorBehaviorAllowedValues.add("atomic"); 684 multiUpdateErrorBehaviorAllowedValues.add("abort-on-error"); 685 multiUpdateErrorBehaviorAllowedValues.add("continue-on-error"); 686 multiUpdateErrorBehavior = new StringArgument(null, 687 "multiUpdateErrorBehavior", false, 1, 688 "{atomic|abort-on-error|continue-on-error}", 689 INFO_LDAPMODIFY_ARG_DESCRIPTION_MULTI_UPDATE_ERROR_BEHAVIOR.get(), 690 multiUpdateErrorBehaviorAllowedValues); 691 multiUpdateErrorBehavior.addLongIdentifier("multi-update-error-behavior"); 692 multiUpdateErrorBehavior.setArgumentGroupName( 693 INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 694 parser.addArgument(multiUpdateErrorBehavior); 695 696 697 assertionFilter = new FilterArgument(null, "assertionFilter", false, 1, 698 INFO_PLACEHOLDER_FILTER.get(), 699 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSERTION_FILTER.get()); 700 assertionFilter.addLongIdentifier("assertion-filter"); 701 assertionFilter.setArgumentGroupName( 702 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 703 parser.addArgument(assertionFilter); 704 705 706 authorizationIdentity = new BooleanArgument('E', 707 "authorizationIdentity", 1, 708 INFO_LDAPMODIFY_ARG_DESCRIPTION_AUTHZ_IDENTITY.get()); 709 authorizationIdentity.addLongIdentifier("reportAuthzID"); 710 authorizationIdentity.addLongIdentifier("authorization-identity"); 711 authorizationIdentity.addLongIdentifier("report-authzID"); 712 authorizationIdentity.addLongIdentifier("report-authz-id"); 713 authorizationIdentity.setArgumentGroupName( 714 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 715 parser.addArgument(authorizationIdentity); 716 717 718 getAuthorizationEntryAttribute = new StringArgument(null, 719 "getAuthorizationEntryAttribute", false, 0, 720 INFO_PLACEHOLDER_ATTR.get(), 721 INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_AUTHZ_ENTRY_ATTR.get()); 722 getAuthorizationEntryAttribute.addLongIdentifier( 723 "get-authorization-entry-attribute"); 724 getAuthorizationEntryAttribute.setArgumentGroupName( 725 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 726 parser.addArgument(getAuthorizationEntryAttribute); 727 728 729 getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits", 730 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_USER_RESOURCE_LIMITS.get()); 731 getUserResourceLimits.addLongIdentifier("get-user-resource-limits"); 732 getUserResourceLimits.setArgumentGroupName( 733 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 734 parser.addArgument(getUserResourceLimits); 735 736 737 ignoreNoUserModification = new BooleanArgument(null, 738 "ignoreNoUserModification", 1, 739 INFO_LDAPMODIFY_ARG_DESCRIPTION_IGNORE_NO_USER_MOD.get()); 740 ignoreNoUserModification.addLongIdentifier("ignore-no-user-modification"); 741 ignoreNoUserModification.setArgumentGroupName( 742 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 743 parser.addArgument(ignoreNoUserModification); 744 745 746 preReadAttribute = new StringArgument(null, "preReadAttribute", false, -1, 747 INFO_PLACEHOLDER_ATTR.get(), 748 INFO_LDAPMODIFY_ARG_DESCRIPTION_PRE_READ_ATTRIBUTE.get()); 749 preReadAttribute.addLongIdentifier("preReadAttributes"); 750 preReadAttribute.addLongIdentifier("pre-read-attribute"); 751 preReadAttribute.addLongIdentifier("pre-read-attributes"); 752 preReadAttribute.setArgumentGroupName( 753 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 754 parser.addArgument(preReadAttribute); 755 756 757 postReadAttribute = new StringArgument(null, "postReadAttribute", false, 758 -1, INFO_PLACEHOLDER_ATTR.get(), 759 INFO_LDAPMODIFY_ARG_DESCRIPTION_POST_READ_ATTRIBUTE.get()); 760 postReadAttribute.addLongIdentifier("postReadAttributes"); 761 postReadAttribute.addLongIdentifier("post-read-attribute"); 762 postReadAttribute.addLongIdentifier("post-read-attributes"); 763 postReadAttribute.setArgumentGroupName( 764 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 765 parser.addArgument(postReadAttribute); 766 767 768 assuredReplication = new BooleanArgument(null, "useAssuredReplication", 1, 769 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPLICATION.get( 770 ARG_ASSURED_REPLICATION_LOCAL_LEVEL, 771 ARG_ASSURED_REPLICATION_REMOTE_LEVEL, 772 ARG_ASSURED_REPLICATION_TIMEOUT)); 773 assuredReplication.addLongIdentifier("assuredReplication"); 774 assuredReplication.addLongIdentifier("use-assured-replication"); 775 assuredReplication.addLongIdentifier("assured-replication"); 776 assuredReplication.setArgumentGroupName( 777 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 778 parser.addArgument(assuredReplication); 779 780 781 final LinkedHashSet<String> assuredReplicationLocalLevelAllowedValues = 782 new LinkedHashSet<String>(3); 783 assuredReplicationLocalLevelAllowedValues.add("none"); 784 assuredReplicationLocalLevelAllowedValues.add("received-any-server"); 785 assuredReplicationLocalLevelAllowedValues.add("processed-all-servers"); 786 assuredReplicationLocalLevel = new StringArgument(null, 787 ARG_ASSURED_REPLICATION_LOCAL_LEVEL, false, 1, 788 INFO_PLACEHOLDER_LEVEL.get(), 789 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_LOCAL_LEVEL.get( 790 assuredReplication.getIdentifierString()), 791 assuredReplicationLocalLevelAllowedValues); 792 assuredReplicationLocalLevel.addLongIdentifier( 793 "assured-replication-local-level"); 794 assuredReplicationLocalLevel.setArgumentGroupName( 795 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 796 parser.addArgument(assuredReplicationLocalLevel); 797 798 799 final LinkedHashSet<String> assuredReplicationRemoteLevelAllowedValues = 800 new LinkedHashSet<String>(4); 801 assuredReplicationRemoteLevelAllowedValues.add("none"); 802 assuredReplicationRemoteLevelAllowedValues.add( 803 "received-any-remote-location"); 804 assuredReplicationRemoteLevelAllowedValues.add( 805 "received-all-remote-locations"); 806 assuredReplicationRemoteLevelAllowedValues.add( 807 "processed-all-remote-servers"); 808 assuredReplicationRemoteLevel = new StringArgument(null, 809 ARG_ASSURED_REPLICATION_REMOTE_LEVEL, false, 1, 810 INFO_PLACEHOLDER_LEVEL.get(), 811 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_REMOTE_LEVEL.get( 812 assuredReplication.getIdentifierString()), 813 assuredReplicationRemoteLevelAllowedValues); 814 assuredReplicationRemoteLevel.addLongIdentifier( 815 "assured-replication-remote-level"); 816 assuredReplicationRemoteLevel.setArgumentGroupName( 817 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 818 parser.addArgument(assuredReplicationRemoteLevel); 819 820 821 assuredReplicationTimeout = new DurationArgument(null, 822 ARG_ASSURED_REPLICATION_TIMEOUT, false, INFO_PLACEHOLDER_TIMEOUT.get(), 823 INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_TIMEOUT.get( 824 assuredReplication.getIdentifierString())); 825 assuredReplicationTimeout.setArgumentGroupName( 826 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 827 parser.addArgument(assuredReplicationTimeout); 828 829 830 replicationRepair = new BooleanArgument(null, "replicationRepair", 831 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_REPLICATION_REPAIR.get()); 832 replicationRepair.addLongIdentifier("replication-repair"); 833 replicationRepair.setArgumentGroupName( 834 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 835 parser.addArgument(replicationRepair); 836 837 838 nameWithEntryUUID = new BooleanArgument(null, "nameWithEntryUUID", 1, 839 INFO_LDAPMODIFY_ARG_DESCRIPTION_NAME_WITH_ENTRY_UUID.get()); 840 nameWithEntryUUID.addLongIdentifier("name-with-entryUUID"); 841 nameWithEntryUUID.addLongIdentifier("name-with-entry-uuid"); 842 nameWithEntryUUID.setArgumentGroupName( 843 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 844 parser.addArgument(nameWithEntryUUID); 845 846 847 noOperation = new BooleanArgument(null, "noOperation", 1, 848 INFO_LDAPMODIFY_ARG_DESCRIPTION_NO_OPERATION.get()); 849 noOperation.addLongIdentifier("noOp"); 850 noOperation.addLongIdentifier("no-operation"); 851 noOperation.addLongIdentifier("no-op"); 852 noOperation.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 853 parser.addArgument(noOperation); 854 855 856 passwordUpdateBehavior = new StringArgument(null, 857 "passwordUpdateBehavior", false, 0, 858 INFO_LDAPMODIFY_PLACEHOLDER_NAME_EQUALS_VALUE.get(), 859 INFO_LDAPMODIFY_ARG_DESCRIPTION_PW_UPDATE_BEHAVIOR.get()); 860 passwordUpdateBehavior.addLongIdentifier("password-update-behavior"); 861 passwordUpdateBehavior.setArgumentGroupName( 862 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 863 parser.addArgument(passwordUpdateBehavior); 864 865 passwordValidationDetails = new BooleanArgument(null, 866 "getPasswordValidationDetails", 1, 867 INFO_LDAPMODIFY_ARG_DESCRIPTION_PASSWORD_VALIDATION_DETAILS.get( 868 ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD)); 869 passwordValidationDetails.addLongIdentifier("passwordValidationDetails"); 870 passwordValidationDetails.addLongIdentifier( 871 "get-password-validation-details"); 872 passwordValidationDetails.addLongIdentifier("password-validation-details"); 873 passwordValidationDetails.setArgumentGroupName( 874 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 875 parser.addArgument(passwordValidationDetails); 876 877 878 permissiveModify = new BooleanArgument(null, "permissiveModify", 879 1, INFO_LDAPMODIFY_ARG_DESCRIPTION_PERMISSIVE_MODIFY.get()); 880 permissiveModify.addLongIdentifier("permissive-modify"); 881 permissiveModify.setArgumentGroupName( 882 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 883 parser.addArgument(permissiveModify); 884 885 886 subtreeDelete = new BooleanArgument(null, "subtreeDelete", 1, 887 INFO_LDAPMODIFY_ARG_DESCRIPTION_SUBTREE_DELETE.get()); 888 subtreeDelete.addLongIdentifier("subtree-delete"); 889 subtreeDelete.setArgumentGroupName( 890 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 891 parser.addArgument(subtreeDelete); 892 893 894 softDelete = new BooleanArgument('s', "softDelete", 1, 895 INFO_LDAPMODIFY_ARG_DESCRIPTION_SOFT_DELETE.get()); 896 softDelete.addLongIdentifier("useSoftDelete"); 897 softDelete.addLongIdentifier("soft-delete"); 898 softDelete.addLongIdentifier("use-soft-delete"); 899 softDelete.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 900 parser.addArgument(softDelete); 901 902 903 hardDelete = new BooleanArgument(null, "hardDelete", 1, 904 INFO_LDAPMODIFY_ARG_DESCRIPTION_HARD_DELETE.get()); 905 hardDelete.addLongIdentifier("hard-delete"); 906 hardDelete.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 907 parser.addArgument(hardDelete); 908 909 910 allowUndelete = new BooleanArgument(null, "allowUndelete", 1, 911 INFO_LDAPMODIFY_ARG_DESCRIPTION_ALLOW_UNDELETE.get( 912 ATTR_UNDELETE_FROM_DN)); 913 allowUndelete.addLongIdentifier("allow-undelete"); 914 allowUndelete.setArgumentGroupName( 915 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 916 parser.addArgument(allowUndelete); 917 918 919 retireCurrentPassword = new BooleanArgument(null, "retireCurrentPassword", 920 1, 921 INFO_LDAPMODIFY_ARG_DESCRIPTION_RETIRE_CURRENT_PASSWORD.get( 922 ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD)); 923 retireCurrentPassword.addLongIdentifier("retire-current-password"); 924 retireCurrentPassword.setArgumentGroupName( 925 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 926 parser.addArgument(retireCurrentPassword); 927 928 929 purgeCurrentPassword = new BooleanArgument(null, "purgeCurrentPassword", 1, 930 INFO_LDAPMODIFY_ARG_DESCRIPTION_PURGE_CURRENT_PASSWORD.get( 931 ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD)); 932 purgeCurrentPassword.addLongIdentifier("purge-current-password"); 933 purgeCurrentPassword.setArgumentGroupName( 934 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 935 parser.addArgument(purgeCurrentPassword); 936 937 938 final LinkedHashSet<String> 939 suppressOperationalAttributeUpdatesAllowedValues = 940 new LinkedHashSet<String>(4); 941 suppressOperationalAttributeUpdatesAllowedValues.add("last-access-time"); 942 suppressOperationalAttributeUpdatesAllowedValues.add("last-login-time"); 943 suppressOperationalAttributeUpdatesAllowedValues.add("last-login-ip"); 944 suppressOperationalAttributeUpdatesAllowedValues.add("lastmod"); 945 suppressOperationalAttributeUpdates = new StringArgument(null, 946 "suppressOperationalAttributeUpdates", false, -1, 947 INFO_PLACEHOLDER_ATTR.get(), 948 INFO_LDAPMODIFY_ARG_DESCRIPTION_SUPPRESS_OP_ATTR_UPDATES.get(), 949 suppressOperationalAttributeUpdatesAllowedValues); 950 suppressOperationalAttributeUpdates.addLongIdentifier( 951 "suppress-operational-attribute-updates"); 952 suppressOperationalAttributeUpdates.setArgumentGroupName( 953 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 954 parser.addArgument(suppressOperationalAttributeUpdates); 955 956 957 suppressReferentialIntegrityUpdates = new BooleanArgument(null, 958 "suppressReferentialIntegrityUpdates", 1, 959 INFO_LDAPMODIFY_ARG_DESCRIPTION_SUPPRESS_REFERINT_UPDATES.get()); 960 suppressReferentialIntegrityUpdates.addLongIdentifier( 961 "suppress-referential-integrity-updates"); 962 suppressReferentialIntegrityUpdates.setArgumentGroupName( 963 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 964 parser.addArgument(suppressReferentialIntegrityUpdates); 965 966 967 usePasswordPolicyControl = new BooleanArgument(null, 968 "usePasswordPolicyControl", 1, 969 INFO_LDAPMODIFY_ARG_DESCRIPTION_PASSWORD_POLICY.get()); 970 usePasswordPolicyControl.addLongIdentifier("use-password-policy-control"); 971 usePasswordPolicyControl.setArgumentGroupName( 972 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 973 parser.addArgument(usePasswordPolicyControl); 974 975 976 operationControl = new ControlArgument('J', "control", false, 0, null, 977 INFO_LDAPMODIFY_ARG_DESCRIPTION_OP_CONTROL.get()); 978 operationControl.setArgumentGroupName( 979 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 980 parser.addArgument(operationControl); 981 982 983 addControl = new ControlArgument(null, "addControl", false, 0, null, 984 INFO_LDAPMODIFY_ARG_DESCRIPTION_ADD_CONTROL.get()); 985 addControl.addLongIdentifier("add-control"); 986 addControl.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 987 parser.addArgument(addControl); 988 989 990 bindControl = new ControlArgument(null, "bindControl", false, 0, null, 991 INFO_LDAPMODIFY_ARG_DESCRIPTION_BIND_CONTROL.get()); 992 bindControl.addLongIdentifier("bind-control"); 993 bindControl.setArgumentGroupName( 994 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 995 parser.addArgument(bindControl); 996 997 998 deleteControl = new ControlArgument(null, "deleteControl", false, 0, null, 999 INFO_LDAPMODIFY_ARG_DESCRIPTION_DELETE_CONTROL.get()); 1000 deleteControl.addLongIdentifier("delete-control"); 1001 deleteControl.setArgumentGroupName( 1002 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1003 parser.addArgument(deleteControl); 1004 1005 1006 modifyControl = new ControlArgument(null, "modifyControl", false, 0, null, 1007 INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_CONTROL.get()); 1008 modifyControl.addLongIdentifier("modify-control"); 1009 modifyControl.setArgumentGroupName( 1010 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1011 parser.addArgument(modifyControl); 1012 1013 1014 modifyDNControl = new ControlArgument(null, "modifyDNControl", false, 0, 1015 null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_DN_CONTROL.get()); 1016 modifyDNControl.addLongIdentifier("modify-dn-control"); 1017 modifyDNControl.setArgumentGroupName( 1018 INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get()); 1019 parser.addArgument(modifyDNControl); 1020 1021 1022 ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1, 1023 INFO_PLACEHOLDER_NUM.get(), 1024 INFO_LDAPMODIFY_ARG_DESCRIPTION_RATE_PER_SECOND.get(), 1, 1025 Integer.MAX_VALUE); 1026 ratePerSecond.addLongIdentifier("rate-per-second"); 1027 ratePerSecond.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get()); 1028 parser.addArgument(ratePerSecond); 1029 1030 1031 // The "--scriptFriendly" argument is provided for compatibility with legacy 1032 // ldapmodify tools, but is not actually used by this tool. 1033 final BooleanArgument scriptFriendly = new BooleanArgument(null, 1034 "scriptFriendly", 1, 1035 INFO_LDAPMODIFY_ARG_DESCRIPTION_SCRIPT_FRIENDLY.get()); 1036 scriptFriendly.addLongIdentifier("script-friendly"); 1037 scriptFriendly.setArgumentGroupName( 1038 INFO_LDAPMODIFY_ARG_GROUP_DATA.get()); 1039 scriptFriendly.setHidden(true); 1040 parser.addArgument(scriptFriendly); 1041 1042 1043 // The "-V" / "--ldapVersion" argument is provided for compatibility with 1044 // legacy ldapmodify tools, but is not actually used by this tool. 1045 final IntegerArgument ldapVersion = new IntegerArgument('V', "ldapVersion", 1046 false, 1, null, INFO_LDAPMODIFY_ARG_DESCRIPTION_LDAP_VERSION.get()); 1047 ldapVersion.addLongIdentifier("ldap-version"); 1048 ldapVersion.setHidden(true); 1049 parser.addArgument(ldapVersion); 1050 1051 1052 // A few assured replication arguments will only be allowed if assured 1053 // replication is to be used. 1054 parser.addDependentArgumentSet(assuredReplicationLocalLevel, 1055 assuredReplication); 1056 parser.addDependentArgumentSet(assuredReplicationRemoteLevel, 1057 assuredReplication); 1058 parser.addDependentArgumentSet(assuredReplicationTimeout, 1059 assuredReplication); 1060 1061 // Transactions will be incompatible with a lot of settings. 1062 parser.addExclusiveArgumentSet(useTransaction, multiUpdateErrorBehavior); 1063 parser.addExclusiveArgumentSet(useTransaction, rejectFile); 1064 parser.addExclusiveArgumentSet(useTransaction, retryFailedOperations); 1065 parser.addExclusiveArgumentSet(useTransaction, continueOnError); 1066 parser.addExclusiveArgumentSet(useTransaction, dryRun); 1067 parser.addExclusiveArgumentSet(useTransaction, followReferrals); 1068 parser.addExclusiveArgumentSet(useTransaction, nameWithEntryUUID); 1069 parser.addExclusiveArgumentSet(useTransaction, noOperation); 1070 parser.addExclusiveArgumentSet(useTransaction, operationControl); 1071 parser.addExclusiveArgumentSet(useTransaction, addControl); 1072 parser.addExclusiveArgumentSet(useTransaction, deleteControl); 1073 parser.addExclusiveArgumentSet(useTransaction, modifyControl); 1074 parser.addExclusiveArgumentSet(useTransaction, modifyDNControl); 1075 parser.addExclusiveArgumentSet(useTransaction, modifyEntriesMatchingFilter); 1076 parser.addExclusiveArgumentSet(useTransaction, 1077 modifyEntriesMatchingFiltersFromFile); 1078 parser.addExclusiveArgumentSet(useTransaction, modifyEntryWithDN); 1079 parser.addExclusiveArgumentSet(useTransaction, 1080 modifyEntriesWithDNsFromFile); 1081 1082 // Multi-update is incompatible with a lot of settings. 1083 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, ratePerSecond); 1084 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, rejectFile); 1085 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1086 retryFailedOperations); 1087 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, continueOnError); 1088 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, dryRun); 1089 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, followReferrals); 1090 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, nameWithEntryUUID); 1091 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, noOperation); 1092 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, operationControl); 1093 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, addControl); 1094 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, deleteControl); 1095 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, modifyControl); 1096 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, modifyDNControl); 1097 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1098 modifyEntriesMatchingFilter); 1099 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1100 modifyEntriesMatchingFiltersFromFile); 1101 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, modifyEntryWithDN); 1102 parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, 1103 modifyEntriesWithDNsFromFile); 1104 1105 // Soft delete cannot be used with either hard delete or subtree delete. 1106 parser.addExclusiveArgumentSet(softDelete, hardDelete); 1107 parser.addExclusiveArgumentSet(softDelete, subtreeDelete); 1108 1109 // Password retiring and purging can't be used together. 1110 parser.addExclusiveArgumentSet(retireCurrentPassword, purgeCurrentPassword); 1111 1112 // Referral following cannot be used in conjunction with the manageDsaIT 1113 // control. 1114 parser.addExclusiveArgumentSet(followReferrals, manageDsaIT); 1115 1116 // The proxyAs and proxyV1As arguments cannot be used together. 1117 parser.addExclusiveArgumentSet(proxyAs, proxyV1As); 1118 1119 // The modifyEntriesMatchingFilter argument is incompatible with a lot of 1120 // settings, since it can only be used for modify operations. 1121 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, allowUndelete); 1122 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, defaultAdd); 1123 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, dryRun); 1124 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, hardDelete); 1125 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1126 ignoreNoUserModification); 1127 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1128 nameWithEntryUUID); 1129 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, softDelete); 1130 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, subtreeDelete); 1131 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1132 suppressReferentialIntegrityUpdates); 1133 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, addControl); 1134 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, deleteControl); 1135 parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, 1136 modifyDNControl); 1137 1138 // The modifyEntriesMatchingFilterFromFile argument is incompatible with a 1139 // lot of settings, since it can only be used for modify operations. 1140 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1141 allowUndelete); 1142 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1143 defaultAdd); 1144 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1145 dryRun); 1146 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1147 hardDelete); 1148 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1149 ignoreNoUserModification); 1150 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1151 nameWithEntryUUID); 1152 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1153 softDelete); 1154 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1155 subtreeDelete); 1156 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1157 suppressReferentialIntegrityUpdates); 1158 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1159 addControl); 1160 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1161 deleteControl); 1162 parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile, 1163 modifyDNControl); 1164 1165 // The modifyEntryWithDN argument is incompatible with a lot of 1166 // settings, since it can only be used for modify operations. 1167 parser.addExclusiveArgumentSet(modifyEntryWithDN, allowUndelete); 1168 parser.addExclusiveArgumentSet(modifyEntryWithDN, defaultAdd); 1169 parser.addExclusiveArgumentSet(modifyEntryWithDN, dryRun); 1170 parser.addExclusiveArgumentSet(modifyEntryWithDN, hardDelete); 1171 parser.addExclusiveArgumentSet(modifyEntryWithDN, ignoreNoUserModification); 1172 parser.addExclusiveArgumentSet(modifyEntryWithDN, nameWithEntryUUID); 1173 parser.addExclusiveArgumentSet(modifyEntryWithDN, softDelete); 1174 parser.addExclusiveArgumentSet(modifyEntryWithDN, subtreeDelete); 1175 parser.addExclusiveArgumentSet(modifyEntryWithDN, 1176 suppressReferentialIntegrityUpdates); 1177 parser.addExclusiveArgumentSet(modifyEntryWithDN, addControl); 1178 parser.addExclusiveArgumentSet(modifyEntryWithDN, deleteControl); 1179 parser.addExclusiveArgumentSet(modifyEntryWithDN, modifyDNControl); 1180 1181 // The modifyEntriesWithDNsFromFile argument is incompatible with a lot of 1182 // settings, since it can only be used for modify operations. 1183 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, allowUndelete); 1184 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, defaultAdd); 1185 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, dryRun); 1186 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, hardDelete); 1187 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1188 ignoreNoUserModification); 1189 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1190 nameWithEntryUUID); 1191 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, softDelete); 1192 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, subtreeDelete); 1193 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1194 suppressReferentialIntegrityUpdates); 1195 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, addControl); 1196 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, deleteControl); 1197 parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, 1198 modifyDNControl); 1199 } 1200 1201 1202 1203 /** 1204 * {@inheritDoc} 1205 */ 1206 @Override() 1207 protected List<Control> getBindControls() 1208 { 1209 final ArrayList<Control> bindControls = new ArrayList<Control>(10); 1210 1211 if (bindControl.isPresent()) 1212 { 1213 bindControls.addAll(bindControl.getValues()); 1214 } 1215 1216 if (authorizationIdentity.isPresent()) 1217 { 1218 bindControls.add(new AuthorizationIdentityRequestControl(false)); 1219 } 1220 1221 if (getAuthorizationEntryAttribute.isPresent()) 1222 { 1223 bindControls.add(new GetAuthorizationEntryRequestControl(true, true, 1224 getAuthorizationEntryAttribute.getValues())); 1225 } 1226 1227 if (getUserResourceLimits.isPresent()) 1228 { 1229 bindControls.add(new GetUserResourceLimitsRequestControl()); 1230 } 1231 1232 if (usePasswordPolicyControl.isPresent()) 1233 { 1234 bindControls.add(new PasswordPolicyRequestControl()); 1235 } 1236 1237 if (suppressOperationalAttributeUpdates.isPresent()) 1238 { 1239 final EnumSet<SuppressType> suppressTypes = 1240 EnumSet.noneOf(SuppressType.class); 1241 for (final String s : suppressOperationalAttributeUpdates.getValues()) 1242 { 1243 if (s.equalsIgnoreCase("last-access-time")) 1244 { 1245 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 1246 } 1247 else if (s.equalsIgnoreCase("last-login-time")) 1248 { 1249 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 1250 } 1251 else if (s.equalsIgnoreCase("last-login-ip")) 1252 { 1253 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 1254 } 1255 } 1256 1257 bindControls.add(new SuppressOperationalAttributeUpdateRequestControl( 1258 suppressTypes)); 1259 } 1260 1261 return bindControls; 1262 } 1263 1264 1265 1266 /** 1267 * {@inheritDoc} 1268 */ 1269 @Override() 1270 protected boolean supportsMultipleServers() 1271 { 1272 // We will support providing information about multiple servers. This tool 1273 // will not communicate with multiple servers concurrently, but it can 1274 // accept information about multiple servers in the event that a large set 1275 // of changes is to be processed and a server goes down in the middle of 1276 // those changes. In this case, we can resume processing on a newly-created 1277 // connection, possibly to a different server. 1278 return true; 1279 } 1280 1281 1282 1283 /** 1284 * {@inheritDoc} 1285 */ 1286 @Override() 1287 public LDAPConnectionOptions getConnectionOptions() 1288 { 1289 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 1290 1291 options.setUseSynchronousMode(true); 1292 options.setFollowReferrals(followReferrals.isPresent()); 1293 options.setUnsolicitedNotificationHandler(this); 1294 1295 return options; 1296 } 1297 1298 1299 1300 /** 1301 * {@inheritDoc} 1302 */ 1303 @Override() 1304 public ResultCode doToolProcessing() 1305 { 1306 // Examine the arguments to determine the sets of controls to use for each 1307 // type of request. 1308 final ArrayList<Control> addControls = new ArrayList<Control>(10); 1309 final ArrayList<Control> deleteControls = new ArrayList<Control>(10); 1310 final ArrayList<Control> modifyControls = new ArrayList<Control>(10); 1311 final ArrayList<Control> modifyDNControls = new ArrayList<Control>(10); 1312 final ArrayList<Control> searchControls = new ArrayList<Control>(10); 1313 try 1314 { 1315 createRequestControls(addControls, deleteControls, modifyControls, 1316 modifyDNControls, searchControls); 1317 } 1318 catch (final LDAPException le) 1319 { 1320 Debug.debugException(le); 1321 for (final String line : 1322 ResultUtils.formatResult(le, true, 0, WRAP_COLUMN)) 1323 { 1324 err(line); 1325 } 1326 return le.getResultCode(); 1327 } 1328 1329 1330 LDAPConnectionPool connectionPool = null; 1331 LDIFReader ldifReader = null; 1332 LDIFWriter rejectWriter = null; 1333 try 1334 { 1335 // Create a connection pool that will be used to communicate with the 1336 // directory server. If we should use an administrative session, then 1337 // create a connect processor that will be used to start the session 1338 // before performing the bind. 1339 try 1340 { 1341 final StartAdministrativeSessionPostConnectProcessor p; 1342 if (useAdministrativeSession.isPresent()) 1343 { 1344 p = new StartAdministrativeSessionPostConnectProcessor( 1345 new StartAdministrativeSessionExtendedRequest(getToolName(), 1346 true)); 1347 } 1348 else 1349 { 1350 p = null; 1351 } 1352 1353 if (! dryRun.isPresent()) 1354 { 1355 connectionPool = getConnectionPool(1, 2, 0, p, null, true, 1356 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 1357 verbose.isPresent())); 1358 } 1359 } 1360 catch (final LDAPException le) 1361 { 1362 Debug.debugException(le); 1363 1364 // Unable to create the connection pool, which means that either the 1365 // connection could not be established or the attempt to authenticate 1366 // the connection failed. If the bind failed, then the report bind 1367 // result health check should have already reported the bind failure. 1368 // If the failure was something else, then display that failure result. 1369 if (le.getResultCode() != ResultCode.INVALID_CREDENTIALS) 1370 { 1371 for (final String line : 1372 ResultUtils.formatResult(le, true, 0, WRAP_COLUMN)) 1373 { 1374 err(line); 1375 } 1376 } 1377 return le.getResultCode(); 1378 } 1379 1380 if ((connectionPool != null) && retryFailedOperations.isPresent()) 1381 { 1382 connectionPool.setRetryFailedOperationsDueToInvalidConnections(true); 1383 } 1384 1385 1386 // Report that the connection was successfully established. 1387 if (connectionPool != null) 1388 { 1389 try 1390 { 1391 final LDAPConnection connection = connectionPool.getConnection(); 1392 final String hostPort = connection.getHostPort(); 1393 connectionPool.releaseConnection(connection); 1394 commentToOut(INFO_LDAPMODIFY_CONNECTION_ESTABLISHED.get(hostPort)); 1395 out(); 1396 } 1397 catch (final LDAPException le) 1398 { 1399 Debug.debugException(le); 1400 // This should never happen. 1401 } 1402 } 1403 1404 1405 // If we should process the operations in a transaction, then start that 1406 // now. 1407 final ASN1OctetString txnID; 1408 if (useTransaction.isPresent()) 1409 { 1410 final Control[] startTxnControls; 1411 if (proxyAs.isPresent()) 1412 { 1413 // In a transaction, the proxied authorization control must only be 1414 // used in the start transaction request and not in any of the 1415 // subsequent operation requests. 1416 startTxnControls = new Control[] 1417 { 1418 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()) 1419 }; 1420 } 1421 else if (proxyV1As.isPresent()) 1422 { 1423 // In a transaction, the proxied authorization control must only be 1424 // used in the start transaction request and not in any of the 1425 // subsequent operation requests. 1426 startTxnControls = new Control[] 1427 { 1428 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()) 1429 }; 1430 } 1431 else 1432 { 1433 startTxnControls = StaticUtils.NO_CONTROLS; 1434 } 1435 1436 try 1437 { 1438 final StartTransactionExtendedResult startTxnResult = 1439 (StartTransactionExtendedResult) 1440 connectionPool.processExtendedOperation( 1441 new StartTransactionExtendedRequest(startTxnControls)); 1442 if (startTxnResult.getResultCode() == ResultCode.SUCCESS) 1443 { 1444 txnID = startTxnResult.getTransactionID(); 1445 1446 final TransactionSpecificationRequestControl c = 1447 new TransactionSpecificationRequestControl(txnID); 1448 addControls.add(c); 1449 deleteControls.add(c); 1450 modifyControls.add(c); 1451 modifyDNControls.add(c); 1452 1453 final String txnIDString; 1454 if (StaticUtils.isPrintableString(txnID.getValue())) 1455 { 1456 txnIDString = txnID.stringValue(); 1457 } 1458 else 1459 { 1460 final StringBuilder hexBuffer = new StringBuilder(); 1461 StaticUtils.toHex(txnID.getValue(), ":", hexBuffer); 1462 txnIDString = hexBuffer.toString(); 1463 } 1464 1465 commentToOut(INFO_LDAPMODIFY_STARTED_TXN.get(txnIDString)); 1466 } 1467 else 1468 { 1469 commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get( 1470 startTxnResult.getResultString())); 1471 return startTxnResult.getResultCode(); 1472 } 1473 } 1474 catch (final LDAPException le) 1475 { 1476 Debug.debugException(le); 1477 commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get( 1478 StaticUtils.getExceptionMessage(le))); 1479 return le.getResultCode(); 1480 } 1481 } 1482 else 1483 { 1484 txnID = null; 1485 } 1486 1487 1488 // Create an LDIF reader that will be used to read the changes to process. 1489 if (ldifFile.isPresent()) 1490 { 1491 final File[] ldifFiles = ldifFile.getValues().toArray(new File[0]); 1492 try 1493 { 1494 ldifReader = new LDIFReader(ldifFiles, 0, null, null, 1495 characterSet.getValue()); 1496 } 1497 catch (final Exception e) 1498 { 1499 Debug.debugException(e); 1500 commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_LDIF_READER.get( 1501 StaticUtils.getExceptionMessage(e))); 1502 return ResultCode.LOCAL_ERROR; 1503 } 1504 } 1505 else 1506 { 1507 ldifReader = new LDIFReader(in, 0, null, null, characterSet.getValue()); 1508 } 1509 1510 if (stripTrailingSpaces.isPresent()) 1511 { 1512 ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.STRIP); 1513 } 1514 1515 1516 // If appropriate, create a reject writer. 1517 if (rejectFile.isPresent()) 1518 { 1519 try 1520 { 1521 rejectWriter = new LDIFWriter(rejectFile.getValue()); 1522 1523 // Set the maximum allowed wrap column. This is better than setting a 1524 // wrap column of zero because it will ensure that comments don't get 1525 // wrapped either. 1526 rejectWriter.setWrapColumn(Integer.MAX_VALUE); 1527 } 1528 catch (final Exception e) 1529 { 1530 Debug.debugException(e); 1531 commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_REJECT_WRITER.get( 1532 rejectFile.getValue().getAbsolutePath(), 1533 StaticUtils.getExceptionMessage(e))); 1534 return ResultCode.LOCAL_ERROR; 1535 } 1536 } 1537 1538 1539 // If appropriate, create a rate limiter. 1540 final FixedRateBarrier rateLimiter; 1541 if (ratePerSecond.isPresent()) 1542 { 1543 rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue()); 1544 } 1545 else 1546 { 1547 rateLimiter = null; 1548 } 1549 1550 1551 // Iterate through the set of changes to process. 1552 boolean commitTransaction = true; 1553 ResultCode resultCode = null; 1554 final ArrayList<LDAPRequest> multiUpdateRequests = 1555 new ArrayList<LDAPRequest>(10); 1556 final boolean isBulkModify = modifyEntriesMatchingFilter.isPresent() || 1557 modifyEntriesMatchingFiltersFromFile.isPresent() || 1558 modifyEntryWithDN.isPresent() || 1559 modifyEntriesWithDNsFromFile.isPresent(); 1560readChangeRecordLoop: 1561 while (true) 1562 { 1563 // If there is a rate limiter, then use it to sleep if necessary. 1564 if ((rateLimiter != null) && (! isBulkModify)) 1565 { 1566 rateLimiter.await(); 1567 } 1568 1569 1570 // Read the next LDIF change record. If we get an error then handle it 1571 // and abort if appropriate. 1572 final LDIFChangeRecord changeRecord; 1573 try 1574 { 1575 changeRecord = ldifReader.readChangeRecord(defaultAdd.isPresent()); 1576 } 1577 catch (final IOException ioe) 1578 { 1579 Debug.debugException(ioe); 1580 1581 final String message = ERR_LDAPMODIFY_IO_ERROR_READING_CHANGE.get( 1582 StaticUtils.getExceptionMessage(ioe)); 1583 commentToErr(message); 1584 writeRejectedChange(rejectWriter, message, null); 1585 commitTransaction = false; 1586 resultCode = ResultCode.LOCAL_ERROR; 1587 break; 1588 } 1589 catch (final LDIFException le) 1590 { 1591 Debug.debugException(le); 1592 1593 final StringBuilder buffer = new StringBuilder(); 1594 if (le.mayContinueReading() && (! useTransaction.isPresent())) 1595 { 1596 buffer.append( 1597 ERR_LDAPMODIFY_RECOVERABLE_LDIF_ERROR_READING_CHANGE.get( 1598 le.getLineNumber(), StaticUtils.getExceptionMessage(le))); 1599 } 1600 else 1601 { 1602 buffer.append( 1603 ERR_LDAPMODIFY_UNRECOVERABLE_LDIF_ERROR_READING_CHANGE.get( 1604 le.getLineNumber(), StaticUtils.getExceptionMessage(le))); 1605 } 1606 1607 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS)) 1608 { 1609 resultCode = ResultCode.LOCAL_ERROR; 1610 } 1611 1612 if ((le.getDataLines() != null) && (! le.getDataLines().isEmpty())) 1613 { 1614 buffer.append(StaticUtils.EOL); 1615 buffer.append(StaticUtils.EOL); 1616 buffer.append(ERR_LDAPMODIFY_INVALID_LINES.get()); 1617 buffer.append(StaticUtils.EOL); 1618 for (final String s : le.getDataLines()) 1619 { 1620 buffer.append(s); 1621 buffer.append(StaticUtils.EOL); 1622 } 1623 } 1624 1625 final String message = buffer.toString(); 1626 commentToErr(message); 1627 writeRejectedChange(rejectWriter, message, null); 1628 1629 if (le.mayContinueReading() && (! useTransaction.isPresent())) 1630 { 1631 continue; 1632 } 1633 else 1634 { 1635 commitTransaction = false; 1636 resultCode = ResultCode.LOCAL_ERROR; 1637 break; 1638 } 1639 } 1640 1641 1642 // If we read a null change record, then there are no more changes to 1643 // process. Otherwise, treat it appropriately based on the operation 1644 // type. 1645 if (changeRecord == null) 1646 { 1647 break; 1648 } 1649 1650 1651 // If we should modify entries matching a specified filter, then convert 1652 // the change record into a set of modifications. 1653 if (modifyEntriesMatchingFilter.isPresent()) 1654 { 1655 for (final Filter filter : modifyEntriesMatchingFilter.getValues()) 1656 { 1657 final ResultCode rc = handleModifyMatchingFilter(connectionPool, 1658 changeRecord, 1659 modifyEntriesMatchingFilter.getIdentifierString(), 1660 filter, searchControls, modifyControls, rateLimiter, 1661 rejectWriter); 1662 if (rc != ResultCode.SUCCESS) 1663 { 1664 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 1665 (resultCode == ResultCode.NO_OPERATION)) 1666 { 1667 resultCode = rc; 1668 } 1669 } 1670 } 1671 } 1672 1673 if (modifyEntriesMatchingFiltersFromFile.isPresent()) 1674 { 1675 for (final File f : modifyEntriesMatchingFiltersFromFile.getValues()) 1676 { 1677 final FilterFileReader filterReader; 1678 try 1679 { 1680 filterReader = new FilterFileReader(f); 1681 } 1682 catch (final Exception e) 1683 { 1684 Debug.debugException(e); 1685 commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_FILTER_FILE.get( 1686 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 1687 return ResultCode.LOCAL_ERROR; 1688 } 1689 1690 try 1691 { 1692 while (true) 1693 { 1694 final Filter filter; 1695 try 1696 { 1697 filter = filterReader.readFilter(); 1698 } 1699 catch (final IOException ioe) 1700 { 1701 Debug.debugException(ioe); 1702 commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_FILTER_FILE.get( 1703 f.getAbsolutePath(), 1704 StaticUtils.getExceptionMessage(ioe))); 1705 return ResultCode.LOCAL_ERROR; 1706 } 1707 catch (final LDAPException le) 1708 { 1709 Debug.debugException(le); 1710 commentToErr(le.getMessage()); 1711 if (continueOnError.isPresent()) 1712 { 1713 if ((resultCode == null) || 1714 (resultCode == ResultCode.SUCCESS) || 1715 (resultCode == ResultCode.NO_OPERATION)) 1716 { 1717 resultCode = le.getResultCode(); 1718 } 1719 continue; 1720 } 1721 else 1722 { 1723 return le.getResultCode(); 1724 } 1725 } 1726 1727 if (filter == null) 1728 { 1729 break; 1730 } 1731 1732 final ResultCode rc = handleModifyMatchingFilter(connectionPool, 1733 changeRecord, 1734 modifyEntriesMatchingFiltersFromFile.getIdentifierString(), 1735 filter, searchControls, modifyControls, rateLimiter, 1736 rejectWriter); 1737 if (rc != ResultCode.SUCCESS) 1738 { 1739 if ((resultCode == null) || 1740 (resultCode == ResultCode.SUCCESS) || 1741 (resultCode == ResultCode.NO_OPERATION)) 1742 { 1743 resultCode = rc; 1744 } 1745 } 1746 } 1747 } 1748 finally 1749 { 1750 try 1751 { 1752 filterReader.close(); 1753 } 1754 catch (final Exception e) 1755 { 1756 Debug.debugException(e); 1757 } 1758 } 1759 } 1760 } 1761 1762 if (modifyEntryWithDN.isPresent()) 1763 { 1764 for (final DN dn : modifyEntryWithDN.getValues()) 1765 { 1766 final ResultCode rc = handleModifyWithDN(connectionPool, 1767 changeRecord, modifyEntryWithDN.getIdentifierString(), dn, 1768 modifyControls, rateLimiter, rejectWriter); 1769 if (rc != ResultCode.SUCCESS) 1770 { 1771 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 1772 (resultCode == ResultCode.NO_OPERATION)) 1773 { 1774 resultCode = rc; 1775 } 1776 } 1777 } 1778 } 1779 1780 if (modifyEntriesWithDNsFromFile.isPresent()) 1781 { 1782 for (final File f : modifyEntriesWithDNsFromFile.getValues()) 1783 { 1784 final DNFileReader dnReader; 1785 try 1786 { 1787 dnReader = new DNFileReader(f); 1788 } 1789 catch (final Exception e) 1790 { 1791 Debug.debugException(e); 1792 commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_DN_FILE.get( 1793 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 1794 return ResultCode.LOCAL_ERROR; 1795 } 1796 1797 try 1798 { 1799 while (true) 1800 { 1801 final DN dn; 1802 try 1803 { 1804 dn = dnReader.readDN(); 1805 } 1806 catch (final IOException ioe) 1807 { 1808 Debug.debugException(ioe); 1809 commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_DN_FILE.get( 1810 f.getAbsolutePath(), 1811 StaticUtils.getExceptionMessage(ioe))); 1812 return ResultCode.LOCAL_ERROR; 1813 } 1814 catch (final LDAPException le) 1815 { 1816 Debug.debugException(le); 1817 commentToErr(le.getMessage()); 1818 if (continueOnError.isPresent()) 1819 { 1820 if ((resultCode == null) || 1821 (resultCode == ResultCode.SUCCESS) || 1822 (resultCode == ResultCode.NO_OPERATION)) 1823 { 1824 resultCode = le.getResultCode(); 1825 } 1826 continue; 1827 } 1828 else 1829 { 1830 return le.getResultCode(); 1831 } 1832 } 1833 1834 if (dn == null) 1835 { 1836 break; 1837 } 1838 1839 final ResultCode rc = handleModifyWithDN(connectionPool, 1840 changeRecord, 1841 modifyEntriesWithDNsFromFile.getIdentifierString(), dn, 1842 modifyControls, rateLimiter, rejectWriter); 1843 if (rc != ResultCode.SUCCESS) 1844 { 1845 if ((resultCode == null) || 1846 (resultCode == ResultCode.SUCCESS) || 1847 (resultCode == ResultCode.NO_OPERATION)) 1848 { 1849 resultCode = rc; 1850 } 1851 } 1852 } 1853 } 1854 finally 1855 { 1856 try 1857 { 1858 dnReader.close(); 1859 } 1860 catch (final Exception e) 1861 { 1862 Debug.debugException(e); 1863 } 1864 } 1865 } 1866 } 1867 1868 if (isBulkModify) 1869 { 1870 continue; 1871 } 1872 1873 try 1874 { 1875 final ResultCode rc; 1876 if (changeRecord instanceof LDIFAddChangeRecord) 1877 { 1878 rc = doAdd((LDIFAddChangeRecord) changeRecord, addControls, 1879 connectionPool, multiUpdateRequests, rejectWriter); 1880 } 1881 else if (changeRecord instanceof LDIFDeleteChangeRecord) 1882 { 1883 rc = doDelete((LDIFDeleteChangeRecord) changeRecord, deleteControls, 1884 connectionPool, multiUpdateRequests, rejectWriter); 1885 } 1886 else if (changeRecord instanceof LDIFModifyChangeRecord) 1887 { 1888 rc = doModify((LDIFModifyChangeRecord) changeRecord, modifyControls, 1889 connectionPool, multiUpdateRequests, rejectWriter); 1890 } 1891 else if (changeRecord instanceof LDIFModifyDNChangeRecord) 1892 { 1893 rc = doModifyDN((LDIFModifyDNChangeRecord) changeRecord, 1894 modifyDNControls, connectionPool, multiUpdateRequests, 1895 rejectWriter); 1896 } 1897 else 1898 { 1899 // This should never happen. 1900 commentToErr(ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get()); 1901 for (final String line : changeRecord.toLDIF()) 1902 { 1903 err("# " + line); 1904 } 1905 throw new LDAPException(ResultCode.PARAM_ERROR, 1906 ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get() + 1907 changeRecord.toString()); 1908 } 1909 1910 if ((resultCode == null) && (rc != ResultCode.SUCCESS)) 1911 { 1912 resultCode = rc; 1913 } 1914 } 1915 catch (final LDAPException le) 1916 { 1917 Debug.debugException(le); 1918 1919 commitTransaction = false; 1920 if (continueOnError.isPresent()) 1921 { 1922 if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) || 1923 (resultCode == ResultCode.NO_OPERATION)) 1924 { 1925 resultCode = le.getResultCode(); 1926 } 1927 } 1928 else 1929 { 1930 resultCode = le.getResultCode(); 1931 break; 1932 } 1933 } 1934 } 1935 1936 1937 // If the operations are part of a transaction, then commit or abort that 1938 // transaction now. Otherwise, if they should be part of a multi-update 1939 // operation, then process that now. 1940 if (useTransaction.isPresent()) 1941 { 1942 LDAPResult endTxnResult; 1943 final EndTransactionExtendedRequest endTxnRequest = 1944 new EndTransactionExtendedRequest(txnID, commitTransaction); 1945 try 1946 { 1947 endTxnResult = connectionPool.processExtendedOperation(endTxnRequest); 1948 } 1949 catch (final LDAPException le) 1950 { 1951 endTxnResult = le.toLDAPResult(); 1952 } 1953 1954 displayResult(endTxnResult, false); 1955 if (((resultCode == null) || (resultCode == ResultCode.SUCCESS)) && 1956 (endTxnResult.getResultCode() != ResultCode.SUCCESS)) 1957 { 1958 resultCode = endTxnResult.getResultCode(); 1959 } 1960 } 1961 else if (multiUpdateErrorBehavior.isPresent()) 1962 { 1963 final MultiUpdateErrorBehavior errorBehavior; 1964 if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase("atomic")) 1965 { 1966 errorBehavior = MultiUpdateErrorBehavior.ATOMIC; 1967 } 1968 else if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase( 1969 "abort-on-error")) 1970 { 1971 errorBehavior = MultiUpdateErrorBehavior.ABORT_ON_ERROR; 1972 } 1973 else 1974 { 1975 errorBehavior = MultiUpdateErrorBehavior.CONTINUE_ON_ERROR; 1976 } 1977 1978 final Control[] multiUpdateControls; 1979 if (proxyAs.isPresent()) 1980 { 1981 multiUpdateControls = new Control[] 1982 { 1983 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()) 1984 }; 1985 } 1986 else if (proxyV1As.isPresent()) 1987 { 1988 multiUpdateControls = new Control[] 1989 { 1990 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()) 1991 }; 1992 } 1993 else 1994 { 1995 multiUpdateControls = StaticUtils.NO_CONTROLS; 1996 } 1997 1998 ExtendedResult multiUpdateResult; 1999 try 2000 { 2001 commentToOut(INFO_LDAPMODIFY_SENDING_MULTI_UPDATE_REQUEST.get()); 2002 final MultiUpdateExtendedRequest multiUpdateRequest = 2003 new MultiUpdateExtendedRequest(errorBehavior, 2004 multiUpdateRequests, multiUpdateControls); 2005 multiUpdateResult = 2006 connectionPool.processExtendedOperation(multiUpdateRequest); 2007 } 2008 catch (final LDAPException le) 2009 { 2010 multiUpdateResult = new ExtendedResult(le); 2011 } 2012 2013 displayResult(multiUpdateResult, false); 2014 resultCode = multiUpdateResult.getResultCode(); 2015 } 2016 2017 2018 if (resultCode == null) 2019 { 2020 return ResultCode.SUCCESS; 2021 } 2022 else 2023 { 2024 return resultCode; 2025 } 2026 } 2027 finally 2028 { 2029 if (rejectWriter != null) 2030 { 2031 try 2032 { 2033 rejectWriter.close(); 2034 } 2035 catch (final Exception e) 2036 { 2037 Debug.debugException(e); 2038 } 2039 } 2040 2041 if (ldifReader != null) 2042 { 2043 try 2044 { 2045 ldifReader.close(); 2046 } 2047 catch (final Exception e) 2048 { 2049 Debug.debugException(e); 2050 } 2051 } 2052 2053 if (connectionPool != null) 2054 { 2055 try 2056 { 2057 connectionPool.close(); 2058 } 2059 catch (final Exception e) 2060 { 2061 Debug.debugException(e); 2062 } 2063 } 2064 } 2065 } 2066 2067 2068 2069 /** 2070 * Handles the processing for a change record when the tool should modify 2071 * entries matching a given filter. 2072 * 2073 * @param connectionPool The connection pool to use to communicate with 2074 * the directory server. 2075 * @param changeRecord The LDIF change record to be processed. 2076 * @param argIdentifierString The identifier string for the argument used to 2077 * specify the filter to use to identify the 2078 * entries to modify. 2079 * @param filter The filter to use to identify the entries to 2080 * modify. 2081 * @param searchControls The set of controls to include in the search 2082 * request. 2083 * @param modifyControls The set of controls to include in the modify 2084 * requests. 2085 * @param rateLimiter The fixed-rate barrier to use for rate 2086 * limiting. It may be {@code null} if no rate 2087 * limiting is required. 2088 * @param rejectWriter The reject writer to use to record information 2089 * about any failed operations. 2090 * 2091 * @return A result code obtained from processing. 2092 */ 2093 private ResultCode handleModifyMatchingFilter( 2094 final LDAPConnectionPool connectionPool, 2095 final LDIFChangeRecord changeRecord, 2096 final String argIdentifierString, final Filter filter, 2097 final List<Control> searchControls, 2098 final List<Control> modifyControls, 2099 final FixedRateBarrier rateLimiter, 2100 final LDIFWriter rejectWriter) 2101 { 2102 // If the provided change record isn't a modify change record, then that's 2103 // an error. Reject it. 2104 if (! (changeRecord instanceof LDIFModifyChangeRecord)) 2105 { 2106 writeRejectedChange(rejectWriter, 2107 ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString), 2108 changeRecord); 2109 return ResultCode.PARAM_ERROR; 2110 } 2111 2112 final LDIFModifyChangeRecord modifyChangeRecord = 2113 (LDIFModifyChangeRecord) changeRecord; 2114 final HashSet<DN> processedDNs = new HashSet<DN>(100); 2115 2116 2117 // If we need to use the simple paged results control, then we may have to 2118 // issue multiple searches. 2119 ASN1OctetString pagedResultsCookie = null; 2120 long entriesProcessed = 0L; 2121 ResultCode resultCode = ResultCode.SUCCESS; 2122 while (true) 2123 { 2124 // Construct the search request to send. 2125 final LDAPModifySearchListener listener = 2126 new LDAPModifySearchListener(this, modifyChangeRecord, filter, 2127 modifyControls, connectionPool, rateLimiter, rejectWriter, 2128 processedDNs); 2129 2130 final SearchRequest searchRequest = 2131 new SearchRequest(listener, modifyChangeRecord.getDN(), 2132 SearchScope.SUB, filter, SearchRequest.NO_ATTRIBUTES); 2133 searchRequest.setControls(searchControls); 2134 if (searchPageSize.isPresent()) 2135 { 2136 searchRequest.addControl(new SimplePagedResultsControl( 2137 searchPageSize.getValue(), pagedResultsCookie)); 2138 } 2139 2140 2141 // The connection pool's automatic retry feature can't work for searches 2142 // that return one or more entries before encountering a failure. To get 2143 // around that, we'll check a connection out of the pool and use it to 2144 // process the search. If an error occurs that indicates the connection 2145 // is no longer valid, we can replace it with a newly-established 2146 // connection and try again. The search result listener will ensure that 2147 // no entry gets updated twice. 2148 LDAPConnection connection; 2149 try 2150 { 2151 connection = connectionPool.getConnection(); 2152 } 2153 catch (final LDAPException le) 2154 { 2155 Debug.debugException(le); 2156 2157 writeRejectedChange(rejectWriter, 2158 ERR_LDAPMODIFY_CANNOT_GET_SEARCH_CONNECTION.get( 2159 modifyChangeRecord.getDN(), String.valueOf(filter), 2160 StaticUtils.getExceptionMessage(le)), 2161 modifyChangeRecord, le.toLDAPResult()); 2162 return le.getResultCode(); 2163 } 2164 2165 SearchResult searchResult; 2166 boolean connectionValid = false; 2167 try 2168 { 2169 try 2170 { 2171 searchResult = connection.search(searchRequest); 2172 } 2173 catch (final LDAPSearchException lse) 2174 { 2175 searchResult = lse.getSearchResult(); 2176 } 2177 2178 if (searchResult.getResultCode() == ResultCode.SUCCESS) 2179 { 2180 connectionValid = true; 2181 } 2182 else if (searchResult.getResultCode().isConnectionUsable()) 2183 { 2184 connectionValid = true; 2185 writeRejectedChange(rejectWriter, 2186 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2187 String.valueOf(filter)), 2188 modifyChangeRecord, searchResult); 2189 return searchResult.getResultCode(); 2190 } 2191 else if (retryFailedOperations.isPresent()) 2192 { 2193 try 2194 { 2195 connection = connectionPool.replaceDefunctConnection(connection); 2196 } 2197 catch (final LDAPException le) 2198 { 2199 Debug.debugException(le); 2200 writeRejectedChange(rejectWriter, 2201 ERR_LDAPMODIFY_SEARCH_FAILED_CANNOT_RECONNECT.get( 2202 modifyChangeRecord.getDN(), String.valueOf(filter)), 2203 modifyChangeRecord, searchResult); 2204 return searchResult.getResultCode(); 2205 } 2206 2207 try 2208 { 2209 searchResult = connection.search(searchRequest); 2210 } 2211 catch (final LDAPSearchException lse) 2212 { 2213 Debug.debugException(lse); 2214 searchResult = lse.getSearchResult(); 2215 } 2216 2217 if (searchResult.getResultCode() == ResultCode.SUCCESS) 2218 { 2219 connectionValid = true; 2220 } 2221 else 2222 { 2223 connectionValid = searchResult.getResultCode().isConnectionUsable(); 2224 writeRejectedChange(rejectWriter, 2225 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2226 String.valueOf(filter)), 2227 modifyChangeRecord, searchResult); 2228 return searchResult.getResultCode(); 2229 } 2230 } 2231 else 2232 { 2233 writeRejectedChange(rejectWriter, 2234 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(), 2235 String.valueOf(filter)), 2236 modifyChangeRecord, searchResult); 2237 return searchResult.getResultCode(); 2238 } 2239 } 2240 finally 2241 { 2242 if (connectionValid) 2243 { 2244 connectionPool.releaseConnection(connection); 2245 } 2246 else 2247 { 2248 connectionPool.releaseDefunctConnection(connection); 2249 } 2250 } 2251 2252 2253 // If we've gotten here, then the search was successful. Check to see if 2254 // any of the modifications failed, and if so then update the result code 2255 // accordingly. 2256 if ((resultCode == ResultCode.SUCCESS) && 2257 (listener.getResultCode() != ResultCode.SUCCESS)) 2258 { 2259 resultCode = listener.getResultCode(); 2260 } 2261 2262 2263 // If the search used the simple paged results control then we may need to 2264 // repeat the search to get the next page. 2265 entriesProcessed += searchResult.getEntryCount(); 2266 if (searchPageSize.isPresent()) 2267 { 2268 final SimplePagedResultsControl responseControl; 2269 try 2270 { 2271 responseControl = SimplePagedResultsControl.get(searchResult); 2272 } 2273 catch (final LDAPException le) 2274 { 2275 Debug.debugException(le); 2276 writeRejectedChange(rejectWriter, 2277 ERR_LDAPMODIFY_CANNOT_DECODE_PAGED_RESULTS_CONTROL.get( 2278 modifyChangeRecord.getDN(), String.valueOf(filter)), 2279 modifyChangeRecord, le.toLDAPResult()); 2280 return le.getResultCode(); 2281 } 2282 2283 if (responseControl == null) 2284 { 2285 writeRejectedChange(rejectWriter, 2286 ERR_LDAPMODIFY_MISSING_PAGED_RESULTS_RESPONSE.get( 2287 modifyChangeRecord.getDN(), String.valueOf(filter)), 2288 modifyChangeRecord); 2289 return ResultCode.CONTROL_NOT_FOUND; 2290 } 2291 else 2292 { 2293 pagedResultsCookie = responseControl.getCookie(); 2294 if (responseControl.moreResultsToReturn()) 2295 { 2296 if (verbose.isPresent()) 2297 { 2298 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED_MORE_PAGES.get( 2299 modifyChangeRecord.getDN(), String.valueOf(filter), 2300 entriesProcessed)); 2301 for (final String resultLine : 2302 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2303 { 2304 out(resultLine); 2305 } 2306 out(); 2307 } 2308 } 2309 else 2310 { 2311 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get( 2312 entriesProcessed, modifyChangeRecord.getDN(), 2313 String.valueOf(filter))); 2314 if (verbose.isPresent()) 2315 { 2316 for (final String resultLine : 2317 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2318 { 2319 out(resultLine); 2320 } 2321 } 2322 2323 out(); 2324 return resultCode; 2325 } 2326 } 2327 } 2328 else 2329 { 2330 commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get( 2331 entriesProcessed, modifyChangeRecord.getDN(), 2332 String.valueOf(filter))); 2333 if (verbose.isPresent()) 2334 { 2335 for (final String resultLine : 2336 ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN)) 2337 { 2338 out(resultLine); 2339 } 2340 } 2341 2342 out(); 2343 return resultCode; 2344 } 2345 } 2346 } 2347 2348 2349 2350 /** 2351 * Handles the processing for a change record when the tool should modify an 2352 * entry with a given DN instead of the DN contained in the change record. 2353 * 2354 * @param connectionPool The connection pool to use to communicate with 2355 * the directory server. 2356 * @param changeRecord The LDIF change record to be processed. 2357 * @param argIdentifierString The identifier string for the argument used to 2358 * specify the DN of the entry to modify. 2359 * @param dn The DN of the entry to modify. 2360 * @param modifyControls The set of controls to include in the modify 2361 * requests. 2362 * @param rateLimiter The fixed-rate barrier to use for rate 2363 * limiting. It may be {@code null} if no rate 2364 * limiting is required. 2365 * @param rejectWriter The reject writer to use to record information 2366 * about any failed operations. 2367 * 2368 * @return A result code obtained from processing. 2369 */ 2370 private ResultCode handleModifyWithDN( 2371 final LDAPConnectionPool connectionPool, 2372 final LDIFChangeRecord changeRecord, 2373 final String argIdentifierString, final DN dn, 2374 final List<Control> modifyControls, 2375 final FixedRateBarrier rateLimiter, 2376 final LDIFWriter rejectWriter) 2377 { 2378 // If the provided change record isn't a modify change record, then that's 2379 // an error. Reject it. 2380 if (! (changeRecord instanceof LDIFModifyChangeRecord)) 2381 { 2382 writeRejectedChange(rejectWriter, 2383 ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString), 2384 changeRecord); 2385 return ResultCode.PARAM_ERROR; 2386 } 2387 2388 2389 // Create a new modify change record with the provided DN instead of the 2390 // original DN. 2391 final LDIFModifyChangeRecord originalChangeRecord = 2392 (LDIFModifyChangeRecord) changeRecord; 2393 final LDIFModifyChangeRecord updatedChangeRecord = 2394 new LDIFModifyChangeRecord(dn.toString(), 2395 originalChangeRecord.getModifications(), 2396 originalChangeRecord.getControls()); 2397 2398 if (rateLimiter != null) 2399 { 2400 rateLimiter.await(); 2401 } 2402 2403 try 2404 { 2405 return doModify(updatedChangeRecord, modifyControls, connectionPool, null, 2406 rejectWriter); 2407 } 2408 catch (final LDAPException le) 2409 { 2410 Debug.debugException(le); 2411 return le.getResultCode(); 2412 } 2413 } 2414 2415 2416 2417 /** 2418 * Populates lists of request controls that should be included in requests 2419 * of various types. 2420 * 2421 * @param addControls The list of controls to include in add requests. 2422 * @param deleteControls The list of controls to include in delete 2423 * requests. 2424 * @param modifyControls The list of controls to include in modify 2425 * requests. 2426 * @param modifyDNControls The list of controls to include in modify DN 2427 * requests. 2428 * @param searchControls The list of controls to include in search 2429 * requests. 2430 * 2431 * @throws LDAPException If a problem is encountered while creating any of 2432 * the requested controls. 2433 */ 2434 private void createRequestControls(final List<Control> addControls, 2435 final List<Control> deleteControls, 2436 final List<Control> modifyControls, 2437 final List<Control> modifyDNControls, 2438 final List<Control> searchControls) 2439 throws LDAPException 2440 { 2441 if (addControl.isPresent()) 2442 { 2443 addControls.addAll(addControl.getValues()); 2444 } 2445 2446 if (deleteControl.isPresent()) 2447 { 2448 deleteControls.addAll(deleteControl.getValues()); 2449 } 2450 2451 if (modifyControl.isPresent()) 2452 { 2453 modifyControls.addAll(modifyControl.getValues()); 2454 } 2455 2456 if (modifyDNControl.isPresent()) 2457 { 2458 modifyDNControls.addAll(modifyDNControl.getValues()); 2459 } 2460 2461 if (operationControl.isPresent()) 2462 { 2463 addControls.addAll(operationControl.getValues()); 2464 deleteControls.addAll(operationControl.getValues()); 2465 modifyControls.addAll(operationControl.getValues()); 2466 modifyDNControls.addAll(operationControl.getValues()); 2467 } 2468 2469 if (noOperation.isPresent()) 2470 { 2471 final NoOpRequestControl c = new NoOpRequestControl(); 2472 addControls.add(c); 2473 deleteControls.add(c); 2474 modifyControls.add(c); 2475 modifyDNControls.add(c); 2476 } 2477 2478 if (ignoreNoUserModification.isPresent()) 2479 { 2480 addControls.add(new IgnoreNoUserModificationRequestControl()); 2481 } 2482 2483 if (nameWithEntryUUID.isPresent()) 2484 { 2485 addControls.add(new NameWithEntryUUIDRequestControl(true)); 2486 } 2487 2488 if (permissiveModify.isPresent()) 2489 { 2490 modifyControls.add(new PermissiveModifyRequestControl(false)); 2491 } 2492 2493 if (suppressReferentialIntegrityUpdates.isPresent()) 2494 { 2495 final SuppressReferentialIntegrityUpdatesRequestControl c = 2496 new SuppressReferentialIntegrityUpdatesRequestControl(true); 2497 deleteControls.add(c); 2498 modifyDNControls.add(c); 2499 } 2500 2501 if (suppressOperationalAttributeUpdates.isPresent()) 2502 { 2503 final EnumSet<SuppressType> suppressTypes = 2504 EnumSet.noneOf(SuppressType.class); 2505 for (final String s : suppressOperationalAttributeUpdates.getValues()) 2506 { 2507 if (s.equalsIgnoreCase("last-access-time")) 2508 { 2509 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 2510 } 2511 else if (s.equalsIgnoreCase("last-login-time")) 2512 { 2513 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 2514 } 2515 else if (s.equalsIgnoreCase("last-login-ip")) 2516 { 2517 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 2518 } 2519 else if (s.equalsIgnoreCase("lastmod")) 2520 { 2521 suppressTypes.add(SuppressType.LASTMOD); 2522 } 2523 } 2524 2525 final SuppressOperationalAttributeUpdateRequestControl c = 2526 new SuppressOperationalAttributeUpdateRequestControl(suppressTypes); 2527 addControls.add(c); 2528 deleteControls.add(c); 2529 modifyControls.add(c); 2530 modifyDNControls.add(c); 2531 } 2532 2533 if (usePasswordPolicyControl.isPresent()) 2534 { 2535 final PasswordPolicyRequestControl c = new PasswordPolicyRequestControl(); 2536 addControls.add(c); 2537 modifyControls.add(c); 2538 } 2539 2540 if (assuredReplication.isPresent()) 2541 { 2542 AssuredReplicationLocalLevel localLevel = null; 2543 if (assuredReplicationLocalLevel.isPresent()) 2544 { 2545 final String level = assuredReplicationLocalLevel.getValue(); 2546 if (level.equalsIgnoreCase("none")) 2547 { 2548 localLevel = AssuredReplicationLocalLevel.NONE; 2549 } 2550 else if (level.equalsIgnoreCase("received-any-server")) 2551 { 2552 localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER; 2553 } 2554 else if (level.equalsIgnoreCase("processed-all-servers")) 2555 { 2556 localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS; 2557 } 2558 } 2559 2560 AssuredReplicationRemoteLevel remoteLevel = null; 2561 if (assuredReplicationRemoteLevel.isPresent()) 2562 { 2563 final String level = assuredReplicationRemoteLevel.getValue(); 2564 if (level.equalsIgnoreCase("none")) 2565 { 2566 remoteLevel = AssuredReplicationRemoteLevel.NONE; 2567 } 2568 else if (level.equalsIgnoreCase("received-any-remote-location")) 2569 { 2570 remoteLevel = 2571 AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION; 2572 } 2573 else if (level.equalsIgnoreCase("received-all-remote-locations")) 2574 { 2575 remoteLevel = 2576 AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS; 2577 } 2578 else if (level.equalsIgnoreCase("processed-all-remote-servers")) 2579 { 2580 remoteLevel = 2581 AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS; 2582 } 2583 } 2584 2585 Long timeoutMillis = null; 2586 if (assuredReplicationTimeout.isPresent()) 2587 { 2588 timeoutMillis = 2589 assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS); 2590 } 2591 2592 final AssuredReplicationRequestControl c = 2593 new AssuredReplicationRequestControl(true, localLevel, localLevel, 2594 remoteLevel, remoteLevel, timeoutMillis, false); 2595 addControls.add(c); 2596 deleteControls.add(c); 2597 modifyControls.add(c); 2598 modifyDNControls.add(c); 2599 } 2600 2601 if (hardDelete.isPresent()) 2602 { 2603 deleteControls.add(new HardDeleteRequestControl(true)); 2604 } 2605 2606 if (replicationRepair.isPresent()) 2607 { 2608 final ReplicationRepairRequestControl c = 2609 new ReplicationRepairRequestControl(); 2610 addControls.add(c); 2611 deleteControls.add(c); 2612 modifyControls.add(c); 2613 modifyDNControls.add(c); 2614 } 2615 2616 if (softDelete.isPresent()) 2617 { 2618 deleteControls.add(new SoftDeleteRequestControl(true, true)); 2619 } 2620 2621 if (subtreeDelete.isPresent()) 2622 { 2623 deleteControls.add(new SubtreeDeleteRequestControl()); 2624 } 2625 2626 if (assertionFilter.isPresent()) 2627 { 2628 final AssertionRequestControl c = new AssertionRequestControl( 2629 assertionFilter.getValue(), true); 2630 addControls.add(c); 2631 deleteControls.add(c); 2632 modifyControls.add(c); 2633 modifyDNControls.add(c); 2634 } 2635 2636 if (operationPurpose.isPresent()) 2637 { 2638 final OperationPurposeRequestControl c = 2639 new OperationPurposeRequestControl(false, "ldapmodify", 2640 Version.NUMERIC_VERSION_STRING, 2641 LDAPModify.class.getName() + ".createRequestControls", 2642 operationPurpose.getValue()); 2643 addControls.add(c); 2644 deleteControls.add(c); 2645 modifyControls.add(c); 2646 modifyDNControls.add(c); 2647 } 2648 2649 if (manageDsaIT.isPresent()) 2650 { 2651 final ManageDsaITRequestControl c = new ManageDsaITRequestControl(true); 2652 addControls.add(c); 2653 deleteControls.add(c); 2654 modifyControls.add(c); 2655 modifyDNControls.add(c); 2656 } 2657 2658 if (passwordUpdateBehavior.isPresent()) 2659 { 2660 final PasswordUpdateBehaviorRequestControl c = 2661 createPasswordUpdateBehaviorRequestControl( 2662 passwordUpdateBehavior.getIdentifierString(), 2663 passwordUpdateBehavior.getValues()); 2664 addControls.add(c); 2665 modifyControls.add(c); 2666 } 2667 2668 if (preReadAttribute.isPresent()) 2669 { 2670 final ArrayList<String> attrList = new ArrayList<String>(10); 2671 for (final String value : preReadAttribute.getValues()) 2672 { 2673 final StringTokenizer tokenizer = new StringTokenizer(value, ", "); 2674 while (tokenizer.hasMoreTokens()) 2675 { 2676 attrList.add(tokenizer.nextToken()); 2677 } 2678 } 2679 2680 final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS); 2681 final PreReadRequestControl c = new PreReadRequestControl(attrArray); 2682 deleteControls.add(c); 2683 modifyControls.add(c); 2684 modifyDNControls.add(c); 2685 } 2686 2687 if (postReadAttribute.isPresent()) 2688 { 2689 final ArrayList<String> attrList = new ArrayList<String>(10); 2690 for (final String value : postReadAttribute.getValues()) 2691 { 2692 final StringTokenizer tokenizer = new StringTokenizer(value, ", "); 2693 while (tokenizer.hasMoreTokens()) 2694 { 2695 attrList.add(tokenizer.nextToken()); 2696 } 2697 } 2698 2699 final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS); 2700 final PostReadRequestControl c = new PostReadRequestControl(attrArray); 2701 addControls.add(c); 2702 modifyControls.add(c); 2703 modifyDNControls.add(c); 2704 } 2705 2706 if (proxyAs.isPresent() && (! useTransaction.isPresent()) && 2707 (! multiUpdateErrorBehavior.isPresent())) 2708 { 2709 final ProxiedAuthorizationV2RequestControl c = 2710 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue()); 2711 addControls.add(c); 2712 deleteControls.add(c); 2713 modifyControls.add(c); 2714 modifyDNControls.add(c); 2715 searchControls.add(c); 2716 } 2717 2718 if (proxyV1As.isPresent() && (! useTransaction.isPresent()) && 2719 (! multiUpdateErrorBehavior.isPresent())) 2720 { 2721 final ProxiedAuthorizationV1RequestControl c = 2722 new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue()); 2723 addControls.add(c); 2724 deleteControls.add(c); 2725 modifyControls.add(c); 2726 modifyDNControls.add(c); 2727 searchControls.add(c); 2728 } 2729 } 2730 2731 2732 2733 /** 2734 * Creates the password update behavior request control that should be 2735 * included in add and modify requests. 2736 * 2737 * @param argIdentifier The identifier string for the argument used to 2738 * configure the password update behavior request 2739 * control. 2740 * @param argValues The set of values for the password update behavior 2741 * request control. 2742 * 2743 * @return The password update behavior request control that was created. 2744 * 2745 * @throws LDAPException If a problem is encountered while creating the 2746 * control. 2747 */ 2748 static PasswordUpdateBehaviorRequestControl 2749 createPasswordUpdateBehaviorRequestControl( 2750 final String argIdentifier, final List<String> argValues) 2751 throws LDAPException 2752 { 2753 final PasswordUpdateBehaviorRequestControlProperties properties = 2754 new PasswordUpdateBehaviorRequestControlProperties(); 2755 2756 for (final String argValue : argValues) 2757 { 2758 int delimiterPos = argValue.indexOf('='); 2759 if (delimiterPos < 0) 2760 { 2761 delimiterPos = argValue.indexOf(':'); 2762 } 2763 2764 if ((delimiterPos <= 0) || (delimiterPos >= (argValue.length() - 1))) 2765 { 2766 throw new LDAPException(ResultCode.PARAM_ERROR, 2767 ERR_LDAPMODIFY_MALFORMED_PW_UPDATE_BEHAVIOR.get(argValue, 2768 argIdentifier)); 2769 } 2770 2771 final String name = argValue.substring(0, delimiterPos).trim(); 2772 final String value = argValue.substring(delimiterPos+1).trim(); 2773 if (name.equalsIgnoreCase("is-self-change") || 2774 name.equalsIgnoreCase("self-change") || 2775 name.equalsIgnoreCase("isSelfChange") || 2776 name.equalsIgnoreCase("selfChange")) 2777 { 2778 properties.setIsSelfChange(parseBooleanValue(name, value)); 2779 } 2780 else if (name.equalsIgnoreCase("allow-pre-encoded-password") || 2781 name.equalsIgnoreCase("allow-pre-encoded-passwords") || 2782 name.equalsIgnoreCase("allow-pre-encoded") || 2783 name.equalsIgnoreCase("allowPreEncodedPassword") || 2784 name.equalsIgnoreCase("allowPreEncodedPasswords") || 2785 name.equalsIgnoreCase("allowPreEncoded")) 2786 { 2787 properties.setAllowPreEncodedPassword(parseBooleanValue(name, value)); 2788 } 2789 else if (name.equalsIgnoreCase("skip-password-validation") || 2790 name.equalsIgnoreCase("skip-password-validators") || 2791 name.equalsIgnoreCase("skip-validation") || 2792 name.equalsIgnoreCase("skip-validators") || 2793 name.equalsIgnoreCase("skipPasswordValidation") || 2794 name.equalsIgnoreCase("skipPasswordValidators") || 2795 name.equalsIgnoreCase("skipValidation") || 2796 name.equalsIgnoreCase("skipValidators")) 2797 { 2798 properties.setSkipPasswordValidation(parseBooleanValue(name, value)); 2799 } 2800 else if (name.equalsIgnoreCase("ignore-password-history") || 2801 name.equalsIgnoreCase("skip-password-history") || 2802 name.equalsIgnoreCase("ignore-history") || 2803 name.equalsIgnoreCase("skip-history") || 2804 name.equalsIgnoreCase("ignorePasswordHistory") || 2805 name.equalsIgnoreCase("skipPasswordHistory") || 2806 name.equalsIgnoreCase("ignoreHistory") || 2807 name.equalsIgnoreCase("skipHistory")) 2808 { 2809 properties.setIgnorePasswordHistory(parseBooleanValue(name, value)); 2810 } 2811 else if (name.equalsIgnoreCase("ignore-minimum-password-age") || 2812 name.equalsIgnoreCase("ignore-min-password-age") || 2813 name.equalsIgnoreCase("ignore-password-age") || 2814 name.equalsIgnoreCase("skip-minimum-password-age") || 2815 name.equalsIgnoreCase("skip-min-password-age") || 2816 name.equalsIgnoreCase("skip-password-age") || 2817 name.equalsIgnoreCase("ignoreMinimumPasswordAge") || 2818 name.equalsIgnoreCase("ignoreMinPasswordAge") || 2819 name.equalsIgnoreCase("ignorePasswordAge") || 2820 name.equalsIgnoreCase("skipMinimumPasswordAge") || 2821 name.equalsIgnoreCase("skipMinPasswordAge") || 2822 name.equalsIgnoreCase("skipPasswordAge")) 2823 { 2824 properties.setIgnoreMinimumPasswordAge(parseBooleanValue(name, value)); 2825 } 2826 else if (name.equalsIgnoreCase("password-storage-scheme") || 2827 name.equalsIgnoreCase("password-scheme") || 2828 name.equalsIgnoreCase("storage-scheme") || 2829 name.equalsIgnoreCase("scheme") || 2830 name.equalsIgnoreCase("passwordStorageScheme") || 2831 name.equalsIgnoreCase("passwordScheme") || 2832 name.equalsIgnoreCase("storageScheme")) 2833 { 2834 properties.setPasswordStorageScheme(value); 2835 } 2836 else if (name.equalsIgnoreCase("must-change-password") || 2837 name.equalsIgnoreCase("mustChangePassword")) 2838 { 2839 properties.setMustChangePassword(parseBooleanValue(name, value)); 2840 } 2841 } 2842 2843 return new PasswordUpdateBehaviorRequestControl(properties, true); 2844 } 2845 2846 2847 2848 /** 2849 * Parses the provided value as the Boolean value for a password update 2850 * behavior property. 2851 * 2852 * @param name The name of the password update behavior property being 2853 * parsed. 2854 * @param value The value to be parsed. 2855 * 2856 * @return The Boolean value that was parsed. 2857 * 2858 * @throws LDAPException If the provided value cannot be parsed as a 2859 * Boolean value. 2860 */ 2861 private static boolean parseBooleanValue(final String name, 2862 final String value) 2863 throws LDAPException 2864 { 2865 if (value.equalsIgnoreCase("true") || 2866 value.equalsIgnoreCase("t") || 2867 value.equalsIgnoreCase("yes") || 2868 value.equalsIgnoreCase("y") || 2869 value.equalsIgnoreCase("1")) 2870 { 2871 return true; 2872 } 2873 else if (value.equalsIgnoreCase("false") || 2874 value.equalsIgnoreCase("f") || 2875 value.equalsIgnoreCase("no") || 2876 value.equalsIgnoreCase("n") || 2877 value.equalsIgnoreCase("0")) 2878 { 2879 return false; 2880 } 2881 else 2882 { 2883 throw new LDAPException(ResultCode.PARAM_ERROR, 2884 ERR_LDAPMODIFY_INVALID_PW_UPDATE_BOOLEAN_VALUE.get(value, name)); 2885 } 2886 } 2887 2888 2889 2890 /** 2891 * Performs the appropriate processing for an LDIF add change record. 2892 * 2893 * @param changeRecord The LDIF add change record to process. 2894 * @param controls The set of controls to include in the request. 2895 * @param pool The connection pool to use to communicate with 2896 * the directory server. 2897 * @param multiUpdateRequests The list to which the request should be added 2898 * if it is to be processed as part of a 2899 * multi-update operation. It may be 2900 * {@code null} if the operation should not be 2901 * processed via the multi-update operation. 2902 * @param rejectWriter The LDIF writer to use for recording 2903 * information about rejected changes. It may be 2904 * {@code null} if no reject writer is 2905 * configured. 2906 * 2907 * @return The result code obtained from processing. 2908 * 2909 * @throws LDAPException If the operation did not complete successfully 2910 * and processing should not continue. 2911 */ 2912 private ResultCode doAdd(final LDIFAddChangeRecord changeRecord, 2913 final List<Control> controls, 2914 final LDAPConnectionPool pool, 2915 final List<LDAPRequest> multiUpdateRequests, 2916 final LDIFWriter rejectWriter) 2917 throws LDAPException 2918 { 2919 // Create the add request to process. 2920 final AddRequest addRequest = changeRecord.toAddRequest(true); 2921 for (final Control c : controls) 2922 { 2923 addRequest.addControl(c); 2924 } 2925 2926 2927 // If we should provide support for undelete operations and the entry 2928 // includes the ds-undelete-from-dn attribute, then add the undelete request 2929 // control. 2930 if (allowUndelete.isPresent() && 2931 addRequest.hasAttribute(ATTR_UNDELETE_FROM_DN)) 2932 { 2933 addRequest.addControl(new UndeleteRequestControl()); 2934 } 2935 2936 2937 // If the entry to add includes a password, then add a password validation 2938 // details request control if appropriate. 2939 if (passwordValidationDetails.isPresent()) 2940 { 2941 final Entry entryToAdd = addRequest.toEntry(); 2942 if ((! entryToAdd.getAttributesWithOptions(ATTR_USER_PASSWORD, 2943 null).isEmpty()) || 2944 (! entryToAdd.getAttributesWithOptions(ATTR_AUTH_PASSWORD, 2945 null).isEmpty())) 2946 { 2947 addRequest.addControl(new PasswordValidationDetailsRequestControl()); 2948 } 2949 } 2950 2951 2952 // If the operation should be processed in a multi-update operation, then 2953 // just add the request to the list and return without doing anything else. 2954 if (multiUpdateErrorBehavior.isPresent()) 2955 { 2956 multiUpdateRequests.add(addRequest); 2957 commentToOut(INFO_LDAPMODIFY_ADD_ADDED_TO_MULTI_UPDATE.get( 2958 addRequest.getDN())); 2959 return ResultCode.SUCCESS; 2960 } 2961 2962 2963 // If the --dryRun argument was provided, then we'll stop here. 2964 if (dryRun.isPresent()) 2965 { 2966 commentToOut(INFO_LDAPMODIFY_DRY_RUN_ADD.get(addRequest.getDN())); 2967 return ResultCode.SUCCESS; 2968 } 2969 2970 2971 // Process the add operation and get the result. 2972 commentToOut(INFO_LDAPMODIFY_ADDING_ENTRY.get(addRequest.getDN())); 2973 if (verbose.isPresent()) 2974 { 2975 for (final String ldifLine : 2976 addRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 2977 { 2978 out(ldifLine); 2979 } 2980 out(); 2981 } 2982 2983 LDAPResult addResult; 2984 try 2985 { 2986 addResult = pool.add(addRequest); 2987 } 2988 catch (final LDAPException le) 2989 { 2990 Debug.debugException(le); 2991 addResult = le.toLDAPResult(); 2992 } 2993 2994 2995 // Display information about the result. 2996 displayResult(addResult, useTransaction.isPresent()); 2997 2998 2999 // See if the add operation succeeded or failed. If it failed, and we 3000 // should end all processing, then throw an exception. 3001 switch (addResult.getResultCode().intValue()) 3002 { 3003 case ResultCode.SUCCESS_INT_VALUE: 3004 case ResultCode.NO_OPERATION_INT_VALUE: 3005 break; 3006 3007 case ResultCode.ASSERTION_FAILED_INT_VALUE: 3008 writeRejectedChange(rejectWriter, 3009 INFO_LDAPMODIFY_ASSERTION_FAILED.get(addRequest.getDN(), 3010 String.valueOf(assertionFilter.getValue())), 3011 addRequest.toLDIFChangeRecord(), addResult); 3012 throw new LDAPException(addResult); 3013 3014 default: 3015 writeRejectedChange(rejectWriter, null, addRequest.toLDIFChangeRecord(), 3016 addResult); 3017 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 3018 { 3019 throw new LDAPException(addResult); 3020 } 3021 break; 3022 } 3023 3024 return addResult.getResultCode(); 3025 } 3026 3027 3028 3029 /** 3030 * Performs the appropriate processing for an LDIF delete change record. 3031 * 3032 * @param changeRecord The LDIF delete change record to process. 3033 * @param controls The set of controls to include in the request. 3034 * @param pool The connection pool to use to communicate with 3035 * the directory server. 3036 * @param multiUpdateRequests The list to which the request should be added 3037 * if it is to be processed as part of a 3038 * multi-update operation. It may be 3039 * {@code null} if the operation should not be 3040 * processed via the multi-update operation. 3041 * @param rejectWriter The LDIF writer to use for recording 3042 * information about rejected changes. It may be 3043 * {@code null} if no reject writer is 3044 * configured. 3045 * 3046 * @return The result code obtained from processing. 3047 * 3048 * @throws LDAPException If the operation did not complete successfully 3049 * and processing should not continue. 3050 */ 3051 private ResultCode doDelete(final LDIFDeleteChangeRecord changeRecord, 3052 final List<Control> controls, 3053 final LDAPConnectionPool pool, 3054 final List<LDAPRequest> multiUpdateRequests, 3055 final LDIFWriter rejectWriter) 3056 throws LDAPException 3057 { 3058 // Create the delete request to process. 3059 final DeleteRequest deleteRequest = changeRecord.toDeleteRequest(true); 3060 for (final Control c : controls) 3061 { 3062 deleteRequest.addControl(c); 3063 } 3064 3065 3066 // If the operation should be processed in a multi-update operation, then 3067 // just add the request to the list and return without doing anything else. 3068 if (multiUpdateErrorBehavior.isPresent()) 3069 { 3070 multiUpdateRequests.add(deleteRequest); 3071 commentToOut(INFO_LDAPMODIFY_DELETE_ADDED_TO_MULTI_UPDATE.get( 3072 deleteRequest.getDN())); 3073 return ResultCode.SUCCESS; 3074 } 3075 3076 3077 // If the --dryRun argument was provided, then we'll stop here. 3078 if (dryRun.isPresent()) 3079 { 3080 commentToOut(INFO_LDAPMODIFY_DRY_RUN_DELETE.get(deleteRequest.getDN())); 3081 return ResultCode.SUCCESS; 3082 } 3083 3084 3085 // Process the delete operation and get the result. 3086 commentToOut(INFO_LDAPMODIFY_DELETING_ENTRY.get(deleteRequest.getDN())); 3087 if (verbose.isPresent()) 3088 { 3089 for (final String ldifLine : 3090 deleteRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 3091 { 3092 out(ldifLine); 3093 } 3094 out(); 3095 } 3096 3097 3098 LDAPResult deleteResult; 3099 try 3100 { 3101 deleteResult = pool.delete(deleteRequest); 3102 } 3103 catch (final LDAPException le) 3104 { 3105 Debug.debugException(le); 3106 deleteResult = le.toLDAPResult(); 3107 } 3108 3109 3110 // Display information about the result. 3111 displayResult(deleteResult, useTransaction.isPresent()); 3112 3113 3114 // See if the delete operation succeeded or failed. If it failed, and we 3115 // should end all processing, then throw an exception. 3116 switch (deleteResult.getResultCode().intValue()) 3117 { 3118 case ResultCode.SUCCESS_INT_VALUE: 3119 case ResultCode.NO_OPERATION_INT_VALUE: 3120 break; 3121 3122 case ResultCode.ASSERTION_FAILED_INT_VALUE: 3123 writeRejectedChange(rejectWriter, 3124 INFO_LDAPMODIFY_ASSERTION_FAILED.get(deleteRequest.getDN(), 3125 String.valueOf(assertionFilter.getValue())), 3126 deleteRequest.toLDIFChangeRecord(), deleteResult); 3127 throw new LDAPException(deleteResult); 3128 3129 default: 3130 writeRejectedChange(rejectWriter, null, 3131 deleteRequest.toLDIFChangeRecord(), deleteResult); 3132 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 3133 { 3134 throw new LDAPException(deleteResult); 3135 } 3136 break; 3137 } 3138 3139 return deleteResult.getResultCode(); 3140 } 3141 3142 3143 3144 /** 3145 * Performs the appropriate processing for an LDIF modify change record. 3146 * 3147 * @param changeRecord The LDIF modify change record to process. 3148 * @param controls The set of controls to include in the request. 3149 * @param pool The connection pool to use to communicate with 3150 * the directory server. 3151 * @param multiUpdateRequests The list to which the request should be added 3152 * if it is to be processed as part of a 3153 * multi-update operation. It may be 3154 * {@code null} if the operation should not be 3155 * processed via the multi-update operation. 3156 * @param rejectWriter The LDIF writer to use for recording 3157 * information about rejected changes. It may be 3158 * {@code null} if no reject writer is 3159 * configured. 3160 * 3161 * @return The result code obtained from processing. 3162 * 3163 * @throws LDAPException If the operation did not complete successfully 3164 * and processing should not continue. 3165 */ 3166 ResultCode doModify(final LDIFModifyChangeRecord changeRecord, 3167 final List<Control> controls, 3168 final LDAPConnectionPool pool, 3169 final List<LDAPRequest> multiUpdateRequests, 3170 final LDIFWriter rejectWriter) 3171 throws LDAPException 3172 { 3173 // Create the modify request to process. 3174 final ModifyRequest modifyRequest = changeRecord.toModifyRequest(true); 3175 for (final Control c : controls) 3176 { 3177 modifyRequest.addControl(c); 3178 } 3179 3180 3181 // If the modify request includes a password change, then add any controls 3182 // that are specific to that. 3183 if (retireCurrentPassword.isPresent() || purgeCurrentPassword.isPresent() || 3184 passwordValidationDetails.isPresent()) 3185 { 3186 for (final Modification m : modifyRequest.getModifications()) 3187 { 3188 final String baseName = m.getAttribute().getBaseName(); 3189 if (baseName.equalsIgnoreCase(ATTR_USER_PASSWORD) || 3190 baseName.equalsIgnoreCase(ATTR_AUTH_PASSWORD)) 3191 { 3192 if (retireCurrentPassword.isPresent()) 3193 { 3194 modifyRequest.addControl(new RetirePasswordRequestControl(false)); 3195 } 3196 else if (purgeCurrentPassword.isPresent()) 3197 { 3198 modifyRequest.addControl(new PurgePasswordRequestControl(false)); 3199 } 3200 3201 if (passwordValidationDetails.isPresent()) 3202 { 3203 modifyRequest.addControl( 3204 new PasswordValidationDetailsRequestControl()); 3205 } 3206 3207 break; 3208 } 3209 } 3210 } 3211 3212 3213 // If the operation should be processed in a multi-update operation, then 3214 // just add the request to the list and return without doing anything else. 3215 if (multiUpdateErrorBehavior.isPresent()) 3216 { 3217 multiUpdateRequests.add(modifyRequest); 3218 commentToOut(INFO_LDAPMODIFY_MODIFY_ADDED_TO_MULTI_UPDATE.get( 3219 modifyRequest.getDN())); 3220 return ResultCode.SUCCESS; 3221 } 3222 3223 3224 // If the --dryRun argument was provided, then we'll stop here. 3225 if (dryRun.isPresent()) 3226 { 3227 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MODIFY.get(modifyRequest.getDN())); 3228 return ResultCode.SUCCESS; 3229 } 3230 3231 3232 // Process the modify operation and get the result. 3233 commentToOut(INFO_LDAPMODIFY_MODIFYING_ENTRY.get(modifyRequest.getDN())); 3234 if (verbose.isPresent()) 3235 { 3236 for (final String ldifLine : 3237 modifyRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 3238 { 3239 out(ldifLine); 3240 } 3241 out(); 3242 } 3243 3244 3245 LDAPResult modifyResult; 3246 try 3247 { 3248 modifyResult = pool.modify(modifyRequest); 3249 } 3250 catch (final LDAPException le) 3251 { 3252 Debug.debugException(le); 3253 modifyResult = le.toLDAPResult(); 3254 } 3255 3256 3257 // Display information about the result. 3258 displayResult(modifyResult, useTransaction.isPresent()); 3259 3260 3261 // See if the modify operation succeeded or failed. If it failed, and we 3262 // should end all processing, then throw an exception. 3263 switch (modifyResult.getResultCode().intValue()) 3264 { 3265 case ResultCode.SUCCESS_INT_VALUE: 3266 case ResultCode.NO_OPERATION_INT_VALUE: 3267 break; 3268 3269 case ResultCode.ASSERTION_FAILED_INT_VALUE: 3270 writeRejectedChange(rejectWriter, 3271 INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyRequest.getDN(), 3272 String.valueOf(assertionFilter.getValue())), 3273 modifyRequest.toLDIFChangeRecord(), modifyResult); 3274 throw new LDAPException(modifyResult); 3275 3276 default: 3277 writeRejectedChange(rejectWriter, null, 3278 modifyRequest.toLDIFChangeRecord(), modifyResult); 3279 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 3280 { 3281 throw new LDAPException(modifyResult); 3282 } 3283 break; 3284 } 3285 3286 return modifyResult.getResultCode(); 3287 } 3288 3289 3290 3291 /** 3292 * Performs the appropriate processing for an LDIF modify DN change record. 3293 * 3294 * @param changeRecord The LDIF modify DN change record to process. 3295 * @param controls The set of controls to include in the request. 3296 * @param pool The connection pool to use to communicate with 3297 * the directory server. 3298 * @param multiUpdateRequests The list to which the request should be added 3299 * if it is to be processed as part of a 3300 * multi-update operation. It may be 3301 * {@code null} if the operation should not be 3302 * processed via the multi-update operation. 3303 * @param rejectWriter The LDIF writer to use for recording 3304 * information about rejected changes. It may be 3305 * {@code null} if no reject writer is 3306 * configured. 3307 * 3308 * @return The result code obtained from processing. 3309 * 3310 * @throws LDAPException If the operation did not complete successfully 3311 * and processing should not continue. 3312 */ 3313 private ResultCode doModifyDN(final LDIFModifyDNChangeRecord changeRecord, 3314 final List<Control> controls, 3315 final LDAPConnectionPool pool, 3316 final List<LDAPRequest> multiUpdateRequests, 3317 final LDIFWriter rejectWriter) 3318 throws LDAPException 3319 { 3320 // Create the modify DN request to process. 3321 final ModifyDNRequest modifyDNRequest = 3322 changeRecord.toModifyDNRequest(true); 3323 for (final Control c : controls) 3324 { 3325 modifyDNRequest.addControl(c); 3326 } 3327 3328 3329 // If the operation should be processed in a multi-update operation, then 3330 // just add the request to the list and return without doing anything else. 3331 if (multiUpdateErrorBehavior.isPresent()) 3332 { 3333 multiUpdateRequests.add(modifyDNRequest); 3334 commentToOut(INFO_LDAPMODIFY_MODIFY_DN_ADDED_TO_MULTI_UPDATE.get( 3335 modifyDNRequest.getDN())); 3336 return ResultCode.SUCCESS; 3337 } 3338 3339 3340 // Try to determine the new DN that the entry will have after the operation. 3341 DN newDN = null; 3342 try 3343 { 3344 newDN = changeRecord.getNewDN(); 3345 } 3346 catch (final Exception e) 3347 { 3348 Debug.debugException(e); 3349 3350 // This should only happen if the provided DN, new RDN, or new superior DN 3351 // was malformed. Although we could reject the operation now, we'll go 3352 // ahead and send the request to the server in case it has some special 3353 // handling for the DN. 3354 } 3355 3356 3357 // If the --dryRun argument was provided, then we'll stop here. 3358 if (dryRun.isPresent()) 3359 { 3360 if (modifyDNRequest.getNewSuperiorDN() == null) 3361 { 3362 if (newDN == null) 3363 { 3364 commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME.get( 3365 modifyDNRequest.getDN())); 3366 } 3367 else 3368 { 3369 commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME_TO.get( 3370 modifyDNRequest.getDN(), newDN.toString())); 3371 } 3372 } 3373 else 3374 { 3375 if (newDN == null) 3376 { 3377 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE.get( 3378 modifyDNRequest.getDN())); 3379 } 3380 else 3381 { 3382 commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE_TO.get( 3383 modifyDNRequest.getDN(), newDN.toString())); 3384 } 3385 } 3386 return ResultCode.SUCCESS; 3387 } 3388 3389 3390 // Process the modify DN operation and get the result. 3391 final String currentDN = modifyDNRequest.getDN(); 3392 if (modifyDNRequest.getNewSuperiorDN() == null) 3393 { 3394 if (newDN == null) 3395 { 3396 commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY.get(currentDN)); 3397 } 3398 else 3399 { 3400 commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY_TO.get(currentDN, 3401 newDN.toString())); 3402 } 3403 } 3404 else 3405 { 3406 if (newDN == null) 3407 { 3408 commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY.get(currentDN)); 3409 } 3410 else 3411 { 3412 commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY_TO.get(currentDN, 3413 newDN.toString())); 3414 } 3415 } 3416 3417 if (verbose.isPresent()) 3418 { 3419 for (final String ldifLine : 3420 modifyDNRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN)) 3421 { 3422 out(ldifLine); 3423 } 3424 out(); 3425 } 3426 3427 3428 LDAPResult modifyDNResult; 3429 try 3430 { 3431 modifyDNResult = pool.modifyDN(modifyDNRequest); 3432 } 3433 catch (final LDAPException le) 3434 { 3435 Debug.debugException(le); 3436 modifyDNResult = le.toLDAPResult(); 3437 } 3438 3439 3440 // Display information about the result. 3441 displayResult(modifyDNResult, useTransaction.isPresent()); 3442 3443 3444 // See if the modify DN operation succeeded or failed. If it failed, and we 3445 // should end all processing, then throw an exception. 3446 switch (modifyDNResult.getResultCode().intValue()) 3447 { 3448 case ResultCode.SUCCESS_INT_VALUE: 3449 case ResultCode.NO_OPERATION_INT_VALUE: 3450 break; 3451 3452 case ResultCode.ASSERTION_FAILED_INT_VALUE: 3453 writeRejectedChange(rejectWriter, 3454 INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyDNRequest.getDN(), 3455 String.valueOf(assertionFilter.getValue())), 3456 modifyDNRequest.toLDIFChangeRecord(), modifyDNResult); 3457 throw new LDAPException(modifyDNResult); 3458 3459 default: 3460 writeRejectedChange(rejectWriter, null, 3461 modifyDNRequest.toLDIFChangeRecord(), modifyDNResult); 3462 if (useTransaction.isPresent() || (! continueOnError.isPresent())) 3463 { 3464 throw new LDAPException(modifyDNResult); 3465 } 3466 break; 3467 } 3468 3469 return modifyDNResult.getResultCode(); 3470 } 3471 3472 3473 3474 /** 3475 * Displays information about the provided result, including special 3476 * processing for a number of supported response controls. 3477 * 3478 * @param result The result to examine. 3479 * @param inTransaction Indicates whether the operation is part of a 3480 * transaction. 3481 */ 3482 void displayResult(final LDAPResult result, final boolean inTransaction) 3483 { 3484 final ArrayList<String> resultLines = new ArrayList<String>(10); 3485 ResultUtils.formatResult(resultLines, result, true, inTransaction, 0, 3486 WRAP_COLUMN); 3487 3488 if (result.getResultCode() == ResultCode.SUCCESS) 3489 { 3490 for (final String line : resultLines) 3491 { 3492 out(line); 3493 } 3494 out(); 3495 } 3496 else 3497 { 3498 for (final String line : resultLines) 3499 { 3500 err(line); 3501 } 3502 err(); 3503 } 3504 } 3505 3506 3507 3508 /** 3509 * Writes a line-wrapped, commented version of the provided message to 3510 * standard output. 3511 * 3512 * @param message The message to be written. 3513 */ 3514 private void commentToOut(final String message) 3515 { 3516 for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) 3517 { 3518 out("# ", line); 3519 } 3520 } 3521 3522 3523 3524 /** 3525 * Writes a line-wrapped, commented version of the provided message to 3526 * standard error. 3527 * 3528 * @param message The message to be written. 3529 */ 3530 private void commentToErr(final String message) 3531 { 3532 for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) 3533 { 3534 err("# ", line); 3535 } 3536 } 3537 3538 3539 3540 /** 3541 * Writes information about the rejected change to the reject writer. 3542 * 3543 * @param writer The LDIF writer to which the information should be 3544 * written. It may be {@code null} if no reject file is 3545 * configured. 3546 * @param comment The comment to include before the change record, in 3547 * addition to the comment generated from the provided 3548 * LDAP result. It may be {@code null} if no additional 3549 * comment should be included. 3550 * @param changeRecord The LDIF change record to be written. It must not 3551 * be {@code null}. 3552 * @param ldapResult The LDAP result for the failed operation. It must 3553 * not be {@code null}. 3554 */ 3555 private void writeRejectedChange(final LDIFWriter writer, 3556 final String comment, 3557 final LDIFChangeRecord changeRecord, 3558 final LDAPResult ldapResult) 3559 { 3560 if (writer == null) 3561 { 3562 return; 3563 } 3564 3565 3566 final StringBuilder buffer = new StringBuilder(); 3567 if (comment != null) 3568 { 3569 buffer.append(comment); 3570 buffer.append(StaticUtils.EOL); 3571 buffer.append(StaticUtils.EOL); 3572 } 3573 3574 final ArrayList<String> resultLines = new ArrayList<String>(10); 3575 ResultUtils.formatResult(resultLines, ldapResult, false, false, 0, 0); 3576 for (final String resultLine : resultLines) 3577 { 3578 buffer.append(resultLine); 3579 buffer.append(StaticUtils.EOL); 3580 } 3581 3582 writeRejectedChange(writer, buffer.toString(), changeRecord); 3583 } 3584 3585 3586 3587 /** 3588 * Writes information about the rejected change to the reject writer. 3589 * 3590 * @param writer The LDIF writer to which the information should be 3591 * written. It may be {@code null} if no reject file is 3592 * configured. 3593 * @param comment The comment to include before the change record. It 3594 * may be {@code null} if no comment should be included. 3595 * @param changeRecord The LDIF change record to be written. It may be 3596 * {@code null} if only a comment should be written. 3597 */ 3598 void writeRejectedChange(final LDIFWriter writer, final String comment, 3599 final LDIFChangeRecord changeRecord) 3600 { 3601 if (writer == null) 3602 { 3603 return; 3604 } 3605 3606 if (rejectWritten.compareAndSet(false, true)) 3607 { 3608 try 3609 { 3610 writer.writeVersionHeader(); 3611 } 3612 catch (final Exception e) 3613 { 3614 Debug.debugException(e); 3615 } 3616 } 3617 3618 try 3619 { 3620 if (comment != null) 3621 { 3622 writer.writeComment(comment, true, false); 3623 } 3624 3625 if (changeRecord != null) 3626 { 3627 writer.writeChangeRecord(changeRecord); 3628 } 3629 } 3630 catch (final Exception e) 3631 { 3632 Debug.debugException(e); 3633 3634 commentToErr(ERR_LDAPMODIFY_UNABLE_TO_WRITE_REJECTED_CHANGE.get( 3635 rejectFile.getValue().getAbsolutePath(), 3636 StaticUtils.getExceptionMessage(e))); 3637 } 3638 } 3639 3640 3641 3642 /** 3643 * {@inheritDoc} 3644 */ 3645 @Override() 3646 public void handleUnsolicitedNotification(final LDAPConnection connection, 3647 final ExtendedResult notification) 3648 { 3649 final ArrayList<String> lines = new ArrayList<String>(10); 3650 ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0, 3651 WRAP_COLUMN); 3652 for (final String line : lines) 3653 { 3654 err(line); 3655 } 3656 err(); 3657 } 3658 3659 3660 3661 /** 3662 * {@inheritDoc} 3663 */ 3664 @Override() 3665 public LinkedHashMap<String[],String> getExampleUsages() 3666 { 3667 final LinkedHashMap<String[],String> examples = 3668 new LinkedHashMap<String[],String>(2); 3669 3670 final String[] args1 = 3671 { 3672 "--hostname", "ldap.example.com", 3673 "--port", "389", 3674 "--bindDN", "uid=admin,dc=example,dc=com", 3675 "--bindPassword", "password", 3676 "--defaultAdd" 3677 }; 3678 examples.put(args1, INFO_LDAPMODIFY_EXAMPLE_1.get()); 3679 3680 final String[] args2 = 3681 { 3682 "--hostname", "ds1.example.com", 3683 "--port", "636", 3684 "--hostname", "ds2.example.com", 3685 "--port", "636", 3686 "--useSSL", 3687 "--bindDN", "uid=admin,dc=example,dc=com", 3688 "--bindPassword", "password", 3689 "--filename", "changes.ldif", 3690 "--modifyEntriesMatchingFilter", "(objectClass=person)", 3691 "--searchPageSize", "100" 3692 }; 3693 examples.put(args2, INFO_LDAPMODIFY_EXAMPLE_2.get()); 3694 3695 return examples; 3696 } 3697}