001/* 002 * Copyright 2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2019 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.BufferedReader; 026import java.io.ByteArrayInputStream; 027import java.io.File; 028import java.io.FileInputStream; 029import java.io.InputStream; 030import java.io.InputStreamReader; 031import java.io.IOException; 032import java.io.OutputStream; 033import java.nio.charset.Charset; 034import java.security.GeneralSecurityException; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Collections; 038import java.util.Iterator; 039import java.util.LinkedHashMap; 040import java.util.List; 041import java.util.Map; 042import java.util.TreeSet; 043import java.util.concurrent.TimeUnit; 044import java.util.concurrent.atomic.AtomicLong; 045import java.util.concurrent.atomic.AtomicReference; 046 047import com.unboundid.asn1.ASN1OctetString; 048import com.unboundid.ldap.sdk.Control; 049import com.unboundid.ldap.sdk.DeleteRequest; 050import com.unboundid.ldap.sdk.DereferencePolicy; 051import com.unboundid.ldap.sdk.DN; 052import com.unboundid.ldap.sdk.ExtendedResult; 053import com.unboundid.ldap.sdk.Filter; 054import com.unboundid.ldap.sdk.LDAPConnectionOptions; 055import com.unboundid.ldap.sdk.LDAPConnection; 056import com.unboundid.ldap.sdk.LDAPConnectionPool; 057import com.unboundid.ldap.sdk.LDAPException; 058import com.unboundid.ldap.sdk.LDAPResult; 059import com.unboundid.ldap.sdk.ResultCode; 060import com.unboundid.ldap.sdk.SearchRequest; 061import com.unboundid.ldap.sdk.SearchResult; 062import com.unboundid.ldap.sdk.SearchScope; 063import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler; 064import com.unboundid.ldap.sdk.Version; 065import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; 066import com.unboundid.ldap.sdk.controls.AssertionRequestControl; 067import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl; 068import com.unboundid.ldap.sdk.controls.PreReadRequestControl; 069import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl; 070import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; 071import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl; 072import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl; 073import com.unboundid.ldap.sdk.unboundidds.controls. 074 AssuredReplicationLocalLevel; 075import com.unboundid.ldap.sdk.unboundidds.controls. 076 AssuredReplicationRemoteLevel; 077import com.unboundid.ldap.sdk.unboundidds.controls. 078 AssuredReplicationRequestControl; 079import com.unboundid.ldap.sdk.unboundidds.controls. 080 GetAuthorizationEntryRequestControl; 081import com.unboundid.ldap.sdk.unboundidds.controls. 082 GetBackendSetIDRequestControl; 083import com.unboundid.ldap.sdk.unboundidds.controls.GetServerIDRequestControl; 084import com.unboundid.ldap.sdk.unboundidds.controls. 085 GetUserResourceLimitsRequestControl; 086import com.unboundid.ldap.sdk.unboundidds.controls.HardDeleteRequestControl; 087import com.unboundid.ldap.sdk.unboundidds.controls.NoOpRequestControl; 088import com.unboundid.ldap.sdk.unboundidds.controls. 089 OperationPurposeRequestControl; 090import com.unboundid.ldap.sdk.unboundidds.controls. 091 ReplicationRepairRequestControl; 092import com.unboundid.ldap.sdk.unboundidds.controls. 093 RouteToBackendSetRequestControl; 094import com.unboundid.ldap.sdk.unboundidds.controls.RouteToServerRequestControl; 095import com.unboundid.ldap.sdk.unboundidds.controls.SoftDeleteRequestControl; 096import com.unboundid.ldap.sdk.unboundidds.controls. 097 SuppressReferentialIntegrityUpdatesRequestControl; 098import com.unboundid.ldap.sdk.unboundidds.extensions. 099 StartAdministrativeSessionExtendedRequest; 100import com.unboundid.ldap.sdk.unboundidds.extensions. 101 StartAdministrativeSessionPostConnectProcessor; 102import com.unboundid.ldif.LDIFWriter; 103import com.unboundid.util.Base64; 104import com.unboundid.util.Debug; 105import com.unboundid.util.FixedRateBarrier; 106import com.unboundid.util.LDAPCommandLineTool; 107import com.unboundid.util.ObjectPair; 108import com.unboundid.util.StaticUtils; 109import com.unboundid.util.SubtreeDeleter; 110import com.unboundid.util.SubtreeDeleterResult; 111import com.unboundid.util.ThreadSafety; 112import com.unboundid.util.ThreadSafetyLevel; 113import com.unboundid.util.args.Argument; 114import com.unboundid.util.args.ArgumentException; 115import com.unboundid.util.args.ArgumentParser; 116import com.unboundid.util.args.BooleanArgument; 117import com.unboundid.util.args.ControlArgument; 118import com.unboundid.util.args.DNArgument; 119import com.unboundid.util.args.DurationArgument; 120import com.unboundid.util.args.FileArgument; 121import com.unboundid.util.args.FilterArgument; 122import com.unboundid.util.args.IntegerArgument; 123import com.unboundid.util.args.StringArgument; 124 125import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 126 127 128 129/** 130 * This class provides a command-line tool that can be used to delete one or 131 * more entries from an LDAP directory server. The DNs of entries to delete 132 * can be provided through command-line arguments, read from a file, or read 133 * from standard input. Alternately, the tool can delete entries matching a 134 * given search filter. 135 * <BR> 136 * <BLOCKQUOTE> 137 * <B>NOTE:</B> This class, and other classes within the 138 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 139 * supported for use against Ping Identity, UnboundID, and 140 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 141 * for proprietary functionality or for external specifications that are not 142 * considered stable or mature enough to be guaranteed to work in an 143 * interoperable way with other types of LDAP servers. 144 * </BLOCKQUOTE> 145 */ 146@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 147public final class LDAPDelete 148 extends LDAPCommandLineTool 149 implements UnsolicitedNotificationHandler 150{ 151 /** 152 * The column at which output should be wrapped. 153 */ 154 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 155 156 157 158 // The set of arguments supported by this program. 159 private ArgumentParser parser = null; 160 private BooleanArgument authorizationIdentity = null; 161 private BooleanArgument clientSideSubtreeDelete = null; 162 private BooleanArgument continueOnError = null; 163 private BooleanArgument dryRun = null; 164 private BooleanArgument followReferrals = null; 165 private BooleanArgument getBackendSetID = null; 166 private BooleanArgument getServerID = null; 167 private BooleanArgument getUserResourceLimits = null; 168 private BooleanArgument hardDelete = null; 169 private BooleanArgument manageDsaIT = null; 170 private BooleanArgument noOperation = null; 171 private BooleanArgument replicationRepair = null; 172 private BooleanArgument retryFailedOperations = null; 173 private BooleanArgument softDelete = null; 174 private BooleanArgument serverSideSubtreeDelete = null; 175 private BooleanArgument suppressReferentialIntegrityUpdates = null; 176 private BooleanArgument useAdministrativeSession = null; 177 private BooleanArgument useAssuredReplication = null; 178 private BooleanArgument verbose = null; 179 private ControlArgument bindControl = null; 180 private ControlArgument deleteControl = null; 181 private DNArgument entryDN = null; 182 private DNArgument proxyV1As = null; 183 private DNArgument searchBaseDN = null; 184 private DurationArgument assuredReplicationTimeout = null; 185 private FileArgument dnFile = null; 186 private FileArgument encryptionPassphraseFile = null; 187 private FileArgument deleteEntriesMatchingFiltersFromFile = null; 188 private FileArgument rejectFile = null; 189 private FilterArgument assertionFilter = null; 190 private FilterArgument deleteEntriesMatchingFilter = null; 191 private IntegerArgument ratePerSecond = null; 192 private IntegerArgument searchPageSize = null; 193 private StringArgument assuredReplicationLocalLevel = null; 194 private StringArgument assuredReplicationRemoteLevel = null; 195 private StringArgument characterSet = null; 196 private StringArgument getAuthorizationEntryAttribute = null; 197 private StringArgument operationPurpose = null; 198 private StringArgument preReadAttribute = null; 199 private StringArgument proxyAs = null; 200 private StringArgument routeToBackendSet = null; 201 private StringArgument routeToServer = null; 202 203 // A reference to the reject writer that has been written, if it has been 204 // created. 205 private final AtomicReference<LDIFWriter> rejectWriter = 206 new AtomicReference<>(); 207 208 // The fixed-rate barrier (if any) used to enforce a rate limit on delete 209 // operations. 210 private volatile FixedRateBarrier deleteRateLimiter = null; 211 212 // The input stream from to use for standard input. 213 private final InputStream in; 214 215 // The connection pool to use to communicate with the directory server. 216 private volatile LDAPConnectionPool connectionPool = null; 217 218 // Controls to include in requests. 219 private volatile List<Control> deleteControls = Collections.emptyList(); 220 private volatile List<Control> searchControls = Collections.emptyList(); 221 private final List<RouteToBackendSetRequestControl> 222 routeToBackendSetRequestControls = new ArrayList<>(10); 223 224 // The subtree deleter to use to process client-side subtree deletes. 225 private volatile SubtreeDeleter subtreeDeleter = null; 226 227 228 229 /** 230 * Runs this tool with the provided command-line arguments. It will use the 231 * JVM-default streams for standard input, output, and error. 232 * 233 * @param args The command-line arguments to provide to this program. 234 */ 235 public static void main(final String... args) 236 { 237 final ResultCode resultCode = main(System.in, System.out, System.err, args); 238 if (resultCode != ResultCode.SUCCESS) 239 { 240 System.exit(resultCode.intValue()); 241 } 242 } 243 244 245 246 /** 247 * Runs this tool with the provided streams and command-line arguments. 248 * 249 * @param in The input stream to use for standard input. If this is 250 * {@code null}, then no standard input will be used. 251 * @param out The output stream to use for standard output. If this is 252 * {@code null}, then standard output will be suppressed. 253 * @param err The output stream to use for standard error. If this is 254 * {@code null}, then standard error will be suppressed. 255 * @param args The command-line arguments provided to this program. 256 * 257 * @return The result code obtained when running the tool. Any result code 258 * other than {@link ResultCode#SUCCESS} indicates an error. 259 */ 260 public static ResultCode main(final InputStream in, final OutputStream out, 261 final OutputStream err, final String... args) 262 { 263 final LDAPDelete ldapDelete = new LDAPDelete(in, out, err); 264 return ldapDelete.runTool(args); 265 } 266 267 268 269 /** 270 * Creates a new instance of this tool with the provided streams. Standard 271 * input will not be available. 272 * 273 * @param out The output stream to use for standard output. If this is 274 * {@code null}, then standard output will be suppressed. 275 * @param err The output stream to use for standard error. If this is 276 * {@code null}, then standard error will be suppressed. 277 */ 278 public LDAPDelete(final OutputStream out, final OutputStream err) 279 { 280 this(null, out, err); 281 } 282 283 284 285 /** 286 * Creates a new instance of this tool with the provided streams. 287 * 288 * @param in The input stream to use for standard input. If this is 289 * {@code null}, then no standard input will be used. 290 * @param out The output stream to use for standard output. If this is 291 * {@code null}, then standard output will be suppressed. 292 * @param err The output stream to use for standard error. If this is 293 * {@code null}, then standard error will be suppressed. 294 */ 295 public LDAPDelete(final InputStream in, final OutputStream out, 296 final OutputStream err) 297 { 298 super(out, err); 299 300 if (in == null) 301 { 302 this.in = new ByteArrayInputStream(StaticUtils.NO_BYTES); 303 } 304 else 305 { 306 this.in = in; 307 } 308 } 309 310 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override() 316 public String getToolName() 317 { 318 return "ldapdelete"; 319 } 320 321 322 323 /** 324 * {@inheritDoc} 325 */ 326 @Override() 327 public String getToolDescription() 328 { 329 return INFO_LDAPDELETE_TOOL_DESCRIPTION.get(); 330 } 331 332 333 334 /** 335 * {@inheritDoc} 336 */ 337 @Override() 338 public String getToolVersion() 339 { 340 return Version.NUMERIC_VERSION_STRING; 341 } 342 343 344 345 /** 346 * {@inheritDoc} 347 */ 348 @Override() 349 public int getMinTrailingArguments() 350 { 351 return 0; 352 } 353 354 355 356 /** 357 * {@inheritDoc} 358 */ 359 @Override() 360 public int getMaxTrailingArguments() 361 { 362 return Integer.MAX_VALUE; 363 } 364 365 366 367 /** 368 * {@inheritDoc} 369 */ 370 @Override() 371 public String getTrailingArgumentsPlaceholder() 372 { 373 return INFO_LDAPDELETE_TRAILING_ARGS_PLACEHOLDER.get(); 374 } 375 376 377 378 /** 379 * {@inheritDoc} 380 */ 381 @Override() 382 public boolean supportsInteractiveMode() 383 { 384 return true; 385 } 386 387 388 389 /** 390 * {@inheritDoc} 391 */ 392 @Override() 393 public boolean defaultsToInteractiveMode() 394 { 395 return true; 396 } 397 398 399 400 /** 401 * {@inheritDoc} 402 */ 403 @Override() 404 public boolean supportsPropertiesFile() 405 { 406 return true; 407 } 408 409 410 411 /** 412 * {@inheritDoc} 413 */ 414 @Override() 415 public boolean supportsOutputFile() 416 { 417 return true; 418 } 419 420 421 422 /** 423 * {@inheritDoc} 424 */ 425 @Override() 426 protected boolean defaultToPromptForBindPassword() 427 { 428 return true; 429 } 430 431 432 433 /** 434 * {@inheritDoc} 435 */ 436 @Override() 437 protected boolean includeAlternateLongIdentifiers() 438 { 439 return true; 440 } 441 442 443 444 /** 445 * {@inheritDoc} 446 */ 447 @Override() 448 protected boolean supportsSSLDebugging() 449 { 450 return true; 451 } 452 453 454 455 /** 456 * {@inheritDoc} 457 */ 458 @Override() 459 protected boolean logToolInvocationByDefault() 460 { 461 return true; 462 } 463 464 465 466 /** 467 * {@inheritDoc} 468 */ 469 @Override() 470 public void addNonLDAPArguments(final ArgumentParser parser) 471 throws ArgumentException 472 { 473 this.parser = parser; 474 475 476 // 477 // Data Arguments 478 // 479 480 final String argGroupData = INFO_LDAPDELETE_ARG_GROUP_DATA.get(); 481 482 entryDN = new DNArgument('b', "entryDN", false, 0, null, 483 INFO_LDAPDELETE_ARG_DESC_DN.get()); 484 entryDN.addLongIdentifier("entry-dn", true); 485 entryDN.addLongIdentifier("dn", true); 486 entryDN.addLongIdentifier("dnToDelete", true); 487 entryDN.addLongIdentifier("dn-to-delete", true); 488 entryDN.addLongIdentifier("entry", true); 489 entryDN.addLongIdentifier("entryToDelete", true); 490 entryDN.addLongIdentifier("entry-to-delete", true); 491 entryDN.setArgumentGroupName(argGroupData); 492 parser.addArgument(entryDN); 493 494 495 dnFile = new FileArgument('f', "dnFile", false, 0, null, 496 INFO_LDAPDELETE_ARG_DESC_DN_FILE.get(), true, true, true, false); 497 dnFile.addLongIdentifier("dn-file", true); 498 dnFile.addLongIdentifier("dnFilename", true); 499 dnFile.addLongIdentifier("dn-filename", true); 500 dnFile.addLongIdentifier("deleteEntriesWithDNsFromFile", true); 501 dnFile.addLongIdentifier("delete-entries0-with-dns-from-file", true); 502 dnFile.addLongIdentifier("file", true); 503 dnFile.addLongIdentifier("filename", true); 504 dnFile.setArgumentGroupName(argGroupData); 505 parser.addArgument(dnFile); 506 507 508 deleteEntriesMatchingFilter = new FilterArgument(null, 509 "deleteEntriesMatchingFilter", false, 0, null, 510 INFO_LDAPDELETE_ARG_DESC_DELETE_ENTRIES_MATCHING_FILTER.get()); 511 deleteEntriesMatchingFilter.addLongIdentifier( 512 "delete-entries-matching-filter", true); 513 deleteEntriesMatchingFilter.addLongIdentifier("deleteFilter", true); 514 deleteEntriesMatchingFilter.addLongIdentifier("delete-filter", true); 515 deleteEntriesMatchingFilter.addLongIdentifier("deleteSearchFilter", true); 516 deleteEntriesMatchingFilter.addLongIdentifier("delete-search-filter", true); 517 deleteEntriesMatchingFilter.addLongIdentifier("filter", true); 518 deleteEntriesMatchingFilter.setArgumentGroupName(argGroupData); 519 parser.addArgument(deleteEntriesMatchingFilter); 520 521 522 deleteEntriesMatchingFiltersFromFile = new FileArgument(null, 523 "deleteEntriesMatchingFiltersFromFile", false, 0, null, 524 INFO_LDAPDELETE_ARG_DESC_DELETE_ENTRIES_MATCHING_FILTER_FILE.get(), 525 true, true, true, false); 526 deleteEntriesMatchingFiltersFromFile.addLongIdentifier( 527 "delete-entries-matching-filters-from-file", true); 528 deleteEntriesMatchingFiltersFromFile.addLongIdentifier( 529 "deleteEntriesMatchingFilterFromFile", true); 530 deleteEntriesMatchingFiltersFromFile.addLongIdentifier( 531 "delete-entries-matching-filter-from-file", true); 532 deleteEntriesMatchingFiltersFromFile.addLongIdentifier("deleteFilterFile", 533 true); 534 deleteEntriesMatchingFiltersFromFile.addLongIdentifier("delete-filter-file", 535 true); 536 deleteEntriesMatchingFiltersFromFile.addLongIdentifier( 537 "deleteSearchFilterFile", true); 538 deleteEntriesMatchingFiltersFromFile.addLongIdentifier( 539 "delete-search-filter-file", true); 540 deleteEntriesMatchingFiltersFromFile.addLongIdentifier("filterFile", true); 541 deleteEntriesMatchingFiltersFromFile.addLongIdentifier("filter-file", true); 542 deleteEntriesMatchingFiltersFromFile.setArgumentGroupName(argGroupData); 543 parser.addArgument(deleteEntriesMatchingFiltersFromFile); 544 545 546 searchBaseDN = new DNArgument(null, "searchBaseDN", false, 0, null, 547 INFO_LDAPDELETE_ARG_DESC_SEARCH_BASE_DN.get(), DN.NULL_DN); 548 searchBaseDN.addLongIdentifier("search-base-dn", true); 549 searchBaseDN.addLongIdentifier("baseDN", true); 550 searchBaseDN.addLongIdentifier("base-dn", true); 551 searchBaseDN.setArgumentGroupName(argGroupData); 552 parser.addArgument(searchBaseDN); 553 554 555 searchPageSize = new IntegerArgument(null, "searchPageSize", false, 1, 556 null, INFO_LDAPDELETE_ARG_DESC_SEARCH_PAGE_SIZE.get(), 1, 557 Integer.MAX_VALUE); 558 searchPageSize.addLongIdentifier("search-page-size", true); 559 searchPageSize.addLongIdentifier("simplePagedResultsPageSize", true); 560 searchPageSize.addLongIdentifier("simple-paged-results-page-size", true); 561 searchPageSize.addLongIdentifier("pageSize", true); 562 searchPageSize.addLongIdentifier("page-size", true); 563 searchPageSize.setArgumentGroupName(argGroupData); 564 parser.addArgument(searchPageSize); 565 566 567 encryptionPassphraseFile = new FileArgument(null, 568 "encryptionPassphraseFile", false, 1, null, 569 INFO_LDAPDELETE_ARG_DESC_ENCRYPTION_PW_FILE.get(), true, true, true, 570 false); 571 encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file", 572 true); 573 encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true); 574 encryptionPassphraseFile.addLongIdentifier("encryption-password-file", 575 true); 576 encryptionPassphraseFile.addLongIdentifier("encryptionPINFile", true); 577 encryptionPassphraseFile.addLongIdentifier("encryption-pin-file", true); 578 encryptionPassphraseFile.setArgumentGroupName(argGroupData); 579 parser.addArgument(encryptionPassphraseFile); 580 581 582 characterSet = new StringArgument('i', "characterSet", false, 1, 583 INFO_LDAPDELETE_ARG_PLACEHOLDER_CHARSET.get(), 584 INFO_LDAPDELETE_ARG_DESC_CHARSET.get(), "UTF-8"); 585 characterSet.addLongIdentifier("character-set", true); 586 characterSet.addLongIdentifier("charSet", true); 587 characterSet.addLongIdentifier("char-set", true); 588 characterSet.addLongIdentifier("encoding", true); 589 characterSet.setArgumentGroupName(argGroupData); 590 parser.addArgument(characterSet); 591 592 593 rejectFile = new FileArgument('R', "rejectFile", false, 1, null, 594 INFO_LDAPDELETE_ARG_DESC_REJECT_FILE.get(), false, true, true, false); 595 rejectFile.addLongIdentifier("reject-file", true); 596 rejectFile.addLongIdentifier("errorFile", true); 597 rejectFile.addLongIdentifier("error-file", true); 598 rejectFile.addLongIdentifier("failureFile", true); 599 rejectFile.addLongIdentifier("failure-file", true); 600 rejectFile.setArgumentGroupName(argGroupData); 601 parser.addArgument(rejectFile); 602 603 604 verbose = new BooleanArgument('v', "verbose", 1, 605 INFO_LDAPDELETE_ARG_DESC_VERBOSE.get()); 606 verbose.setArgumentGroupName(argGroupData); 607 parser.addArgument(verbose); 608 609 // This argument has no effect. It is provided for compatibility with a 610 // legacy ldapdelete tool, where the argument was also offered but had no 611 // effect. In this tool, it is hidden. 612 final BooleanArgument scriptFriendly = new BooleanArgument(null, 613 "scriptFriendly", 1, INFO_LDAPDELETE_ARG_DESC_SCRIPT_FRIENDLY.get()); 614 scriptFriendly.addLongIdentifier("script-friendly", true); 615 scriptFriendly.setArgumentGroupName(argGroupData); 616 scriptFriendly.setHidden(true); 617 parser.addArgument(scriptFriendly); 618 619 620 621 // 622 // Operation Arguments 623 // 624 625 final String argGroupOp = INFO_LDAPDELETE_ARG_GROUP_OPERATION.get(); 626 627 retryFailedOperations = new BooleanArgument(null, "retryFailedOperations", 628 1, INFO_LDAPDELETE_ARG_DESC_RETRY_FAILED_OPS.get()); 629 retryFailedOperations.addLongIdentifier("retry-failed-operations", true); 630 retryFailedOperations.addLongIdentifier("retryFailedOps", true); 631 retryFailedOperations.addLongIdentifier("retry-failed-ops", true); 632 retryFailedOperations.addLongIdentifier("retry", true); 633 retryFailedOperations.setArgumentGroupName(argGroupOp); 634 parser.addArgument(retryFailedOperations); 635 636 637 dryRun = new BooleanArgument('n', "dryRun", 1, 638 INFO_LDAPDELETE_ARG_DESC_DRY_RUN.get()); 639 dryRun.addLongIdentifier("dry-run", true); 640 dryRun.setArgumentGroupName(argGroupOp); 641 parser.addArgument(dryRun); 642 643 644 continueOnError = new BooleanArgument('c', "continueOnError", 1, 645 INFO_LDAPDELETE_ARG_DESC_CONTINUE_ON_ERROR.get()); 646 continueOnError.addLongIdentifier("continue-on-error", true); 647 continueOnError.setArgumentGroupName(argGroupOp); 648 parser.addArgument(continueOnError); 649 650 651 followReferrals = new BooleanArgument(null, "followReferrals", 1, 652 INFO_LDAPDELETE_ARG_DESC_FOLLOW_REFERRALS.get()); 653 followReferrals.addLongIdentifier("follow-referrals"); 654 followReferrals.setArgumentGroupName(argGroupOp); 655 parser.addArgument(followReferrals); 656 657 658 useAdministrativeSession = new BooleanArgument(null, 659 "useAdministrativeSession", 1, 660 INFO_LDAPDELETE_ARG_DESC_USE_ADMIN_SESSION.get()); 661 useAdministrativeSession.addLongIdentifier("use-administrative-session", 662 true); 663 useAdministrativeSession.addLongIdentifier("useAdminSession", true); 664 useAdministrativeSession.addLongIdentifier("use-admin-session", true); 665 useAdministrativeSession.setArgumentGroupName(argGroupOp); 666 parser.addArgument(useAdministrativeSession); 667 668 669 ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1, 670 INFO_LDAPDELETE_ARG_PLACEHOLDER_RATE_PER_SECOND.get(), 671 INFO_LDAPDELETE_ARG_DESC_RATE_PER_SECOND.get(), 1, Integer.MAX_VALUE); 672 ratePerSecond.addLongIdentifier("rate-per-second", true); 673 ratePerSecond.addLongIdentifier("deletesPerSecond", true); 674 ratePerSecond.addLongIdentifier("deletes-per-second", true); 675 ratePerSecond.addLongIdentifier("operationsPerSecond", true); 676 ratePerSecond.addLongIdentifier("operations-per-second", true); 677 ratePerSecond.addLongIdentifier("opsPerSecond", true); 678 ratePerSecond.addLongIdentifier("ops-per-second", true); 679 ratePerSecond.setArgumentGroupName(argGroupOp); 680 parser.addArgument(ratePerSecond); 681 682 683 // This argument has no effect. It is provided for compatibility with a 684 // legacy ldapdelete tool, but this version only supports LDAPv3, so this 685 // argument is hidden. 686 final IntegerArgument ldapVersion = new IntegerArgument('V', "ldapVersion", 687 false, 1, "{version}", INFO_LDAPDELETE_ARG_DESC_LDAP_VERSION.get(), 688 3, 3, 3); 689 ldapVersion.addLongIdentifier("ldap-version", true); 690 ldapVersion.setArgumentGroupName(argGroupOp); 691 ldapVersion.setHidden(true); 692 parser.addArgument(ldapVersion); 693 694 695 696 // 697 // Control Arguments 698 // 699 700 final String argGroupControls = INFO_LDAPDELETE_ARG_GROUP_CONTROLS.get(); 701 702 clientSideSubtreeDelete = new BooleanArgument(null, 703 "clientSideSubtreeDelete", 1, 704 INFO_LDAPDELETE_ARG_DESC_CLIENT_SIDE_SUB_DEL.get()); 705 clientSideSubtreeDelete.addLongIdentifier("client-side-subtree-delete", 706 true); 707 clientSideSubtreeDelete.setArgumentGroupName(argGroupControls); 708 parser.addArgument(clientSideSubtreeDelete); 709 710 711 serverSideSubtreeDelete = new BooleanArgument('x', 712 "serverSideSubtreeDelete", 1, 713 INFO_LDAPDELETE_ARG_DESC_SERVER_SIDE_SUB_DEL.get()); 714 serverSideSubtreeDelete.addLongIdentifier("server-side-subtree-delete", 715 true); 716 serverSideSubtreeDelete.addLongIdentifier("deleteSubtree", true); 717 serverSideSubtreeDelete.addLongIdentifier("delete-subtree", true); 718 serverSideSubtreeDelete.addLongIdentifier("useSubtreeDeleteControl", true); 719 serverSideSubtreeDelete.addLongIdentifier("use-subtree-delete-control", 720 true); 721 serverSideSubtreeDelete.setArgumentGroupName(argGroupControls); 722 parser.addArgument(serverSideSubtreeDelete); 723 724 725 softDelete = new BooleanArgument('s', "softDelete", 1, 726 INFO_LDAPDELETE_ARG_DESC_SOFT_DELETE.get()); 727 softDelete.addLongIdentifier("soft-delete", true); 728 softDelete.addLongIdentifier("useSoftDelete", true); 729 softDelete.addLongIdentifier("use-soft-delete", true); 730 softDelete.addLongIdentifier("useSoftDeleteControl", true); 731 softDelete.addLongIdentifier("use-soft-delete-control", true); 732 softDelete.setArgumentGroupName(argGroupControls); 733 parser.addArgument(softDelete); 734 735 736 hardDelete = new BooleanArgument(null, "hardDelete", 1, 737 INFO_LDAPDELETE_ARG_DESC_HARD_DELETE.get()); 738 hardDelete.addLongIdentifier("hard-delete", true); 739 hardDelete.addLongIdentifier("useHardDelete", true); 740 hardDelete.addLongIdentifier("use-hard-delete", true); 741 hardDelete.addLongIdentifier("useHardDeleteControl", true); 742 hardDelete.addLongIdentifier("use-hard-delete-control", true); 743 hardDelete.setArgumentGroupName(argGroupControls); 744 parser.addArgument(hardDelete); 745 746 747 proxyAs = new StringArgument('Y', "proxyAs", false, 1, 748 INFO_LDAPDELETE_ARG_PLACEHOLDER_AUTHZ_ID.get(), 749 INFO_LDAPDELETE_ARG_DESC_PROXY_AS.get()); 750 proxyAs.addLongIdentifier("proxy-as", true); 751 proxyAs.addLongIdentifier("proxyV2As", true); 752 proxyAs.addLongIdentifier("proxy-v2-as", true); 753 proxyAs.addLongIdentifier("proxiedAuth", true); 754 proxyAs.addLongIdentifier("proxied-auth", true); 755 proxyAs.addLongIdentifier("proxiedAuthorization", true); 756 proxyAs.addLongIdentifier("proxied-authorization", true); 757 proxyAs.addLongIdentifier("useProxiedAuth", true); 758 proxyAs.addLongIdentifier("use-proxied-auth", true); 759 proxyAs.addLongIdentifier("useProxiedAuthorization", true); 760 proxyAs.addLongIdentifier("use-proxied-authorization", true); 761 proxyAs.addLongIdentifier("useProxiedAuthControl", true); 762 proxyAs.addLongIdentifier("use-proxied-auth-control", true); 763 proxyAs.addLongIdentifier("useProxiedAuthorizationControl", true); 764 proxyAs.addLongIdentifier("use-proxied-authorization-control", true); 765 proxyAs.setArgumentGroupName(argGroupControls); 766 parser.addArgument(proxyAs); 767 768 769 proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null, 770 INFO_LDAPDELETE_ARG_DESC_PROXY_V1_AS.get()); 771 proxyV1As.addLongIdentifier("proxy-v1-as", true); 772 proxyV1As.setArgumentGroupName(argGroupControls); 773 parser.addArgument(proxyV1As); 774 775 776 manageDsaIT = new BooleanArgument(null, "useManageDsaIT", 1, 777 INFO_LDAPDELETE_ARG_DESC_MANAGE_DSA_IT.get()); 778 manageDsaIT.addLongIdentifier("use-manage-dsa-it", true); 779 manageDsaIT.addLongIdentifier("manageDsaIT", true); 780 manageDsaIT.addLongIdentifier("manage-dsa-it", true); 781 manageDsaIT.addLongIdentifier("manageDsaITControl", true); 782 manageDsaIT.addLongIdentifier("manage-dsa-it-control", true); 783 manageDsaIT.addLongIdentifier("useManageDsaITControl", true); 784 manageDsaIT.addLongIdentifier("use-manage-dsa-it-control", true); 785 manageDsaIT.setArgumentGroupName(argGroupControls); 786 parser.addArgument(manageDsaIT); 787 788 789 assertionFilter = new FilterArgument(null, "assertionFilter", false, 1, 790 null, INFO_LDAPDELETE_ARG_DESC_ASSERTION_FILTER.get()); 791 assertionFilter.addLongIdentifier("assertion-filter", true); 792 assertionFilter.addLongIdentifier("useAssertionFilter", true); 793 assertionFilter.addLongIdentifier("use-assertion-filter", true); 794 assertionFilter.addLongIdentifier("assertionControl", true); 795 assertionFilter.addLongIdentifier("assertion-control", true); 796 assertionFilter.addLongIdentifier("useAssertionControl", true); 797 assertionFilter.addLongIdentifier("use-assertion-control", true); 798 assertionFilter.setArgumentGroupName(argGroupControls); 799 parser.addArgument(assertionFilter); 800 801 802 preReadAttribute = new StringArgument(null, "preReadAttribute", false, 0, 803 INFO_LDAPDELETE_ARG_PLACEHOLDER_ATTR.get(), 804 INFO_LDAPDELETE_ARG_DESC_PRE_READ_ATTR.get()); 805 preReadAttribute.addLongIdentifier("pre-read-attribute", true); 806 preReadAttribute.setArgumentGroupName(argGroupControls); 807 parser.addArgument(preReadAttribute); 808 809 810 noOperation = new BooleanArgument(null, "noOperation", 1, 811 INFO_LDAPDELETE_ARG_DESC_NO_OP.get()); 812 noOperation.addLongIdentifier("no-operation", true); 813 noOperation.addLongIdentifier("noOp", true); 814 noOperation.addLongIdentifier("no-op", true); 815 noOperation.setArgumentGroupName(argGroupControls); 816 parser.addArgument(noOperation); 817 818 819 getBackendSetID = new BooleanArgument(null, "getBackendSetID", 1, 820 INFO_LDAPDELETE_ARG_DESC_GET_BACKEND_SET_ID.get()); 821 getBackendSetID.addLongIdentifier("get-backend-set-id", true); 822 getBackendSetID.addLongIdentifier("useGetBackendSetID", true); 823 getBackendSetID.addLongIdentifier("use-get-backend-set-id", true); 824 getBackendSetID.addLongIdentifier("useGetBackendSetIDControl", true); 825 getBackendSetID.addLongIdentifier("use-get-backend-set-id-control", true); 826 getBackendSetID.setArgumentGroupName(argGroupControls); 827 parser.addArgument(getBackendSetID); 828 829 830 routeToBackendSet = new StringArgument(null, "routeToBackendSet", false, 0, 831 INFO_LDAPDELETE_ARG_PLACEHOLDER_ROUTE_TO_BACKEND_SET.get(), 832 INFO_LDAPDELETE_ARG_DESC_ROUTE_TO_BACKEND_SET.get()); 833 routeToBackendSet.addLongIdentifier("route-to-backend-set", true); 834 routeToBackendSet.addLongIdentifier("useRouteToBackendSet", true); 835 routeToBackendSet.addLongIdentifier("use0route-to-backend-set", true); 836 routeToBackendSet.addLongIdentifier("useRouteToBackendSetControl", true); 837 routeToBackendSet.addLongIdentifier("use-route-to-backend-set-control", 838 true); 839 routeToBackendSet.setArgumentGroupName(argGroupControls); 840 parser.addArgument(routeToBackendSet); 841 842 843 getServerID = new BooleanArgument(null, "getServerID", 1, 844 INFO_LDAPDELETE_ARG_DESC_GET_SERVER_ID.get()); 845 getServerID.addLongIdentifier("get-server-id", true); 846 getServerID.addLongIdentifier("getBackendServerID", true); 847 getServerID.addLongIdentifier("get-backend-server-id", true); 848 getServerID.addLongIdentifier("useGetServerID", true); 849 getServerID.addLongIdentifier("use-get-server-id", true); 850 getServerID.addLongIdentifier("useGetServerIDControl", true); 851 getServerID.addLongIdentifier("use-get-server-id-control", true); 852 getServerID.setArgumentGroupName(argGroupControls); 853 parser.addArgument(getServerID); 854 855 856 routeToServer = new StringArgument(null, "routeToServer", false, 1, 857 INFO_LDAPDELETE_ARG_PLACEHOLDER_ID.get(), 858 INFO_LDAPDELETE_ARG_DESC_ROUTE_TO_SERVER.get()); 859 routeToServer.addLongIdentifier("route-to-server", true); 860 routeToServer.addLongIdentifier("routeToBackendServer", true); 861 routeToServer.addLongIdentifier("route-to-backend-server", true); 862 routeToServer.addLongIdentifier("useRouteToServer", true); 863 routeToServer.addLongIdentifier("use-route-to-server", true); 864 routeToServer.addLongIdentifier("useRouteToBackendServer", true); 865 routeToServer.addLongIdentifier("use-route-to-backend-server", true); 866 routeToServer.addLongIdentifier("useRouteToServerControl", true); 867 routeToServer.addLongIdentifier("use-route-to-server-control", true); 868 routeToServer.addLongIdentifier("useRouteToBackendServerControl", true); 869 routeToServer.addLongIdentifier("use-route-to-backend-server-control", 870 true); 871 routeToServer.setArgumentGroupName(argGroupControls); 872 parser.addArgument(routeToServer); 873 874 875 useAssuredReplication = new BooleanArgument(null, "useAssuredReplication", 876 1, INFO_LDAPDELETE_ARG_DESC_USE_ASSURED_REPLICATION.get()); 877 useAssuredReplication.addLongIdentifier("use-assured-replication", true); 878 useAssuredReplication.addLongIdentifier("assuredReplication", true); 879 useAssuredReplication.addLongIdentifier("assured-replication", true); 880 useAssuredReplication.addLongIdentifier("assuredReplicationControl", true); 881 useAssuredReplication.addLongIdentifier("assured-replication-control", 882 true); 883 useAssuredReplication.addLongIdentifier("useAssuredReplicationControl", 884 true); 885 useAssuredReplication.addLongIdentifier("use-assured-replication-control", 886 true); 887 useAssuredReplication.setArgumentGroupName(argGroupControls); 888 parser.addArgument(useAssuredReplication); 889 890 891 assuredReplicationLocalLevel = new StringArgument(null, 892 "assuredReplicationLocalLevel", false, 1, 893 INFO_LDAPDELETE_ARG_PLACEHOLDER_ASSURED_REPLICATION_LOCAL_LEVEL.get(), 894 INFO_LDAPDELETE_ARG_DESC_ASSURED_REPLICATION_LOCAL_LEVEL.get(), 895 StaticUtils.setOf( 896 "none", 897 "received-any-server", 898 "processed-all-servers")); 899 assuredReplicationLocalLevel.addLongIdentifier( 900 "assured-replication-local-level", true); 901 assuredReplicationLocalLevel.setArgumentGroupName(argGroupControls); 902 parser.addArgument(assuredReplicationLocalLevel); 903 904 905 assuredReplicationRemoteLevel = new StringArgument(null, 906 "assuredReplicationRemoteLevel", false, 1, 907 INFO_LDAPDELETE_ARG_PLACEHOLDER_ASSURED_REPLICATION_REMOTE_LEVEL.get(), 908 INFO_LDAPDELETE_ARG_DESC_ASSURED_REPLICATION_REMOTE_LEVEL.get(), 909 StaticUtils.setOf( 910 "none", 911 "received-any-remote-location", 912 "received-all-remote-locations", 913 "processed-all-remote-servers")); 914 assuredReplicationRemoteLevel.addLongIdentifier( 915 "assured-replication-remote-level", true); 916 assuredReplicationRemoteLevel.setArgumentGroupName(argGroupControls); 917 parser.addArgument(assuredReplicationRemoteLevel); 918 919 920 assuredReplicationTimeout = new DurationArgument(null, 921 "assuredReplicationTimeout", false, null, 922 INFO_LDAPDELETE_ARG_DESC_ASSURED_REPLICATION_TIMEOUT.get()); 923 assuredReplicationTimeout.addLongIdentifier("assured-replication-timeout", 924 true); 925 assuredReplicationTimeout.setArgumentGroupName(argGroupControls); 926 parser.addArgument(assuredReplicationTimeout); 927 928 929 replicationRepair = new BooleanArgument(null, "replicationRepair", 1, 930 INFO_LDAPDELETE_ARG_DESC_REPLICATION_REPAIR.get()); 931 replicationRepair.addLongIdentifier("replication-repair", true); 932 replicationRepair.addLongIdentifier("replicationRepairControl", true); 933 replicationRepair.addLongIdentifier("replication-repair-control", true); 934 replicationRepair.addLongIdentifier("useReplicationRepair", true); 935 replicationRepair.addLongIdentifier("use-replication-repair", true); 936 replicationRepair.addLongIdentifier("useReplicationRepairControl", true); 937 replicationRepair.addLongIdentifier("use-replication-repair-control", true); 938 replicationRepair.setArgumentGroupName(argGroupControls); 939 parser.addArgument(replicationRepair); 940 941 942 suppressReferentialIntegrityUpdates = new BooleanArgument(null, 943 "suppressReferentialIntegrityUpdates", 1, 944 INFO_LDAPDELETE_ARG_DESC_SUPPRESS_REFINT_UPDATES.get()); 945 suppressReferentialIntegrityUpdates.addLongIdentifier( 946 "suppress-referential-integrity-updates", true); 947 suppressReferentialIntegrityUpdates.addLongIdentifier( 948 "useSuppressReferentialIntegrityUpdates", true); 949 suppressReferentialIntegrityUpdates.addLongIdentifier( 950 "use-suppress-referential-integrity-updates", true); 951 suppressReferentialIntegrityUpdates.addLongIdentifier( 952 "useSuppressReferentialIntegrityUpdatesControl", true); 953 suppressReferentialIntegrityUpdates.addLongIdentifier( 954 "use-suppress-referential-integrity-updates-control", true); 955 suppressReferentialIntegrityUpdates.setArgumentGroupName(argGroupControls); 956 parser.addArgument(suppressReferentialIntegrityUpdates); 957 958 959 operationPurpose = new StringArgument(null, "operationPurpose", false, 1, 960 null, INFO_LDAPDELETE_ARG_DESC_OP_PURPOSE.get()); 961 operationPurpose.addLongIdentifier("operation-purpose", true); 962 operationPurpose.addLongIdentifier("operationPurposeControl", true); 963 operationPurpose.addLongIdentifier("operation-purpose-control", true); 964 operationPurpose.addLongIdentifier("useOperationPurpose", true); 965 operationPurpose.addLongIdentifier("use-operation-purpose", true); 966 operationPurpose.addLongIdentifier("useOperationPurposeControl", true); 967 operationPurpose.addLongIdentifier("use-operation-purpose-control", true); 968 operationPurpose.setArgumentGroupName(argGroupControls); 969 parser.addArgument(operationPurpose); 970 971 972 authorizationIdentity = new BooleanArgument('E', "authorizationIdentity", 973 1, INFO_LDAPDELETE_ARG_DESC_AUTHZ_ID.get()); 974 authorizationIdentity.addLongIdentifier("authorization-identity", true); 975 authorizationIdentity.addLongIdentifier("useAuthorizationIdentity", true); 976 authorizationIdentity.addLongIdentifier("use-authorization-identity", true); 977 authorizationIdentity.addLongIdentifier( 978 "useAuthorizationIdentityControl", true); 979 authorizationIdentity.addLongIdentifier( 980 "use-authorization-identity-control", true); 981 authorizationIdentity.setArgumentGroupName(argGroupControls); 982 parser.addArgument(authorizationIdentity); 983 984 985 getAuthorizationEntryAttribute = new StringArgument(null, 986 "getAuthorizationEntryAttribute", false, 0, 987 INFO_LDAPDELETE_ARG_PLACEHOLDER_ATTR.get(), 988 INFO_LDAPDELETE_ARG_DESC_GET_AUTHZ_ENTRY_ATTR.get()); 989 getAuthorizationEntryAttribute.addLongIdentifier( 990 "get-authorization-entry-attribute", true); 991 getAuthorizationEntryAttribute.setArgumentGroupName(argGroupControls); 992 parser.addArgument(getAuthorizationEntryAttribute); 993 994 995 getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits", 996 1, INFO_LDAPDELETE_ARG_DESC_GET_USER_RESOURCE_LIMITS.get()); 997 getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true); 998 getUserResourceLimits.addLongIdentifier("getUserResourceLimitsControl", 999 true); 1000 getUserResourceLimits.addLongIdentifier("get-user-resource-limits-control", 1001 true); 1002 getUserResourceLimits.addLongIdentifier("useGetUserResourceLimits", true); 1003 getUserResourceLimits.addLongIdentifier("use-get-user-resource-limits", 1004 true); 1005 getUserResourceLimits.addLongIdentifier( 1006 "useGetUserResourceLimitsControl", true); 1007 getUserResourceLimits.addLongIdentifier( 1008 "use-get-user-resource-limits-control", true); 1009 getUserResourceLimits.setArgumentGroupName(argGroupControls); 1010 parser.addArgument(getUserResourceLimits); 1011 1012 1013 deleteControl = new ControlArgument('J', "deleteControl", false, 0, null, 1014 INFO_LDAPDELETE_ARG_DESC_DELETE_CONTROL.get()); 1015 deleteControl.addLongIdentifier("delete-control", true); 1016 deleteControl.addLongIdentifier("operationControl", true); 1017 deleteControl.addLongIdentifier("operation-control", true); 1018 deleteControl.addLongIdentifier("control", true); 1019 deleteControl.setArgumentGroupName(argGroupControls); 1020 parser.addArgument(deleteControl); 1021 1022 1023 bindControl = new ControlArgument(null, "bindControl", false, 0, null, 1024 INFO_LDAPDELETE_ARG_DESC_BIND_CONTROL.get()); 1025 bindControl.addLongIdentifier("bind-control", true); 1026 bindControl.setArgumentGroupName(argGroupControls); 1027 parser.addArgument(bindControl); 1028 1029 1030 1031 // 1032 // Argument Constraints 1033 // 1034 1035 // At most one argument may be provided to select the entries to delete. 1036 parser.addExclusiveArgumentSet(entryDN, dnFile, deleteEntriesMatchingFilter, 1037 deleteEntriesMatchingFiltersFromFile); 1038 1039 // The searchBaseDN argument can only be used if identifying entries with 1040 // search filters. 1041 parser.addDependentArgumentSet(searchBaseDN, deleteEntriesMatchingFilter, 1042 deleteEntriesMatchingFiltersFromFile); 1043 1044 // The search page size argument can only be used if identifying entries 1045 // with search filters or performing a client-side subtree delete. 1046 parser.addDependentArgumentSet(searchPageSize, deleteEntriesMatchingFilter, 1047 deleteEntriesMatchingFiltersFromFile, clientSideSubtreeDelete); 1048 1049 // Follow referrals and manage DSA IT can't be used together. 1050 parser.addExclusiveArgumentSet(followReferrals, manageDsaIT); 1051 1052 // Client-side and server-side subtree delete can't be used together. 1053 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, 1054 serverSideSubtreeDelete); 1055 1056 // A lot of options can't be used in conjunction with client-side 1057 // subtree delete. 1058 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, followReferrals); 1059 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, preReadAttribute); 1060 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, getBackendSetID); 1061 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, getServerID); 1062 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, noOperation); 1063 parser.addExclusiveArgumentSet(clientSideSubtreeDelete, dryRun); 1064 1065 // Soft delete and hard delete can't be used together. 1066 parser.addExclusiveArgumentSet(softDelete, hardDelete); 1067 } 1068 1069 1070 1071 /** 1072 * {@inheritDoc} 1073 */ 1074 @Override() 1075 public void doExtendedNonLDAPArgumentValidation() 1076 throws ArgumentException 1077 { 1078 // Trailing arguments can only be used if none of the other arguments used 1079 // to identify entries to delete are provided. 1080 if (! parser.getTrailingArguments().isEmpty()) 1081 { 1082 for (final Argument a : 1083 Arrays.asList(entryDN, dnFile, deleteEntriesMatchingFilter, 1084 deleteEntriesMatchingFiltersFromFile)) 1085 { 1086 if (a.isPresent()) 1087 { 1088 throw new ArgumentException( 1089 ERR_LDAPDELETE_TRAILING_ARG_CONFLICT.get( 1090 a.getIdentifierString())); 1091 } 1092 } 1093 } 1094 1095 1096 // If we should use the route to backend set request control, then validate 1097 // and pre-create those controls. 1098 if (routeToBackendSet.isPresent()) 1099 { 1100 final List<String> values = routeToBackendSet.getValues(); 1101 final Map<String,List<String>> idsByRP = new LinkedHashMap<>( 1102 StaticUtils.computeMapCapacity(values.size())); 1103 for (final String value : values) 1104 { 1105 final int colonPos = value.indexOf(':'); 1106 if (colonPos <= 0) 1107 { 1108 throw new ArgumentException( 1109 ERR_LDAPDELETE_ROUTE_TO_BACKEND_SET_INVALID_FORMAT.get(value, 1110 routeToBackendSet.getIdentifierString())); 1111 } 1112 1113 final String rpID = value.substring(0, colonPos); 1114 final String bsID = value.substring(colonPos+1); 1115 1116 List<String> idsForRP = idsByRP.get(rpID); 1117 if (idsForRP == null) 1118 { 1119 idsForRP = new ArrayList<>(values.size()); 1120 idsByRP.put(rpID, idsForRP); 1121 } 1122 idsForRP.add(bsID); 1123 } 1124 1125 for (final Map.Entry<String,List<String>> e : idsByRP.entrySet()) 1126 { 1127 final String rpID = e.getKey(); 1128 final List<String> bsIDs = e.getValue(); 1129 routeToBackendSetRequestControls.add( 1130 RouteToBackendSetRequestControl.createAbsoluteRoutingRequest( 1131 true, rpID, bsIDs)); 1132 } 1133 } 1134 } 1135 1136 1137 1138 /** 1139 * {@inheritDoc} 1140 */ 1141 @Override() 1142 protected List<Control> getBindControls() 1143 { 1144 final ArrayList<Control> bindControls = new ArrayList<>(10); 1145 1146 if (bindControl.isPresent()) 1147 { 1148 bindControls.addAll(bindControl.getValues()); 1149 } 1150 1151 if (authorizationIdentity.isPresent()) 1152 { 1153 bindControls.add(new AuthorizationIdentityRequestControl(true)); 1154 } 1155 1156 if (getAuthorizationEntryAttribute.isPresent()) 1157 { 1158 bindControls.add(new GetAuthorizationEntryRequestControl(true, true, 1159 getAuthorizationEntryAttribute.getValues())); 1160 } 1161 1162 if (getUserResourceLimits.isPresent()) 1163 { 1164 bindControls.add(new GetUserResourceLimitsRequestControl(true)); 1165 } 1166 1167 return bindControls; 1168 } 1169 1170 1171 1172 /** 1173 * {@inheritDoc} 1174 */ 1175 @Override() 1176 protected boolean supportsMultipleServers() 1177 { 1178 // We will support providing information about multiple servers. This tool 1179 // will not communicate with multiple servers concurrently, but it can 1180 // accept information about multiple servers in the event that a large set 1181 // of changes is to be processed and a server goes down in the middle of 1182 // those changes. In this case, we can resume processing on a newly-created 1183 // connection, possibly to a different server. 1184 return true; 1185 } 1186 1187 1188 1189 /** 1190 * {@inheritDoc} 1191 */ 1192 @Override() 1193 public LDAPConnectionOptions getConnectionOptions() 1194 { 1195 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 1196 1197 options.setUseSynchronousMode(true); 1198 options.setFollowReferrals(followReferrals.isPresent()); 1199 options.setUnsolicitedNotificationHandler(this); 1200 options.setResponseTimeoutMillis(0L); 1201 1202 return options; 1203 } 1204 1205 1206 1207 /** 1208 * {@inheritDoc} 1209 */ 1210 @Override() 1211 public ResultCode doToolProcessing() 1212 { 1213 // Get the controls that should be included in search and delete requests. 1214 searchControls = getSearchControls(); 1215 deleteControls = getDeleteControls(); 1216 1217 // If the ratePerSecond argument was provided, then create the fixed-rate 1218 // barrier. 1219 if (ratePerSecond.isPresent()) 1220 { 1221 deleteRateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue()); 1222 } 1223 1224 // Create a subtree deleter instance if appropriate. 1225 if (clientSideSubtreeDelete.isPresent()) 1226 { 1227 subtreeDeleter = new SubtreeDeleter(); 1228 subtreeDeleter.setAdditionalSearchControls(searchControls); 1229 subtreeDeleter.setAdditionalSearchControls(deleteControls); 1230 subtreeDeleter.setDeleteRateLimiter(deleteRateLimiter); 1231 if (searchPageSize.isPresent()) 1232 { 1233 subtreeDeleter.setSimplePagedResultsPageSize(searchPageSize.getValue()); 1234 } 1235 } 1236 1237 // If the encryptionPassphraseFile argument was provided, then read that 1238 // passphrase. 1239 final char[] encryptionPassphrase; 1240 if (encryptionPassphraseFile.isPresent()) 1241 { 1242 try 1243 { 1244 encryptionPassphrase = getPasswordFileReader().readPassword( 1245 encryptionPassphraseFile.getValue()); 1246 } 1247 catch (final LDAPException e) 1248 { 1249 Debug.debugException(e); 1250 commentToErr(e.getMessage()); 1251 return e.getResultCode(); 1252 } 1253 catch (final Exception e) 1254 { 1255 Debug.debugException(e); 1256 commentToErr(ERR_LDAPDELETE_CANNOT_READ_ENCRYPTION_PW_FILE.get( 1257 encryptionPassphraseFile.getValue().getAbsolutePath(), 1258 StaticUtils.getExceptionMessage(e))); 1259 return ResultCode.LOCAL_ERROR; 1260 } 1261 } 1262 else 1263 { 1264 encryptionPassphrase = null; 1265 } 1266 1267 1268 // If the character set argument was specified, then make sure it's valid. 1269 final Charset charset; 1270 try 1271 { 1272 charset = Charset.forName(characterSet.getValue()); 1273 } 1274 catch (final Exception e) 1275 { 1276 Debug.debugException(e); 1277 commentToErr(ERR_LDAPDELETE_UNSUPPORTED_CHARSET.get( 1278 characterSet.getValue())); 1279 return ResultCode.PARAM_ERROR; 1280 } 1281 1282 1283 // Get the connection pool. 1284 final StartAdministrativeSessionPostConnectProcessor p; 1285 if (useAdministrativeSession.isPresent()) 1286 { 1287 p = new StartAdministrativeSessionPostConnectProcessor( 1288 new StartAdministrativeSessionExtendedRequest(getToolName(), 1289 true)); 1290 } 1291 else 1292 { 1293 p = null; 1294 } 1295 1296 try 1297 { 1298 connectionPool = getConnectionPool(1, 2, 0, p, null, true, 1299 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 1300 verbose.isPresent())); 1301 connectionPool.setRetryFailedOperationsDueToInvalidConnections( 1302 retryFailedOperations.isPresent()); 1303 } 1304 catch (final LDAPException e) 1305 { 1306 Debug.debugException(e); 1307 1308 // Unable to create the connection pool, which means that either the 1309 // connection could not be established or the attempt to authenticate 1310 // the connection failed. If the bind failed, then the report bind 1311 // result health check should have already reported the bind failure. 1312 // If the failure was something else, then display that failure result. 1313 if (e.getResultCode() != ResultCode.INVALID_CREDENTIALS) 1314 { 1315 for (final String line : 1316 ResultUtils.formatResult(e, true, 0, WRAP_COLUMN)) 1317 { 1318 err(line); 1319 } 1320 } 1321 return e.getResultCode(); 1322 } 1323 1324 1325 // Figure out the method that we'll identify the entries to delete and 1326 // take the appropriate action. 1327 final AtomicReference<ResultCode> returnCode = new AtomicReference<>(); 1328 if (entryDN.isPresent()) 1329 { 1330 deleteFromEntryDNArgument(returnCode); 1331 } 1332 else if (dnFile.isPresent()) 1333 { 1334 deleteFromDNFile(returnCode, charset, encryptionPassphrase); 1335 } 1336 else if (deleteEntriesMatchingFilter.isPresent()) 1337 { 1338 deleteFromFilters(returnCode); 1339 } 1340 else if (deleteEntriesMatchingFiltersFromFile.isPresent()) 1341 { 1342 deleteFromFilterFile(returnCode, charset, encryptionPassphrase); 1343 } 1344 else if (! parser.getTrailingArguments().isEmpty()) 1345 { 1346 deleteFromTrailingArguments(returnCode); 1347 } 1348 else 1349 { 1350 deleteFromStandardInput(returnCode, charset, encryptionPassphrase); 1351 } 1352 1353 1354 // Close the reject writer. 1355 final LDIFWriter rw = rejectWriter.get(); 1356 if (rw != null) 1357 { 1358 try 1359 { 1360 rw.close(); 1361 } 1362 catch (final Exception e) 1363 { 1364 Debug.debugException(e); 1365 commentToErr(ERR_LDAPDELETE_ERROR_CLOSING_REJECT_WRITER.get( 1366 rejectFile.getValue().getAbsolutePath(), 1367 StaticUtils.getExceptionMessage(e))); 1368 returnCode.compareAndSet(null, ResultCode.LOCAL_ERROR); 1369 } 1370 } 1371 1372 1373 // Close the connection pool. 1374 connectionPool.close(); 1375 1376 1377 returnCode.compareAndSet(null, ResultCode.SUCCESS); 1378 return returnCode.get(); 1379 } 1380 1381 1382 1383 /** 1384 * Deletes entries whose DNs are specified in the entryDN argument. 1385 * 1386 * @param returnCode A reference that should be updated with the result code 1387 * from the first failure that is encountered. It must 1388 * not be {@code null}, but may be unset. 1389 */ 1390 private void deleteFromEntryDNArgument( 1391 final AtomicReference<ResultCode> returnCode) 1392 { 1393 for (final DN dn : entryDN.getValues()) 1394 { 1395 if ((! deleteEntry(dn.toString(), returnCode)) && 1396 (! continueOnError.isPresent())) 1397 { 1398 return; 1399 } 1400 } 1401 } 1402 1403 1404 1405 /** 1406 * Deletes entries whose DNs are contained in the files provided to the dnFile 1407 * argument. 1408 * 1409 * @param returnCode A reference that should be updated with the 1410 * result code from the first failure that is 1411 * encountered. It must not be {@code null}, 1412 * but may be unset. 1413 * @param charset The character set to use when reading the 1414 * data from the file. It must not be 1415 * {@code null}. 1416 * @param encryptionPassphrase The passphrase to use to decrypt the data 1417 * read from the file if it happens to be 1418 * encrypted. This may be {@code null} if the 1419 * user should be interactively prompted for the 1420 * passphrase if a file happens to be encrypted. 1421 */ 1422 private void deleteFromDNFile(final AtomicReference<ResultCode> returnCode, 1423 final Charset charset, 1424 final char[] encryptionPassphrase) 1425 { 1426 final List<char[]> potentialPassphrases = 1427 new ArrayList<>(dnFile.getValues().size()); 1428 if (encryptionPassphrase != null) 1429 { 1430 potentialPassphrases.add(encryptionPassphrase); 1431 } 1432 1433 for (final File f : dnFile.getValues()) 1434 { 1435 if (verbose.isPresent()) 1436 { 1437 commentToOut(INFO_LDAPDELETE_READING_DNS_FROM_FILE.get( 1438 f.getAbsolutePath())); 1439 out(); 1440 } 1441 1442 try (FileInputStream fis = new FileInputStream(f)) 1443 { 1444 if ((! deleteDNsFromInputStream(returnCode, fis, charset, 1445 potentialPassphrases)) && 1446 (! continueOnError.isPresent())) 1447 { 1448 return; 1449 } 1450 } 1451 catch (final Exception e) 1452 { 1453 commentToErr(ERR_LDAPDELETE_ERROR_OPENING_DN_FILE.get( 1454 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 1455 if (! continueOnError.isPresent()) 1456 { 1457 return; 1458 } 1459 } 1460 } 1461 } 1462 1463 1464 1465 /** 1466 * Deletes entries whose DNs are read from the provided input stream. 1467 * 1468 * @param returnCode A reference that should be updated with the 1469 * result code from the first failure that is 1470 * encountered. It must not be {@code null}, 1471 * but may be unset. 1472 * @param inputStream The input stream from which the data is to be 1473 * read. 1474 * @param charset The character set to use when reading the 1475 * data from the input stream. It must not be 1476 * {@code null}. 1477 * @param potentialPassphrases A list of the potential passphrases that may 1478 * be used to decrypt data read from the 1479 * provided input stream. It must not be 1480 * {@code null}, and must be updatable, but may 1481 * be empty. 1482 * 1483 * @return {@code true} if all processing completed successfully, or 1484 * {@code false} if not. 1485 * 1486 * @throws IOException If an error occurs while trying to read data from the 1487 * input stream or create the buffered reader. 1488 * 1489 * @throws GeneralSecurityException If a problem is encountered while 1490 * attempting to interact with encrypted 1491 * data read from the input stream. 1492 */ 1493 private boolean deleteDNsFromInputStream( 1494 final AtomicReference<ResultCode> returnCode, 1495 final InputStream inputStream, final Charset charset, 1496 final List<char[]> potentialPassphrases) 1497 throws IOException, GeneralSecurityException 1498 { 1499 boolean successful = true; 1500 long lineNumber = 0; 1501 1502 final BufferedReader reader = 1503 getBufferedReader(inputStream, charset, potentialPassphrases); 1504 while (true) 1505 { 1506 final String line = reader.readLine(); 1507 lineNumber++; 1508 if (line == null) 1509 { 1510 return successful; 1511 } 1512 1513 if (line.isEmpty() || line.startsWith("#")) 1514 { 1515 // The line is empty or contains a comment. Ignore it. 1516 } 1517 else 1518 { 1519 // This is the DN of the entry to delete. 1520 if (! deleteDNFromInputStream(returnCode, line)) 1521 { 1522 if (continueOnError.isPresent()) 1523 { 1524 successful = false; 1525 } 1526 else 1527 { 1528 return false; 1529 } 1530 } 1531 } 1532 } 1533 } 1534 1535 1536 1537 /** 1538 * Extracts the DN of an entry to delete from the provided buffer and tries 1539 * to delete it. The buffer may contain one of three things: 1540 * <UL> 1541 * <LI>The bare string representation of a DN.</LI> 1542 * <LI>The string "dn:" followed by an optional space and the bare string 1543 * representation of a DN.</LI> 1544 * <LI>The string "dn::" followed by an optional space and the 1545 * base64-encoded representation of a DN.</LI> 1546 * </UL> 1547 * 1548 * @param returnCode A reference that should be updated with the result code 1549 * from the first failure that is encountered. It must 1550 * not be {@code null}, but may be unset. 1551 * @param rawString The string representation of the DN to delete. 1552 * 1553 * @return {@code true} if the buffer was empty or if it contained the DN of 1554 * an entry that was successfully deleted, or {@code false} if an 1555 * error occurred while extracting the DN or attempting to delete the 1556 * target entry. 1557 */ 1558 private boolean deleteDNFromInputStream( 1559 final AtomicReference<ResultCode> returnCode, 1560 final String rawString) 1561 { 1562 final String lowerString = StaticUtils.toLowerCase(rawString); 1563 if (lowerString.startsWith("dn::")) 1564 { 1565 final String base64EncodedDN = rawString.substring(4).trim(); 1566 if (base64EncodedDN.isEmpty()) 1567 { 1568 returnCode.compareAndSet(null, ResultCode.PARAM_ERROR); 1569 commentToErr(ERR_LDAPDELETE_BASE64_DN_EMPTY.get(rawString)); 1570 return false; 1571 } 1572 1573 final String base64DecodedDN; 1574 try 1575 { 1576 base64DecodedDN = Base64.decodeToString(base64EncodedDN); 1577 } 1578 catch (final Exception e) 1579 { 1580 Debug.debugException(e); 1581 returnCode.compareAndSet(null, ResultCode.PARAM_ERROR); 1582 commentToErr(ERR_LDAPDELETE_BASE64_DN_NOT_BASE64.get(rawString)); 1583 return false; 1584 } 1585 1586 return deleteEntry(base64DecodedDN, returnCode); 1587 } 1588 else if (lowerString.startsWith("dn:")) 1589 { 1590 final String dn = rawString.substring(3).trim(); 1591 if (dn.isEmpty()) 1592 { 1593 returnCode.compareAndSet(null, ResultCode.PARAM_ERROR); 1594 commentToErr(ERR_LDAPDELETE_DN_EMPTY.get(rawString)); 1595 return false; 1596 } 1597 1598 return deleteEntry(dn, returnCode); 1599 } 1600 else 1601 { 1602 return deleteEntry(rawString, returnCode); 1603 } 1604 } 1605 1606 1607 1608 /** 1609 * Creates a buffered reader that can read data from the provided input stream 1610 * using the specified character set. The data to be read may optionally be 1611 * passphrase-encrypted and/or gzip-compressed. 1612 * 1613 * @param inputStream The input stream from which the data is to be 1614 * read. 1615 * @param charset The character set to use when reading the 1616 * data from the input stream. It must not be 1617 * {@code null}. 1618 * @param potentialPassphrases A list of the potential passphrases that may 1619 * be used to decrypt data read from the 1620 * provided input stream. It must not be 1621 * {@code null}, and must be updatable, but may 1622 * be empty. 1623 * 1624 * @return The buffered reader that can be used to read data from the 1625 * provided input stream. 1626 * 1627 * @throws IOException If an error occurs while trying to read data from the 1628 * input stream or create the buffered reader. 1629 * 1630 * @throws GeneralSecurityException If a problem is encountered while 1631 * attempting to interact with encrypted 1632 * data read from the input stream. 1633 */ 1634 private BufferedReader getBufferedReader(final InputStream inputStream, 1635 final Charset charset, 1636 final List<char[]> potentialPassphrases) 1637 throws IOException, GeneralSecurityException 1638 { 1639 // Check to see if the input stream is encrypted. If so, then get access to 1640 // a decrypted representation of its contents. 1641 final ObjectPair<InputStream,char[]> decryptedInputStreamData = 1642 ToolUtils.getPossiblyPassphraseEncryptedInputStream(inputStream, 1643 potentialPassphrases, (! encryptionPassphraseFile.isPresent()), 1644 INFO_LDAPDELETE_ENCRYPTION_PASSPHRASE_PROMPT.get(), 1645 ERR_LDAPDELETE_ENCRYPTION_PASSPHRASE_ERROR.get(), getOut(), 1646 getErr()); 1647 final InputStream decryptedInputStream = 1648 decryptedInputStreamData.getFirst(); 1649 final char[] passphrase = decryptedInputStreamData.getSecond(); 1650 if (passphrase != null) 1651 { 1652 boolean isExistingPassphrase = false; 1653 for (final char[] existingPassphrase : potentialPassphrases) 1654 { 1655 if (Arrays.equals(passphrase, existingPassphrase)) 1656 { 1657 isExistingPassphrase = true; 1658 break; 1659 } 1660 } 1661 1662 if (! isExistingPassphrase) 1663 { 1664 potentialPassphrases.add(passphrase); 1665 } 1666 } 1667 1668 1669 // Check to see if the input stream is compressed. 1670 final InputStream decompressedInputStream = 1671 ToolUtils.getPossiblyGZIPCompressedInputStream(decryptedInputStream); 1672 1673 1674 // Get an input stream reader that uses the specified character set, and 1675 // then wrap that with a buffered reader. 1676 final InputStreamReader inputStreamReader = 1677 new InputStreamReader(decompressedInputStream, charset); 1678 return new BufferedReader(inputStreamReader); 1679 } 1680 1681 1682 1683 /** 1684 * Deletes entries that match filters specified in the 1685 * deleteEntriesMatchingFilter argument. 1686 * 1687 * @param returnCode A reference that should be updated with the result code 1688 * from the first failure that is encountered. It must 1689 * not be {@code null}, but may be unset. 1690 */ 1691 private void deleteFromFilters(final AtomicReference<ResultCode> returnCode) 1692 { 1693 for (final Filter f : deleteEntriesMatchingFilter.getValues()) 1694 { 1695 if ((! searchAndDelete(f.toString(), returnCode)) && 1696 (! continueOnError.isPresent())) 1697 { 1698 return; 1699 } 1700 } 1701 } 1702 1703 1704 1705 /** 1706 * Deletes entries that match filters specified in the 1707 * deleteEntriesMatchingFilterFromFile argument. 1708 * 1709 * @param returnCode A reference that should be updated with the 1710 * result code from the first failure that is 1711 * encountered. It must not be {@code null}, 1712 * but may be unset. 1713 * @param charset The character set to use when reading the 1714 * data from the file. It must not be 1715 * {@code null}. 1716 * @param encryptionPassphrase The passphrase to use to decrypt the data 1717 * read from the file if it happens to be 1718 * encrypted. This may be {@code null} if the 1719 * user should be interactively prompted for the 1720 * passphrase if a file happens to be encrypted. 1721 */ 1722 private void deleteFromFilterFile( 1723 final AtomicReference<ResultCode> returnCode, 1724 final Charset charset, final char[] encryptionPassphrase) 1725 { 1726 final List<char[]> potentialPassphrases = 1727 new ArrayList<>(dnFile.getValues().size()); 1728 if (encryptionPassphrase != null) 1729 { 1730 potentialPassphrases.add(encryptionPassphrase); 1731 } 1732 1733 for (final File f : deleteEntriesMatchingFiltersFromFile.getValues()) 1734 { 1735 if (verbose.isPresent()) 1736 { 1737 commentToOut(INFO_LDAPDELETE_READING_FILTERS_FROM_FILE.get( 1738 f.getAbsolutePath())); 1739 out(); 1740 } 1741 1742 try (FileInputStream fis = new FileInputStream(f); 1743 BufferedReader reader = 1744 getBufferedReader(fis, charset, potentialPassphrases)) 1745 { 1746 while (true) 1747 { 1748 final String line = reader.readLine(); 1749 if (line == null) 1750 { 1751 break; 1752 } 1753 1754 if (line.isEmpty() || line.startsWith("#")) 1755 { 1756 continue; 1757 } 1758 1759 if ((! searchAndDelete(line, returnCode)) && 1760 (! continueOnError.isPresent())) 1761 { 1762 return; 1763 } 1764 } 1765 } 1766 catch (final IOException | GeneralSecurityException e) 1767 { 1768 commentToErr(ERR_LDAPDELETE_ERROR_READING_FILTER_FILE.get( 1769 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 1770 if (! continueOnError.isPresent()) 1771 { 1772 return; 1773 } 1774 } 1775 } 1776 } 1777 1778 1779 1780 1781 /** 1782 * Issues a search with the provided filter and attempts to delete all 1783 * matching entries. 1784 * 1785 * @param filterString The string representation of the filter to use when 1786 * processing the search. It must not be {@code null}. 1787 * @param returnCode A reference that should be updated with the result 1788 * code from the first failure that is encountered. It 1789 * must not be {@code null}, but may be unset. 1790 * 1791 * @return {@code true} if the search and all deletes were processed 1792 * successfully, or {@code false} if any problems were encountered. 1793 */ 1794 private boolean searchAndDelete(final String filterString, 1795 final AtomicReference<ResultCode> returnCode) 1796 { 1797 boolean successful = true; 1798 final AtomicLong entriesDeleted = new AtomicLong(0L); 1799 for (final DN baseDN : searchBaseDN.getValues()) 1800 { 1801 if (searchPageSize.isPresent()) 1802 { 1803 successful &= doPagedSearchAndDelete(baseDN.toString(), filterString, 1804 returnCode, entriesDeleted); 1805 } 1806 else 1807 { 1808 successful &= doNonPagedSearchAndDelete(baseDN.toString(), filterString, 1809 returnCode, entriesDeleted); 1810 } 1811 } 1812 1813 if (successful && (entriesDeleted.get() == 0)) 1814 { 1815 commentToErr(ERR_LDAPDELETE_SEARCH_RETURNED_NO_ENTRIES.get(filterString)); 1816 returnCode.compareAndSet(null, ResultCode.NO_RESULTS_RETURNED); 1817 successful = false; 1818 } 1819 1820 return successful; 1821 } 1822 1823 1824 1825 /** 1826 * Issues the provided search using the simple paged results control and 1827 * attempts to delete all of the matching entries. 1828 * 1829 * @param baseDN The base DN for the search request. It must not 1830 * be {@code null}. 1831 * @param filterString The string representation of the filter ot use for 1832 * the search request. It must not be {@code nulL}. 1833 * @param returnCode A reference that should be updated with the result 1834 * code from the first failure that is encountered. 1835 * It must not be {@code null}, but may be unset. 1836 * @param entriesDeleted A counter that will be updated for each entry that 1837 * is successfully deleted. It must not be 1838 * {@code null}. 1839 * 1840 * @return {@code true} if all entries matching the search criteria were 1841 * successfully deleted (even if there were no matching entries), or 1842 * {@code false} if an error occurred while attempting to process a 1843 * search or delete operation. 1844 */ 1845 private boolean doPagedSearchAndDelete(final String baseDN, 1846 final String filterString, 1847 final AtomicReference<ResultCode> returnCode, 1848 final AtomicLong entriesDeleted) 1849 { 1850 ASN1OctetString cookie = null; 1851 final TreeSet<DN> matchingEntryDNs = new TreeSet<>(); 1852 final LDAPDeleteSearchListener searchListener = 1853 new LDAPDeleteSearchListener(this, matchingEntryDNs, baseDN, 1854 filterString, returnCode); 1855 while (true) 1856 { 1857 try 1858 { 1859 final ArrayList<Control> requestControls = new ArrayList<>(10); 1860 requestControls.addAll(searchControls); 1861 requestControls.add(new SimplePagedResultsControl( 1862 searchPageSize.getValue(), cookie, true)); 1863 1864 final SearchRequest searchRequest = new SearchRequest(searchListener, 1865 baseDN, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false, 1866 filterString, SearchRequest.NO_ATTRIBUTES); 1867 searchRequest.setControls(requestControls); 1868 1869 if (verbose.isPresent()) 1870 { 1871 commentToOut(INFO_LDAPDELETE_ISSUING_SEARCH_REQUEST.get( 1872 String.valueOf(searchRequest))); 1873 } 1874 1875 final SearchResult searchResult = connectionPool.search(searchRequest); 1876 1877 if (verbose.isPresent()) 1878 { 1879 commentToOut(INFO_LDAPDELETE_RECEIVED_SEARCH_RESULT.get( 1880 String.valueOf(searchResult))); 1881 } 1882 1883 final SimplePagedResultsControl responseControl = 1884 SimplePagedResultsControl.get(searchResult); 1885 if (responseControl == null) 1886 { 1887 throw new LDAPException(ResultCode.CONTROL_NOT_FOUND, 1888 ERR_LDAPDELETE_MISSING_PAGED_RESULTS_RESPONSE.get(searchResult)); 1889 } 1890 else if (responseControl.moreResultsToReturn()) 1891 { 1892 cookie = responseControl.getCookie(); 1893 } 1894 else 1895 { 1896 break; 1897 } 1898 } 1899 catch (final LDAPException e) 1900 { 1901 Debug.debugException(e); 1902 returnCode.compareAndSet(null, e.getResultCode()); 1903 commentToErr(ERR_LDAPDELETE_SEARCH_ERROR.get(baseDN, filterString, 1904 String.valueOf(e.getResultCode()), e.getMessage())); 1905 } 1906 } 1907 1908 boolean allSuccessful = true; 1909 final Iterator<DN> iterator = matchingEntryDNs.descendingIterator(); 1910 while (iterator.hasNext()) 1911 { 1912 if (deleteEntry(iterator.next().toString(), returnCode)) 1913 { 1914 entriesDeleted.incrementAndGet(); 1915 } 1916 else 1917 { 1918 allSuccessful = false; 1919 if (! continueOnError.isPresent()) 1920 { 1921 break; 1922 } 1923 } 1924 } 1925 1926 return allSuccessful; 1927 } 1928 1929 1930 1931 /** 1932 * Issues the provided search (without using the simple paged results control) 1933 * and attempts to delete all of the matching entries. 1934 * 1935 * @param baseDN The base DN for the search request. It must not 1936 * be {@code null}. 1937 * @param filterString The string representation of the filter ot use for 1938 * the search request. It must not be {@code nulL}. 1939 * @param returnCode A reference that should be updated with the result 1940 * code from the first failure that is encountered. 1941 * It must not be {@code null}, but may be unset. 1942 * @param entriesDeleted A counter that will be updated for each entry that 1943 * is successfully deleted. It must not be 1944 * {@code null}. 1945 * 1946 * @return {@code true} if all entries matching the search criteria were 1947 * successfully deleted (even if there were no matching entries), or 1948 * {@code false} if an error occurred while attempting to process a 1949 * search or delete operation. 1950 */ 1951 private boolean doNonPagedSearchAndDelete(final String baseDN, 1952 final String filterString, 1953 final AtomicReference<ResultCode> returnCode, 1954 final AtomicLong entriesDeleted) 1955 { 1956 final TreeSet<DN> matchingEntryDNs = new TreeSet<>(); 1957 final LDAPDeleteSearchListener searchListener = 1958 new LDAPDeleteSearchListener(this, matchingEntryDNs, baseDN, 1959 filterString, returnCode); 1960 try 1961 { 1962 final SearchRequest searchRequest = new SearchRequest(searchListener, 1963 baseDN, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false, 1964 filterString, SearchRequest.NO_ATTRIBUTES); 1965 searchRequest.setControls(searchControls); 1966 1967 if (verbose.isPresent()) 1968 { 1969 commentToOut(INFO_LDAPDELETE_ISSUING_SEARCH_REQUEST.get( 1970 String.valueOf(searchRequest))); 1971 } 1972 1973 final SearchResult searchResult = connectionPool.search(searchRequest); 1974 1975 if (verbose.isPresent()) 1976 { 1977 commentToOut(INFO_LDAPDELETE_RECEIVED_SEARCH_RESULT.get( 1978 String.valueOf(searchResult))); 1979 } 1980 } 1981 catch (final LDAPException e) 1982 { 1983 Debug.debugException(e); 1984 returnCode.compareAndSet(null, e.getResultCode()); 1985 commentToErr(ERR_LDAPDELETE_SEARCH_ERROR.get(baseDN, filterString, 1986 String.valueOf(e.getResultCode()), e.getMessage())); 1987 } 1988 1989 1990 boolean allSuccessful = true; 1991 final Iterator<DN> iterator = matchingEntryDNs.descendingIterator(); 1992 while (iterator.hasNext()) 1993 { 1994 if (deleteEntry(iterator.next().toString(), returnCode)) 1995 { 1996 entriesDeleted.incrementAndGet(); 1997 } 1998 else 1999 { 2000 allSuccessful = false; 2001 if (! continueOnError.isPresent()) 2002 { 2003 break; 2004 } 2005 } 2006 } 2007 2008 return allSuccessful; 2009 } 2010 2011 2012 2013 /** 2014 * Deletes entries whose DNs are specified as trailing arguments. 2015 * 2016 * @param returnCode A reference that should be updated with the result code 2017 * from the first failure that is encountered. It must 2018 * not be {@code null}, but may be unset. 2019 */ 2020 private void deleteFromTrailingArguments( 2021 final AtomicReference<ResultCode> returnCode) 2022 { 2023 for (final String dn : parser.getTrailingArguments()) 2024 { 2025 if ((! deleteEntry(dn, returnCode)) && (! continueOnError.isPresent())) 2026 { 2027 return; 2028 } 2029 } 2030 } 2031 2032 2033 2034 /** 2035 * Deletes entries whose DNs are read from standard input. 2036 * 2037 * @param returnCode A reference that should be updated with the 2038 * result code from the first failure that is 2039 * encountered. It must not be {@code null}, 2040 * but may be unset. 2041 * @param charset The character set to use when reading the 2042 * data from standard input. It must not be 2043 * {@code null}. 2044 * @param encryptionPassphrase The passphrase to use to decrypt the data 2045 * read from standard input if it happens to be 2046 * encrypted. This may be {@code null} if the 2047 * user should be interactively prompted for the 2048 * passphrase if the data happens to be 2049 * encrypted. 2050 */ 2051 private void deleteFromStandardInput( 2052 final AtomicReference<ResultCode> returnCode, 2053 final Charset charset, final char[] encryptionPassphrase) 2054 { 2055 final List<char[]> potentialPassphrases = new ArrayList<>(1); 2056 if (encryptionPassphrase != null) 2057 { 2058 potentialPassphrases.add(encryptionPassphrase); 2059 } 2060 2061 commentToOut(INFO_LDAPDELETE_READING_FROM_STDIN.get()); 2062 out(); 2063 2064 try 2065 { 2066 deleteDNsFromInputStream(returnCode, in, charset, potentialPassphrases); 2067 } 2068 catch (final Exception e) 2069 { 2070 Debug.debugException(e); 2071 returnCode.compareAndSet(null, ResultCode.LOCAL_ERROR); 2072 commentToErr(ERR_LDAPDELETE_ERROR_READING_STDIN.get( 2073 StaticUtils.getExceptionMessage(e))); 2074 } 2075 } 2076 2077 2078 2079 /** 2080 * Attempts to delete the specified entry. 2081 * 2082 * @param dn The DN of the entry to delete. It must not be 2083 * {@code null}. 2084 * @param returnCode A reference to the result code to be returned. It must 2085 * not be {@code null}, but may be unset. If it is unset 2086 * and the delete attempt fails, then this should be set 2087 * to the result code for the failed delete operation. 2088 * 2089 * @return {@code true} if the entry was successfully deleted, or 2090 * {@code false} if not. 2091 */ 2092 private boolean deleteEntry(final String dn, 2093 final AtomicReference<ResultCode> returnCode) 2094 { 2095 // Display a message indicating that we're going to delete the entry. 2096 if (subtreeDeleter == null) 2097 { 2098 commentToOut(INFO_LDAPDELETE_DELETING_ENTRY.get(dn)); 2099 } 2100 else 2101 { 2102 commentToOut(INFO_LDAPDELETE_CLIENT_SIDE_SUBTREE_DELETING.get(dn)); 2103 } 2104 2105 2106 // If the --dryRun argument was provided, then don't actually delete the 2107 // entry. Just pretend that it succeeded. 2108 if (dryRun.isPresent()) 2109 { 2110 commentToOut(INFO_LDAPDELETE_NOT_DELETING_BECAUSE_OF_DRY_RUN.get(dn)); 2111 return true; 2112 } 2113 2114 if (subtreeDeleter == null) 2115 { 2116 // If we need to rate limit the delete operations, then do that now. 2117 if (deleteRateLimiter != null) 2118 { 2119 deleteRateLimiter.await(); 2120 } 2121 2122 2123 // Create and process the delete request. 2124 final DeleteRequest deleteRequest = new DeleteRequest(dn); 2125 deleteRequest.setControls(deleteControls); 2126 2127 boolean successlful; 2128 LDAPResult deleteResult; 2129 try 2130 { 2131 if (verbose.isPresent()) 2132 { 2133 commentToOut(INFO_LDAPDELETE_SENDING_DELETE_REQUEST.get( 2134 String.valueOf(deleteRequest))); 2135 } 2136 2137 deleteResult = connectionPool.delete(deleteRequest); 2138 successlful = true; 2139 } 2140 catch (final LDAPException e) 2141 { 2142 Debug.debugException(e); 2143 deleteResult = e.toLDAPResult(); 2144 successlful = false; 2145 } 2146 2147 2148 // Display information about the result. 2149 for (final String resultLine : 2150 ResultUtils.formatResult(deleteResult, true, 0, WRAP_COLUMN)) 2151 { 2152 if (successlful) 2153 { 2154 out(resultLine); 2155 } 2156 else 2157 { 2158 err(resultLine); 2159 } 2160 } 2161 2162 2163 // If the delete attempt failed, then update the return code and/or 2164 // write to the reject writer, if appropriate. 2165 final ResultCode deleteResultCode = deleteResult.getResultCode(); 2166 if ((deleteResultCode != ResultCode.SUCCESS) && 2167 (deleteResultCode != ResultCode.NO_OPERATION)) 2168 { 2169 returnCode.compareAndSet(null, deleteResultCode); 2170 writeToRejects(deleteRequest, deleteResult); 2171 err(); 2172 return false; 2173 } 2174 else 2175 { 2176 out(); 2177 return true; 2178 } 2179 } 2180 else 2181 { 2182 // Use the subtree deleter to attempt a client-side subtree delete. 2183 final SubtreeDeleterResult subtreeDeleterResult; 2184 try 2185 { 2186 subtreeDeleterResult = subtreeDeleter.delete(connectionPool, dn); 2187 } 2188 catch (final LDAPException e) 2189 { 2190 Debug.debugException(e); 2191 commentToErr(e.getMessage()); 2192 writeToRejects(new DeleteRequest(dn), e.toLDAPResult()); 2193 returnCode.compareAndSet(null, e.getResultCode()); 2194 return false; 2195 } 2196 2197 if (subtreeDeleterResult.completelySuccessful()) 2198 { 2199 final long entriesDeleted = subtreeDeleterResult.getEntriesDeleted(); 2200 if (entriesDeleted == 0L) 2201 { 2202 final DeleteRequest deleteRequest = new DeleteRequest(dn); 2203 final LDAPResult result = new LDAPResult(-1, 2204 ResultCode.NO_SUCH_OBJECT, 2205 ERR_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_NO_BASE_ENTRY.get(dn), 2206 null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); 2207 for (final String line : 2208 ResultUtils.formatResult(result, true, 0, WRAP_COLUMN)) 2209 { 2210 err(line); 2211 } 2212 writeToRejects(deleteRequest, result); 2213 returnCode.compareAndSet(null, ResultCode.NO_SUCH_OBJECT); 2214 err(); 2215 return false; 2216 } 2217 else if (entriesDeleted == 1L) 2218 { 2219 commentToOut( 2220 INFO_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_ONLY_BASE.get(dn)); 2221 out(); 2222 return true; 2223 } 2224 else 2225 { 2226 final long numSubordinates = entriesDeleted - 1L; 2227 commentToOut(INFO_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_WITH_SUBS.get(dn, 2228 numSubordinates)); 2229 out(); 2230 return true; 2231 } 2232 } 2233 else 2234 { 2235 commentToErr(ERR_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_FAILED.get()); 2236 err(); 2237 2238 final SearchResult searchError = subtreeDeleterResult.getSearchError(); 2239 if (searchError != null) 2240 { 2241 returnCode.compareAndSet(null, searchError.getResultCode()); 2242 commentToErr( 2243 ERR_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_SEARCH_ERROR.get(dn)); 2244 for (final String line : 2245 ResultUtils.formatResult(searchError, true, 0, WRAP_COLUMN)) 2246 { 2247 err(line); 2248 } 2249 err(); 2250 } 2251 2252 for (final Map.Entry<DN,LDAPResult> deleteError : 2253 subtreeDeleterResult.getDeleteErrorsDescendingMap().entrySet()) 2254 { 2255 final String failureDN = deleteError.getKey().toString(); 2256 final LDAPResult failureResult = deleteError.getValue(); 2257 returnCode.compareAndSet(null, failureResult.getResultCode()); 2258 commentToErr(ERR_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_DEL_ERROR.get( 2259 failureDN, dn)); 2260 writeToRejects(new DeleteRequest(failureDN), failureResult); 2261 for (final String line : 2262 ResultUtils.formatResult(failureResult, true, 0, WRAP_COLUMN)) 2263 { 2264 err(line); 2265 } 2266 err(); 2267 } 2268 2269 return false; 2270 } 2271 } 2272 } 2273 2274 2275 2276 /** 2277 * Writes information about a failed operation to the reject writer. If an 2278 * error occurs while writing the rejected change, then that error will be 2279 * written to standard error. 2280 * 2281 * @param deleteRequest The delete request that failed. 2282 * @param deleteResult The result for the failed delete. 2283 */ 2284 private void writeToRejects(final DeleteRequest deleteRequest, 2285 final LDAPResult deleteResult) 2286 { 2287 if (! rejectFile.isPresent()) 2288 { 2289 return; 2290 } 2291 2292 LDIFWriter w; 2293 try 2294 { 2295 w = rejectWriter.get(); 2296 if (w == null) 2297 { 2298 w = new LDIFWriter(rejectFile.getValue()); 2299 rejectWriter.set(w); 2300 } 2301 } 2302 catch (final Exception e) 2303 { 2304 Debug.debugException(e); 2305 commentToErr(ERR_LDAPDELETE_WRITE_TO_REJECTS_FAILED.get( 2306 StaticUtils.getExceptionMessage(e))); 2307 return; 2308 } 2309 2310 try 2311 { 2312 boolean firstLine = true; 2313 for (final String commentLine : 2314 ResultUtils.formatResult(deleteResult, false, 0, (WRAP_COLUMN - 2))) 2315 { 2316 w.writeComment(commentLine, firstLine, false); 2317 firstLine = false; 2318 } 2319 w.writeChangeRecord(deleteRequest.toLDIFChangeRecord()); 2320 w.flush(); 2321 } 2322 catch (final Exception e) 2323 { 2324 Debug.debugException(e); 2325 commentToErr(ERR_LDAPDELETE_WRITE_TO_REJECTS_FAILED.get( 2326 StaticUtils.getExceptionMessage(e))); 2327 } 2328 } 2329 2330 2331 2332 /** 2333 * Retrieves the set of controls that should be included in delete requests. 2334 * 2335 * @return The set of controls that should be included in delete requests. 2336 */ 2337 private List<Control> getDeleteControls() 2338 { 2339 final List<Control> controlList = new ArrayList<>(10); 2340 2341 if (deleteControl.isPresent()) 2342 { 2343 controlList.addAll(deleteControl.getValues()); 2344 } 2345 2346 controlList.addAll(routeToBackendSetRequestControls); 2347 2348 if (serverSideSubtreeDelete.isPresent()) 2349 { 2350 controlList.add(new SubtreeDeleteRequestControl(true)); 2351 } 2352 2353 if (softDelete.isPresent()) 2354 { 2355 controlList.add(new SoftDeleteRequestControl(true, true)); 2356 } 2357 2358 if (hardDelete.isPresent() && (! clientSideSubtreeDelete.isPresent())) 2359 { 2360 controlList.add(new HardDeleteRequestControl(true)); 2361 } 2362 2363 if (proxyAs.isPresent()) 2364 { 2365 controlList.add( 2366 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue())); 2367 } 2368 2369 if (proxyV1As.isPresent()) 2370 { 2371 controlList.add(new ProxiedAuthorizationV1RequestControl( 2372 proxyV1As.getValue().toString())); 2373 } 2374 2375 if (manageDsaIT.isPresent() && (! clientSideSubtreeDelete.isPresent())) 2376 { 2377 controlList.add(new ManageDsaITRequestControl(true)); 2378 } 2379 2380 if (assertionFilter.isPresent()) 2381 { 2382 controlList.add( 2383 new AssertionRequestControl(assertionFilter.getValue(), true)); 2384 } 2385 2386 if (preReadAttribute.isPresent()) 2387 { 2388 controlList.add(new PreReadRequestControl(true, 2389 preReadAttribute.getValues().toArray(StaticUtils.NO_STRINGS))); 2390 } 2391 2392 if (noOperation.isPresent()) 2393 { 2394 controlList.add(new NoOpRequestControl()); 2395 } 2396 2397 if (getBackendSetID.isPresent()) 2398 { 2399 controlList.add(new GetBackendSetIDRequestControl(true)); 2400 } 2401 2402 if (getServerID.isPresent()) 2403 { 2404 controlList.add(new GetServerIDRequestControl(true)); 2405 } 2406 2407 if (routeToServer.isPresent()) 2408 { 2409 controlList.add(new RouteToServerRequestControl(true, 2410 routeToServer.getValue(), false, false, false)); 2411 } 2412 2413 if (useAssuredReplication.isPresent()) 2414 { 2415 AssuredReplicationLocalLevel localLevel = null; 2416 if (assuredReplicationLocalLevel.isPresent()) 2417 { 2418 final String level = assuredReplicationLocalLevel.getValue(); 2419 if (level.equalsIgnoreCase("none")) 2420 { 2421 localLevel = AssuredReplicationLocalLevel.NONE; 2422 } 2423 else if (level.equalsIgnoreCase("received-any-server")) 2424 { 2425 localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER; 2426 } 2427 else if (level.equalsIgnoreCase("processed-all-servers")) 2428 { 2429 localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS; 2430 } 2431 } 2432 2433 AssuredReplicationRemoteLevel remoteLevel = null; 2434 if (assuredReplicationRemoteLevel.isPresent()) 2435 { 2436 final String level = assuredReplicationRemoteLevel.getValue(); 2437 if (level.equalsIgnoreCase("none")) 2438 { 2439 remoteLevel = AssuredReplicationRemoteLevel.NONE; 2440 } 2441 else if (level.equalsIgnoreCase("received-any-remote-location")) 2442 { 2443 remoteLevel = 2444 AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION; 2445 } 2446 else if (level.equalsIgnoreCase("received-all-remote-locations")) 2447 { 2448 remoteLevel = 2449 AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS; 2450 } 2451 else if (level.equalsIgnoreCase("processed-all-remote-servers")) 2452 { 2453 remoteLevel = 2454 AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS; 2455 } 2456 } 2457 2458 Long timeoutMillis = null; 2459 if (assuredReplicationTimeout.isPresent()) 2460 { 2461 timeoutMillis = 2462 assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS); 2463 } 2464 2465 final AssuredReplicationRequestControl c = 2466 new AssuredReplicationRequestControl(true, localLevel, localLevel, 2467 remoteLevel, remoteLevel, timeoutMillis, false); 2468 controlList.add(c); 2469 } 2470 2471 if (replicationRepair.isPresent()) 2472 { 2473 controlList.add(new ReplicationRepairRequestControl()); 2474 } 2475 2476 if (suppressReferentialIntegrityUpdates.isPresent()) 2477 { 2478 controlList.add( 2479 new SuppressReferentialIntegrityUpdatesRequestControl(true)); 2480 } 2481 2482 if (operationPurpose.isPresent()) 2483 { 2484 controlList.add(new OperationPurposeRequestControl(true, 2485 "ldapdelete", Version.NUMERIC_VERSION_STRING, 2486 LDAPDelete.class.getName() + ".getDeleteControls", 2487 operationPurpose.getValue())); 2488 } 2489 2490 return Collections.unmodifiableList(controlList); 2491 } 2492 2493 2494 2495 /** 2496 * Retrieves the set of controls that should be included in search requests. 2497 * 2498 * @return The set of controls that should be included in delete requests. 2499 */ 2500 private List<Control> getSearchControls() 2501 { 2502 final List<Control> controlList = new ArrayList<>(10); 2503 2504 controlList.addAll(routeToBackendSetRequestControls); 2505 2506 if (manageDsaIT.isPresent()) 2507 { 2508 controlList.add(new ManageDsaITRequestControl(true)); 2509 } 2510 2511 if (proxyV1As.isPresent()) 2512 { 2513 controlList.add(new ProxiedAuthorizationV1RequestControl( 2514 proxyV1As.getValue().toString())); 2515 } 2516 2517 if (proxyAs.isPresent()) 2518 { 2519 controlList.add( 2520 new ProxiedAuthorizationV2RequestControl(proxyAs.getValue())); 2521 } 2522 2523 if (operationPurpose.isPresent()) 2524 { 2525 controlList.add(new OperationPurposeRequestControl(true, 2526 "ldapdelete", Version.NUMERIC_VERSION_STRING, 2527 LDAPDelete.class.getName() + ".getSearchControls", 2528 operationPurpose.getValue())); 2529 } 2530 2531 if (routeToServer.isPresent()) 2532 { 2533 controlList.add(new RouteToServerRequestControl(true, 2534 routeToServer.getValue(), false, false, false)); 2535 } 2536 2537 return Collections.unmodifiableList(controlList); 2538 } 2539 2540 2541 2542 /** 2543 * {@inheritDoc} 2544 */ 2545 @Override() 2546 public void handleUnsolicitedNotification(final LDAPConnection connection, 2547 final ExtendedResult notification) 2548 { 2549 final ArrayList<String> lines = new ArrayList<>(10); 2550 ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0, 2551 WRAP_COLUMN); 2552 for (final String line : lines) 2553 { 2554 err(line); 2555 } 2556 err(); 2557 } 2558 2559 2560 2561 /** 2562 * Writes a line-wrapped, commented version of the provided message to 2563 * standard output. 2564 * 2565 * @param message The message to be written. 2566 */ 2567 void commentToOut(final String message) 2568 { 2569 for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) 2570 { 2571 out("# ", line); 2572 } 2573 } 2574 2575 2576 2577 /** 2578 * Writes a line-wrapped, commented version of the provided message to 2579 * standard error. 2580 * 2581 * @param message The message to be written. 2582 */ 2583 void commentToErr(final String message) 2584 { 2585 for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) 2586 { 2587 err("# ", line); 2588 } 2589 } 2590 2591 2592 2593 /** 2594 * {@inheritDoc} 2595 */ 2596 @Override() 2597 public LinkedHashMap<String[],String> getExampleUsages() 2598 { 2599 final LinkedHashMap<String[],String> examples = 2600 new LinkedHashMap<>(StaticUtils.computeMapCapacity(4)); 2601 2602 examples.put( 2603 new String[] 2604 { 2605 "--hostname", "ds.example.com", 2606 "--port", "636", 2607 "--useSSL", 2608 "--bindDN", "uid=admin,dc=example,dc=com", 2609 "uid=test.user,ou=People,dc=example,dc=com" 2610 }, 2611 INFO_LDAPDELETE_EXAMPLE_1.get()); 2612 2613 examples.put( 2614 new String[] 2615 { 2616 "--hostname", "ds.example.com", 2617 "--port", "636", 2618 "--useSSL", 2619 "--trustStorePath", "trust-store.jks", 2620 "--bindDN", "uid=admin,dc=example,dc=com", 2621 "--bindPasswordFile", "admin-password.txt", 2622 "--dnFile", "dns-to-delete.txt" 2623 }, 2624 INFO_LDAPDELETE_EXAMPLE_2.get()); 2625 2626 examples.put( 2627 new String[] 2628 { 2629 "--hostname", "ds.example.com", 2630 "--port", "389", 2631 "--useStartTLS", 2632 "--trustStorePath", "trust-store.jks", 2633 "--bindDN", "uid=admin,dc=example,dc=com", 2634 "--bindPasswordFile", "admin-password.txt", 2635 "--deleteEntriesMatchingFilter", "(description=delete)" 2636 }, 2637 INFO_LDAPDELETE_EXAMPLE_3.get()); 2638 2639 examples.put( 2640 new String[] 2641 { 2642 "--hostname", "ds.example.com", 2643 "--port", "389", 2644 "--bindDN", "uid=admin,dc=example,dc=com" 2645 }, 2646 INFO_LDAPDELETE_EXAMPLE_4.get()); 2647 2648 return examples; 2649 } 2650}