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