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

com.unboundid.ldif.LDIFSearch Maven / Gradle / Ivy

Go to download

The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for communicating with LDAP directory servers and performing related tasks like reading and writing LDIF, encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for communicating with LDAPv3 directory servers.

The newest version!
/*
 * Copyright 2020-2024 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2020-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) 2020-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.ldif;



import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;

import com.unboundid.ldap.listener.SearchEntryParer;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.InternalSDKHelper;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPURL;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.Version;
import com.unboundid.ldap.sdk.schema.EntryValidator;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.ldap.sdk.unboundidds.tools.ColumnBasedLDAPResultWriter;
import com.unboundid.ldap.sdk.unboundidds.tools.DNsOnlyLDAPResultWriter;
import com.unboundid.ldap.sdk.unboundidds.tools.JSONLDAPResultWriter;
import com.unboundid.ldap.sdk.unboundidds.tools.LDAPResultWriter;
import com.unboundid.ldap.sdk.unboundidds.tools.LDIFLDAPResultWriter;
import com.unboundid.ldap.sdk.unboundidds.tools.ToolUtils;
import com.unboundid.ldap.sdk.unboundidds.tools.ValuesOnlyLDAPResultWriter;
import com.unboundid.util.CommandLineTool;
import com.unboundid.util.Debug;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.OutputFormat;
import com.unboundid.util.PassphraseEncryptedOutputStream;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.BooleanArgument;
import com.unboundid.util.args.DNArgument;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.IntegerArgument;
import com.unboundid.util.args.ScopeArgument;
import com.unboundid.util.args.StringArgument;

import static com.unboundid.ldif.LDIFMessages.*;



/**
 * This class provides a command-line tool that can be used to search for
 * entries matching a given set of criteria in an LDIF file.
 */
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public final class LDIFSearch
       extends CommandLineTool
{
  /**
   * The server root directory for the Ping Identity Directory Server (or
   * related Ping Identity server product) that contains this tool, if
   * applicable.
   */
  @Nullable private static final File PING_SERVER_ROOT =
       InternalSDKHelper.getPingIdentityServerRoot();



  /**
   * Indicates whether the tool is running as part of a Ping Identity Directory
   * Server (or related Ping Identity Server Product) installation.
   */
  private static final boolean PING_SERVER_AVAILABLE =
       (PING_SERVER_ROOT != null);



  /**
   * The column at which to wrap long lines.
   */
  private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;



  // The argument parser for this tool.
  @Nullable private volatile ArgumentParser parser;

  // The completion message for this tool.
  @NotNull private final AtomicReference completionMessage;

  // Indicates whether the LDIF encryption passphrase file has been read.
  private volatile boolean ldifEncryptionPassphraseFileRead;

  // Encryption passphrases used thus far.
  @NotNull private final List inputEncryptionPassphrases;

  // The list of LDAP URLs to use when processing searches, mapped to the
  // corresponding search entry parers.
  @NotNull private final List searchURLs;

  // The LDAP result writer for this tool.
  @NotNull private volatile LDAPResultWriter resultWriter;

  // The command-line arguments supported by this tool.
  @Nullable private BooleanArgument checkSchema;
  @Nullable private BooleanArgument compressOutput;
  @Nullable private BooleanArgument doNotWrap;
  @Nullable private BooleanArgument encryptOutput;
  @Nullable private BooleanArgument isCompressed;
  @Nullable private BooleanArgument overwriteExistingOutputFile;
  @Nullable private BooleanArgument separateOutputFilePerSearch;
  @Nullable private BooleanArgument stripTrailingSpaces;
  @Nullable private DNArgument baseDN;
  @Nullable private FileArgument filterFile;
  @Nullable private FileArgument ldapURLFile;
  @Nullable private FileArgument ldifEncryptionPassphraseFile;
  @Nullable private FileArgument ldifFile;
  @Nullable private FileArgument outputFile;
  @Nullable private FileArgument outputEncryptionPassphraseFile;
  @Nullable private FileArgument schemaPath;
  @Nullable private IntegerArgument sizeLimit;
  @Nullable private IntegerArgument timeLimitSeconds;
  @Nullable private IntegerArgument wrapColumn;
  @Nullable private ScopeArgument scope;
  @Nullable private StringArgument outputFormat = null;



  /**
   * Invokes this tool with the provided set of command-line arguments.
   *
   * @param  args  The set of arguments provided to this tool.  It may be
   *               empty but must not be {@code null}.
   */
  public static void main(@NotNull final String... args)
  {
    final ResultCode resultCode = main(System.out, System.err, args);
    if (resultCode != ResultCode.SUCCESS)
    {
      System.exit(resultCode.intValue());
    }
  }



  /**
   * Invokes this tool with the provided set of command-line arguments, using
   * the given output and error streams.
   *
   * @param  out   The output stream to use for standard output.  It may be
   *               {@code null} if standard output should be suppressed.
   * @param  err   The output stream to use for standard error.  It may be
   *               {@code null} if standard error should be suppressed.
   * @param  args  The set of arguments provided to this tool.  It may be
   *               empty but must not be {@code null}.
   *
   * @return  A result code indicating the status of processing.  Any result
   *          code other than {@link ResultCode#SUCCESS} should be considered
   *          an error.
   */
  @NotNull()
  public static ResultCode main(@Nullable final OutputStream out,
                                @Nullable final OutputStream err,
                                @NotNull final String... args)
  {
    final LDIFSearch tool = new LDIFSearch(out, err);
    return tool.runTool(args);
  }



  /**
   * Creates a new instance of this tool with the provided output and error
   * streams.
   *
   * @param  out  The output stream to use for standard output.  It may be
   *              {@code null} if standard output should be suppressed.
   * @param  err  The output stream to use for standard error.  It may be
   *              {@code null} if standard error should be suppressed.
   */
  public LDIFSearch(@Nullable final OutputStream out,
                    @Nullable final OutputStream err)
  {
    super(out, err);

    resultWriter = new LDIFLDAPResultWriter(getOut(), WRAP_COLUMN);

    parser = null;
    completionMessage = new AtomicReference<>();
    inputEncryptionPassphrases = new ArrayList<>(5);
    searchURLs = new ArrayList<>();
    ldifEncryptionPassphraseFileRead = false;

    checkSchema = null;
    compressOutput = null;
    doNotWrap = null;
    encryptOutput = null;
    isCompressed = null;
    overwriteExistingOutputFile = null;
    separateOutputFilePerSearch = null;
    stripTrailingSpaces = null;
    baseDN = null;
    filterFile = null;
    ldapURLFile = null;
    ldifEncryptionPassphraseFile = null;
    ldifFile = null;
    outputFile = null;
    outputFormat = null;
    outputEncryptionPassphraseFile = null;
    schemaPath = null;
    sizeLimit = null;
    timeLimitSeconds = null;
    wrapColumn = null;
    scope = null;
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  @NotNull()
  public String getToolName()
  {
    return "ldifsearch";
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  @NotNull()
  public String getToolDescription()
  {
    return INFO_LDIFSEARCH_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 -1;
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  @NotNull()
  public String getTrailingArgumentsPlaceholder()
  {
    return INFO_LDIFSEARCH_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()
  protected boolean supportsDebugLogging()
  {
    return true;
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  @Nullable()
  protected String getToolCompletionMessage()
  {
    return completionMessage.get();
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  public void addToolArguments(@NotNull final ArgumentParser parser)
         throws ArgumentException
  {
    this.parser = parser;


    ldifFile = new FileArgument('l', "ldifFile", true, 0, null,
         INFO_LDIFSEARCH_ARG_DESC_LDIF_FILE.get(), true, true, true, false);
    ldifFile.addLongIdentifier("ldif-file", true);
    ldifFile.addLongIdentifier("inputFile", true);
    ldifFile.addLongIdentifier("input-file", true);
    ldifFile.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_INPUT.get());
    parser.addArgument(ldifFile);


    final String ldifPWDesc;
    if (PING_SERVER_AVAILABLE)
    {
      ldifPWDesc = INFO_LDIFSEARCH_ARG_DESC_LDIF_PW_FILE_PING_SERVER.get();
    }
    else
    {
      ldifPWDesc = INFO_LDIFSEARCH_ARG_DESC_LDIF_PW_FILE_STANDALONE.get();
    }
    ldifEncryptionPassphraseFile = new FileArgument(null,
         "ldifEncryptionPassphraseFile", false, 1, null, ldifPWDesc, true,
         true, true, false);
    ldifEncryptionPassphraseFile.addLongIdentifier(
         "ldif-encryption-passphrase-file", true);
    ldifEncryptionPassphraseFile.addLongIdentifier("ldifPassphraseFile", true);
    ldifEncryptionPassphraseFile.addLongIdentifier("ldif-passphrase-file",
         true);
    ldifEncryptionPassphraseFile.addLongIdentifier("ldifEncryptionPasswordFile",
         true);
    ldifEncryptionPassphraseFile.addLongIdentifier(
         "ldif-encryption-password-file", true);
    ldifEncryptionPassphraseFile.addLongIdentifier("ldifPasswordFile", true);
    ldifEncryptionPassphraseFile.addLongIdentifier("ldif-password-file", true);
    ldifEncryptionPassphraseFile.addLongIdentifier(
         "inputEncryptionPassphraseFile", true);
    ldifEncryptionPassphraseFile.addLongIdentifier(
         "input-encryption-passphrase-file", true);
    ldifEncryptionPassphraseFile.addLongIdentifier("inputPassphraseFile", true);
    ldifEncryptionPassphraseFile.addLongIdentifier("input-passphrase-file",
         true);
    ldifEncryptionPassphraseFile.addLongIdentifier(
         "inputEncryptionPasswordFile", true);
    ldifEncryptionPassphraseFile.addLongIdentifier(
         "input-encryption-password-file", true);
    ldifEncryptionPassphraseFile.addLongIdentifier("inputPasswordFile", true);
    ldifEncryptionPassphraseFile.addLongIdentifier("input-password-file", true);
    ldifEncryptionPassphraseFile.setArgumentGroupName(
         INFO_LDIFSEARCH_ARG_GROUP_INPUT.get());
    parser.addArgument(ldifEncryptionPassphraseFile);


    stripTrailingSpaces = new BooleanArgument(null, "stripTrailingSpaces", 1,
         INFO_LDIFSEARCH_ARG_DESC_STRIP_TRAILING_SPACES.get());
    stripTrailingSpaces.addLongIdentifier("strip-trailing-spaces", true);
    stripTrailingSpaces.addLongIdentifier("ignoreTrailingSpaces", true);
    stripTrailingSpaces.addLongIdentifier("ignore-trailing-spaces", true);
    stripTrailingSpaces.setArgumentGroupName(
         INFO_LDIFSEARCH_ARG_GROUP_INPUT.get());
    parser.addArgument(stripTrailingSpaces);


    final String schemaPathDesc;
    if (PING_SERVER_AVAILABLE)
    {
      schemaPathDesc = INFO_LDIFSEARCH_ARG_DESC_SCHEMA_PATH_PING_SERVER.get();
    }
    else
    {
      schemaPathDesc = INFO_LDIFSEARCH_ARG_DESC_SCHEMA_PATH_STANDALONE.get();
    }
    schemaPath = new FileArgument(null, "schemaPath", false, 0, null,
         schemaPathDesc, true, true, false, false);
    schemaPath.addLongIdentifier("schema-path", true);
    schemaPath.addLongIdentifier("schemaFile", true);
    schemaPath.addLongIdentifier("schema-file", true);
    schemaPath.addLongIdentifier("schemaDirectory", true);
    schemaPath.addLongIdentifier("schema-directory", true);
    schemaPath.addLongIdentifier("schema", true);
    schemaPath.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_INPUT.get());
    parser.addArgument(schemaPath);


    checkSchema = new BooleanArgument(null, "checkSchema", 1,
         INFO_LDIFSEARCH_ARG_DESC_CHECK_SCHEMA.get());
    checkSchema.addLongIdentifier("check-schema", true);
    checkSchema.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_INPUT.get());
    parser.addArgument(checkSchema);


    isCompressed = new BooleanArgument(null, "isCompressed", 1,
         INFO_LDIFSEARCH_ARG_DESC_IS_COMPRESSED.get());
    isCompressed.addLongIdentifier("is-compressed", true);
    isCompressed.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_INPUT.get());
    isCompressed.setHidden(true);
    parser.addArgument(isCompressed);


    outputFile = new FileArgument('o', "outputFile", false, 1, null,
         INFO_LDIFSEARCH_ARG_DESC_OUTPUT_FILE.get(), false, true, true, false);
    outputFile.addLongIdentifier("output-file", true);
    outputFile.addLongIdentifier("outputLDIF", true);
    outputFile.addLongIdentifier("output-ldif", true);
    outputFile.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(outputFile);


    separateOutputFilePerSearch = new BooleanArgument(null,
         "separateOutputFilePerSearch", 1,
         INFO_LDIFSEARCH_ARG_DESC_SEPARATE_OUTPUT_FILES.get());
    separateOutputFilePerSearch.addLongIdentifier(
         "separate-output-file-per-search", true);
    separateOutputFilePerSearch.addLongIdentifier("separateOutputFiles", true);
    separateOutputFilePerSearch.addLongIdentifier("separate-output-files",
         true);
    separateOutputFilePerSearch.setArgumentGroupName(
         INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(separateOutputFilePerSearch);


    compressOutput = new BooleanArgument(null, "compressOutput", 1,
         INFO_LDIFSEARCH_ARG_DESC_COMPRESS_OUTPUT.get());
    compressOutput.addLongIdentifier("compress-output", true);
    compressOutput.addLongIdentifier("compressLDIF", true);
    compressOutput.addLongIdentifier("compress-ldif", true);
    compressOutput.addLongIdentifier("compress", true);
    compressOutput.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(compressOutput);


    encryptOutput = new BooleanArgument(null, "encryptOutput", 1,
         INFO_LDIFSEARCH_ARG_DESC_ENCRYPT_OUTPUT.get());
    encryptOutput.addLongIdentifier("encrypt-output", true);
    encryptOutput.addLongIdentifier("encryptLDIF", true);
    encryptOutput.addLongIdentifier("encrypt-ldif", true);
    encryptOutput.addLongIdentifier("encrypt", true);
    encryptOutput.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(encryptOutput);


    outputEncryptionPassphraseFile = new FileArgument(null,
         "outputEncryptionPassphraseFile", false, 1, null,
         INFO_LDIFSEARCH_ARG_DESC_OUTPUT_PW_FILE.get(), true, true, true,
         false);
    outputEncryptionPassphraseFile.addLongIdentifier(
         "output-encryption-passphrase-file", true);
    outputEncryptionPassphraseFile.addLongIdentifier("outputPassphraseFile",
         true);
    outputEncryptionPassphraseFile.addLongIdentifier("output-passphrase-file",
         true);
    outputEncryptionPassphraseFile.addLongIdentifier(
         "outputEncryptionPasswordFile", true);
    outputEncryptionPassphraseFile.addLongIdentifier(
         "output-encryption-password-file", true);
    outputEncryptionPassphraseFile.addLongIdentifier("outputPasswordFile",
         true);
    outputEncryptionPassphraseFile.addLongIdentifier("output-password-file",
         true);
    outputEncryptionPassphraseFile.addLongIdentifier(
         "outputEncryptionPasswordFile", true);
    outputEncryptionPassphraseFile.addLongIdentifier(
         "output-encryption-password-file", true);
    outputEncryptionPassphraseFile.addLongIdentifier("outputPasswordFile",
         true);
    outputEncryptionPassphraseFile.addLongIdentifier("output-password-file",
         true);
    outputEncryptionPassphraseFile.setArgumentGroupName(
         INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(outputEncryptionPassphraseFile);


    overwriteExistingOutputFile = new BooleanArgument('O',
         "overwriteExistingOutputFile", 1,
         INFO_LDIFSEARCH_ARG_DESC_OVERWRITE_EXISTING.get());
    overwriteExistingOutputFile.addLongIdentifier(
         "overwrite-existing-output-file", true);
    overwriteExistingOutputFile.addLongIdentifier(
         "overwriteExistingOutputFiles", true);
    overwriteExistingOutputFile.addLongIdentifier(
         "overwrite-existing-output-files", true);
    overwriteExistingOutputFile.addLongIdentifier("overwriteExistingOutput",
         true);
    overwriteExistingOutputFile.addLongIdentifier("overwrite-existing-output",
         true);
    overwriteExistingOutputFile.addLongIdentifier("overwriteExisting", true);
    overwriteExistingOutputFile.addLongIdentifier("overwrite-existing", true);
    overwriteExistingOutputFile.addLongIdentifier("overwrite", true);
    overwriteExistingOutputFile.setArgumentGroupName(
         INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(overwriteExistingOutputFile);


    final Set outputFormatAllowedValues = StaticUtils.setOf("ldif",
         "json", "csv", "multi-valued-csv", "tab-delimited",
         "multi-valued-tab-delimited", "dns-only", "values-only");
    outputFormat = new StringArgument(null, "outputFormat", false, 1,
         "{ldif|json|csv|multi-valued-csv|tab-delimited|" +
              "multi-valued-tab-delimited|dns-only|values-only}",
         INFO_LDIFSEARCH_ARG_DESC_OUTPUT_FORMAT.get(),
         outputFormatAllowedValues, "ldif");
    outputFormat.addLongIdentifier("output-format", true);
    outputFormat.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(outputFormat);


    wrapColumn = new IntegerArgument(null, "wrapColumn", false, 1, null,
         INFO_LDIFSEARCH_ARG_DESC_WRAP_COLUMN.get(), 5, Integer.MAX_VALUE);
    wrapColumn.addLongIdentifier("wrap-column", true);
    wrapColumn.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(wrapColumn);


    doNotWrap = new BooleanArgument('T', "doNotWrap", 1,
         INFO_LDIFSEARCH_ARG_DESC_DO_NOT_WRAP.get());
    doNotWrap.addLongIdentifier("do-not-wrap", true);
    doNotWrap.addLongIdentifier("dontWrap", true);
    doNotWrap.addLongIdentifier("dont-wrap", true);
    doNotWrap.addLongIdentifier("noWrap", true);
    doNotWrap.addLongIdentifier("no-wrap", true);
    doNotWrap.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get());
    parser.addArgument(doNotWrap);


    baseDN = new DNArgument('b', "baseDN", false, 1, null,
         INFO_LDIFSEARCH_ARG_DESC_BASE_DN.get());
    baseDN.addLongIdentifier("base-dn", true);
    baseDN.addLongIdentifier("searchBaseDN", true);
    baseDN.addLongIdentifier("search-base-dn", true);
    baseDN.addLongIdentifier("searchBase", true);
    baseDN.addLongIdentifier("search-base", true);
    baseDN.addLongIdentifier("base", true);
    baseDN.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get());
    parser.addArgument(baseDN);


    scope = new ScopeArgument('s', "scope", false, null,
         INFO_LDIFSEARCH_ARG_DESC_SCOPE.get());
    scope.addLongIdentifier("searchScope", true);
    scope.addLongIdentifier("search-scope", true);
    scope.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get());
    parser.addArgument(scope);


    filterFile = new FileArgument('f', "filterFile", false, 0, null,
         INFO_LDIFSEARCH_ARG_DESC_FILTER_FILE.get(), true, true, true, false);
    filterFile.addLongIdentifier("filter-file", true);
    filterFile.addLongIdentifier("filtersFile", true);
    filterFile.addLongIdentifier("filters-file", true);
    filterFile.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get());
    parser.addArgument(filterFile);


    ldapURLFile = new FileArgument(null, "ldapURLFile", false, 0, null,
         INFO_LDIFSEARCH_ARG_DESC_LDAP_URL_FILE.get(), true, true, true, false);
    ldapURLFile.addLongIdentifier("ldap-url-file", true);
    ldapURLFile.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get());
    parser.addArgument(ldapURLFile);


    sizeLimit = new IntegerArgument('z', "sizeLimit", false, 1, null,
         INFO_LDIFSEARCH_ARG_DESC_SIZE_LIMIT.get(), 0, Integer.MAX_VALUE, 0);
    sizeLimit.addLongIdentifier("size-limit", true);
    sizeLimit.addLongIdentifier("searchSizeLimit", true);
    sizeLimit.addLongIdentifier("search-size-limit", true);
    sizeLimit.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get());
    sizeLimit.setHidden(true);
    parser.addArgument(sizeLimit);


    timeLimitSeconds = new IntegerArgument('t', "timeLimitSeconds", false, 1,
         null, INFO_LDIFSEARCH_ARG_DESC_TIME_LIMIT_SECONDS.get(), 0,
         Integer.MAX_VALUE, 0);
    timeLimitSeconds.addLongIdentifier("time-limit-seconds", true);
    timeLimitSeconds.addLongIdentifier("timeLimit", true);
    timeLimitSeconds.setArgumentGroupName(
         INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get());
    timeLimitSeconds.setHidden(true);
    parser.addArgument(timeLimitSeconds);


    parser.addDependentArgumentSet(separateOutputFilePerSearch, outputFile);
    parser.addDependentArgumentSet(compressOutput, outputFile);
    parser.addDependentArgumentSet(encryptOutput, outputFile);
    parser.addDependentArgumentSet(overwriteExistingOutputFile, outputFile);
    parser.addDependentArgumentSet(outputEncryptionPassphraseFile,
         encryptOutput);

    parser.addExclusiveArgumentSet(wrapColumn, doNotWrap);
    parser.addExclusiveArgumentSet(baseDN, ldapURLFile);
    parser.addExclusiveArgumentSet(scope, ldapURLFile);
    parser.addExclusiveArgumentSet(filterFile, ldapURLFile);
    parser.addExclusiveArgumentSet(outputFormat, separateOutputFilePerSearch);
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  public void doExtendedArgumentValidation()
         throws ArgumentException
  {
    // If the output file exists and either compressOutput or encryptOutput is
    // present, then the overwrite argument must also be present.
    final File outFile = outputFile.getValue();
    if ((outFile != null) && outFile.exists() &&
         (compressOutput.isPresent() || encryptOutput.isPresent()) &&
         (! overwriteExistingOutputFile.isPresent()))
    {
      throw new ArgumentException(
           ERR_LDIFSEARCH_APPEND_WITH_COMPRESSION_OR_ENCRYPTION.get(
                compressOutput.getIdentifierString(),
                encryptOutput.getIdentifierString(),
                overwriteExistingOutputFile.getIdentifierString()));
    }


    // Create the set of LDAP URLs to use when issuing the searches.
    final List trailingArgs = parser.getTrailingArguments();
    final List requestedAttributes = new ArrayList<>();
    if (filterFile.isPresent())
    {
      // If there are trailing arguments, then make sure the first one is not a
      // valid filter.
      if (! trailingArgs.isEmpty())
      {
        try
        {
          Filter.create(trailingArgs.get(0));
          throw new ArgumentException(
               ERR_LDIFSEARCH_FILTER_FILE_WITH_TRAILING_FILTER.get());
        }
        catch (final LDAPException e)
        {
          // This was expected.
        }
      }

      requestedAttributes.addAll(trailingArgs);
      readFilterFile();
    }
    else if (ldapURLFile.isPresent())
    {
      // Make sure there aren't any trailing arguments.
      if (! trailingArgs.isEmpty())
      {
        throw new ArgumentException(
             ERR_LDIFSEARCH_LDAP_URL_FILE_WITH_TRAILING_ARGS.get());
      }

      readLDAPURLFile();


      // If there are multiple LDAP URLs, and if they should not be sent to
      // separate output files, then they must all have the same set of
      // requested attributes.
      if ((searchURLs.size() > 1) &&
           (! separateOutputFilePerSearch.isPresent()))
      {
        final Iterator iterator = searchURLs.iterator();
        final Set requestedAttrs =
             new HashSet<>(Arrays.asList(iterator.next().getAttributes()));
        while (iterator.hasNext())
        {
          final Set attrSet = new HashSet<>(Arrays.asList(
               iterator.next().getAttributes()));
          if (! requestedAttrs.equals(attrSet))
          {
            throw new ArgumentException(
                 ERR_LDIFSEARCH_DIFFERENT_URL_ATTRS_IN_SAME_FILE.get(
                      ldapURLFile.getIdentifierString(),
                      separateOutputFilePerSearch.getIdentifierString()));
          }
        }
      }
    }
    else
    {
      // Make sure there is at least one trailing argument, and that it's a
      // valid filter.  If there are any others, then they must be the
      // requested arguments.
      if (trailingArgs.isEmpty())
      {
        throw new ArgumentException(ERR_LDIFSEARCH_NO_FILTER.get());
      }


      final Filter filter;
      try
      {
        final List trailingArgList = new ArrayList<>(trailingArgs);
        final Iterator trailingArgIterator = trailingArgList.iterator();
        filter = Filter.create(trailingArgIterator.next());

        while (trailingArgIterator.hasNext())
        {
          requestedAttributes.add(trailingArgIterator.next());
        }
      }
      catch (final LDAPException e)
      {
        Debug.debugException(e);
        throw new ArgumentException(
             ERR_LDIFSEARCH_FIRST_TRAILING_ARG_NOT_FILTER.get(
                  trailingArgs.get(0)),
             e);
      }


      DN dn = baseDN.getValue();
      if (dn == null)
      {
        dn = DN.NULL_DN;
      }

      SearchScope searchScope = scope.getValue();
      if (searchScope == null)
      {
        searchScope = SearchScope.SUB;
      }

      try
      {
        searchURLs.add(new LDAPURL("ldap", null, null, dn,
             requestedAttributes.toArray(StaticUtils.NO_STRINGS),
             searchScope, filter));
      }
      catch (final LDAPException e)
      {
        Debug.debugException(e);
        // This should never happen.
        throw new ArgumentException(StaticUtils.getExceptionMessage(e), e);
      }
    }


    // Create the result writer.
    final String outputFormatStr =
         StaticUtils.toLowerCase(outputFormat.getValue());
    if (outputFormatStr.equals("json"))
    {
      resultWriter = new JSONLDAPResultWriter(getOut());
    }
    else if (outputFormatStr.equals("csv") ||
             outputFormatStr.equals("multi-valued-csv") ||
             outputFormatStr.equals("tab-delimited") ||
             outputFormatStr.equals("multi-valued-tab-delimited"))
    {
      // These output formats cannot be used with the --ldapURLFile argument.
      if (ldapURLFile.isPresent())
      {
        throw new ArgumentException(
             ERR_LDIFSEARCH_OUTPUT_FORMAT_NOT_SUPPORTED_WITH_URLS.get(
                  outputFormat.getValue(), ldapURLFile.getIdentifierString()));
      }


      // These output formats require a set of requested attributes.
      if (requestedAttributes.isEmpty())
      {
        throw new ArgumentException(
             ERR_LDIFSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTRS.get(
                  outputFormat.getValue()));
      }

      final OutputFormat format;
      final boolean includeAllValues;
      switch (outputFormatStr)
      {
        case "multi-valued-csv":
          format = OutputFormat.CSV;
          includeAllValues = true;
          break;
        case "tab-delimited":
          format = OutputFormat.TAB_DELIMITED_TEXT;
          includeAllValues = false;
          break;
        case "multi-valued-tab-delimited":
          format = OutputFormat.TAB_DELIMITED_TEXT;
          includeAllValues = true;
          break;
        case "csv":
        default:
          format = OutputFormat.CSV;
          includeAllValues = false;
          break;
      }


      resultWriter = new ColumnBasedLDAPResultWriter(getOut(),
           format, requestedAttributes, WRAP_COLUMN, includeAllValues);
    }
    else if (outputFormatStr.equals("dns-only"))
    {
      resultWriter = new DNsOnlyLDAPResultWriter(getOut());
    }
    else if (outputFormatStr.equals("values-only"))
    {
      resultWriter = new ValuesOnlyLDAPResultWriter(getOut());
    }
    else
    {
      final int wc;
      if (doNotWrap.isPresent())
      {
        wc = Integer.MAX_VALUE;
      }
      else if (wrapColumn.isPresent())
      {
        wc = wrapColumn.getValue();
      }
      else
      {
        wc = WRAP_COLUMN;
      }

      resultWriter = new LDIFLDAPResultWriter(getOut(), wc);
    }
  }



  /**
   * Uses the contents of any specified filter files, along with the configured
   * base DN, scope, and requested attributes, to populate the set of search
   * URLs.
   *
   * @throws  ArgumentException  If a problem is encountered while constructing
   *                             the search URLs.
   */
  private void readFilterFile()
          throws ArgumentException
  {
    DN dn = baseDN.getValue();
    if (dn == null)
    {
      dn = DN.NULL_DN;
    }

    SearchScope searchScope = scope.getValue();
    if (searchScope == null)
    {
      searchScope = SearchScope.SUB;
    }

    final String[] requestedAttributes =
         parser.getTrailingArguments().toArray(StaticUtils.NO_STRINGS);

    for (final File f : filterFile.getValues())
    {
      final InputStream inputStream;
      try
      {
        inputStream = openInputStream(f);
      }
      catch (final LDAPException e)
      {
        Debug.debugException(e);
        throw new ArgumentException(e.getMessage(), e);
      }

      try (BufferedReader reader =
                new BufferedReader(new InputStreamReader(inputStream)))
      {
        while (true)
        {
          final String line = reader.readLine();
          if (line == null)
          {
            break;
          }

          if (line.isEmpty() || line.startsWith("#"))
          {
            continue;
          }

          try
          {
            final Filter filter = Filter.create(line.trim());
            searchURLs.add(new LDAPURL("ldap", null, null, dn,
                 requestedAttributes, searchScope, filter));
          }
          catch (final LDAPException e)
          {
            Debug.debugException(e);
            throw new ArgumentException(
                 ERR_LDIFSEARCH_FILTER_FILE_INVALID_FILTER.get(line,
                      f.getAbsolutePath(), e.getMessage()),
                 e);
          }
        }
      }
      catch (final IOException e)
      {
        Debug.debugException(e);
        throw new ArgumentException(
             ERR_LDIFSEARCH_ERROR_READING_FILTER_FILE.get(f.getAbsolutePath(),
                  StaticUtils.getExceptionMessage(e)),
             e);
      }
      finally
      {
        try
        {
          inputStream.close();
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
        }
      }

    }

    if (searchURLs.isEmpty())
    {
      throw new ArgumentException(ERR_LDIFSEARCH_NO_FILTERS_FROM_FILE.get(
           filterFile.getValues().get(0).getAbsolutePath()));
    }
  }



  /**
   * Uses the contents of any specified LDAP URL files to populate the set of
   * search URLs.
   *
   * @throws  ArgumentException  If a problem is encountered while constructing
   *                             the search URLs.
   */
  private void readLDAPURLFile()
          throws ArgumentException
  {
    for (final File f : ldapURLFile.getValues())
    {
      final InputStream inputStream;
      try
      {
        inputStream = openInputStream(f);
      }
      catch (final LDAPException e)
      {
        Debug.debugException(e);
        throw new ArgumentException(e.getMessage(), e);
      }

      try (BufferedReader reader =
                new BufferedReader(new InputStreamReader(inputStream)))
      {
        while (true)
        {
          final String line = reader.readLine();
          if (line == null)
          {
            break;
          }

          if (line.isEmpty() || line.startsWith("#"))
          {
            continue;
          }

          try
          {
            searchURLs.add(new LDAPURL(line.trim()));
          }
          catch (final LDAPException e)
          {
            Debug.debugException(e);
            throw new ArgumentException(
                 ERR_LDIFSEARCH_LDAP_URL_FILE_INVALID_URL.get(line,
                      f.getAbsolutePath(), e.getMessage()),
                 e);
          }
        }
      }
      catch (final IOException e)
      {
        Debug.debugException(e);
        throw new ArgumentException(
             ERR_LDIFSEARCH_ERROR_READING_LDAP_URL_FILE.get(f.getAbsolutePath(),
                  StaticUtils.getExceptionMessage(e)),
             e);
      }
      finally
      {
        try
        {
          inputStream.close();
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
        }
      }
    }

    if (searchURLs.isEmpty())
    {
      throw new ArgumentException(ERR_LDIFSEARCH_NO_URLS_FROM_FILE.get(
           ldapURLFile.getValues().get(0).getAbsolutePath()));
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  @NotNull()
  public ResultCode doToolProcessing()
  {
    // Get the schema to use when performing LDIF processing.
    final Schema schema;
    try
    {
      if (schemaPath.isPresent())
      {
        schema = getSchema(schemaPath.getValues());
      }
      else if (PING_SERVER_AVAILABLE)
      {
        schema = getSchema(Collections.singletonList(StaticUtils.constructPath(
             PING_SERVER_ROOT, "config", "schema")));
      }
      else
      {
        schema = Schema.getDefaultStandardSchema();
      }
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      logCompletionMessage(true,
           ERR_LDIFSEARCH_CANNOT_GET_SCHEMA.get(
                StaticUtils.getExceptionMessage(e)));
      return ResultCode.LOCAL_ERROR;
    }


    // Create search entry parers for all of the search URLs.
    final Map urlMap = new LinkedHashMap<>();
    for (final LDAPURL url : searchURLs)
    {
      final SearchEntryParer parer = new SearchEntryParer(
           Arrays.asList(url.getAttributes()), schema);
      urlMap.put(url, parer);
    }


    // If we should check schema, then create the entry validator.
    final EntryValidator entryValidator;
    if (checkSchema.isPresent())
    {
      entryValidator = new EntryValidator(schema);
    }
    else
    {
      entryValidator = null;
    }


    // Create the output files, if appropriate.
    OutputStream outputStream = null;
    SearchEntryParer singleParer = null;
    final Map separateWriters =
         new LinkedHashMap<>();
    try
    {
      if (outputFile.isPresent())
      {
        final int numURLs = searchURLs.size();
        if (separateOutputFilePerSearch.isPresent() && (numURLs > 1))
        {
          int i=1;
          for (final LDAPURL url : searchURLs)
          {
            final File f = new
                 File(outputFile.getValue().getAbsolutePath() + '.' + i);
            final LDIFSearchSeparateSearchDetails details =
                 new LDIFSearchSeparateSearchDetails(url, f,
                      createLDIFWriter(f, url), schema);
            separateWriters.put(url, details);
            i++;
          }
        }
        else
        {
          try
          {
            outputStream = createOutputStream(outputFile.getValue());
            resultWriter.updateOutputStream(outputStream);
          }
          catch (final Exception e)
          {
            Debug.debugException(e);
            throw new LDAPException(ResultCode.LOCAL_ERROR,
                 ERR_LDIFSEARCH_CANNOT_WRITE_TO_FILE.get(
                      outputFile.getValue().getAbsolutePath(),
                      StaticUtils.getExceptionMessage(e)),
                 e);
          }
        }
      }


      // If we're not using separate writers, then write any appropriate header
      // to the top of the output.
      if (separateWriters.isEmpty())
      {
        resultWriter.writeHeader();
      }


      // Iterate through the LDIF files and process the entries they contain.
      boolean errorEncountered = false;
      final List matchingURLs = new ArrayList<>();
      final List entryInvalidReasons = new ArrayList<>();
      for (final File f : ldifFile.getValues())
      {
        final LDIFReader ldifReader;
        try
        {
          ldifReader = new LDIFReader(openInputStream(f));

          if (stripTrailingSpaces.isPresent())
          {
            ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.STRIP);
          }
          else
          {
            ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.REJECT);
          }
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
          logCompletionMessage(true,
               ERR_LDIFSEARCH_CANNOT_OPEN_LDIF_FILE.get(f.getName(),
                    StaticUtils.getExceptionMessage(e)));
          return ResultCode.LOCAL_ERROR;
        }

        try
        {
          while (true)
          {
            final Entry entry;
            try
            {
              entry = ldifReader.readEntry();
            }
            catch (final LDIFException e)
            {
              Debug.debugException(e);
              if (e.mayContinueReading())
              {
                commentToErr(ERR_LDIFSEARCH_RECOVERABLE_READ_ERROR.get(
                     f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
                errorEncountered = true;
                continue;
              }
              else
              {
                logCompletionMessage(true,
                     ERR_LDIFSEARCH_UNRECOVERABLE_READ_ERROR.get(
                          f.getAbsolutePath(),
                          StaticUtils.getExceptionMessage(e)));
                return ResultCode.LOCAL_ERROR;
              }
            }
            catch (final Exception e)
            {
              logCompletionMessage(true,
                   ERR_LDIFSEARCH_UNRECOVERABLE_READ_ERROR.get(
                        f.getAbsolutePath(),
                        StaticUtils.getExceptionMessage(e)));
              return ResultCode.LOCAL_ERROR;
            }

            if (entry == null)
            {
              break;
            }

            if (entryValidator != null)
            {
              entryInvalidReasons.clear();
              if (! entryValidator.entryIsValid(entry, entryInvalidReasons))
              {
                commentToErr(ERR_LDIFSEARCH_ENTRY_VIOLATES_SCHEMA.get(
                     entry.getDN()));
                for (final String invalidReason : entryInvalidReasons)
                {
                  commentToErr("- " + invalidReason);
                }

                err();
                errorEncountered = true;
                continue;
              }
            }

            if (separateWriters.isEmpty())
            {
              matchingURLs.clear();
              for (final LDAPURL url : searchURLs)
              {
                if (urlMatchesEntry(url, entry))
                {
                  matchingURLs.add(url);
                }
              }

              if (matchingURLs.isEmpty())
              {
                continue;
              }

              try
              {
                if (searchURLs.size() > 1)
                {
                  resultWriter.writeComment(
                       INFO_LDIFSEARCH_ENTRY_MATCHES_URLS.get(entry.getDN()));
                  for (final LDAPURL url : matchingURLs)
                  {
                    resultWriter.writeComment(url.toString());
                  }
                }

                if (singleParer == null)
                {
                  singleParer = new SearchEntryParer(
                       Arrays.asList(searchURLs.get(0).getAttributes()),
                       schema);
                }

                resultWriter.writeSearchResultEntry(
                     new SearchResultEntry(singleParer.pareEntry(entry)));

                if (! outputFile.isPresent())
                {
                  resultWriter.flush();
                }
              }
              catch (final Exception e)
              {
                Debug.debugException(e);
                if (outputFile.isPresent())
                {
                  logCompletionMessage(true,
                       ERR_LDIFSEARCH_WRITE_ERROR_WITH_FILE.get(entry.getDN(),
                            outputFile.getValue().getAbsolutePath(),
                            StaticUtils.getExceptionMessage(e)));
                }
                else
                {
                  logCompletionMessage(true,
                       ERR_LDIFSEARCH_WRITE_ERROR_NO_FILE.get(entry.getDN(),
                            StaticUtils.getExceptionMessage(e)));
                }
                return ResultCode.LOCAL_ERROR;
              }
            }
            else
            {
              for (final LDIFSearchSeparateSearchDetails details :
                   separateWriters.values())
              {
                final LDAPURL url = details.getLDAPURL();
                if (urlMatchesEntry(url, entry))
                {
                  try
                  {
                    final Entry paredEntry =
                         details.getSearchEntryParer().pareEntry(entry);
                    details.getLDIFWriter().writeEntry(paredEntry);
                  }
                  catch (final Exception ex)
                  {
                    Debug.debugException(ex);
                    logCompletionMessage(true,
                         ERR_LDIFSEARCH_WRITE_ERROR_WITH_FILE.get(entry.getDN(),
                              details.getOutputFile().getAbsolutePath(),
                              StaticUtils.getExceptionMessage(ex)));
                    return ResultCode.LOCAL_ERROR;
                  }
                }
              }
            }
          }
        }
        finally
        {
          try
          {
            ldifReader.close();
          }
          catch (final Exception e)
          {
            Debug.debugException(e);
          }
        }
      }

      if (errorEncountered)
      {
        logCompletionMessage(true,
             WARN_LDIFSEARCH_COMPLETED_WITH_ERRORS.get());
        return ResultCode.PARAM_ERROR;
      }
      else
      {
        logCompletionMessage(false,
             INFO_LDIFSEARCH_COMPLETED_SUCCESSFULLY.get());
        return ResultCode.SUCCESS;
      }
    }
    catch (final LDAPException e)
    {
      Debug.debugException(e);
      logCompletionMessage(true, e.getMessage());
      return e.getResultCode();
    }
    finally
    {
      try
      {
        resultWriter.flush();
        if (outputStream != null)
        {
          outputStream.close();
        }
      }
      catch (final Exception e)
      {
        Debug.debugException(e);
      }

      for (final LDIFSearchSeparateSearchDetails details :
           separateWriters.values())
      {
        try
        {
          details.getLDIFWriter().close();
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
        }
      }
    }
  }



  /**
   * Retrieves the schema contained in the specified paths.
   *
   * @param  paths  The paths to use to access the schema.
   *
   * @return  The schema read from the specified files.
   *
   * @throws  Exception  If a problem is encountered while loading the schema.
   */
  @NotNull()
  private static Schema getSchema(@NotNull final List paths)
          throws Exception
  {
    final Set schemaFiles = new LinkedHashSet<>();
    for (final File f : paths)
    {
      if (f.exists())
      {
        if (f.isFile())
        {
          schemaFiles.add(f);
        }
        else if (f.isDirectory())
        {
          final TreeMap sortedFiles = new TreeMap<>();
          for (final File fileInDir : f.listFiles())
          {
            if (fileInDir.isFile())
            {
              sortedFiles.put(fileInDir.getName(), fileInDir);
            }
          }

          schemaFiles.addAll(sortedFiles.values());
        }
      }
    }

    return Schema.getSchema(new ArrayList<>(schemaFiles));
  }



  /**
   * Opens the input stream to use to read from the specified file.
   *
   * @param  f  The file for which to open the input stream.  It may optionally
   *            be compressed and/or encrypted.
   *
   * @return  The input stream that was created.
   *
   * @throws  LDAPException  If a problem is encountered while opening the file.
   */
  @NotNull()
  private InputStream openInputStream(@NotNull final File f)
          throws LDAPException
  {
    if (ldifEncryptionPassphraseFile.isPresent() &&
       (! ldifEncryptionPassphraseFileRead))
    {
      readPassphraseFile(ldifEncryptionPassphraseFile.getValue());
      ldifEncryptionPassphraseFileRead = true;
    }


    boolean closeStream = true;
    InputStream inputStream = null;
    try
    {
      inputStream = new FileInputStream(f);

      final ObjectPair p =
           ToolUtils.getPossiblyPassphraseEncryptedInputStream(
                inputStream, inputEncryptionPassphrases,
                (! ldifEncryptionPassphraseFile.isPresent()),
                INFO_LDIFSEARCH_ENTER_ENCRYPTION_PW.get(f.getName()),
                ERR_LDIFSEARCH_WRONG_ENCRYPTION_PW.get(), getOut(), getErr());
      inputStream = p.getFirst();
      addPassphrase(p.getSecond());

      inputStream = ToolUtils.getPossiblyGZIPCompressedInputStream(inputStream);
      closeStream = false;
      return inputStream;
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      throw new LDAPException(ResultCode.LOCAL_ERROR,
           ERR_LDIFSEARCH_ERROR_OPENING_INPUT_FILE.get(f.getAbsolutePath(),
                StaticUtils.getExceptionMessage(e)),
           e);
    }
    finally
    {
      if ((inputStream != null) && closeStream)
      {
        try
        {
          inputStream.close();
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
        }
      }
    }
  }



  /**
   * Reads the contents of the specified passphrase file and adds it to the list
   * of passphrases.
   *
   * @param  f  The passphrase file to read.
   *
   * @throws  LDAPException  If a problem is encountered while trying to read
   *                         the passphrase from the provided file.
   */
  private void readPassphraseFile(@NotNull final File f)
          throws LDAPException
  {
    try
    {
      addPassphrase(getPasswordFileReader().readPassword(f));
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      throw new LDAPException(ResultCode.LOCAL_ERROR,
           ERR_LDIFSEARCH_CANNOT_READ_PW_FILE.get(f.getAbsolutePath(),
                StaticUtils.getExceptionMessage(e)),
           e);
    }
  }



  /**
   * Updates the list of encryption passphrases with the provided passphrase, if
   * it is not already present.
   *
   * @param  passphrase  The passphrase to be added.  It may optionally be
   *                     {@code null} (in which case no action will be taken).
   */
  private void addPassphrase(@Nullable final char[] passphrase)
  {
    if (passphrase == null)
    {
      return;
    }

    for (final char[] existingPassphrase : inputEncryptionPassphrases)
    {
      if (Arrays.equals(existingPassphrase, passphrase))
      {
        return;
      }
    }

    inputEncryptionPassphrases.add(passphrase);
  }



  /**
   * Creates an output stream that may be used to write to the specified file.
   *
   * @param  f  The file to be written.
   *
   * @return  The output stream that was created.
   *
   * @throws  LDAPException  If a problem occurs while creating the output
   *                         stream.
   */
  @NotNull()
  private OutputStream createOutputStream(@NotNull final File f)
          throws LDAPException
  {
    OutputStream outputStream = null;
    boolean closeOutputStream = true;
    try
    {
      try
      {

        outputStream = new FileOutputStream(f,
             (! overwriteExistingOutputFile.isPresent()));
      }
      catch (final Exception e)
      {
        Debug.debugException(e);
        throw new LDAPException(ResultCode.LOCAL_ERROR,
             ERR_LDIFSEARCH_CANNOT_OPEN_OUTPUT_FILE.get(f.getAbsolutePath(),
                  StaticUtils.getExceptionMessage(e)),
             e);
      }

      if (encryptOutput.isPresent())
      {
        try
        {
          final char[] passphrase;
          if (outputEncryptionPassphraseFile.isPresent())
          {
            passphrase = getPasswordFileReader().readPassword(
                 outputEncryptionPassphraseFile.getValue());
          }
          else
          {
            passphrase = ToolUtils.promptForEncryptionPassphrase(false, true,
                 INFO_LDIFSEARCH_PROMPT_OUTPUT_FILE_ENC_PW.get(),
                 INFO_LDIFSEARCH_CONFIRM_OUTPUT_FILE_ENC_PW.get(), getOut(),
                 getErr()).toCharArray();
          }

          outputStream = new PassphraseEncryptedOutputStream(passphrase,
               outputStream, null, true, true);
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
          throw new LDAPException(ResultCode.LOCAL_ERROR,
               ERR_LDIFSEARCH_CANNOT_ENCRYPT_OUTPUT_FILE.get(
                    StaticUtils.getExceptionMessage(e)),
               e);
        }
      }

      if (compressOutput.isPresent())
      {
        try
        {
          outputStream = new GZIPOutputStream(outputStream);
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
          throw new LDAPException(ResultCode.LOCAL_ERROR,
               ERR_LDIFSEARCH_CANNOT_COMPRESS_OUTPUT_FILE.get(
                    f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)),
               e);
        }
      }

      closeOutputStream = false;
      return outputStream;
    }
    finally
    {
      if (closeOutputStream && (outputStream != null))
      {
        try
        {
          outputStream.close();
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
        }
      }
    }
  }



  /**
   * Creates an LDIF writer to write to the specified file.
   *
   * @param  f        The file to be written.
   * @param  ldapURL  The LDAP URL with which the file will be associated.  It
   *                  may be {@code null} if the file is shared across multiple
   *                  URLs.
   *
   * @return  The LDIF writer that was created.
   *
   * @throws  LDAPException  If a problem occurs while creating the LDIF writer.
   */
  @NotNull()
  private LDIFWriter createLDIFWriter(@NotNull final File f,
                                      @Nullable final LDAPURL ldapURL)
          throws LDAPException
  {
    boolean closeOutputStream = true;
    final OutputStream outputStream = createOutputStream(f);
    try
    {
      final LDIFWriter ldifWriter = new LDIFWriter(outputStream);
      if (doNotWrap.isPresent())
      {
        ldifWriter.setWrapColumn(0);
      }
      else if (wrapColumn.isPresent())
      {
        ldifWriter.setWrapColumn(wrapColumn.getValue());
      }
      else
      {
        ldifWriter.setWrapColumn(WRAP_COLUMN);
      }

      if (ldapURL != null)
      {
        try
        {
          ldifWriter.writeComment(
               INFO_LDIFSEARCH_ENTRIES_MATCHING_URL.get(ldapURL.toString()),
               false, true);
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
        }
      }

      closeOutputStream = false;
      return ldifWriter;
    }
    finally
    {
      if (closeOutputStream)
      {
        try
        {
          outputStream.close();
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
        }
      }
    }
  }



  /**
   * Indicates whether the given entry matches the criteria in the provided LDAP
   * URL.
   *
   * @param  url    The URL for which to make the determination.
   * @param  entry  The entry for which to make the determination.
   *
   * @return  {@code true} if the entry matches the criteria in the LDAP URL, or
   *          {@code false} if not.
   */
  private boolean urlMatchesEntry(@NotNull final LDAPURL url,
                                  @NotNull final Entry entry)
  {
    try
    {
      return (entry.matchesBaseAndScope(url.getBaseDN(), url.getScope()) &&
           url.getFilter().matchesEntry(entry));
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      return false;
    }
  }



  /**
   * Writes a line-wrapped, commented version of the provided message to
   * standard output.
   *
   * @param  message  The message to be written.
   */
  private void commentToOut(@NotNull final String message)
  {
    getOut().flush();
    for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2)))
    {
      out("# " + line);
    }
    getOut().flush();
  }



  /**
   * Writes a line-wrapped, commented version of the provided message to
   * standard error.
   *
   * @param  message  The message to be written.
   */
  private void commentToErr(@NotNull final String message)
  {
    getErr().flush();
    for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2)))
    {
      err("# " + line);
    }
    getErr().flush();
  }



  /**
   * Writes the provided message and sets it as the completion message.
   *
   * @param  isError  Indicates whether the message should be written to
   *                  standard error rather than standard output.
   * @param  message  The message to be written.
   */
  private void logCompletionMessage(final boolean isError,
                                    @NotNull final String message)
  {
    completionMessage.compareAndSet(null, message);

    if (! outputFile.isPresent())
    {
      resultWriter.writeComment(message);
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  @NotNull()
  public LinkedHashMap getExampleUsages()
  {
    final LinkedHashMap examples = new LinkedHashMap<>();

    examples.put(
         new String[]
         {
           "--ldifFile", "data.ldif",
           "(uid=jdoe)"
         },
         INFO_LDIFSEARCH_EXAMPLE_1.get());

    examples.put(
         new String[]
         {
           "--ldifFile", "data.ldif",
           "--outputFile", "people.ldif",
           "--baseDN", "dc=example,dc=com",
           "--scope", "sub",
           "(objectClass=person)",
           "givenName",
           "sn",
           "cn",
         },
         INFO_LDIFSEARCH_EXAMPLE_2.get());

    return examples;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy