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