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