All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.unboundidds.tools.LDAPDelete Maven / Gradle / Ivy

/*
 * Copyright 2019-2024 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2019-2024 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2019-2024 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.ldap.sdk.unboundidds.tools;



import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.DeleteRequest;
import com.unboundid.ldap.sdk.DereferencePolicy;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.ExtendedResult;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPConnectionPool;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler;
import com.unboundid.ldap.sdk.Version;
import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
import com.unboundid.ldap.sdk.controls.AssertionRequestControl;
import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl;
import com.unboundid.ldap.sdk.controls.PreReadRequestControl;
import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl;
import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            AssuredReplicationLocalLevel;
import com.unboundid.ldap.sdk.unboundidds.controls.
            AssuredReplicationRemoteLevel;
import com.unboundid.ldap.sdk.unboundidds.controls.
            AssuredReplicationRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            GetAuthorizationEntryRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            GetBackendSetIDRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.GetServerIDRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            GetUserResourceLimitsRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.HardDeleteRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.NoOpRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            OperationPurposeRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            ReplicationRepairRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            RouteToBackendSetRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.RouteToServerRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.SoftDeleteRequestControl;
import com.unboundid.ldap.sdk.unboundidds.controls.
            SuppressReferentialIntegrityUpdatesRequestControl;
import com.unboundid.ldap.sdk.unboundidds.extensions.
            StartAdministrativeSessionExtendedRequest;
import com.unboundid.ldap.sdk.unboundidds.extensions.
            StartAdministrativeSessionPostConnectProcessor;
import com.unboundid.ldif.LDIFWriter;
import com.unboundid.util.Base64;
import com.unboundid.util.Debug;
import com.unboundid.util.FixedRateBarrier;
import com.unboundid.util.LDAPCommandLineTool;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.SubtreeDeleter;
import com.unboundid.util.SubtreeDeleterResult;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.args.Argument;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.BooleanArgument;
import com.unboundid.util.args.ControlArgument;
import com.unboundid.util.args.DNArgument;
import com.unboundid.util.args.DurationArgument;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.FilterArgument;
import com.unboundid.util.args.IntegerArgument;
import com.unboundid.util.args.StringArgument;

import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;



/**
 * This class provides a command-line tool that can be used to delete one or
 * more entries from an LDAP directory server.  The DNs of entries to delete
 * can be provided through command-line arguments, read from a file, or read
 * from standard input.  Alternately, the tool can delete entries matching a
 * given search filter.
 * 
*
* NOTE: This class, and other classes within the * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only * supported for use against Ping Identity, UnboundID, and * Nokia/Alcatel-Lucent 8661 server products. These classes provide support * for proprietary functionality or for external specifications that are not * considered stable or mature enough to be guaranteed to work in an * interoperable way with other types of LDAP servers. *
*/ @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) public final class LDAPDelete extends LDAPCommandLineTool implements UnsolicitedNotificationHandler { /** * The column at which output should be wrapped. */ private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; // The set of arguments supported by this program. @Nullable private ArgumentParser parser = null; @Nullable private BooleanArgument authorizationIdentity = null; @Nullable private BooleanArgument clientSideSubtreeDelete = null; @Nullable private BooleanArgument continueOnError = null; @Nullable private BooleanArgument dryRun = null; @Nullable private BooleanArgument followReferrals = null; @Nullable private BooleanArgument getBackendSetID = null; @Nullable private BooleanArgument getServerID = null; @Nullable private BooleanArgument getUserResourceLimits = null; @Nullable private BooleanArgument hardDelete = null; @Nullable private BooleanArgument manageDsaIT = null; @Nullable private BooleanArgument neverRetry = null; @Nullable private BooleanArgument noOperation = null; @Nullable private BooleanArgument replicationRepair = null; @Nullable private BooleanArgument retryFailedOperations = null; @Nullable private BooleanArgument softDelete = null; @Nullable private BooleanArgument serverSideSubtreeDelete = null; @Nullable private BooleanArgument suppressReferentialIntegrityUpdates = null; @Nullable private BooleanArgument useAdministrativeSession = null; @Nullable private BooleanArgument useAssuredReplication = null; @Nullable private BooleanArgument verbose = null; @Nullable private ControlArgument bindControl = null; @Nullable private ControlArgument deleteControl = null; @Nullable private DNArgument entryDN = null; @Nullable private DNArgument proxyV1As = null; @Nullable private DNArgument searchBaseDN = null; @Nullable private DurationArgument assuredReplicationTimeout = null; @Nullable private FileArgument dnFile = null; @Nullable private FileArgument encryptionPassphraseFile = null; @Nullable private FileArgument deleteEntriesMatchingFiltersFromFile = null; @Nullable private FileArgument rejectFile = null; @Nullable private FilterArgument assertionFilter = null; @Nullable private FilterArgument deleteEntriesMatchingFilter = null; @Nullable private IntegerArgument ratePerSecond = null; @Nullable private IntegerArgument searchPageSize = null; @Nullable private StringArgument assuredReplicationLocalLevel = null; @Nullable private StringArgument assuredReplicationRemoteLevel = null; @Nullable private StringArgument characterSet = null; @Nullable private StringArgument getAuthorizationEntryAttribute = null; @Nullable private StringArgument operationPurpose = null; @Nullable private StringArgument preReadAttribute = null; @Nullable private StringArgument proxyAs = null; @Nullable private StringArgument routeToBackendSet = null; @Nullable private StringArgument routeToServer = null; // A reference to the reject writer that has been written, if it has been // created. @NotNull private final AtomicReference rejectWriter = new AtomicReference<>(); // The fixed-rate barrier (if any) used to enforce a rate limit on delete // operations. @Nullable private volatile FixedRateBarrier deleteRateLimiter = null; // The input stream from to use for standard input. @NotNull private final InputStream in; // The connection pool to use to communicate with the directory server. @Nullable private volatile LDAPConnectionPool connectionPool = null; // Controls to include in requests. @NotNull private volatile List deleteControls = Collections.emptyList(); @NotNull private volatile List searchControls = Collections.emptyList(); @NotNull private final List routeToBackendSetRequestControls = new ArrayList<>(10); // The subtree deleter to use to process client-side subtree deletes. @Nullable private volatile SubtreeDeleter subtreeDeleter = null; /** * Runs this tool with the provided command-line arguments. It will use the * JVM-default streams for standard input, output, and error. * * @param args The command-line arguments to provide to this program. */ public static void main(@NotNull final String... args) { final ResultCode resultCode = main(System.in, System.out, System.err, args); if (resultCode != ResultCode.SUCCESS) { System.exit(resultCode.intValue()); } } /** * Runs this tool with the provided streams and command-line arguments. * * @param in The input stream to use for standard input. If this is * {@code null}, then no standard input will be used. * @param out The output stream to use for standard output. If this is * {@code null}, then standard output will be suppressed. * @param err The output stream to use for standard error. If this is * {@code null}, then standard error will be suppressed. * @param args The command-line arguments provided to this program. * * @return The result code obtained when running the tool. Any result code * other than {@link ResultCode#SUCCESS} indicates an error. */ @NotNull() public static ResultCode main(@Nullable final InputStream in, @Nullable final OutputStream out, @Nullable final OutputStream err, @NotNull final String... args) { final LDAPDelete ldapDelete = new LDAPDelete(in, out, err); return ldapDelete.runTool(args); } /** * Creates a new instance of this tool with the provided streams. Standard * input will not be available. * * @param out The output stream to use for standard output. If this is * {@code null}, then standard output will be suppressed. * @param err The output stream to use for standard error. If this is * {@code null}, then standard error will be suppressed. */ public LDAPDelete(@Nullable final OutputStream out, @Nullable final OutputStream err) { this(null, out, err); } /** * Creates a new instance of this tool with the provided streams. * * @param in The input stream to use for standard input. If this is * {@code null}, then no standard input will be used. * @param out The output stream to use for standard output. If this is * {@code null}, then standard output will be suppressed. * @param err The output stream to use for standard error. If this is * {@code null}, then standard error will be suppressed. */ public LDAPDelete(@Nullable final InputStream in, @Nullable final OutputStream out, @Nullable final OutputStream err) { super(out, err); if (in == null) { this.in = new ByteArrayInputStream(StaticUtils.NO_BYTES); } else { this.in = in; } } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolName() { return "ldapdelete"; } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolDescription() { return INFO_LDAPDELETE_TOOL_DESCRIPTION.get(); } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolVersion() { return Version.NUMERIC_VERSION_STRING; } /** * {@inheritDoc} */ @Override() public int getMinTrailingArguments() { return 0; } /** * {@inheritDoc} */ @Override() public int getMaxTrailingArguments() { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ @Override() @NotNull() public String getTrailingArgumentsPlaceholder() { return INFO_LDAPDELETE_TRAILING_ARGS_PLACEHOLDER.get(); } /** * {@inheritDoc} */ @Override() public boolean supportsInteractiveMode() { return true; } /** * {@inheritDoc} */ @Override() public boolean defaultsToInteractiveMode() { return true; } /** * {@inheritDoc} */ @Override() public boolean supportsPropertiesFile() { return true; } /** * {@inheritDoc} */ @Override() public boolean supportsOutputFile() { return true; } /** * {@inheritDoc} */ @Override() protected boolean supportsDebugLogging() { return true; } /** * {@inheritDoc} */ @Override() protected boolean defaultToPromptForBindPassword() { return true; } /** * {@inheritDoc} */ @Override() protected boolean includeAlternateLongIdentifiers() { return true; } /** * {@inheritDoc} */ @Override() protected boolean supportsSSLDebugging() { return true; } /** * {@inheritDoc} */ @Override() protected boolean logToolInvocationByDefault() { return true; } /** * {@inheritDoc} */ @Override() public void addNonLDAPArguments(@NotNull final ArgumentParser parser) throws ArgumentException { this.parser = parser; // // Data Arguments // final String argGroupData = INFO_LDAPDELETE_ARG_GROUP_DATA.get(); entryDN = new DNArgument('b', "entryDN", false, 0, null, INFO_LDAPDELETE_ARG_DESC_DN.get()); entryDN.addLongIdentifier("entry-dn", true); entryDN.addLongIdentifier("dn", true); entryDN.addLongIdentifier("dnToDelete", true); entryDN.addLongIdentifier("dn-to-delete", true); entryDN.addLongIdentifier("entry", true); entryDN.addLongIdentifier("entryToDelete", true); entryDN.addLongIdentifier("entry-to-delete", true); entryDN.setArgumentGroupName(argGroupData); parser.addArgument(entryDN); dnFile = new FileArgument('f', "dnFile", false, 0, null, INFO_LDAPDELETE_ARG_DESC_DN_FILE.get(), true, true, true, false); dnFile.addLongIdentifier("dn-file", true); dnFile.addLongIdentifier("dnFilename", true); dnFile.addLongIdentifier("dn-filename", true); dnFile.addLongIdentifier("deleteEntriesWithDNsFromFile", true); dnFile.addLongIdentifier("delete-entries0-with-dns-from-file", true); dnFile.addLongIdentifier("file", true); dnFile.addLongIdentifier("filename", true); dnFile.setArgumentGroupName(argGroupData); parser.addArgument(dnFile); deleteEntriesMatchingFilter = new FilterArgument(null, "deleteEntriesMatchingFilter", false, 0, null, INFO_LDAPDELETE_ARG_DESC_DELETE_ENTRIES_MATCHING_FILTER.get()); deleteEntriesMatchingFilter.addLongIdentifier( "delete-entries-matching-filter", true); deleteEntriesMatchingFilter.addLongIdentifier("deleteFilter", true); deleteEntriesMatchingFilter.addLongIdentifier("delete-filter", true); deleteEntriesMatchingFilter.addLongIdentifier("deleteSearchFilter", true); deleteEntriesMatchingFilter.addLongIdentifier("delete-search-filter", true); deleteEntriesMatchingFilter.addLongIdentifier("filter", true); deleteEntriesMatchingFilter.setArgumentGroupName(argGroupData); parser.addArgument(deleteEntriesMatchingFilter); deleteEntriesMatchingFiltersFromFile = new FileArgument(null, "deleteEntriesMatchingFiltersFromFile", false, 0, null, INFO_LDAPDELETE_ARG_DESC_DELETE_ENTRIES_MATCHING_FILTER_FILE.get(), true, true, true, false); deleteEntriesMatchingFiltersFromFile.addLongIdentifier( "delete-entries-matching-filters-from-file", true); deleteEntriesMatchingFiltersFromFile.addLongIdentifier( "deleteEntriesMatchingFilterFromFile", true); deleteEntriesMatchingFiltersFromFile.addLongIdentifier( "delete-entries-matching-filter-from-file", true); deleteEntriesMatchingFiltersFromFile.addLongIdentifier("deleteFilterFile", true); deleteEntriesMatchingFiltersFromFile.addLongIdentifier("delete-filter-file", true); deleteEntriesMatchingFiltersFromFile.addLongIdentifier( "deleteSearchFilterFile", true); deleteEntriesMatchingFiltersFromFile.addLongIdentifier( "delete-search-filter-file", true); deleteEntriesMatchingFiltersFromFile.addLongIdentifier("filterFile", true); deleteEntriesMatchingFiltersFromFile.addLongIdentifier("filter-file", true); deleteEntriesMatchingFiltersFromFile.setArgumentGroupName(argGroupData); parser.addArgument(deleteEntriesMatchingFiltersFromFile); searchBaseDN = new DNArgument(null, "searchBaseDN", false, 0, null, INFO_LDAPDELETE_ARG_DESC_SEARCH_BASE_DN.get(), DN.NULL_DN); searchBaseDN.addLongIdentifier("search-base-dn", true); searchBaseDN.addLongIdentifier("baseDN", true); searchBaseDN.addLongIdentifier("base-dn", true); searchBaseDN.setArgumentGroupName(argGroupData); parser.addArgument(searchBaseDN); searchPageSize = new IntegerArgument(null, "searchPageSize", false, 1, null, INFO_LDAPDELETE_ARG_DESC_SEARCH_PAGE_SIZE.get(), 1, Integer.MAX_VALUE); searchPageSize.addLongIdentifier("search-page-size", true); searchPageSize.addLongIdentifier("simplePagedResultsPageSize", true); searchPageSize.addLongIdentifier("simple-paged-results-page-size", true); searchPageSize.addLongIdentifier("pageSize", true); searchPageSize.addLongIdentifier("page-size", true); searchPageSize.setArgumentGroupName(argGroupData); parser.addArgument(searchPageSize); encryptionPassphraseFile = new FileArgument(null, "encryptionPassphraseFile", false, 1, null, INFO_LDAPDELETE_ARG_DESC_ENCRYPTION_PW_FILE.get(), true, true, true, false); encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file", true); encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true); encryptionPassphraseFile.addLongIdentifier("encryption-password-file", true); encryptionPassphraseFile.addLongIdentifier("encryptionPINFile", true); encryptionPassphraseFile.addLongIdentifier("encryption-pin-file", true); encryptionPassphraseFile.setArgumentGroupName(argGroupData); parser.addArgument(encryptionPassphraseFile); characterSet = new StringArgument('i', "characterSet", false, 1, INFO_LDAPDELETE_ARG_PLACEHOLDER_CHARSET.get(), INFO_LDAPDELETE_ARG_DESC_CHARSET.get(), "UTF-8"); characterSet.addLongIdentifier("character-set", true); characterSet.addLongIdentifier("charSet", true); characterSet.addLongIdentifier("char-set", true); characterSet.addLongIdentifier("encoding", true); characterSet.setArgumentGroupName(argGroupData); parser.addArgument(characterSet); rejectFile = new FileArgument('R', "rejectFile", false, 1, null, INFO_LDAPDELETE_ARG_DESC_REJECT_FILE.get(), false, true, true, false); rejectFile.addLongIdentifier("reject-file", true); rejectFile.addLongIdentifier("errorFile", true); rejectFile.addLongIdentifier("error-file", true); rejectFile.addLongIdentifier("failureFile", true); rejectFile.addLongIdentifier("failure-file", true); rejectFile.setArgumentGroupName(argGroupData); parser.addArgument(rejectFile); verbose = new BooleanArgument('v', "verbose", 1, INFO_LDAPDELETE_ARG_DESC_VERBOSE.get()); verbose.setArgumentGroupName(argGroupData); parser.addArgument(verbose); // This argument has no effect. It is provided for compatibility with a // legacy ldapdelete tool, where the argument was also offered but had no // effect. In this tool, it is hidden. final BooleanArgument scriptFriendly = new BooleanArgument(null, "scriptFriendly", 1, INFO_LDAPDELETE_ARG_DESC_SCRIPT_FRIENDLY.get()); scriptFriendly.addLongIdentifier("script-friendly", true); scriptFriendly.setArgumentGroupName(argGroupData); scriptFriendly.setHidden(true); parser.addArgument(scriptFriendly); // // Operation Arguments // final String argGroupOp = INFO_LDAPDELETE_ARG_GROUP_OPERATION.get(); // NOTE: The retryFailedOperations argument is now hidden, as we will retry // operations by default. The neverRetry argument can be used to disable // this. retryFailedOperations = new BooleanArgument(null, "retryFailedOperations", 1, INFO_LDAPDELETE_ARG_DESC_RETRY_FAILED_OPS.get()); retryFailedOperations.addLongIdentifier("retry-failed-operations", true); retryFailedOperations.addLongIdentifier("retryFailedOps", true); retryFailedOperations.addLongIdentifier("retry-failed-ops", true); retryFailedOperations.addLongIdentifier("retry", true); retryFailedOperations.setArgumentGroupName(argGroupOp); retryFailedOperations.setHidden(true); parser.addArgument(retryFailedOperations); neverRetry = new BooleanArgument(null, "neverRetry", 1, INFO_LDAPDELETE_ARG_DESC_NEVER_RETRY.get()); neverRetry.addLongIdentifier("never-retry", true); neverRetry.setArgumentGroupName(argGroupOp); parser.addArgument(neverRetry); dryRun = new BooleanArgument('n', "dryRun", 1, INFO_LDAPDELETE_ARG_DESC_DRY_RUN.get()); dryRun.addLongIdentifier("dry-run", true); dryRun.setArgumentGroupName(argGroupOp); parser.addArgument(dryRun); continueOnError = new BooleanArgument('c', "continueOnError", 1, INFO_LDAPDELETE_ARG_DESC_CONTINUE_ON_ERROR.get()); continueOnError.addLongIdentifier("continue-on-error", true); continueOnError.setArgumentGroupName(argGroupOp); parser.addArgument(continueOnError); followReferrals = new BooleanArgument(null, "followReferrals", 1, INFO_LDAPDELETE_ARG_DESC_FOLLOW_REFERRALS.get()); followReferrals.addLongIdentifier("follow-referrals", true); followReferrals.setArgumentGroupName(argGroupOp); parser.addArgument(followReferrals); useAdministrativeSession = new BooleanArgument(null, "useAdministrativeSession", 1, INFO_LDAPDELETE_ARG_DESC_USE_ADMIN_SESSION.get()); useAdministrativeSession.addLongIdentifier("use-administrative-session", true); useAdministrativeSession.addLongIdentifier("useAdminSession", true); useAdministrativeSession.addLongIdentifier("use-admin-session", true); useAdministrativeSession.setArgumentGroupName(argGroupOp); parser.addArgument(useAdministrativeSession); ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1, INFO_LDAPDELETE_ARG_PLACEHOLDER_RATE_PER_SECOND.get(), INFO_LDAPDELETE_ARG_DESC_RATE_PER_SECOND.get(), 1, Integer.MAX_VALUE); ratePerSecond.addLongIdentifier("rate-per-second", true); ratePerSecond.addLongIdentifier("deletesPerSecond", true); ratePerSecond.addLongIdentifier("deletes-per-second", true); ratePerSecond.addLongIdentifier("operationsPerSecond", true); ratePerSecond.addLongIdentifier("operations-per-second", true); ratePerSecond.addLongIdentifier("opsPerSecond", true); ratePerSecond.addLongIdentifier("ops-per-second", true); ratePerSecond.setArgumentGroupName(argGroupOp); parser.addArgument(ratePerSecond); // This argument has no effect. It is provided for compatibility with a // legacy ldapdelete tool, but this version only supports LDAPv3, so this // argument is hidden. final IntegerArgument ldapVersion = new IntegerArgument('V', "ldapVersion", false, 1, "{version}", INFO_LDAPDELETE_ARG_DESC_LDAP_VERSION.get(), 3, 3, 3); ldapVersion.addLongIdentifier("ldap-version", true); ldapVersion.setArgumentGroupName(argGroupOp); ldapVersion.setHidden(true); parser.addArgument(ldapVersion); // // Control Arguments // final String argGroupControls = INFO_LDAPDELETE_ARG_GROUP_CONTROLS.get(); clientSideSubtreeDelete = new BooleanArgument(null, "clientSideSubtreeDelete", 1, INFO_LDAPDELETE_ARG_DESC_CLIENT_SIDE_SUB_DEL.get()); clientSideSubtreeDelete.addLongIdentifier("client-side-subtree-delete", true); clientSideSubtreeDelete.setArgumentGroupName(argGroupControls); parser.addArgument(clientSideSubtreeDelete); serverSideSubtreeDelete = new BooleanArgument('x', "serverSideSubtreeDelete", 1, INFO_LDAPDELETE_ARG_DESC_SERVER_SIDE_SUB_DEL.get()); serverSideSubtreeDelete.addLongIdentifier("server-side-subtree-delete", true); serverSideSubtreeDelete.addLongIdentifier("deleteSubtree", true); serverSideSubtreeDelete.addLongIdentifier("delete-subtree", true); serverSideSubtreeDelete.addLongIdentifier("useSubtreeDeleteControl", true); serverSideSubtreeDelete.addLongIdentifier("use-subtree-delete-control", true); serverSideSubtreeDelete.setArgumentGroupName(argGroupControls); parser.addArgument(serverSideSubtreeDelete); softDelete = new BooleanArgument('s', "softDelete", 1, INFO_LDAPDELETE_ARG_DESC_SOFT_DELETE.get()); softDelete.addLongIdentifier("soft-delete", true); softDelete.addLongIdentifier("useSoftDelete", true); softDelete.addLongIdentifier("use-soft-delete", true); softDelete.addLongIdentifier("useSoftDeleteControl", true); softDelete.addLongIdentifier("use-soft-delete-control", true); softDelete.setArgumentGroupName(argGroupControls); parser.addArgument(softDelete); hardDelete = new BooleanArgument(null, "hardDelete", 1, INFO_LDAPDELETE_ARG_DESC_HARD_DELETE.get()); hardDelete.addLongIdentifier("hard-delete", true); hardDelete.addLongIdentifier("useHardDelete", true); hardDelete.addLongIdentifier("use-hard-delete", true); hardDelete.addLongIdentifier("useHardDeleteControl", true); hardDelete.addLongIdentifier("use-hard-delete-control", true); hardDelete.setArgumentGroupName(argGroupControls); parser.addArgument(hardDelete); proxyAs = new StringArgument('Y', "proxyAs", false, 1, INFO_LDAPDELETE_ARG_PLACEHOLDER_AUTHZ_ID.get(), INFO_LDAPDELETE_ARG_DESC_PROXY_AS.get()); proxyAs.addLongIdentifier("proxy-as", true); proxyAs.addLongIdentifier("proxyV2As", true); proxyAs.addLongIdentifier("proxy-v2-as", true); proxyAs.addLongIdentifier("proxiedAuth", true); proxyAs.addLongIdentifier("proxied-auth", true); proxyAs.addLongIdentifier("proxiedAuthorization", true); proxyAs.addLongIdentifier("proxied-authorization", true); proxyAs.addLongIdentifier("useProxiedAuth", true); proxyAs.addLongIdentifier("use-proxied-auth", true); proxyAs.addLongIdentifier("useProxiedAuthorization", true); proxyAs.addLongIdentifier("use-proxied-authorization", true); proxyAs.addLongIdentifier("useProxiedAuthControl", true); proxyAs.addLongIdentifier("use-proxied-auth-control", true); proxyAs.addLongIdentifier("useProxiedAuthorizationControl", true); proxyAs.addLongIdentifier("use-proxied-authorization-control", true); proxyAs.setArgumentGroupName(argGroupControls); parser.addArgument(proxyAs); proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null, INFO_LDAPDELETE_ARG_DESC_PROXY_V1_AS.get()); proxyV1As.addLongIdentifier("proxy-v1-as", true); proxyV1As.setArgumentGroupName(argGroupControls); parser.addArgument(proxyV1As); manageDsaIT = new BooleanArgument(null, "useManageDsaIT", 1, INFO_LDAPDELETE_ARG_DESC_MANAGE_DSA_IT.get()); manageDsaIT.addLongIdentifier("use-manage-dsa-it", true); manageDsaIT.addLongIdentifier("manageDsaIT", true); manageDsaIT.addLongIdentifier("manage-dsa-it", true); manageDsaIT.addLongIdentifier("manageDsaITControl", true); manageDsaIT.addLongIdentifier("manage-dsa-it-control", true); manageDsaIT.addLongIdentifier("useManageDsaITControl", true); manageDsaIT.addLongIdentifier("use-manage-dsa-it-control", true); manageDsaIT.setArgumentGroupName(argGroupControls); parser.addArgument(manageDsaIT); assertionFilter = new FilterArgument(null, "assertionFilter", false, 1, null, INFO_LDAPDELETE_ARG_DESC_ASSERTION_FILTER.get()); assertionFilter.addLongIdentifier("assertion-filter", true); assertionFilter.addLongIdentifier("useAssertionFilter", true); assertionFilter.addLongIdentifier("use-assertion-filter", true); assertionFilter.addLongIdentifier("assertionControl", true); assertionFilter.addLongIdentifier("assertion-control", true); assertionFilter.addLongIdentifier("useAssertionControl", true); assertionFilter.addLongIdentifier("use-assertion-control", true); assertionFilter.setArgumentGroupName(argGroupControls); parser.addArgument(assertionFilter); preReadAttribute = new StringArgument(null, "preReadAttribute", false, 0, INFO_LDAPDELETE_ARG_PLACEHOLDER_ATTR.get(), INFO_LDAPDELETE_ARG_DESC_PRE_READ_ATTR.get()); preReadAttribute.addLongIdentifier("pre-read-attribute", true); preReadAttribute.setArgumentGroupName(argGroupControls); parser.addArgument(preReadAttribute); noOperation = new BooleanArgument(null, "noOperation", 1, INFO_LDAPDELETE_ARG_DESC_NO_OP.get()); noOperation.addLongIdentifier("no-operation", true); noOperation.addLongIdentifier("noOp", true); noOperation.addLongIdentifier("no-op", true); noOperation.setArgumentGroupName(argGroupControls); parser.addArgument(noOperation); getBackendSetID = new BooleanArgument(null, "getBackendSetID", 1, INFO_LDAPDELETE_ARG_DESC_GET_BACKEND_SET_ID.get()); getBackendSetID.addLongIdentifier("get-backend-set-id", true); getBackendSetID.addLongIdentifier("useGetBackendSetID", true); getBackendSetID.addLongIdentifier("use-get-backend-set-id", true); getBackendSetID.addLongIdentifier("useGetBackendSetIDControl", true); getBackendSetID.addLongIdentifier("use-get-backend-set-id-control", true); getBackendSetID.setArgumentGroupName(argGroupControls); parser.addArgument(getBackendSetID); routeToBackendSet = new StringArgument(null, "routeToBackendSet", false, 0, INFO_LDAPDELETE_ARG_PLACEHOLDER_ROUTE_TO_BACKEND_SET.get(), INFO_LDAPDELETE_ARG_DESC_ROUTE_TO_BACKEND_SET.get()); routeToBackendSet.addLongIdentifier("route-to-backend-set", true); routeToBackendSet.addLongIdentifier("useRouteToBackendSet", true); routeToBackendSet.addLongIdentifier("use0route-to-backend-set", true); routeToBackendSet.addLongIdentifier("useRouteToBackendSetControl", true); routeToBackendSet.addLongIdentifier("use-route-to-backend-set-control", true); routeToBackendSet.setArgumentGroupName(argGroupControls); parser.addArgument(routeToBackendSet); getServerID = new BooleanArgument(null, "getServerID", 1, INFO_LDAPDELETE_ARG_DESC_GET_SERVER_ID.get()); getServerID.addLongIdentifier("get-server-id", true); getServerID.addLongIdentifier("getBackendServerID", true); getServerID.addLongIdentifier("get-backend-server-id", true); getServerID.addLongIdentifier("useGetServerID", true); getServerID.addLongIdentifier("use-get-server-id", true); getServerID.addLongIdentifier("useGetServerIDControl", true); getServerID.addLongIdentifier("use-get-server-id-control", true); getServerID.setArgumentGroupName(argGroupControls); parser.addArgument(getServerID); routeToServer = new StringArgument(null, "routeToServer", false, 1, INFO_LDAPDELETE_ARG_PLACEHOLDER_ID.get(), INFO_LDAPDELETE_ARG_DESC_ROUTE_TO_SERVER.get()); routeToServer.addLongIdentifier("route-to-server", true); routeToServer.addLongIdentifier("routeToBackendServer", true); routeToServer.addLongIdentifier("route-to-backend-server", true); routeToServer.addLongIdentifier("useRouteToServer", true); routeToServer.addLongIdentifier("use-route-to-server", true); routeToServer.addLongIdentifier("useRouteToBackendServer", true); routeToServer.addLongIdentifier("use-route-to-backend-server", true); routeToServer.addLongIdentifier("useRouteToServerControl", true); routeToServer.addLongIdentifier("use-route-to-server-control", true); routeToServer.addLongIdentifier("useRouteToBackendServerControl", true); routeToServer.addLongIdentifier("use-route-to-backend-server-control", true); routeToServer.setArgumentGroupName(argGroupControls); parser.addArgument(routeToServer); useAssuredReplication = new BooleanArgument(null, "useAssuredReplication", 1, INFO_LDAPDELETE_ARG_DESC_USE_ASSURED_REPLICATION.get()); useAssuredReplication.addLongIdentifier("use-assured-replication", true); useAssuredReplication.addLongIdentifier("assuredReplication", true); useAssuredReplication.addLongIdentifier("assured-replication", true); useAssuredReplication.addLongIdentifier("assuredReplicationControl", true); useAssuredReplication.addLongIdentifier("assured-replication-control", true); useAssuredReplication.addLongIdentifier("useAssuredReplicationControl", true); useAssuredReplication.addLongIdentifier("use-assured-replication-control", true); useAssuredReplication.setArgumentGroupName(argGroupControls); parser.addArgument(useAssuredReplication); assuredReplicationLocalLevel = new StringArgument(null, "assuredReplicationLocalLevel", false, 1, INFO_LDAPDELETE_ARG_PLACEHOLDER_ASSURED_REPLICATION_LOCAL_LEVEL.get(), INFO_LDAPDELETE_ARG_DESC_ASSURED_REPLICATION_LOCAL_LEVEL.get(), StaticUtils.setOf( "none", "received-any-server", "processed-all-servers")); assuredReplicationLocalLevel.addLongIdentifier( "assured-replication-local-level", true); assuredReplicationLocalLevel.setArgumentGroupName(argGroupControls); parser.addArgument(assuredReplicationLocalLevel); assuredReplicationRemoteLevel = new StringArgument(null, "assuredReplicationRemoteLevel", false, 1, INFO_LDAPDELETE_ARG_PLACEHOLDER_ASSURED_REPLICATION_REMOTE_LEVEL.get(), INFO_LDAPDELETE_ARG_DESC_ASSURED_REPLICATION_REMOTE_LEVEL.get(), StaticUtils.setOf( "none", "received-any-remote-location", "received-all-remote-locations", "processed-all-remote-servers")); assuredReplicationRemoteLevel.addLongIdentifier( "assured-replication-remote-level", true); assuredReplicationRemoteLevel.setArgumentGroupName(argGroupControls); parser.addArgument(assuredReplicationRemoteLevel); assuredReplicationTimeout = new DurationArgument(null, "assuredReplicationTimeout", false, null, INFO_LDAPDELETE_ARG_DESC_ASSURED_REPLICATION_TIMEOUT.get()); assuredReplicationTimeout.addLongIdentifier("assured-replication-timeout", true); assuredReplicationTimeout.setArgumentGroupName(argGroupControls); parser.addArgument(assuredReplicationTimeout); replicationRepair = new BooleanArgument(null, "replicationRepair", 1, INFO_LDAPDELETE_ARG_DESC_REPLICATION_REPAIR.get()); replicationRepair.addLongIdentifier("replication-repair", true); replicationRepair.addLongIdentifier("replicationRepairControl", true); replicationRepair.addLongIdentifier("replication-repair-control", true); replicationRepair.addLongIdentifier("useReplicationRepair", true); replicationRepair.addLongIdentifier("use-replication-repair", true); replicationRepair.addLongIdentifier("useReplicationRepairControl", true); replicationRepair.addLongIdentifier("use-replication-repair-control", true); replicationRepair.setArgumentGroupName(argGroupControls); parser.addArgument(replicationRepair); suppressReferentialIntegrityUpdates = new BooleanArgument(null, "suppressReferentialIntegrityUpdates", 1, INFO_LDAPDELETE_ARG_DESC_SUPPRESS_REFINT_UPDATES.get()); suppressReferentialIntegrityUpdates.addLongIdentifier( "suppress-referential-integrity-updates", true); suppressReferentialIntegrityUpdates.addLongIdentifier( "useSuppressReferentialIntegrityUpdates", true); suppressReferentialIntegrityUpdates.addLongIdentifier( "use-suppress-referential-integrity-updates", true); suppressReferentialIntegrityUpdates.addLongIdentifier( "useSuppressReferentialIntegrityUpdatesControl", true); suppressReferentialIntegrityUpdates.addLongIdentifier( "use-suppress-referential-integrity-updates-control", true); suppressReferentialIntegrityUpdates.setArgumentGroupName(argGroupControls); parser.addArgument(suppressReferentialIntegrityUpdates); operationPurpose = new StringArgument(null, "operationPurpose", false, 1, null, INFO_LDAPDELETE_ARG_DESC_OP_PURPOSE.get()); operationPurpose.addLongIdentifier("operation-purpose", true); operationPurpose.addLongIdentifier("operationPurposeControl", true); operationPurpose.addLongIdentifier("operation-purpose-control", true); operationPurpose.addLongIdentifier("useOperationPurpose", true); operationPurpose.addLongIdentifier("use-operation-purpose", true); operationPurpose.addLongIdentifier("useOperationPurposeControl", true); operationPurpose.addLongIdentifier("use-operation-purpose-control", true); operationPurpose.setArgumentGroupName(argGroupControls); parser.addArgument(operationPurpose); authorizationIdentity = new BooleanArgument('E', "authorizationIdentity", 1, INFO_LDAPDELETE_ARG_DESC_AUTHZ_ID.get()); authorizationIdentity.addLongIdentifier("authorization-identity", true); authorizationIdentity.addLongIdentifier("useAuthorizationIdentity", true); authorizationIdentity.addLongIdentifier("use-authorization-identity", true); authorizationIdentity.addLongIdentifier( "useAuthorizationIdentityControl", true); authorizationIdentity.addLongIdentifier( "use-authorization-identity-control", true); authorizationIdentity.setArgumentGroupName(argGroupControls); parser.addArgument(authorizationIdentity); getAuthorizationEntryAttribute = new StringArgument(null, "getAuthorizationEntryAttribute", false, 0, INFO_LDAPDELETE_ARG_PLACEHOLDER_ATTR.get(), INFO_LDAPDELETE_ARG_DESC_GET_AUTHZ_ENTRY_ATTR.get()); getAuthorizationEntryAttribute.addLongIdentifier( "get-authorization-entry-attribute", true); getAuthorizationEntryAttribute.setArgumentGroupName(argGroupControls); parser.addArgument(getAuthorizationEntryAttribute); getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits", 1, INFO_LDAPDELETE_ARG_DESC_GET_USER_RESOURCE_LIMITS.get()); getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true); getUserResourceLimits.addLongIdentifier("getUserResourceLimitsControl", true); getUserResourceLimits.addLongIdentifier("get-user-resource-limits-control", true); getUserResourceLimits.addLongIdentifier("useGetUserResourceLimits", true); getUserResourceLimits.addLongIdentifier("use-get-user-resource-limits", true); getUserResourceLimits.addLongIdentifier( "useGetUserResourceLimitsControl", true); getUserResourceLimits.addLongIdentifier( "use-get-user-resource-limits-control", true); getUserResourceLimits.setArgumentGroupName(argGroupControls); parser.addArgument(getUserResourceLimits); deleteControl = new ControlArgument('J', "deleteControl", false, 0, null, INFO_LDAPDELETE_ARG_DESC_DELETE_CONTROL.get()); deleteControl.addLongIdentifier("delete-control", true); deleteControl.addLongIdentifier("operationControl", true); deleteControl.addLongIdentifier("operation-control", true); deleteControl.addLongIdentifier("control", true); deleteControl.setArgumentGroupName(argGroupControls); parser.addArgument(deleteControl); bindControl = new ControlArgument(null, "bindControl", false, 0, null, INFO_LDAPDELETE_ARG_DESC_BIND_CONTROL.get()); bindControl.addLongIdentifier("bind-control", true); bindControl.setArgumentGroupName(argGroupControls); parser.addArgument(bindControl); // // Argument Constraints // // At most one argument may be provided to select the entries to delete. parser.addExclusiveArgumentSet(entryDN, dnFile, deleteEntriesMatchingFilter, deleteEntriesMatchingFiltersFromFile); // The searchBaseDN argument can only be used if identifying entries with // search filters. parser.addDependentArgumentSet(searchBaseDN, deleteEntriesMatchingFilter, deleteEntriesMatchingFiltersFromFile); // The search page size argument can only be used if identifying entries // with search filters or performing a client-side subtree delete. parser.addDependentArgumentSet(searchPageSize, deleteEntriesMatchingFilter, deleteEntriesMatchingFiltersFromFile, clientSideSubtreeDelete); // Follow referrals and manage DSA IT can't be used together. parser.addExclusiveArgumentSet(followReferrals, manageDsaIT); // Client-side and server-side subtree delete can't be used together. parser.addExclusiveArgumentSet(clientSideSubtreeDelete, serverSideSubtreeDelete); // A lot of options can't be used in conjunction with client-side // subtree delete. parser.addExclusiveArgumentSet(clientSideSubtreeDelete, followReferrals); parser.addExclusiveArgumentSet(clientSideSubtreeDelete, preReadAttribute); parser.addExclusiveArgumentSet(clientSideSubtreeDelete, getBackendSetID); parser.addExclusiveArgumentSet(clientSideSubtreeDelete, getServerID); parser.addExclusiveArgumentSet(clientSideSubtreeDelete, noOperation); parser.addExclusiveArgumentSet(clientSideSubtreeDelete, dryRun); // Soft delete and hard delete can't be used together. parser.addExclusiveArgumentSet(softDelete, hardDelete); } /** * {@inheritDoc} */ @Override() public void doExtendedNonLDAPArgumentValidation() throws ArgumentException { // Trailing arguments can only be used if none of the other arguments used // to identify entries to delete are provided. if (! parser.getTrailingArguments().isEmpty()) { for (final Argument a : Arrays.asList(entryDN, dnFile, deleteEntriesMatchingFilter, deleteEntriesMatchingFiltersFromFile)) { if (a.isPresent()) { throw new ArgumentException( ERR_LDAPDELETE_TRAILING_ARG_CONFLICT.get( a.getIdentifierString())); } } } // If we should use the route to backend set request control, then validate // and pre-create those controls. if (routeToBackendSet.isPresent()) { final List values = routeToBackendSet.getValues(); final Map> idsByRP = new LinkedHashMap<>( StaticUtils.computeMapCapacity(values.size())); for (final String value : values) { final int colonPos = value.indexOf(':'); if (colonPos <= 0) { throw new ArgumentException( ERR_LDAPDELETE_ROUTE_TO_BACKEND_SET_INVALID_FORMAT.get(value, routeToBackendSet.getIdentifierString())); } final String rpID = value.substring(0, colonPos); final String bsID = value.substring(colonPos+1); List idsForRP = idsByRP.get(rpID); if (idsForRP == null) { idsForRP = new ArrayList<>(values.size()); idsByRP.put(rpID, idsForRP); } idsForRP.add(bsID); } for (final Map.Entry> e : idsByRP.entrySet()) { final String rpID = e.getKey(); final List bsIDs = e.getValue(); routeToBackendSetRequestControls.add( RouteToBackendSetRequestControl.createAbsoluteRoutingRequest( true, rpID, bsIDs)); } } } /** * {@inheritDoc} */ @Override() @NotNull() protected List getBindControls() { final ArrayList bindControls = new ArrayList<>(10); if (bindControl.isPresent()) { bindControls.addAll(bindControl.getValues()); } if (authorizationIdentity.isPresent()) { bindControls.add(new AuthorizationIdentityRequestControl(true)); } if (getAuthorizationEntryAttribute.isPresent()) { bindControls.add(new GetAuthorizationEntryRequestControl(true, true, getAuthorizationEntryAttribute.getValues())); } if (getUserResourceLimits.isPresent()) { bindControls.add(new GetUserResourceLimitsRequestControl(true)); } return bindControls; } /** * {@inheritDoc} */ @Override() protected boolean supportsMultipleServers() { // We will support providing information about multiple servers. This tool // will not communicate with multiple servers concurrently, but it can // accept information about multiple servers in the event that a large set // of changes is to be processed and a server goes down in the middle of // those changes. In this case, we can resume processing on a newly-created // connection, possibly to a different server. return true; } /** * {@inheritDoc} */ @Override() @NotNull() public LDAPConnectionOptions getConnectionOptions() { final LDAPConnectionOptions options = new LDAPConnectionOptions(); options.setUseSynchronousMode(true); options.setFollowReferrals(followReferrals.isPresent()); options.setUnsolicitedNotificationHandler(this); options.setResponseTimeoutMillis(0L); return options; } /** * {@inheritDoc} */ @Override() @NotNull() public ResultCode doToolProcessing() { // Get the controls that should be included in search and delete requests. searchControls = getSearchControls(); deleteControls = getDeleteControls(); // If the ratePerSecond argument was provided, then create the fixed-rate // barrier. if (ratePerSecond.isPresent()) { deleteRateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue()); } // Create a subtree deleter instance if appropriate. if (clientSideSubtreeDelete.isPresent()) { subtreeDeleter = new SubtreeDeleter(); subtreeDeleter.setAdditionalSearchControls(searchControls); subtreeDeleter.setAdditionalSearchControls(deleteControls); subtreeDeleter.setDeleteRateLimiter(deleteRateLimiter); if (searchPageSize.isPresent()) { subtreeDeleter.setSimplePagedResultsPageSize(searchPageSize.getValue()); } } // If the encryptionPassphraseFile argument was provided, then read that // passphrase. final char[] encryptionPassphrase; if (encryptionPassphraseFile.isPresent()) { try { encryptionPassphrase = getPasswordFileReader().readPassword( encryptionPassphraseFile.getValue()); } catch (final LDAPException e) { Debug.debugException(e); commentToErr(e.getMessage()); return e.getResultCode(); } catch (final Exception e) { Debug.debugException(e); commentToErr(ERR_LDAPDELETE_CANNOT_READ_ENCRYPTION_PW_FILE.get( encryptionPassphraseFile.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e))); return ResultCode.LOCAL_ERROR; } } else { encryptionPassphrase = null; } // If the character set argument was specified, then make sure it's valid. final Charset charset; try { charset = Charset.forName(characterSet.getValue()); } catch (final Exception e) { Debug.debugException(e); commentToErr(ERR_LDAPDELETE_UNSUPPORTED_CHARSET.get( characterSet.getValue())); return ResultCode.PARAM_ERROR; } // Get the connection pool. final StartAdministrativeSessionPostConnectProcessor p; if (useAdministrativeSession.isPresent()) { p = new StartAdministrativeSessionPostConnectProcessor( new StartAdministrativeSessionExtendedRequest(getToolName(), true)); } else { p = null; } try { connectionPool = getConnectionPool(1, 2, 0, p, null, true, new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, verbose.isPresent())); connectionPool.setRetryFailedOperationsDueToInvalidConnections( (! neverRetry.isPresent())); } catch (final LDAPException e) { Debug.debugException(e); // Unable to create the connection pool, which means that either the // connection could not be established or the attempt to authenticate // the connection failed. If the bind failed, then the report bind // result health check should have already reported the bind failure. // If the failure was something else, then display that failure result. if (e.getResultCode() != ResultCode.INVALID_CREDENTIALS) { for (final String line : ResultUtils.formatResult(e, true, 0, WRAP_COLUMN)) { err(line); } } return e.getResultCode(); } // Figure out the method that we'll identify the entries to delete and // take the appropriate action. final AtomicReference returnCode = new AtomicReference<>(); if (entryDN.isPresent()) { deleteFromEntryDNArgument(returnCode); } else if (dnFile.isPresent()) { deleteFromDNFile(returnCode, charset, encryptionPassphrase); } else if (deleteEntriesMatchingFilter.isPresent()) { deleteFromFilters(returnCode); } else if (deleteEntriesMatchingFiltersFromFile.isPresent()) { deleteFromFilterFile(returnCode, charset, encryptionPassphrase); } else if (! parser.getTrailingArguments().isEmpty()) { deleteFromTrailingArguments(returnCode); } else { deleteFromStandardInput(returnCode, charset, encryptionPassphrase); } // Close the reject writer. final LDIFWriter rw = rejectWriter.get(); if (rw != null) { try { rw.close(); } catch (final Exception e) { Debug.debugException(e); commentToErr(ERR_LDAPDELETE_ERROR_CLOSING_REJECT_WRITER.get( rejectFile.getValue().getAbsolutePath(), StaticUtils.getExceptionMessage(e))); returnCode.compareAndSet(null, ResultCode.LOCAL_ERROR); } } // Close the connection pool. connectionPool.close(); returnCode.compareAndSet(null, ResultCode.SUCCESS); return returnCode.get(); } /** * Deletes entries whose DNs are specified in the entryDN argument. * * @param returnCode A reference that should be updated with the result code * from the first failure that is encountered. It must * not be {@code null}, but may be unset. */ private void deleteFromEntryDNArgument( @NotNull final AtomicReference returnCode) { for (final DN dn : entryDN.getValues()) { if ((! deleteEntry(dn.toString(), returnCode)) && (! continueOnError.isPresent())) { return; } } } /** * Deletes entries whose DNs are contained in the files provided to the dnFile * argument. * * @param returnCode A reference that should be updated with the * result code from the first failure that is * encountered. It must not be {@code null}, * but may be unset. * @param charset The character set to use when reading the * data from the file. It must not be * {@code null}. * @param encryptionPassphrase The passphrase to use to decrypt the data * read from the file if it happens to be * encrypted. This may be {@code null} if the * user should be interactively prompted for the * passphrase if a file happens to be encrypted. */ private void deleteFromDNFile( @NotNull final AtomicReference returnCode, @NotNull final Charset charset, @Nullable final char[] encryptionPassphrase) { final List potentialPassphrases = new ArrayList<>(dnFile.getValues().size()); if (encryptionPassphrase != null) { potentialPassphrases.add(encryptionPassphrase); } for (final File f : dnFile.getValues()) { if (verbose.isPresent()) { commentToOut(INFO_LDAPDELETE_READING_DNS_FROM_FILE.get( f.getAbsolutePath())); out(); } try (FileInputStream fis = new FileInputStream(f)) { if ((! deleteDNsFromInputStream(returnCode, fis, charset, potentialPassphrases)) && (! continueOnError.isPresent())) { return; } } catch (final Exception e) { commentToErr(ERR_LDAPDELETE_ERROR_OPENING_DN_FILE.get( f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); if (! continueOnError.isPresent()) { return; } } } } /** * Deletes entries whose DNs are read from the provided input stream. * * @param returnCode A reference that should be updated with the * result code from the first failure that is * encountered. It must not be {@code null}, * but may be unset. * @param inputStream The input stream from which the data is to be * read. * @param charset The character set to use when reading the * data from the input stream. It must not be * {@code null}. * @param potentialPassphrases A list of the potential passphrases that may * be used to decrypt data read from the * provided input stream. It must not be * {@code null}, and must be updatable, but may * be empty. * * @return {@code true} if all processing completed successfully, or * {@code false} if not. * * @throws IOException If an error occurs while trying to read data from the * input stream or create the buffered reader. * * @throws GeneralSecurityException If a problem is encountered while * attempting to interact with encrypted * data read from the input stream. */ private boolean deleteDNsFromInputStream( @NotNull final AtomicReference returnCode, @NotNull final InputStream inputStream, @NotNull final Charset charset, @NotNull final List potentialPassphrases) throws IOException, GeneralSecurityException { boolean successful = true; long lineNumber = 0; final BufferedReader reader = getBufferedReader(inputStream, charset, potentialPassphrases); while (true) { final String line = reader.readLine(); lineNumber++; if (line == null) { return successful; } if (line.isEmpty() || line.startsWith("#")) { // The line is empty or contains a comment. Ignore it. } else { // This is the DN of the entry to delete. if (! deleteDNFromInputStream(returnCode, line)) { if (continueOnError.isPresent()) { successful = false; } else { return false; } } } } } /** * Extracts the DN of an entry to delete from the provided buffer and tries * to delete it. The buffer may contain one of three things: *
    *
  • The bare string representation of a DN.
  • *
  • The string "dn:" followed by an optional space and the bare string * representation of a DN.
  • *
  • The string "dn::" followed by an optional space and the * base64-encoded representation of a DN.
  • *
* * @param returnCode A reference that should be updated with the result code * from the first failure that is encountered. It must * not be {@code null}, but may be unset. * @param rawString The string representation of the DN to delete. * * @return {@code true} if the buffer was empty or if it contained the DN of * an entry that was successfully deleted, or {@code false} if an * error occurred while extracting the DN or attempting to delete the * target entry. */ private boolean deleteDNFromInputStream( @NotNull final AtomicReference returnCode, @NotNull final String rawString) { final String lowerString = StaticUtils.toLowerCase(rawString); if (lowerString.startsWith("dn::")) { final String base64EncodedDN = rawString.substring(4).trim(); if (base64EncodedDN.isEmpty()) { returnCode.compareAndSet(null, ResultCode.PARAM_ERROR); commentToErr(ERR_LDAPDELETE_BASE64_DN_EMPTY.get(rawString)); return false; } final String base64DecodedDN; try { base64DecodedDN = Base64.decodeToString(base64EncodedDN); } catch (final Exception e) { Debug.debugException(e); returnCode.compareAndSet(null, ResultCode.PARAM_ERROR); commentToErr(ERR_LDAPDELETE_BASE64_DN_NOT_BASE64.get(rawString)); return false; } return deleteEntry(base64DecodedDN, returnCode); } else if (lowerString.startsWith("dn:")) { final String dn = rawString.substring(3).trim(); if (dn.isEmpty()) { returnCode.compareAndSet(null, ResultCode.PARAM_ERROR); commentToErr(ERR_LDAPDELETE_DN_EMPTY.get(rawString)); return false; } return deleteEntry(dn, returnCode); } else { return deleteEntry(rawString, returnCode); } } /** * Creates a buffered reader that can read data from the provided input stream * using the specified character set. The data to be read may optionally be * passphrase-encrypted and/or gzip-compressed. * * @param inputStream The input stream from which the data is to be * read. * @param charset The character set to use when reading the * data from the input stream. It must not be * {@code null}. * @param potentialPassphrases A list of the potential passphrases that may * be used to decrypt data read from the * provided input stream. It must not be * {@code null}, and must be updatable, but may * be empty. * * @return The buffered reader that can be used to read data from the * provided input stream. * * @throws IOException If an error occurs while trying to read data from the * input stream or create the buffered reader. * * @throws GeneralSecurityException If a problem is encountered while * attempting to interact with encrypted * data read from the input stream. */ @NotNull() private BufferedReader getBufferedReader( @NotNull final InputStream inputStream, @NotNull final Charset charset, @NotNull final List potentialPassphrases) throws IOException, GeneralSecurityException { // Check to see if the input stream is encrypted. If so, then get access to // a decrypted representation of its contents. final ObjectPair decryptedInputStreamData = ToolUtils.getPossiblyPassphraseEncryptedInputStream(inputStream, potentialPassphrases, (! encryptionPassphraseFile.isPresent()), INFO_LDAPDELETE_ENCRYPTION_PASSPHRASE_PROMPT.get(), ERR_LDAPDELETE_ENCRYPTION_PASSPHRASE_ERROR.get(), getOut(), getErr()); final InputStream decryptedInputStream = decryptedInputStreamData.getFirst(); final char[] passphrase = decryptedInputStreamData.getSecond(); if (passphrase != null) { boolean isExistingPassphrase = false; for (final char[] existingPassphrase : potentialPassphrases) { if (Arrays.equals(passphrase, existingPassphrase)) { isExistingPassphrase = true; break; } } if (! isExistingPassphrase) { potentialPassphrases.add(passphrase); } } // Check to see if the input stream is compressed. final InputStream decompressedInputStream = ToolUtils.getPossiblyGZIPCompressedInputStream(decryptedInputStream); // Get an input stream reader that uses the specified character set, and // then wrap that with a buffered reader. final InputStreamReader inputStreamReader = new InputStreamReader(decompressedInputStream, charset); return new BufferedReader(inputStreamReader); } /** * Deletes entries that match filters specified in the * deleteEntriesMatchingFilter argument. * * @param returnCode A reference that should be updated with the result code * from the first failure that is encountered. It must * not be {@code null}, but may be unset. */ private void deleteFromFilters( @NotNull final AtomicReference returnCode) { for (final Filter f : deleteEntriesMatchingFilter.getValues()) { if ((! searchAndDelete(f.toString(), returnCode)) && (! continueOnError.isPresent())) { return; } } } /** * Deletes entries that match filters specified in the * deleteEntriesMatchingFilterFromFile argument. * * @param returnCode A reference that should be updated with the * result code from the first failure that is * encountered. It must not be {@code null}, * but may be unset. * @param charset The character set to use when reading the * data from the file. It must not be * {@code null}. * @param encryptionPassphrase The passphrase to use to decrypt the data * read from the file if it happens to be * encrypted. This may be {@code null} if the * user should be interactively prompted for the * passphrase if a file happens to be encrypted. */ private void deleteFromFilterFile( @NotNull final AtomicReference returnCode, @NotNull final Charset charset, @Nullable final char[] encryptionPassphrase) { final List potentialPassphrases = new ArrayList<>(dnFile.getValues().size()); if (encryptionPassphrase != null) { potentialPassphrases.add(encryptionPassphrase); } for (final File f : deleteEntriesMatchingFiltersFromFile.getValues()) { if (verbose.isPresent()) { commentToOut(INFO_LDAPDELETE_READING_FILTERS_FROM_FILE.get( f.getAbsolutePath())); out(); } try (FileInputStream fis = new FileInputStream(f); BufferedReader reader = getBufferedReader(fis, charset, potentialPassphrases)) { while (true) { final String line = reader.readLine(); if (line == null) { break; } if (line.isEmpty() || line.startsWith("#")) { continue; } if ((! searchAndDelete(line, returnCode)) && (! continueOnError.isPresent())) { return; } } } catch (final IOException | GeneralSecurityException e) { commentToErr(ERR_LDAPDELETE_ERROR_READING_FILTER_FILE.get( f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); if (! continueOnError.isPresent()) { return; } } } } /** * Issues a search with the provided filter and attempts to delete all * matching entries. * * @param filterString The string representation of the filter to use when * processing the search. It must not be {@code null}. * @param returnCode A reference that should be updated with the result * code from the first failure that is encountered. It * must not be {@code null}, but may be unset. * * @return {@code true} if the search and all deletes were processed * successfully, or {@code false} if any problems were encountered. */ private boolean searchAndDelete(@NotNull final String filterString, @NotNull final AtomicReference returnCode) { boolean successful = true; final AtomicLong entriesDeleted = new AtomicLong(0L); for (final DN baseDN : searchBaseDN.getValues()) { if (searchPageSize.isPresent()) { successful &= doPagedSearchAndDelete(baseDN.toString(), filterString, returnCode, entriesDeleted); } else { successful &= doNonPagedSearchAndDelete(baseDN.toString(), filterString, returnCode, entriesDeleted); } } if (successful && (entriesDeleted.get() == 0)) { commentToErr(ERR_LDAPDELETE_SEARCH_RETURNED_NO_ENTRIES.get(filterString)); returnCode.compareAndSet(null, ResultCode.NO_RESULTS_RETURNED); successful = false; } return successful; } /** * Issues the provided search using the simple paged results control and * attempts to delete all of the matching entries. * * @param baseDN The base DN for the search request. It must not * be {@code null}. * @param filterString The string representation of the filter ot use for * the search request. It must not be {@code null}. * @param returnCode A reference that should be updated with the result * code from the first failure that is encountered. * It must not be {@code null}, but may be unset. * @param entriesDeleted A counter that will be updated for each entry that * is successfully deleted. It must not be * {@code null}. * * @return {@code true} if all entries matching the search criteria were * successfully deleted (even if there were no matching entries), or * {@code false} if an error occurred while attempting to process a * search or delete operation. */ private boolean doPagedSearchAndDelete(@NotNull final String baseDN, @NotNull final String filterString, @NotNull final AtomicReference returnCode, @NotNull final AtomicLong entriesDeleted) { ASN1OctetString cookie = null; final TreeSet matchingEntryDNs = new TreeSet<>(); final LDAPDeleteSearchListener searchListener = new LDAPDeleteSearchListener(this, matchingEntryDNs, baseDN, filterString, returnCode); while (true) { try { final ArrayList requestControls = new ArrayList<>(10); requestControls.addAll(searchControls); requestControls.add(new SimplePagedResultsControl( searchPageSize.getValue(), cookie, true)); final SearchRequest searchRequest = new SearchRequest(searchListener, baseDN, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false, filterString, SearchRequest.NO_ATTRIBUTES); searchRequest.setControls(requestControls); if (verbose.isPresent()) { commentToOut(INFO_LDAPDELETE_ISSUING_SEARCH_REQUEST.get( String.valueOf(searchRequest))); } final SearchResult searchResult = connectionPool.search(searchRequest); if (verbose.isPresent()) { commentToOut(INFO_LDAPDELETE_RECEIVED_SEARCH_RESULT.get( String.valueOf(searchResult))); } final SimplePagedResultsControl responseControl = SimplePagedResultsControl.get(searchResult); if (responseControl == null) { throw new LDAPException(ResultCode.CONTROL_NOT_FOUND, ERR_LDAPDELETE_MISSING_PAGED_RESULTS_RESPONSE.get(searchResult)); } else if (responseControl.moreResultsToReturn()) { cookie = responseControl.getCookie(); } else { break; } } catch (final LDAPException e) { Debug.debugException(e); returnCode.compareAndSet(null, e.getResultCode()); commentToErr(ERR_LDAPDELETE_SEARCH_ERROR.get(baseDN, filterString, String.valueOf(e.getResultCode()), e.getMessage())); } } boolean allSuccessful = true; final Iterator iterator = matchingEntryDNs.descendingIterator(); while (iterator.hasNext()) { if (deleteEntry(iterator.next().toString(), returnCode)) { entriesDeleted.incrementAndGet(); } else { allSuccessful = false; if (! continueOnError.isPresent()) { break; } } } return allSuccessful; } /** * Issues the provided search (without using the simple paged results control) * and attempts to delete all of the matching entries. * * @param baseDN The base DN for the search request. It must not * be {@code null}. * @param filterString The string representation of the filter ot use for * the search request. It must not be {@code null}. * @param returnCode A reference that should be updated with the result * code from the first failure that is encountered. * It must not be {@code null}, but may be unset. * @param entriesDeleted A counter that will be updated for each entry that * is successfully deleted. It must not be * {@code null}. * * @return {@code true} if all entries matching the search criteria were * successfully deleted (even if there were no matching entries), or * {@code false} if an error occurred while attempting to process a * search or delete operation. */ private boolean doNonPagedSearchAndDelete(@NotNull final String baseDN, @NotNull final String filterString, @NotNull final AtomicReference returnCode, @NotNull final AtomicLong entriesDeleted) { final TreeSet matchingEntryDNs = new TreeSet<>(); final LDAPDeleteSearchListener searchListener = new LDAPDeleteSearchListener(this, matchingEntryDNs, baseDN, filterString, returnCode); try { final SearchRequest searchRequest = new SearchRequest(searchListener, baseDN, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false, filterString, SearchRequest.NO_ATTRIBUTES); searchRequest.setControls(searchControls); if (verbose.isPresent()) { commentToOut(INFO_LDAPDELETE_ISSUING_SEARCH_REQUEST.get( String.valueOf(searchRequest))); } final SearchResult searchResult = connectionPool.search(searchRequest); if (verbose.isPresent()) { commentToOut(INFO_LDAPDELETE_RECEIVED_SEARCH_RESULT.get( String.valueOf(searchResult))); } } catch (final LDAPException e) { Debug.debugException(e); returnCode.compareAndSet(null, e.getResultCode()); commentToErr(ERR_LDAPDELETE_SEARCH_ERROR.get(baseDN, filterString, String.valueOf(e.getResultCode()), e.getMessage())); } boolean allSuccessful = true; final Iterator iterator = matchingEntryDNs.descendingIterator(); while (iterator.hasNext()) { if (deleteEntry(iterator.next().toString(), returnCode)) { entriesDeleted.incrementAndGet(); } else { allSuccessful = false; if (! continueOnError.isPresent()) { break; } } } return allSuccessful; } /** * Deletes entries whose DNs are specified as trailing arguments. * * @param returnCode A reference that should be updated with the result code * from the first failure that is encountered. It must * not be {@code null}, but may be unset. */ private void deleteFromTrailingArguments( @NotNull final AtomicReference returnCode) { for (final String dn : parser.getTrailingArguments()) { if ((! deleteEntry(dn, returnCode)) && (! continueOnError.isPresent())) { return; } } } /** * Deletes entries whose DNs are read from standard input. * * @param returnCode A reference that should be updated with the * result code from the first failure that is * encountered. It must not be {@code null}, * but may be unset. * @param charset The character set to use when reading the * data from standard input. It must not be * {@code null}. * @param encryptionPassphrase The passphrase to use to decrypt the data * read from standard input if it happens to be * encrypted. This may be {@code null} if the * user should be interactively prompted for the * passphrase if the data happens to be * encrypted. */ private void deleteFromStandardInput( @NotNull final AtomicReference returnCode, @NotNull final Charset charset, @Nullable final char[] encryptionPassphrase) { final List potentialPassphrases = new ArrayList<>(1); if (encryptionPassphrase != null) { potentialPassphrases.add(encryptionPassphrase); } commentToOut(INFO_LDAPDELETE_READING_FROM_STDIN.get()); out(); try { deleteDNsFromInputStream(returnCode, in, charset, potentialPassphrases); } catch (final Exception e) { Debug.debugException(e); returnCode.compareAndSet(null, ResultCode.LOCAL_ERROR); commentToErr(ERR_LDAPDELETE_ERROR_READING_STDIN.get( StaticUtils.getExceptionMessage(e))); } } /** * Attempts to delete the specified entry. * * @param dn The DN of the entry to delete. It must not be * {@code null}. * @param returnCode A reference to the result code to be returned. It must * not be {@code null}, but may be unset. If it is unset * and the delete attempt fails, then this should be set * to the result code for the failed delete operation. * * @return {@code true} if the entry was successfully deleted, or * {@code false} if not. */ private boolean deleteEntry(@NotNull final String dn, @NotNull final AtomicReference returnCode) { // Display a message indicating that we're going to delete the entry. if (subtreeDeleter == null) { commentToOut(INFO_LDAPDELETE_DELETING_ENTRY.get(dn)); } else { commentToOut(INFO_LDAPDELETE_CLIENT_SIDE_SUBTREE_DELETING.get(dn)); } // If the --dryRun argument was provided, then don't actually delete the // entry. Just pretend that it succeeded. if (dryRun.isPresent()) { commentToOut(INFO_LDAPDELETE_NOT_DELETING_BECAUSE_OF_DRY_RUN.get(dn)); return true; } if (subtreeDeleter == null) { // If we need to rate limit the delete operations, then do that now. if (deleteRateLimiter != null) { deleteRateLimiter.await(); } // Create and process the delete request. final DeleteRequest deleteRequest = new DeleteRequest(dn); deleteRequest.setControls(deleteControls); boolean successlful; LDAPResult deleteResult; try { if (verbose.isPresent()) { commentToOut(INFO_LDAPDELETE_SENDING_DELETE_REQUEST.get( String.valueOf(deleteRequest))); } deleteResult = connectionPool.delete(deleteRequest); successlful = true; } catch (final LDAPException e) { Debug.debugException(e); deleteResult = e.toLDAPResult(); successlful = false; } // Display information about the result. for (final String resultLine : ResultUtils.formatResult(deleteResult, true, 0, WRAP_COLUMN)) { if (successlful) { out(resultLine); } else { err(resultLine); } } // If the delete attempt failed, then update the return code and/or // write to the reject writer, if appropriate. final ResultCode deleteResultCode = deleteResult.getResultCode(); if ((deleteResultCode != ResultCode.SUCCESS) && (deleteResultCode != ResultCode.NO_OPERATION)) { returnCode.compareAndSet(null, deleteResultCode); writeToRejects(deleteRequest, deleteResult); err(); return false; } else { out(); return true; } } else { // Use the subtree deleter to attempt a client-side subtree delete. final SubtreeDeleterResult subtreeDeleterResult; try { subtreeDeleterResult = subtreeDeleter.delete(connectionPool, dn); } catch (final LDAPException e) { Debug.debugException(e); commentToErr(e.getMessage()); writeToRejects(new DeleteRequest(dn), e.toLDAPResult()); returnCode.compareAndSet(null, e.getResultCode()); return false; } if (subtreeDeleterResult.completelySuccessful()) { final long entriesDeleted = subtreeDeleterResult.getEntriesDeleted(); if (entriesDeleted == 0L) { final DeleteRequest deleteRequest = new DeleteRequest(dn); final LDAPResult result = new LDAPResult(-1, ResultCode.NO_SUCH_OBJECT, ERR_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_NO_BASE_ENTRY.get(dn), null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS); for (final String line : ResultUtils.formatResult(result, true, 0, WRAP_COLUMN)) { err(line); } writeToRejects(deleteRequest, result); returnCode.compareAndSet(null, ResultCode.NO_SUCH_OBJECT); err(); return false; } else if (entriesDeleted == 1L) { commentToOut( INFO_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_ONLY_BASE.get(dn)); out(); return true; } else { final long numSubordinates = entriesDeleted - 1L; commentToOut(INFO_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_WITH_SUBS.get(dn, numSubordinates)); out(); return true; } } else { commentToErr(ERR_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_FAILED.get()); err(); final SearchResult searchError = subtreeDeleterResult.getSearchError(); if (searchError != null) { returnCode.compareAndSet(null, searchError.getResultCode()); commentToErr( ERR_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_SEARCH_ERROR.get(dn)); for (final String line : ResultUtils.formatResult(searchError, true, 0, WRAP_COLUMN)) { err(line); } err(); } for (final Map.Entry deleteError : subtreeDeleterResult.getDeleteErrorsDescendingMap().entrySet()) { final String failureDN = deleteError.getKey().toString(); final LDAPResult failureResult = deleteError.getValue(); returnCode.compareAndSet(null, failureResult.getResultCode()); commentToErr(ERR_LDAPDELETE_CLIENT_SIDE_SUBTREE_DEL_DEL_ERROR.get( failureDN, dn)); writeToRejects(new DeleteRequest(failureDN), failureResult); for (final String line : ResultUtils.formatResult(failureResult, true, 0, WRAP_COLUMN)) { err(line); } err(); } return false; } } } /** * Writes information about a failed operation to the reject writer. If an * error occurs while writing the rejected change, then that error will be * written to standard error. * * @param deleteRequest The delete request that failed. * @param deleteResult The result for the failed delete. */ private void writeToRejects(@NotNull final DeleteRequest deleteRequest, @NotNull final LDAPResult deleteResult) { if (! rejectFile.isPresent()) { return; } LDIFWriter w; try { w = rejectWriter.get(); if (w == null) { w = new LDIFWriter(rejectFile.getValue()); rejectWriter.set(w); } } catch (final Exception e) { Debug.debugException(e); commentToErr(ERR_LDAPDELETE_WRITE_TO_REJECTS_FAILED.get( StaticUtils.getExceptionMessage(e))); return; } try { boolean firstLine = true; for (final String commentLine : ResultUtils.formatResult(deleteResult, false, 0, (WRAP_COLUMN - 2))) { w.writeComment(commentLine, firstLine, false); firstLine = false; } w.writeChangeRecord(deleteRequest.toLDIFChangeRecord()); w.flush(); } catch (final Exception e) { Debug.debugException(e); commentToErr(ERR_LDAPDELETE_WRITE_TO_REJECTS_FAILED.get( StaticUtils.getExceptionMessage(e))); } } /** * Retrieves the set of controls that should be included in delete requests. * * @return The set of controls that should be included in delete requests. */ @NotNull() private List getDeleteControls() { final List controlList = new ArrayList<>(10); if (deleteControl.isPresent()) { controlList.addAll(deleteControl.getValues()); } controlList.addAll(routeToBackendSetRequestControls); if (serverSideSubtreeDelete.isPresent()) { controlList.add(new SubtreeDeleteRequestControl(true)); } if (softDelete.isPresent()) { controlList.add(new SoftDeleteRequestControl(true, true)); } if (hardDelete.isPresent() && (! clientSideSubtreeDelete.isPresent())) { controlList.add(new HardDeleteRequestControl(true)); } if (proxyAs.isPresent()) { controlList.add( new ProxiedAuthorizationV2RequestControl(proxyAs.getValue())); } if (proxyV1As.isPresent()) { controlList.add(new ProxiedAuthorizationV1RequestControl( proxyV1As.getValue().toString())); } if (manageDsaIT.isPresent() && (! clientSideSubtreeDelete.isPresent())) { controlList.add(new ManageDsaITRequestControl(true)); } if (assertionFilter.isPresent()) { controlList.add( new AssertionRequestControl(assertionFilter.getValue(), true)); } if (preReadAttribute.isPresent()) { controlList.add(new PreReadRequestControl(true, preReadAttribute.getValues().toArray(StaticUtils.NO_STRINGS))); } if (noOperation.isPresent()) { controlList.add(new NoOpRequestControl()); } if (getBackendSetID.isPresent()) { controlList.add(new GetBackendSetIDRequestControl(true)); } if (getServerID.isPresent()) { controlList.add(new GetServerIDRequestControl(true)); } if (routeToServer.isPresent()) { controlList.add(new RouteToServerRequestControl(true, routeToServer.getValue(), false, false, false)); } if (useAssuredReplication.isPresent()) { AssuredReplicationLocalLevel localLevel = null; if (assuredReplicationLocalLevel.isPresent()) { final String level = assuredReplicationLocalLevel.getValue(); if (level.equalsIgnoreCase("none")) { localLevel = AssuredReplicationLocalLevel.NONE; } else if (level.equalsIgnoreCase("received-any-server")) { localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER; } else if (level.equalsIgnoreCase("processed-all-servers")) { localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS; } } AssuredReplicationRemoteLevel remoteLevel = null; if (assuredReplicationRemoteLevel.isPresent()) { final String level = assuredReplicationRemoteLevel.getValue(); if (level.equalsIgnoreCase("none")) { remoteLevel = AssuredReplicationRemoteLevel.NONE; } else if (level.equalsIgnoreCase("received-any-remote-location")) { remoteLevel = AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION; } else if (level.equalsIgnoreCase("received-all-remote-locations")) { remoteLevel = AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS; } else if (level.equalsIgnoreCase("processed-all-remote-servers")) { remoteLevel = AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS; } } Long timeoutMillis = null; if (assuredReplicationTimeout.isPresent()) { timeoutMillis = assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS); } final AssuredReplicationRequestControl c = new AssuredReplicationRequestControl(true, localLevel, localLevel, remoteLevel, remoteLevel, timeoutMillis, false); controlList.add(c); } if (replicationRepair.isPresent()) { controlList.add(new ReplicationRepairRequestControl()); } if (suppressReferentialIntegrityUpdates.isPresent()) { controlList.add( new SuppressReferentialIntegrityUpdatesRequestControl(true)); } if (operationPurpose.isPresent()) { controlList.add(new OperationPurposeRequestControl(true, "ldapdelete", Version.NUMERIC_VERSION_STRING, LDAPDelete.class.getName() + ".getDeleteControls", operationPurpose.getValue())); } return Collections.unmodifiableList(controlList); } /** * Retrieves the set of controls that should be included in search requests. * * @return The set of controls that should be included in delete requests. */ @NotNull() private List getSearchControls() { final List controlList = new ArrayList<>(10); controlList.addAll(routeToBackendSetRequestControls); if (manageDsaIT.isPresent()) { controlList.add(new ManageDsaITRequestControl(true)); } if (proxyV1As.isPresent()) { controlList.add(new ProxiedAuthorizationV1RequestControl( proxyV1As.getValue().toString())); } if (proxyAs.isPresent()) { controlList.add( new ProxiedAuthorizationV2RequestControl(proxyAs.getValue())); } if (operationPurpose.isPresent()) { controlList.add(new OperationPurposeRequestControl(true, "ldapdelete", Version.NUMERIC_VERSION_STRING, LDAPDelete.class.getName() + ".getSearchControls", operationPurpose.getValue())); } if (routeToServer.isPresent()) { controlList.add(new RouteToServerRequestControl(true, routeToServer.getValue(), false, false, false)); } return Collections.unmodifiableList(controlList); } /** * {@inheritDoc} */ @Override() public void handleUnsolicitedNotification( @NotNull final LDAPConnection connection, @NotNull final ExtendedResult notification) { final ArrayList lines = new ArrayList<>(10); ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0, WRAP_COLUMN); for (final String line : lines) { err(line); } err(); } /** * Writes a line-wrapped, commented version of the provided message to * standard output. * * @param message The message to be written. */ void commentToOut(@NotNull final String message) { for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) { out("# ", line); } } /** * Writes a line-wrapped, commented version of the provided message to * standard error. * * @param message The message to be written. */ void commentToErr(@NotNull final String message) { for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2)) { err("# ", line); } } /** * {@inheritDoc} */ @Override() @NotNull() public LinkedHashMap getExampleUsages() { final LinkedHashMap examples = new LinkedHashMap<>(StaticUtils.computeMapCapacity(4)); examples.put( new String[] { "--hostname", "ds.example.com", "--port", "636", "--useSSL", "--bindDN", "uid=admin,dc=example,dc=com", "uid=test.user,ou=People,dc=example,dc=com" }, INFO_LDAPDELETE_EXAMPLE_1.get()); examples.put( new String[] { "--hostname", "ds.example.com", "--port", "636", "--useSSL", "--trustStorePath", "trust-store.jks", "--bindDN", "uid=admin,dc=example,dc=com", "--bindPasswordFile", "admin-password.txt", "--dnFile", "dns-to-delete.txt" }, INFO_LDAPDELETE_EXAMPLE_2.get()); examples.put( new String[] { "--hostname", "ds.example.com", "--port", "389", "--useStartTLS", "--trustStorePath", "trust-store.jks", "--bindDN", "uid=admin,dc=example,dc=com", "--bindPasswordFile", "admin-password.txt", "--deleteEntriesMatchingFilter", "(description=delete)" }, INFO_LDAPDELETE_EXAMPLE_3.get()); examples.put( new String[] { "--hostname", "ds.example.com", "--port", "389", "--bindDN", "uid=admin,dc=example,dc=com" }, INFO_LDAPDELETE_EXAMPLE_4.get()); return examples; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy