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