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