001/*
002 * Copyright 2017-2023 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2023 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2017-2023 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.tools;
037
038
039
040import java.io.BufferedReader;
041import java.io.File;
042import java.io.FileOutputStream;
043import java.io.FileReader;
044import java.io.IOException;
045import java.io.OutputStream;
046import java.io.PrintStream;
047import java.math.BigDecimal;
048import java.util.ArrayList;
049import java.util.Arrays;
050import java.util.Collections;
051import java.util.EnumSet;
052import java.util.Iterator;
053import java.util.LinkedHashMap;
054import java.util.List;
055import java.util.Map;
056import java.util.Set;
057import java.util.StringTokenizer;
058import java.util.concurrent.atomic.AtomicLong;
059import java.util.zip.GZIPOutputStream;
060
061import com.unboundid.asn1.ASN1OctetString;
062import com.unboundid.ldap.sdk.Control;
063import com.unboundid.ldap.sdk.DN;
064import com.unboundid.ldap.sdk.DereferencePolicy;
065import com.unboundid.ldap.sdk.ExtendedResult;
066import com.unboundid.ldap.sdk.Filter;
067import com.unboundid.ldap.sdk.LDAPConnectionOptions;
068import com.unboundid.ldap.sdk.LDAPConnection;
069import com.unboundid.ldap.sdk.LDAPConnectionPool;
070import com.unboundid.ldap.sdk.LDAPException;
071import com.unboundid.ldap.sdk.LDAPResult;
072import com.unboundid.ldap.sdk.LDAPSearchException;
073import com.unboundid.ldap.sdk.LDAPURL;
074import com.unboundid.ldap.sdk.ResultCode;
075import com.unboundid.ldap.sdk.SearchRequest;
076import com.unboundid.ldap.sdk.SearchResult;
077import com.unboundid.ldap.sdk.SearchScope;
078import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler;
079import com.unboundid.ldap.sdk.Version;
080import com.unboundid.ldap.sdk.controls.AssertionRequestControl;
081import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
082import com.unboundid.ldap.sdk.controls.DraftLDUPSubentriesRequestControl;
083import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl;
084import com.unboundid.ldap.sdk.controls.MatchedValuesFilter;
085import com.unboundid.ldap.sdk.controls.MatchedValuesRequestControl;
086import com.unboundid.ldap.sdk.controls.PersistentSearchChangeType;
087import com.unboundid.ldap.sdk.controls.PersistentSearchRequestControl;
088import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl;
089import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
090import com.unboundid.ldap.sdk.controls.RFC3672SubentriesRequestControl;
091import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl;
092import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
093import com.unboundid.ldap.sdk.controls.SortKey;
094import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl;
095import com.unboundid.ldap.sdk.persist.PersistUtils;
096import com.unboundid.ldap.sdk.transformations.EntryTransformation;
097import com.unboundid.ldap.sdk.transformations.ExcludeAttributeTransformation;
098import com.unboundid.ldap.sdk.transformations.MoveSubtreeTransformation;
099import com.unboundid.ldap.sdk.transformations.RedactAttributeTransformation;
100import com.unboundid.ldap.sdk.transformations.RenameAttributeTransformation;
101import com.unboundid.ldap.sdk.transformations.ScrambleAttributeTransformation;
102import com.unboundid.ldap.sdk.unboundidds.controls.AccessLogFieldRequestControl;
103import com.unboundid.ldap.sdk.unboundidds.controls.AccountUsableRequestControl;
104import com.unboundid.ldap.sdk.unboundidds.controls.ExcludeBranchRequestControl;
105import com.unboundid.ldap.sdk.unboundidds.controls.
106            GenerateAccessTokenRequestControl;
107import com.unboundid.ldap.sdk.unboundidds.controls.
108            GetAuthorizationEntryRequestControl;
109import com.unboundid.ldap.sdk.unboundidds.controls.
110            GetBackendSetIDRequestControl;
111import com.unboundid.ldap.sdk.unboundidds.controls.
112            GetEffectiveRightsRequestControl;
113import com.unboundid.ldap.sdk.unboundidds.controls.
114            GetRecentLoginHistoryRequestControl;
115import com.unboundid.ldap.sdk.unboundidds.controls.GetServerIDRequestControl;
116import com.unboundid.ldap.sdk.unboundidds.controls.
117            GetUserResourceLimitsRequestControl;
118import com.unboundid.ldap.sdk.unboundidds.controls.
119            JSONFormattedControlDecodeBehavior;
120import com.unboundid.ldap.sdk.unboundidds.controls.JSONFormattedRequestControl;
121import com.unboundid.ldap.sdk.unboundidds.controls.JSONFormattedResponseControl;
122import com.unboundid.ldap.sdk.unboundidds.controls.JoinBaseDN;
123import com.unboundid.ldap.sdk.unboundidds.controls.JoinRequestControl;
124import com.unboundid.ldap.sdk.unboundidds.controls.JoinRequestValue;
125import com.unboundid.ldap.sdk.unboundidds.controls.JoinRule;
126import com.unboundid.ldap.sdk.unboundidds.controls.
127            MatchingEntryCountRequestControl;
128import com.unboundid.ldap.sdk.unboundidds.controls.
129            MatchingEntryCountRequestControlProperties;
130import com.unboundid.ldap.sdk.unboundidds.controls.
131            OperationPurposeRequestControl;
132import com.unboundid.ldap.sdk.unboundidds.controls.
133            OverrideSearchLimitsRequestControl;
134import com.unboundid.ldap.sdk.unboundidds.controls.PasswordPolicyRequestControl;
135import com.unboundid.ldap.sdk.unboundidds.controls.
136            PermitUnindexedSearchRequestControl;
137import com.unboundid.ldap.sdk.unboundidds.controls.
138            RealAttributesOnlyRequestControl;
139import com.unboundid.ldap.sdk.unboundidds.controls.
140            RejectUnindexedSearchRequestControl;
141import com.unboundid.ldap.sdk.unboundidds.controls.
142            ReturnConflictEntriesRequestControl;
143import com.unboundid.ldap.sdk.unboundidds.controls.
144            RouteToBackendSetRequestControl;
145import com.unboundid.ldap.sdk.unboundidds.controls.RouteToServerRequestControl;
146import com.unboundid.ldap.sdk.unboundidds.controls.
147            SoftDeletedEntryAccessRequestControl;
148import com.unboundid.ldap.sdk.unboundidds.controls.
149            SuppressOperationalAttributeUpdateRequestControl;
150import com.unboundid.ldap.sdk.unboundidds.controls.SuppressType;
151import com.unboundid.ldap.sdk.unboundidds.controls.
152            VirtualAttributesOnlyRequestControl;
153import com.unboundid.ldap.sdk.unboundidds.extensions.
154            StartAdministrativeSessionExtendedRequest;
155import com.unboundid.ldap.sdk.unboundidds.extensions.
156            StartAdministrativeSessionPostConnectProcessor;
157import com.unboundid.ldif.LDIFWriter;
158import com.unboundid.util.Debug;
159import com.unboundid.util.FilterFileReader;
160import com.unboundid.util.FixedRateBarrier;
161import com.unboundid.util.LDAPCommandLineTool;
162import com.unboundid.util.NotNull;
163import com.unboundid.util.Nullable;
164import com.unboundid.util.OutputFormat;
165import com.unboundid.util.PassphraseEncryptedOutputStream;
166import com.unboundid.util.StaticUtils;
167import com.unboundid.util.TeeOutputStream;
168import com.unboundid.util.ThreadSafety;
169import com.unboundid.util.ThreadSafetyLevel;
170import com.unboundid.util.args.ArgumentException;
171import com.unboundid.util.args.ArgumentParser;
172import com.unboundid.util.args.BooleanArgument;
173import com.unboundid.util.args.BooleanValueArgument;
174import com.unboundid.util.args.ControlArgument;
175import com.unboundid.util.args.DNArgument;
176import com.unboundid.util.args.FileArgument;
177import com.unboundid.util.args.FilterArgument;
178import com.unboundid.util.args.IntegerArgument;
179import com.unboundid.util.args.ScopeArgument;
180import com.unboundid.util.args.StringArgument;
181import com.unboundid.util.json.JSONBoolean;
182import com.unboundid.util.json.JSONNumber;
183import com.unboundid.util.json.JSONObject;
184import com.unboundid.util.json.JSONString;
185import com.unboundid.util.json.JSONValue;
186
187import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;
188
189
190
191/**
192 * This class provides an implementation of an LDAP command-line tool that may
193 * be used to issue searches to a directory server.  Matching entries will be
194 * output in the LDAP data interchange format (LDIF), to standard output and/or
195 * to a specified file.  This is a much more full-featured tool than the
196 * {@link com.unboundid.ldap.sdk.examples.LDAPSearch} tool, and includes a
197 * number of features only intended for use with Ping Identity, UnboundID, and
198 * Nokia/Alcatel-Lucent 8661 server products.
199 * <BR>
200 * <BLOCKQUOTE>
201 *   <B>NOTE:</B>  This class, and other classes within the
202 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
203 *   supported for use against Ping Identity, UnboundID, and
204 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
205 *   for proprietary functionality or for external specifications that are not
206 *   considered stable or mature enough to be guaranteed to work in an
207 *   interoperable way with other types of LDAP servers.
208 * </BLOCKQUOTE>
209 */
210@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
211public final class LDAPSearch
212       extends LDAPCommandLineTool
213       implements UnsolicitedNotificationHandler
214{
215  /**
216   * The column at which to wrap long lines.
217   */
218  private static int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
219
220
221
222  // The set of arguments supported by this program.
223  @Nullable private BooleanArgument accountUsable = null;
224  @Nullable private BooleanArgument authorizationIdentity = null;
225  @Nullable private BooleanArgument compressOutput = null;
226  @Nullable private BooleanArgument continueOnError = null;
227  @Nullable private BooleanArgument countEntries = null;
228  @Nullable private BooleanArgument dontWrap = null;
229  @Nullable private BooleanArgument draftLDUPSubentries = null;
230  @Nullable private BooleanArgument dryRun = null;
231  @Nullable private BooleanArgument encryptOutput = null;
232  @Nullable private BooleanArgument followReferrals = null;
233  @Nullable private BooleanArgument generateAccessToken = null;
234  @Nullable private BooleanArgument getBackendSetID = null;
235  @Nullable private BooleanArgument getServerID = null;
236  @Nullable private BooleanArgument getRecentLoginHistory = null;
237  @Nullable private BooleanArgument hideRedactedValueCount = null;
238  @Nullable private BooleanArgument getUserResourceLimits = null;
239  @Nullable private BooleanArgument includeReplicationConflictEntries = null;
240  @Nullable private BooleanArgument joinRequireMatch = null;
241  @Nullable private BooleanArgument manageDsaIT = null;
242  @Nullable private BooleanArgument permitUnindexedSearch = null;
243  @Nullable private BooleanArgument realAttributesOnly = null;
244  @Nullable private BooleanArgument rejectUnindexedSearch = null;
245  @Nullable private BooleanArgument requireMatch = null;
246  @Nullable private BooleanArgument retryFailedOperations = null;
247  @Nullable private BooleanArgument separateOutputFilePerSearch = null;
248  @Nullable private BooleanArgument suppressBase64EncodedValueComments = null;
249  @Nullable private BooleanArgument teeResultsToStandardOut = null;
250  @Nullable private BooleanArgument useAdministrativeSession = null;
251  @Nullable private BooleanArgument useJSONFormattedRequestControls = null;
252  @Nullable private BooleanArgument usePasswordPolicyControl = null;
253  @Nullable private BooleanArgument terse = null;
254  @Nullable private BooleanArgument typesOnly = null;
255  @Nullable private BooleanArgument verbose = null;
256  @Nullable private BooleanArgument virtualAttributesOnly = null;
257  @Nullable private BooleanValueArgument rfc3672Subentries = null;
258  @Nullable private ControlArgument bindControl = null;
259  @Nullable private ControlArgument searchControl = null;
260  @Nullable private DNArgument baseDN = null;
261  @Nullable private DNArgument excludeBranch = null;
262  @Nullable private DNArgument moveSubtreeFrom = null;
263  @Nullable private DNArgument moveSubtreeTo = null;
264  @Nullable private DNArgument proxyV1As = null;
265  @Nullable private FileArgument encryptionPassphraseFile = null;
266  @Nullable private FileArgument filterFile = null;
267  @Nullable private FileArgument ldapURLFile = null;
268  @Nullable private FileArgument outputFile = null;
269  @Nullable private FilterArgument assertionFilter = null;
270  @Nullable private FilterArgument filter = null;
271  @Nullable private FilterArgument joinFilter = null;
272  @Nullable private FilterArgument matchedValuesFilter = null;
273  @Nullable private IntegerArgument joinSizeLimit = null;
274  @Nullable private IntegerArgument ratePerSecond = null;
275  @Nullable private IntegerArgument scrambleRandomSeed = null;
276  @Nullable private IntegerArgument simplePageSize = null;
277  @Nullable private IntegerArgument sizeLimit = null;
278  @Nullable private IntegerArgument timeLimitSeconds = null;
279  @Nullable private IntegerArgument wrapColumn = null;
280  @Nullable private ScopeArgument joinScope = null;
281  @Nullable private ScopeArgument scope = null;
282  @Nullable private StringArgument accessLogField = null;
283  @Nullable private StringArgument dereferencePolicy = null;
284  @Nullable private StringArgument excludeAttribute = null;
285  @Nullable private StringArgument getAuthorizationEntryAttribute = null;
286  @Nullable private StringArgument getEffectiveRightsAttribute = null;
287  @Nullable private StringArgument getEffectiveRightsAuthzID = null;
288  @Nullable private StringArgument includeSoftDeletedEntries = null;
289  @Nullable private StringArgument joinBaseDN = null;
290  @Nullable private StringArgument joinRequestedAttribute = null;
291  @Nullable private StringArgument joinRule = null;
292  @Nullable private StringArgument matchingEntryCountControl = null;
293  @Nullable private StringArgument operationPurpose = null;
294  @Nullable private StringArgument outputFormat = null;
295  @Nullable private StringArgument overrideSearchLimit = null;
296  @Nullable private StringArgument persistentSearch = null;
297  @Nullable private StringArgument proxyAs = null;
298  @Nullable private StringArgument redactAttribute = null;
299  @Nullable private StringArgument renameAttributeFrom = null;
300  @Nullable private StringArgument renameAttributeTo = null;
301  @Nullable private StringArgument requestedAttribute = null;
302  @Nullable private StringArgument routeToBackendSet = null;
303  @Nullable private StringArgument routeToServer = null;
304  @Nullable private StringArgument scrambleAttribute = null;
305  @Nullable private StringArgument scrambleJSONField = null;
306  @Nullable private StringArgument sortOrder = null;
307  @Nullable private StringArgument suppressOperationalAttributeUpdates = null;
308  @Nullable private StringArgument virtualListView = null;
309
310  // The argument parser used by this tool.
311  @Nullable private volatile ArgumentParser parser = null;
312
313  // Controls that should be sent to the server but need special validation.
314  @Nullable private volatile JoinRequestControl joinRequestControl = null;
315  @NotNull private final List<RouteToBackendSetRequestControl>
316       routeToBackendSetRequestControls = new ArrayList<>(10);
317  @Nullable private volatile MatchedValuesRequestControl
318       matchedValuesRequestControl = null;
319  @Nullable private volatile MatchingEntryCountRequestControl
320       matchingEntryCountRequestControl = null;
321  @Nullable private volatile OverrideSearchLimitsRequestControl
322       overrideSearchLimitsRequestControl = null;
323  @Nullable private volatile PersistentSearchRequestControl
324       persistentSearchRequestControl = null;
325  @Nullable private volatile ServerSideSortRequestControl sortRequestControl =
326       null;
327  @Nullable private volatile VirtualListViewRequestControl vlvRequestControl =
328       null;
329
330  // Other values decoded from arguments.
331  @Nullable private volatile DereferencePolicy derefPolicy = null;
332
333  // The print streams used for standard output and error.
334  @NotNull private final AtomicLong outputFileCounter = new AtomicLong(1);
335  @Nullable private volatile PrintStream errStream = null;
336  @Nullable private volatile PrintStream outStream = null;
337
338  // The LDAP result writer for this tool.
339  @NotNull private volatile LDAPResultWriter resultWriter;
340
341  // The list of entry transformations to apply.
342  @Nullable private volatile List<EntryTransformation> entryTransformations =
343       null;
344
345  // The encryption passphrase to use if the output is to be encrypted.
346  @Nullable private String encryptionPassphrase = null;
347
348
349
350  /**
351   * Runs this tool with the provided command-line arguments.  It will use the
352   * JVM-default streams for standard input, output, and error.
353   *
354   * @param  args  The command-line arguments to provide to this program.
355   */
356  public static void main(@NotNull final String... args)
357  {
358    final ResultCode resultCode = main(System.out, System.err, args);
359    if (resultCode != ResultCode.SUCCESS)
360    {
361      System.exit(Math.min(resultCode.intValue(), 255));
362    }
363  }
364
365
366
367  /**
368   * Runs this tool with the provided streams and command-line arguments.
369   *
370   * @param  out   The output stream to use for standard output.  If this is
371   *               {@code null}, then standard output will be suppressed.
372   * @param  err   The output stream to use for standard error.  If this is
373   *               {@code null}, then standard error will be suppressed.
374   * @param  args  The command-line arguments provided to this program.
375   *
376   * @return  The result code obtained when running the tool.  Any result code
377   *          other than {@link ResultCode#SUCCESS} indicates an error.
378   */
379  @NotNull()
380  public static ResultCode main(@Nullable final OutputStream out,
381                                @Nullable final OutputStream err,
382                                @NotNull final String... args)
383  {
384    final LDAPSearch tool = new LDAPSearch(out, err);
385    return tool.runTool(args);
386  }
387
388
389
390  /**
391   * Creates a new instance of this tool with the provided streams.
392   *
393   * @param  out  The output stream to use for standard output.  If this is
394   *              {@code null}, then standard output will be suppressed.
395   * @param  err  The output stream to use for standard error.  If this is
396   *              {@code null}, then standard error will be suppressed.
397   */
398  public LDAPSearch(@Nullable final OutputStream out,
399                    @Nullable final OutputStream err)
400  {
401    super(out, err);
402
403    resultWriter = new LDIFLDAPResultWriter(getOut(), WRAP_COLUMN);
404  }
405
406
407
408  /**
409   * {@inheritDoc}
410   */
411  @Override()
412  @NotNull()
413  public String getToolName()
414  {
415    return "ldapsearch";
416  }
417
418
419
420  /**
421   * {@inheritDoc}
422   */
423  @Override()
424  @NotNull()
425  public String getToolDescription()
426  {
427    return INFO_LDAPSEARCH_TOOL_DESCRIPTION.get();
428  }
429
430
431
432  /**
433   * {@inheritDoc}
434   */
435  @Override()
436  @NotNull()
437  public List<String> getAdditionalDescriptionParagraphs()
438  {
439    return Arrays.asList(
440         INFO_LDAPSEARCH_ADDITIONAL_DESCRIPTION_PARAGRAPH_1.get(),
441         INFO_LDAPSEARCH_ADDITIONAL_DESCRIPTION_PARAGRAPH_2.get());
442  }
443
444
445
446  /**
447   * {@inheritDoc}
448   */
449  @Override()
450  @NotNull()
451  public String getToolVersion()
452  {
453    return Version.NUMERIC_VERSION_STRING;
454  }
455
456
457
458  /**
459   * {@inheritDoc}
460   */
461  @Override()
462  public int getMinTrailingArguments()
463  {
464    return 0;
465  }
466
467
468
469  /**
470   * {@inheritDoc}
471   */
472  @Override()
473  public int getMaxTrailingArguments()
474  {
475    return -1;
476  }
477
478
479
480  /**
481   * {@inheritDoc}
482   */
483  @Override()
484  @NotNull()
485  public String getTrailingArgumentsPlaceholder()
486  {
487    return INFO_LDAPSEARCH_TRAILING_ARGS_PLACEHOLDER.get();
488  }
489
490
491
492  /**
493   * {@inheritDoc}
494   */
495  @Override()
496  public boolean supportsInteractiveMode()
497  {
498    return true;
499  }
500
501
502
503  /**
504   * {@inheritDoc}
505   */
506  @Override()
507  public boolean defaultsToInteractiveMode()
508  {
509    return true;
510  }
511
512
513
514  /**
515   * {@inheritDoc}
516   */
517  @Override()
518  public boolean supportsPropertiesFile()
519  {
520    return true;
521  }
522
523
524
525  /**
526   * {@inheritDoc}
527   */
528  @Override()
529  protected boolean defaultToPromptForBindPassword()
530  {
531    return true;
532  }
533
534
535
536  /**
537   * {@inheritDoc}
538   */
539  @Override()
540  protected boolean includeAlternateLongIdentifiers()
541  {
542    return true;
543  }
544
545
546
547  /**
548   * {@inheritDoc}
549   */
550  @Override()
551  protected boolean supportsSSLDebugging()
552  {
553    return true;
554  }
555
556
557
558  /**
559   * {@inheritDoc}
560   */
561  @Override()
562  @NotNull()
563  protected Set<Character> getSuppressedShortIdentifiers()
564  {
565    return Collections.singleton('T');
566  }
567
568
569
570  /**
571   * {@inheritDoc}
572   */
573  @Override()
574  public void addNonLDAPArguments(@NotNull final ArgumentParser parser)
575         throws ArgumentException
576  {
577    this.parser = parser;
578
579    baseDN = new DNArgument('b', "baseDN", false, 1, null,
580         INFO_LDAPSEARCH_ARG_DESCRIPTION_BASE_DN.get());
581    baseDN.addLongIdentifier("base-dn", true);
582    baseDN.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
583    parser.addArgument(baseDN);
584
585    scope = new ScopeArgument('s', "scope", false, null,
586         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCOPE.get(), SearchScope.SUB);
587    scope.addLongIdentifier("searchScope", true);
588    scope.addLongIdentifier("search-scope", true);
589    scope.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
590    parser.addArgument(scope);
591
592    sizeLimit = new IntegerArgument('z', "sizeLimit", false, 1, null,
593         INFO_LDAPSEARCH_ARG_DESCRIPTION_SIZE_LIMIT.get(), 0,
594         Integer.MAX_VALUE, 0);
595    sizeLimit.addLongIdentifier("size-limit", true);
596    sizeLimit.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
597    parser.addArgument(sizeLimit);
598
599    timeLimitSeconds = new IntegerArgument('l', "timeLimitSeconds", false, 1,
600         null, INFO_LDAPSEARCH_ARG_DESCRIPTION_TIME_LIMIT.get(), 0,
601         Integer.MAX_VALUE, 0);
602    timeLimitSeconds.addLongIdentifier("timeLimit", true);
603    timeLimitSeconds.addLongIdentifier("time-limit-seconds", true);
604    timeLimitSeconds.addLongIdentifier("time-limit", true);
605    timeLimitSeconds.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
606    parser.addArgument(timeLimitSeconds);
607
608    final Set<String> derefAllowedValues =
609         StaticUtils.setOf("never", "always", "search", "find");
610    dereferencePolicy = new StringArgument('a', "dereferencePolicy", false, 1,
611         "{never|always|search|find}",
612         INFO_LDAPSEARCH_ARG_DESCRIPTION_DEREFERENCE_POLICY.get(),
613         derefAllowedValues, "never");
614    dereferencePolicy.addLongIdentifier("dereference-policy", true);
615    dereferencePolicy.setArgumentGroupName(
616         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
617    parser.addArgument(dereferencePolicy);
618
619    typesOnly = new BooleanArgument('A', "typesOnly", 1,
620         INFO_LDAPSEARCH_ARG_DESCRIPTION_TYPES_ONLY.get());
621    typesOnly.addLongIdentifier("types-only", true);
622    typesOnly.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
623    parser.addArgument(typesOnly);
624
625    requestedAttribute = new StringArgument(null, "requestedAttribute", false,
626         0, INFO_PLACEHOLDER_ATTR.get(),
627         INFO_LDAPSEARCH_ARG_DESCRIPTION_REQUESTED_ATTR.get());
628    requestedAttribute.addLongIdentifier("requested-attribute", true);
629    requestedAttribute.setArgumentGroupName(
630         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
631    parser.addArgument(requestedAttribute);
632
633    filter = new FilterArgument(null, "filter", false, 0,
634         INFO_PLACEHOLDER_FILTER.get(),
635         INFO_LDAPSEARCH_ARG_DESCRIPTION_FILTER.get());
636    filter.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
637    parser.addArgument(filter);
638
639    filterFile = new FileArgument('f', "filterFile", false, 0, null,
640         INFO_LDAPSEARCH_ARG_DESCRIPTION_FILTER_FILE.get(), true, true,
641         true, false);
642    filterFile.addLongIdentifier("filename", true);
643    filterFile.addLongIdentifier("filter-file", true);
644    filterFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
645    parser.addArgument(filterFile);
646
647    ldapURLFile = new FileArgument(null, "ldapURLFile", false, 0, null,
648         INFO_LDAPSEARCH_ARG_DESCRIPTION_LDAP_URL_FILE.get(), true, true,
649         true, false);
650    ldapURLFile.addLongIdentifier("ldap-url-file", true);
651    ldapURLFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
652    parser.addArgument(ldapURLFile);
653
654    followReferrals = new BooleanArgument(null, "followReferrals", 1,
655         INFO_LDAPSEARCH_ARG_DESCRIPTION_FOLLOW_REFERRALS.get());
656    followReferrals.addLongIdentifier("follow-referrals", true);
657    followReferrals.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
658    parser.addArgument(followReferrals);
659
660    retryFailedOperations = new BooleanArgument(null, "retryFailedOperations",
661         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_RETRY_FAILED_OPERATIONS.get());
662    retryFailedOperations.addLongIdentifier("retry-failed-operations", true);
663    retryFailedOperations.setArgumentGroupName(
664         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
665    parser.addArgument(retryFailedOperations);
666
667    continueOnError = new BooleanArgument('c', "continueOnError", 1,
668         INFO_LDAPSEARCH_ARG_DESCRIPTION_CONTINUE_ON_ERROR.get());
669    continueOnError.addLongIdentifier("continue-on-error", true);
670    continueOnError.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
671    parser.addArgument(continueOnError);
672
673    ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1,
674         INFO_PLACEHOLDER_NUM.get(),
675         INFO_LDAPSEARCH_ARG_DESCRIPTION_RATE_PER_SECOND.get(), 1,
676         Integer.MAX_VALUE);
677    ratePerSecond.addLongIdentifier("rate-per-second", true);
678    ratePerSecond.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
679    parser.addArgument(ratePerSecond);
680
681    useAdministrativeSession = new BooleanArgument(null,
682         "useAdministrativeSession", 1,
683         INFO_LDAPSEARCH_ARG_DESCRIPTION_USE_ADMIN_SESSION.get());
684    useAdministrativeSession.addLongIdentifier("use-administrative-session",
685         true);
686    useAdministrativeSession.setArgumentGroupName(
687         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
688    parser.addArgument(useAdministrativeSession);
689
690    dryRun = new BooleanArgument('n', "dryRun", 1,
691         INFO_LDAPSEARCH_ARG_DESCRIPTION_DRY_RUN.get());
692    dryRun.addLongIdentifier("dry-run", true);
693    dryRun.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
694    parser.addArgument(dryRun);
695
696    wrapColumn = new IntegerArgument(null, "wrapColumn", false, 1, null,
697         INFO_LDAPSEARCH_ARG_DESCRIPTION_WRAP_COLUMN.get(), 0,
698         Integer.MAX_VALUE);
699    wrapColumn.addLongIdentifier("wrap-column", true);
700    wrapColumn.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
701    parser.addArgument(wrapColumn);
702
703    dontWrap = new BooleanArgument('T', "dontWrap", 1,
704         INFO_LDAPSEARCH_ARG_DESCRIPTION_DONT_WRAP.get());
705    dontWrap.addLongIdentifier("doNotWrap", true);
706    dontWrap.addLongIdentifier("dont-wrap", true);
707    dontWrap.addLongIdentifier("do-not-wrap", true);
708    dontWrap.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
709    parser.addArgument(dontWrap);
710
711    suppressBase64EncodedValueComments = new BooleanArgument(null,
712         "suppressBase64EncodedValueComments", 1,
713         INFO_LDAPSEARCH_ARG_DESCRIPTION_SUPPRESS_BASE64_COMMENTS.get());
714    suppressBase64EncodedValueComments.addLongIdentifier(
715         "suppress-base64-encoded-value-comments", true);
716    suppressBase64EncodedValueComments.setArgumentGroupName(
717         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
718    parser.addArgument(suppressBase64EncodedValueComments);
719
720    countEntries = new BooleanArgument(null, "countEntries", 1,
721         INFO_LDAPSEARCH_ARG_DESCRIPTION_COUNT_ENTRIES.get());
722    countEntries.addLongIdentifier("count-entries", true);
723    countEntries.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
724    countEntries.setHidden(true);
725    parser.addArgument(countEntries);
726
727    outputFile = new FileArgument(null, "outputFile", false, 1, null,
728         INFO_LDAPSEARCH_ARG_DESCRIPTION_OUTPUT_FILE.get(), false, true, true,
729         false);
730    outputFile.addLongIdentifier("output-file", true);
731    outputFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
732    parser.addArgument(outputFile);
733
734    compressOutput = new BooleanArgument(null, "compressOutput", 1,
735         INFO_LDAPSEARCH_ARG_DESCRIPTION_COMPRESS_OUTPUT.get());
736    compressOutput.addLongIdentifier("compress-output", true);
737    compressOutput.addLongIdentifier("compress", true);
738    compressOutput.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
739    parser.addArgument(compressOutput);
740
741    encryptOutput = new BooleanArgument(null, "encryptOutput", 1,
742         INFO_LDAPSEARCH_ARG_DESCRIPTION_ENCRYPT_OUTPUT.get());
743    encryptOutput.addLongIdentifier("encrypt-output", true);
744    encryptOutput.addLongIdentifier("encrypt", true);
745    encryptOutput.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
746    parser.addArgument(encryptOutput);
747
748    encryptionPassphraseFile = new FileArgument(null,
749         "encryptionPassphraseFile", false, 1, null,
750         INFO_LDAPSEARCH_ARG_DESCRIPTION_ENCRYPTION_PW_FILE.get(), true, true,
751         true, false);
752    encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file",
753         true);
754    encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true);
755    encryptionPassphraseFile.addLongIdentifier("encryption-password-file",
756         true);
757    encryptionPassphraseFile.setArgumentGroupName(
758         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
759    parser.addArgument(encryptionPassphraseFile);
760
761    separateOutputFilePerSearch = new BooleanArgument(null,
762         "separateOutputFilePerSearch", 1,
763         INFO_LDAPSEARCH_ARG_DESCRIPTION_SEPARATE_OUTPUT_FILES.get());
764    separateOutputFilePerSearch.addLongIdentifier(
765         "separate-output-file-per-search", true);
766    separateOutputFilePerSearch.setArgumentGroupName(
767         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
768    parser.addArgument(separateOutputFilePerSearch);
769
770    teeResultsToStandardOut = new BooleanArgument(null,
771         "teeResultsToStandardOut", 1,
772         INFO_LDAPSEARCH_ARG_DESCRIPTION_TEE.get("outputFile"));
773    teeResultsToStandardOut.addLongIdentifier(
774         "tee-results-to-standard-out", true);
775    teeResultsToStandardOut.setArgumentGroupName(
776         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
777    parser.addArgument(teeResultsToStandardOut);
778
779    final Set<String> outputFormatAllowedValues = StaticUtils.setOf("ldif",
780         "json", "csv", "multi-valued-csv", "tab-delimited",
781         "multi-valued-tab-delimited", "dns-only", "values-only");
782    outputFormat = new StringArgument(null, "outputFormat", false, 1,
783         "{ldif|json|csv|multi-valued-csv|tab-delimited|" +
784              "multi-valued-tab-delimited|dns-only|values-only}",
785         INFO_LDAPSEARCH_ARG_DESCRIPTION_OUTPUT_FORMAT.get(
786              requestedAttribute.getIdentifierString(),
787              ldapURLFile.getIdentifierString()),
788         outputFormatAllowedValues, "ldif");
789    outputFormat.addLongIdentifier("output-format", true);
790    outputFormat.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
791    parser.addArgument(outputFormat);
792
793    requireMatch = new BooleanArgument(null, "requireMatch", 1,
794         INFO_LDAPSEARCH_ARG_DESCRIPTION_REQUIRE_MATCH.get(
795              getToolName(),
796              String.valueOf(ResultCode.NO_RESULTS_RETURNED)));
797    requireMatch.addLongIdentifier("require-match", true);
798    requireMatch.addLongIdentifier("requireMatchingEntry", true);
799    requireMatch.addLongIdentifier("require-matching-entry", true);
800    requireMatch.addLongIdentifier("requireMatchingEntries", true);
801    requireMatch.addLongIdentifier("require-matching-entries", true);
802    requireMatch.addLongIdentifier("requireEntry", true);
803    requireMatch.addLongIdentifier("require-entry", true);
804    requireMatch.addLongIdentifier("requireEntries", true);
805    requireMatch.addLongIdentifier("require-entries", true);
806    requireMatch.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
807    parser.addArgument(requireMatch);
808
809    terse = new BooleanArgument(null, "terse", 1,
810         INFO_LDAPSEARCH_ARG_DESCRIPTION_TERSE.get());
811    terse.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
812    parser.addArgument(terse);
813
814    verbose = new BooleanArgument('v', "verbose", 1,
815         INFO_LDAPSEARCH_ARG_DESCRIPTION_VERBOSE.get());
816    verbose.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
817    parser.addArgument(verbose);
818
819    bindControl = new ControlArgument(null, "bindControl", false, 0, null,
820         INFO_LDAPSEARCH_ARG_DESCRIPTION_BIND_CONTROL.get());
821    bindControl.addLongIdentifier("bind-control", true);
822    bindControl.setArgumentGroupName(
823         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
824    parser.addArgument(bindControl);
825
826    searchControl = new ControlArgument('J', "control", false, 0, null,
827         INFO_LDAPSEARCH_ARG_DESCRIPTION_SEARCH_CONTROL.get());
828    searchControl.addLongIdentifier("searchControl", true);
829    searchControl.addLongIdentifier("search-control", true);
830    searchControl.setArgumentGroupName(
831         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
832    parser.addArgument(searchControl);
833
834    accessLogField = new StringArgument(null, "accessLogField", false, 0,
835         INFO_LDAPSEARCH_ARG_PLACEHOLDER_NAME_VALUE.get(),
836         INFO_LDAPSEARCH_ARG_DESCRIPTION_ACCESS_LOG_FIELD.get());
837    accessLogField.addLongIdentifier("access-log-field", true);
838    accessLogField.setArgumentGroupName(
839         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
840    parser.addArgument(accessLogField);
841
842    accountUsable = new BooleanArgument(null, "accountUsable", 1,
843         INFO_LDAPSEARCH_ARG_DESCRIPTION_ACCOUNT_USABLE.get());
844    accountUsable.addLongIdentifier("account-usable", true);
845    accountUsable.setArgumentGroupName(
846         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
847    parser.addArgument(accountUsable);
848
849    authorizationIdentity = new BooleanArgument('E', "authorizationIdentity",
850         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_AUTHZ_IDENTITY.get());
851    authorizationIdentity.addLongIdentifier("reportAuthzID", true);
852    authorizationIdentity.addLongIdentifier("authorization-identity", true);
853    authorizationIdentity.addLongIdentifier("report-authzid", true);
854    authorizationIdentity.setArgumentGroupName(
855         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
856    parser.addArgument(authorizationIdentity);
857
858    assertionFilter = new FilterArgument(null, "assertionFilter", false, 1,
859         INFO_PLACEHOLDER_FILTER.get(),
860         INFO_LDAPSEARCH_ARG_DESCRIPTION_ASSERTION_FILTER.get());
861    assertionFilter.addLongIdentifier("assertion-filter", true);
862    assertionFilter.setArgumentGroupName(
863         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
864    parser.addArgument(assertionFilter);
865
866    excludeBranch = new DNArgument(null, "excludeBranch", false, 0, null,
867         INFO_LDAPSEARCH_ARG_DESCRIPTION_EXCLUDE_BRANCH.get());
868    excludeBranch.addLongIdentifier("exclude-branch", true);
869    excludeBranch.setArgumentGroupName(
870         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
871    parser.addArgument(excludeBranch);
872
873    generateAccessToken = new BooleanArgument(null, "generateAccessToken", 1,
874         INFO_LDAPSEARCH_ARG_DESCRIPTION_GENERATE_ACCESS_TOKEN.get());
875    generateAccessToken.addLongIdentifier("generate-access-token", true);
876    generateAccessToken.addLongIdentifier("requestAccessToken", true);
877    generateAccessToken.addLongIdentifier("request-access-token", true);
878    generateAccessToken.setArgumentGroupName(
879         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
880    parser.addArgument(generateAccessToken);
881
882    getAuthorizationEntryAttribute = new StringArgument(null,
883         "getAuthorizationEntryAttribute", false, 0,
884         INFO_PLACEHOLDER_ATTR.get(),
885         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_AUTHZ_ENTRY_ATTR.get());
886    getAuthorizationEntryAttribute.addLongIdentifier(
887         "get-authorization-entry-attribute", true);
888    getAuthorizationEntryAttribute.setArgumentGroupName(
889         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
890    parser.addArgument(getAuthorizationEntryAttribute);
891
892    getBackendSetID = new BooleanArgument(null, "getBackendSetID",
893         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_BACKEND_SET_ID.get());
894    getBackendSetID.addLongIdentifier("get-backend-set-id", true);
895    getBackendSetID.setArgumentGroupName(
896         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
897    parser.addArgument(getBackendSetID);
898
899    getEffectiveRightsAuthzID = new StringArgument('g',
900         "getEffectiveRightsAuthzID", false, 1,
901         INFO_PLACEHOLDER_AUTHZID.get(),
902         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_EFFECTIVE_RIGHTS_AUTHZID.get(
903              "getEffectiveRightsAttribute"));
904    getEffectiveRightsAuthzID.addLongIdentifier(
905         "get-effective-rights-authzid", true);
906    getEffectiveRightsAuthzID.setArgumentGroupName(
907         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
908    parser.addArgument(getEffectiveRightsAuthzID);
909
910    getEffectiveRightsAttribute = new StringArgument('e',
911         "getEffectiveRightsAttribute", false, 0,
912         INFO_PLACEHOLDER_ATTR.get(),
913         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_EFFECTIVE_RIGHTS_ATTR.get());
914    getEffectiveRightsAttribute.addLongIdentifier(
915         "get-effective-rights-attribute", true);
916    getEffectiveRightsAttribute.setArgumentGroupName(
917         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
918    parser.addArgument(getEffectiveRightsAttribute);
919
920    getRecentLoginHistory = new BooleanArgument(null, "getRecentLoginHistory",
921         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_RECENT_LOGIN_HISTORY.get());
922    getRecentLoginHistory.addLongIdentifier("get-recent-login-history", true);
923    getRecentLoginHistory.setArgumentGroupName(
924         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
925    parser.addArgument(getRecentLoginHistory);
926
927    getServerID = new BooleanArgument(null, "getServerID",
928         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_SERVER_ID.get());
929    getServerID.addLongIdentifier("get-server-id", true);
930    getServerID.setArgumentGroupName(
931         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
932    parser.addArgument(getServerID);
933
934    getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits",
935         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_USER_RESOURCE_LIMITS.get());
936    getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true);
937    getUserResourceLimits.setArgumentGroupName(
938         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
939    parser.addArgument(getUserResourceLimits);
940
941    includeReplicationConflictEntries = new BooleanArgument(null,
942         "includeReplicationConflictEntries", 1,
943         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_REPL_CONFLICTS.get());
944    includeReplicationConflictEntries.addLongIdentifier(
945         "include-replication-conflict-entries", true);
946    includeReplicationConflictEntries.setArgumentGroupName(
947         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
948    parser.addArgument(includeReplicationConflictEntries);
949
950    final Set<String> softDeleteAllowedValues = StaticUtils.setOf(
951         "with-non-deleted-entries", "without-non-deleted-entries",
952         "deleted-entries-in-undeleted-form");
953    includeSoftDeletedEntries = new StringArgument(null,
954         "includeSoftDeletedEntries", false, 1,
955         "{with-non-deleted-entries|without-non-deleted-entries|" +
956              "deleted-entries-in-undeleted-form}",
957         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_SOFT_DELETED.get(),
958         softDeleteAllowedValues);
959    includeSoftDeletedEntries.addLongIdentifier(
960         "include-soft-deleted-entries", true);
961    includeSoftDeletedEntries.setArgumentGroupName(
962         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
963    parser.addArgument(includeSoftDeletedEntries);
964
965    draftLDUPSubentries = new BooleanArgument(null, "draftLDUPSubentries", 1,
966         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_DRAFT_LDUP_SUBENTRIES.get());
967    draftLDUPSubentries.addLongIdentifier("draftIETFLDUPSubentries", true);
968    draftLDUPSubentries.addLongIdentifier("includeSubentries", true);
969    draftLDUPSubentries.addLongIdentifier("includeLDAPSubentries", true);
970    draftLDUPSubentries.addLongIdentifier("draft-ldup-subentries", true);
971    draftLDUPSubentries.addLongIdentifier("draft-ietf-ldup-subentries", true);
972    draftLDUPSubentries.addLongIdentifier("include-subentries", true);
973    draftLDUPSubentries.addLongIdentifier("include-ldap-subentries", true);
974    draftLDUPSubentries.setArgumentGroupName(
975         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
976    parser.addArgument(draftLDUPSubentries);
977
978    rfc3672Subentries = new BooleanValueArgument(null, "rfc3672Subentries",
979         false,
980         INFO_LDAPSEARCH_ARG_PLACEHOLDER_INCLUDE_RFC_3672_SUBENTRIES.get(),
981         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_RFC_3672_SUBENTRIES.get());
982    rfc3672Subentries.addLongIdentifier("rfc-3672-subentries", true);
983    rfc3672Subentries.addLongIdentifier("rfc3672-subentries", true);
984    rfc3672Subentries.setArgumentGroupName(
985         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
986    parser.addArgument(rfc3672Subentries);
987
988    joinRule = new StringArgument(null, "joinRule", false, 1,
989         "{dn:sourceAttr|reverse-dn:targetAttr|equals:sourceAttr:targetAttr|" +
990              "contains:sourceAttr:targetAttr }",
991         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_RULE.get());
992    joinRule.addLongIdentifier("join-rule", true);
993    joinRule.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
994    parser.addArgument(joinRule);
995
996    joinBaseDN = new StringArgument(null, "joinBaseDN", false, 1,
997         "{search-base|source-entry-dn|{dn}}",
998         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_BASE_DN.get());
999    joinBaseDN.addLongIdentifier("join-base-dn", true);
1000    joinBaseDN.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1001    parser.addArgument(joinBaseDN);
1002
1003    joinScope = new ScopeArgument(null, "joinScope", false, null,
1004         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_SCOPE.get());
1005    joinScope.addLongIdentifier("join-scope", true);
1006    joinScope.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1007    parser.addArgument(joinScope);
1008
1009    joinSizeLimit = new IntegerArgument(null, "joinSizeLimit", false, 1,
1010         INFO_PLACEHOLDER_NUM.get(),
1011         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_SIZE_LIMIT.get(), 0,
1012         Integer.MAX_VALUE);
1013    joinSizeLimit.addLongIdentifier("join-size-limit", true);
1014    joinSizeLimit.setArgumentGroupName(
1015         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1016    parser.addArgument(joinSizeLimit);
1017
1018    joinFilter = new FilterArgument(null, "joinFilter", false, 1, null,
1019         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_FILTER.get());
1020    joinFilter.addLongIdentifier("join-filter", true);
1021    joinFilter.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1022    parser.addArgument(joinFilter);
1023
1024    joinRequestedAttribute = new StringArgument(null, "joinRequestedAttribute",
1025         false, 0, INFO_PLACEHOLDER_ATTR.get(),
1026         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_ATTR.get());
1027    joinRequestedAttribute.addLongIdentifier("join-requested-attribute", true);
1028    joinRequestedAttribute.setArgumentGroupName(
1029         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1030    parser.addArgument(joinRequestedAttribute);
1031
1032    joinRequireMatch = new BooleanArgument(null, "joinRequireMatch", 1,
1033         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_REQUIRE_MATCH.get());
1034    joinRequireMatch.addLongIdentifier("join-require-match", true);
1035    joinRequireMatch.setArgumentGroupName(
1036         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1037    parser.addArgument(joinRequireMatch);
1038
1039    manageDsaIT = new BooleanArgument(null, "manageDsaIT", 1,
1040         INFO_LDAPSEARCH_ARG_DESCRIPTION_MANAGE_DSA_IT.get());
1041    manageDsaIT.addLongIdentifier("manage-dsa-it", true);
1042    manageDsaIT.setArgumentGroupName(
1043         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1044    parser.addArgument(manageDsaIT);
1045
1046    matchedValuesFilter = new FilterArgument(null, "matchedValuesFilter",
1047         false, 0, INFO_PLACEHOLDER_FILTER.get(),
1048         INFO_LDAPSEARCH_ARG_DESCRIPTION_MATCHED_VALUES_FILTER.get());
1049    matchedValuesFilter.addLongIdentifier("matched-values-filter", true);
1050    matchedValuesFilter.setArgumentGroupName(
1051         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1052    parser.addArgument(matchedValuesFilter);
1053
1054    matchingEntryCountControl = new StringArgument(null,
1055         "matchingEntryCountControl", false, 1,
1056         "{examineCount=NNN[:alwaysExamine][:allowUnindexed]" +
1057              "[:skipResolvingExplodedIndexes]" +
1058              "[:fastShortCircuitThreshold=NNN]" +
1059              "[:slowShortCircuitThreshold=NNN][:extendedResponseData]" +
1060              "[:debug]}",
1061         INFO_LDAPSEARCH_ARG_DESCRIPTION_MATCHING_ENTRY_COUNT_CONTROL.get());
1062    matchingEntryCountControl.addLongIdentifier("matchingEntryCount", true);
1063    matchingEntryCountControl.addLongIdentifier(
1064         "matching-entry-count-control", true);
1065    matchingEntryCountControl.addLongIdentifier("matching-entry-count", true);
1066    matchingEntryCountControl.setArgumentGroupName(
1067         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1068    parser.addArgument(matchingEntryCountControl);
1069
1070    operationPurpose = new StringArgument(null, "operationPurpose", false, 1,
1071         INFO_PLACEHOLDER_PURPOSE.get(),
1072         INFO_LDAPSEARCH_ARG_DESCRIPTION_OPERATION_PURPOSE.get());
1073    operationPurpose.addLongIdentifier("operation-purpose", true);
1074    operationPurpose.setArgumentGroupName(
1075         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1076    parser.addArgument(operationPurpose);
1077
1078    overrideSearchLimit = new StringArgument(null, "overrideSearchLimit",
1079         false, 0, INFO_LDAPSEARCH_NAME_VALUE_PLACEHOLDER.get(),
1080         INFO_LDAPSEARCH_ARG_DESCRIPTION_OVERRIDE_SEARCH_LIMIT.get());
1081    overrideSearchLimit.addLongIdentifier("overrideSearchLimits", true);
1082    overrideSearchLimit.addLongIdentifier("override-search-limit", true);
1083    overrideSearchLimit.addLongIdentifier("override-search-limits", true);
1084    overrideSearchLimit.setArgumentGroupName(
1085         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1086    parser.addArgument(overrideSearchLimit);
1087
1088    persistentSearch = new StringArgument('C', "persistentSearch", false, 1,
1089         "ps[:changetype[:changesonly[:entrychgcontrols]]]",
1090         INFO_LDAPSEARCH_ARG_DESCRIPTION_PERSISTENT_SEARCH.get());
1091    persistentSearch.addLongIdentifier("persistent-search", true);
1092    persistentSearch.setArgumentGroupName(
1093         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1094    parser.addArgument(persistentSearch);
1095
1096    permitUnindexedSearch = new BooleanArgument(null, "permitUnindexedSearch",
1097         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_PERMIT_UNINDEXED_SEARCH.get());
1098    permitUnindexedSearch.addLongIdentifier("permitUnindexedSearches", true);
1099    permitUnindexedSearch.addLongIdentifier("permitUnindexed", true);
1100    permitUnindexedSearch.addLongIdentifier("permitIfUnindexed", true);
1101    permitUnindexedSearch.addLongIdentifier("permit-unindexed-search", true);
1102    permitUnindexedSearch.addLongIdentifier("permit-unindexed-searches", true);
1103    permitUnindexedSearch.addLongIdentifier("permit-unindexed", true);
1104    permitUnindexedSearch.addLongIdentifier("permit-if-unindexed", true);
1105    permitUnindexedSearch.setArgumentGroupName(
1106         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1107    parser.addArgument(permitUnindexedSearch);
1108
1109    proxyAs = new StringArgument('Y', "proxyAs", false, 1,
1110         INFO_PLACEHOLDER_AUTHZID.get(),
1111         INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_AS.get());
1112    proxyAs.addLongIdentifier("proxy-as", true);
1113    proxyAs.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1114    parser.addArgument(proxyAs);
1115
1116    proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null,
1117         INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_V1_AS.get());
1118    proxyV1As.addLongIdentifier("proxy-v1-as", true);
1119    proxyV1As.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1120    parser.addArgument(proxyV1As);
1121
1122    rejectUnindexedSearch = new BooleanArgument(null, "rejectUnindexedSearch",
1123         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_REJECT_UNINDEXED_SEARCH.get());
1124    rejectUnindexedSearch.addLongIdentifier("rejectUnindexedSearches", true);
1125    rejectUnindexedSearch.addLongIdentifier("rejectUnindexed", true);
1126    rejectUnindexedSearch.addLongIdentifier("rejectIfUnindexed", true);
1127    rejectUnindexedSearch.addLongIdentifier("reject-unindexed-search", true);
1128    rejectUnindexedSearch.addLongIdentifier("reject-unindexed-searches", true);
1129    rejectUnindexedSearch.addLongIdentifier("reject-unindexed", true);
1130    rejectUnindexedSearch.addLongIdentifier("reject-if-unindexed", true);
1131    rejectUnindexedSearch.setArgumentGroupName(
1132         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1133    parser.addArgument(rejectUnindexedSearch);
1134
1135    routeToBackendSet = new StringArgument(null, "routeToBackendSet",
1136         false, 0,
1137         INFO_LDAPSEARCH_ARG_PLACEHOLDER_ROUTE_TO_BACKEND_SET.get(),
1138         INFO_LDAPSEARCH_ARG_DESCRIPTION_ROUTE_TO_BACKEND_SET.get());
1139    routeToBackendSet.addLongIdentifier("route-to-backend-set", true);
1140    routeToBackendSet.setArgumentGroupName(
1141         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1142    parser.addArgument(routeToBackendSet);
1143
1144    routeToServer = new StringArgument(null, "routeToServer", false, 1,
1145         INFO_LDAPSEARCH_ARG_PLACEHOLDER_ROUTE_TO_SERVER.get(),
1146         INFO_LDAPSEARCH_ARG_DESCRIPTION_ROUTE_TO_SERVER.get());
1147    routeToServer.addLongIdentifier("route-to-server", true);
1148    routeToServer.setArgumentGroupName(
1149         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1150    parser.addArgument(routeToServer);
1151
1152    final Set<String> suppressOperationalAttributeUpdatesAllowedValues =
1153         StaticUtils.setOf("last-access-time", "last-login-time",
1154              "last-login-ip", "lastmod");
1155    suppressOperationalAttributeUpdates = new StringArgument(null,
1156         "suppressOperationalAttributeUpdates", false, -1,
1157         INFO_PLACEHOLDER_ATTR.get(),
1158         INFO_LDAPSEARCH_ARG_DESCRIPTION_SUPPRESS_OP_ATTR_UPDATES.get(),
1159         suppressOperationalAttributeUpdatesAllowedValues);
1160    suppressOperationalAttributeUpdates.addLongIdentifier(
1161         "suppress-operational-attribute-updates", true);
1162    suppressOperationalAttributeUpdates.setArgumentGroupName(
1163         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1164    parser.addArgument(suppressOperationalAttributeUpdates);
1165
1166    usePasswordPolicyControl = new BooleanArgument(null,
1167         "usePasswordPolicyControl", 1,
1168         INFO_LDAPSEARCH_ARG_DESCRIPTION_PASSWORD_POLICY.get());
1169    usePasswordPolicyControl.addLongIdentifier("use-password-policy-control",
1170         true);
1171    usePasswordPolicyControl.setArgumentGroupName(
1172         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1173    parser.addArgument(usePasswordPolicyControl);
1174
1175    realAttributesOnly = new BooleanArgument(null, "realAttributesOnly", 1,
1176         INFO_LDAPSEARCH_ARG_DESCRIPTION_REAL_ATTRS_ONLY.get());
1177    realAttributesOnly.addLongIdentifier("real-attributes-only", true);
1178    realAttributesOnly.setArgumentGroupName(
1179         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1180    parser.addArgument(realAttributesOnly);
1181
1182    sortOrder = new StringArgument('S', "sortOrder", false, 1, null,
1183         INFO_LDAPSEARCH_ARG_DESCRIPTION_SORT_ORDER.get());
1184    sortOrder.addLongIdentifier("sort-order", true);
1185    sortOrder.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1186    parser.addArgument(sortOrder);
1187
1188    simplePageSize = new IntegerArgument(null, "simplePageSize", false, 1,
1189         null, INFO_LDAPSEARCH_ARG_DESCRIPTION_PAGE_SIZE.get(), 1,
1190         Integer.MAX_VALUE);
1191    simplePageSize.addLongIdentifier("simple-page-size", true);
1192    simplePageSize.setArgumentGroupName(
1193         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1194    parser.addArgument(simplePageSize);
1195
1196    virtualAttributesOnly = new BooleanArgument(null,
1197         "virtualAttributesOnly", 1,
1198         INFO_LDAPSEARCH_ARG_DESCRIPTION_VIRTUAL_ATTRS_ONLY.get());
1199    virtualAttributesOnly.addLongIdentifier("virtual-attributes-only", true);
1200    virtualAttributesOnly.setArgumentGroupName(
1201         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1202    parser.addArgument(virtualAttributesOnly);
1203
1204    virtualListView = new StringArgument('G', "virtualListView", false, 1,
1205         "{before:after:index:count | before:after:value}",
1206         INFO_LDAPSEARCH_ARG_DESCRIPTION_VLV.get("sortOrder"));
1207    virtualListView.addLongIdentifier("vlv", true);
1208    virtualListView.addLongIdentifier("virtual-list-view", true);
1209    virtualListView.setArgumentGroupName(
1210         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1211    parser.addArgument(virtualListView);
1212
1213    useJSONFormattedRequestControls = new BooleanArgument(null,
1214         "useJSONFormattedRequestControls", 1,
1215         INFO_LDAPSEARCH_ARG_DESCRIPTION_USE_JSON_FORMATTED_CONTROLS.get());
1216    useJSONFormattedRequestControls.addLongIdentifier(
1217         "use-json-formatted-request-controls", true);
1218    useJSONFormattedRequestControls.addLongIdentifier(
1219         "useJSONFormattedControls", true);
1220    useJSONFormattedRequestControls.addLongIdentifier(
1221         "use-json-formatted-controls", true);
1222    useJSONFormattedRequestControls.setArgumentGroupName(
1223         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1224    parser.addArgument(useJSONFormattedRequestControls);
1225
1226    excludeAttribute = new StringArgument(null, "excludeAttribute", false, 0,
1227         INFO_PLACEHOLDER_ATTR.get(),
1228         INFO_LDAPSEARCH_ARG_DESCRIPTION_EXCLUDE_ATTRIBUTE.get());
1229    excludeAttribute.addLongIdentifier("exclude-attribute", true);
1230    excludeAttribute.setArgumentGroupName(
1231         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1232    parser.addArgument(excludeAttribute);
1233
1234    redactAttribute = new StringArgument(null, "redactAttribute", false, 0,
1235         INFO_PLACEHOLDER_ATTR.get(),
1236         INFO_LDAPSEARCH_ARG_DESCRIPTION_REDACT_ATTRIBUTE.get());
1237    redactAttribute.addLongIdentifier("redact-attribute", true);
1238    redactAttribute.setArgumentGroupName(
1239         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1240    parser.addArgument(redactAttribute);
1241
1242    hideRedactedValueCount = new BooleanArgument(null, "hideRedactedValueCount",
1243         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_HIDE_REDACTED_VALUE_COUNT.get());
1244    hideRedactedValueCount.addLongIdentifier("hide-redacted-value-count", true);
1245    hideRedactedValueCount.setArgumentGroupName(
1246         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1247    parser.addArgument(hideRedactedValueCount);
1248
1249    scrambleAttribute = new StringArgument(null, "scrambleAttribute", false, 0,
1250         INFO_PLACEHOLDER_ATTR.get(),
1251         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_ATTRIBUTE.get());
1252    scrambleAttribute.addLongIdentifier("scramble-attribute", true);
1253    scrambleAttribute.setArgumentGroupName(
1254         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1255    parser.addArgument(scrambleAttribute);
1256
1257    scrambleJSONField = new StringArgument(null, "scrambleJSONField", false, 0,
1258         INFO_PLACEHOLDER_FIELD_NAME.get(),
1259         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_JSON_FIELD.get());
1260    scrambleJSONField.addLongIdentifier("scramble-json-field", true);
1261    scrambleJSONField.setArgumentGroupName(
1262         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1263    parser.addArgument(scrambleJSONField);
1264
1265    scrambleRandomSeed = new IntegerArgument(null, "scrambleRandomSeed", false,
1266         1, null, INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_RANDOM_SEED.get());
1267    scrambleRandomSeed.addLongIdentifier("scramble-random-seed", true);
1268    scrambleRandomSeed.setArgumentGroupName(
1269         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1270    parser.addArgument(scrambleRandomSeed);
1271
1272    renameAttributeFrom = new StringArgument(null, "renameAttributeFrom", false,
1273         0, INFO_PLACEHOLDER_ATTR.get(),
1274         INFO_LDAPSEARCH_ARG_DESCRIPTION_RENAME_ATTRIBUTE_FROM.get());
1275    renameAttributeFrom.addLongIdentifier("rename-attribute-from", true);
1276    renameAttributeFrom.setArgumentGroupName(
1277         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1278    parser.addArgument(renameAttributeFrom);
1279
1280    renameAttributeTo = new StringArgument(null, "renameAttributeTo", false,
1281         0, INFO_PLACEHOLDER_ATTR.get(),
1282         INFO_LDAPSEARCH_ARG_DESCRIPTION_RENAME_ATTRIBUTE_TO.get());
1283    renameAttributeTo.addLongIdentifier("rename-attribute-to", true);
1284    renameAttributeTo.setArgumentGroupName(
1285         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1286    parser.addArgument(renameAttributeTo);
1287
1288    moveSubtreeFrom = new DNArgument(null, "moveSubtreeFrom", false, 0,
1289         INFO_PLACEHOLDER_ATTR.get(),
1290         INFO_LDAPSEARCH_ARG_DESCRIPTION_MOVE_SUBTREE_FROM.get());
1291    moveSubtreeFrom.addLongIdentifier("move-subtree-from", true);
1292    moveSubtreeFrom.setArgumentGroupName(
1293         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1294    parser.addArgument(moveSubtreeFrom);
1295
1296    moveSubtreeTo = new DNArgument(null, "moveSubtreeTo", false, 0,
1297         INFO_PLACEHOLDER_ATTR.get(),
1298         INFO_LDAPSEARCH_ARG_DESCRIPTION_MOVE_SUBTREE_TO.get());
1299    moveSubtreeTo.addLongIdentifier("move-subtree-to", true);
1300    moveSubtreeTo.setArgumentGroupName(
1301         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1302    parser.addArgument(moveSubtreeTo);
1303
1304
1305    // The "--scriptFriendly" argument is provided for compatibility with legacy
1306    // ldapsearch tools, but is not actually used by this tool.
1307    final BooleanArgument scriptFriendly = new BooleanArgument(null,
1308         "scriptFriendly", 1,
1309         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRIPT_FRIENDLY.get());
1310    scriptFriendly.addLongIdentifier("script-friendly", true);
1311    scriptFriendly.setHidden(true);
1312    parser.addArgument(scriptFriendly);
1313
1314
1315    // The "-V" / "--ldapVersion" argument is provided for compatibility with
1316    // legacy ldapsearch tools, but is not actually used by this tool.
1317    final IntegerArgument ldapVersion = new IntegerArgument('V', "ldapVersion",
1318         false, 1, null, INFO_LDAPSEARCH_ARG_DESCRIPTION_LDAP_VERSION.get());
1319    ldapVersion.addLongIdentifier("ldap-version", true);
1320    ldapVersion.setHidden(true);
1321    parser.addArgument(ldapVersion);
1322
1323
1324    // The baseDN and ldapURLFile arguments can't be used together.
1325    parser.addExclusiveArgumentSet(baseDN, ldapURLFile);
1326
1327    // The scope and ldapURLFile arguments can't be used together.
1328    parser.addExclusiveArgumentSet(scope, ldapURLFile);
1329
1330    // The requestedAttribute and ldapURLFile arguments can't be used together.
1331    parser.addExclusiveArgumentSet(requestedAttribute, ldapURLFile);
1332
1333    // The filter and ldapURLFile arguments can't be used together.
1334    parser.addExclusiveArgumentSet(filter, ldapURLFile);
1335
1336    // The filterFile and ldapURLFile arguments can't be used together.
1337    parser.addExclusiveArgumentSet(filterFile, ldapURLFile);
1338
1339    // The followReferrals and manageDsaIT arguments can't be used together.
1340    parser.addExclusiveArgumentSet(followReferrals, manageDsaIT);
1341
1342    // The persistent search argument can't be used with either the filterFile
1343    // or ldapURLFile arguments.
1344    parser.addExclusiveArgumentSet(persistentSearch, filterFile);
1345    parser.addExclusiveArgumentSet(persistentSearch, ldapURLFile);
1346
1347    // The draft-ietf-ldup-subentry and RFC 3672 subentries controls cannot be
1348    // used together.
1349    parser.addExclusiveArgumentSet(draftLDUPSubentries, rfc3672Subentries);
1350
1351    // The realAttributesOnly and virtualAttributesOnly arguments can't be used
1352    // together.
1353    parser.addExclusiveArgumentSet(realAttributesOnly, virtualAttributesOnly);
1354
1355    // The simplePageSize and virtualListView arguments can't be used together.
1356    parser.addExclusiveArgumentSet(simplePageSize, virtualListView);
1357
1358    // The terse and verbose arguments can't be used together.
1359    parser.addExclusiveArgumentSet(terse, verbose);
1360
1361    // The getEffectiveRightsAttribute argument requires the
1362    // getEffectiveRightsAuthzID argument.
1363    parser.addDependentArgumentSet(getEffectiveRightsAttribute,
1364         getEffectiveRightsAuthzID);
1365
1366    // The virtualListView argument requires the sortOrder argument.
1367    parser.addDependentArgumentSet(virtualListView, sortOrder);
1368
1369    // The rejectUnindexedSearch and permitUnindexedSearch arguments can't be
1370    // used together.
1371    parser.addExclusiveArgumentSet(rejectUnindexedSearch,
1372         permitUnindexedSearch);
1373
1374    // The separateOutputFilePerSearch argument requires the outputFile
1375    // argument.  It also requires either the filter, filterFile or ldapURLFile
1376    // argument.
1377    parser.addDependentArgumentSet(separateOutputFilePerSearch, outputFile);
1378    parser.addDependentArgumentSet(separateOutputFilePerSearch, filter,
1379         filterFile, ldapURLFile);
1380
1381    // The teeResultsToStandardOut argument requires the outputFile argument.
1382    parser.addDependentArgumentSet(teeResultsToStandardOut, outputFile);
1383
1384    // The wrapColumn and dontWrap arguments must not be used together.
1385    parser.addExclusiveArgumentSet(wrapColumn, dontWrap);
1386
1387    // All arguments that specifically pertain to join processing can only be
1388    // used if the joinRule argument is provided.
1389    parser.addDependentArgumentSet(joinBaseDN, joinRule);
1390    parser.addDependentArgumentSet(joinScope, joinRule);
1391    parser.addDependentArgumentSet(joinSizeLimit, joinRule);
1392    parser.addDependentArgumentSet(joinFilter, joinRule);
1393    parser.addDependentArgumentSet(joinRequestedAttribute, joinRule);
1394    parser.addDependentArgumentSet(joinRequireMatch, joinRule);
1395
1396    // The countEntries argument must not be used in conjunction with the
1397    // filter, filterFile, LDAPURLFile, or persistentSearch arguments.
1398    parser.addExclusiveArgumentSet(countEntries, filter);
1399    parser.addExclusiveArgumentSet(countEntries, filterFile);
1400    parser.addExclusiveArgumentSet(countEntries, ldapURLFile);
1401    parser.addExclusiveArgumentSet(countEntries, persistentSearch);
1402
1403
1404    // The hideRedactedValueCount argument requires the redactAttribute
1405    // argument.
1406    parser.addDependentArgumentSet(hideRedactedValueCount, redactAttribute);
1407
1408    // The scrambleJSONField and scrambleRandomSeed arguments require the
1409    // scrambleAttribute argument.
1410    parser.addDependentArgumentSet(scrambleJSONField, scrambleAttribute);
1411    parser.addDependentArgumentSet(scrambleRandomSeed, scrambleAttribute);
1412
1413    // The renameAttributeFrom and renameAttributeTo arguments must be provided
1414    // together.
1415    parser.addDependentArgumentSet(renameAttributeFrom, renameAttributeTo);
1416    parser.addDependentArgumentSet(renameAttributeTo, renameAttributeFrom);
1417
1418    // The moveSubtreeFrom and moveSubtreeTo arguments must be provided
1419    // together.
1420    parser.addDependentArgumentSet(moveSubtreeFrom, moveSubtreeTo);
1421    parser.addDependentArgumentSet(moveSubtreeTo, moveSubtreeFrom);
1422
1423
1424    // The compressOutput argument can only be used if an output file is
1425    // specified and results aren't going to be teed.
1426    parser.addDependentArgumentSet(compressOutput, outputFile);
1427    parser.addExclusiveArgumentSet(compressOutput, teeResultsToStandardOut);
1428
1429
1430    // The encryptOutput argument can only be used if an output file is
1431    // specified and results aren't going to be teed.
1432    parser.addDependentArgumentSet(encryptOutput, outputFile);
1433    parser.addExclusiveArgumentSet(encryptOutput, teeResultsToStandardOut);
1434
1435
1436    // The encryptionPassphraseFile argument can only be used if the
1437    // encryptOutput argument is also provided.
1438    parser.addDependentArgumentSet(encryptionPassphraseFile, encryptOutput);
1439  }
1440
1441
1442
1443  /**
1444   * {@inheritDoc}
1445   */
1446  @Override()
1447  @NotNull()
1448  protected List<Control> getBindControls()
1449  {
1450    final ArrayList<Control> bindControls = new ArrayList<>(10);
1451
1452    if (bindControl.isPresent())
1453    {
1454      bindControls.addAll(bindControl.getValues());
1455    }
1456
1457    if (authorizationIdentity.isPresent())
1458    {
1459      bindControls.add(new AuthorizationIdentityRequestControl(false));
1460    }
1461
1462    if (generateAccessToken.isPresent())
1463    {
1464      bindControls.add(new GenerateAccessTokenRequestControl());
1465    }
1466
1467    if (getAuthorizationEntryAttribute.isPresent())
1468    {
1469      bindControls.add(new GetAuthorizationEntryRequestControl(true, true,
1470           getAuthorizationEntryAttribute.getValues()));
1471    }
1472
1473    if (getRecentLoginHistory.isPresent())
1474    {
1475      bindControls.add(new GetRecentLoginHistoryRequestControl());
1476    }
1477
1478    if (getUserResourceLimits.isPresent())
1479    {
1480      bindControls.add(new GetUserResourceLimitsRequestControl());
1481    }
1482
1483    if (usePasswordPolicyControl.isPresent())
1484    {
1485      bindControls.add(new PasswordPolicyRequestControl());
1486    }
1487
1488    if (suppressOperationalAttributeUpdates.isPresent())
1489    {
1490      final EnumSet<SuppressType> suppressTypes =
1491           EnumSet.noneOf(SuppressType.class);
1492      for (final String s : suppressOperationalAttributeUpdates.getValues())
1493      {
1494        if (s.equalsIgnoreCase("last-access-time"))
1495        {
1496          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
1497        }
1498        else if (s.equalsIgnoreCase("last-login-time"))
1499        {
1500          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
1501        }
1502        else if (s.equalsIgnoreCase("last-login-ip"))
1503        {
1504          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
1505        }
1506      }
1507
1508      bindControls.add(new SuppressOperationalAttributeUpdateRequestControl(
1509           suppressTypes));
1510    }
1511
1512    if (useJSONFormattedRequestControls.isPresent())
1513    {
1514      final JSONFormattedRequestControl jsonFormattedRequestControl =
1515           JSONFormattedRequestControl.createWithControls(true, bindControls);
1516      bindControls.clear();
1517      bindControls.add(jsonFormattedRequestControl);
1518    }
1519
1520    return bindControls;
1521  }
1522
1523
1524
1525  /**
1526   * {@inheritDoc}
1527   */
1528  @Override()
1529  protected boolean supportsMultipleServers()
1530  {
1531    // We will support providing information about multiple servers.  This tool
1532    // will not communicate with multiple servers concurrently, but it can
1533    // accept information about multiple servers in the event that multiple
1534    // searches are to be performed and a server goes down in the middle of
1535    // those searches.  In this case, we can resume processing on a
1536    // newly-created connection, possibly to a different server.
1537    return true;
1538  }
1539
1540
1541
1542  /**
1543   * {@inheritDoc}
1544   */
1545  @Override()
1546  public void doExtendedNonLDAPArgumentValidation()
1547         throws ArgumentException
1548  {
1549    // If wrapColumn was provided, then use its value.  Otherwise, if dontWrap
1550    // was provided, then use that.
1551    if (wrapColumn.isPresent())
1552    {
1553      final int wc = wrapColumn.getValue();
1554      if (wc <= 0)
1555      {
1556        WRAP_COLUMN = Integer.MAX_VALUE;
1557      }
1558      else
1559      {
1560        WRAP_COLUMN = wc;
1561      }
1562    }
1563    else if (dontWrap.isPresent())
1564    {
1565      WRAP_COLUMN = Integer.MAX_VALUE;
1566    }
1567
1568
1569    // If the ldapURLFile argument was provided, then there must not be any
1570    // trailing arguments.
1571    final List<String> trailingArgs = parser.getTrailingArguments();
1572    if (ldapURLFile.isPresent())
1573    {
1574      if (! trailingArgs.isEmpty())
1575      {
1576        throw new ArgumentException(
1577             ERR_LDAPSEARCH_TRAILING_ARGS_WITH_URL_FILE.get(
1578                  ldapURLFile.getIdentifierString()));
1579      }
1580    }
1581
1582
1583    // If the filter or filterFile argument was provided, then there may
1584    // optionally be trailing arguments, but the first trailing argument must
1585    // not be a filter.
1586    if (filter.isPresent() || filterFile.isPresent())
1587    {
1588      if (! trailingArgs.isEmpty())
1589      {
1590        try
1591        {
1592          Filter.create(trailingArgs.get(0));
1593          throw new ArgumentException(
1594               ERR_LDAPSEARCH_TRAILING_FILTER_WITH_FILTER_FILE.get(
1595                    filterFile.getIdentifierString()));
1596        }
1597        catch (final LDAPException le)
1598        {
1599          // This is the normal condition.  Not even worth debugging the
1600          // exception.
1601        }
1602      }
1603    }
1604
1605
1606    // If none of the ldapURLFile, filter, or filterFile arguments was provided,
1607    // then there must be at least one trailing argument, and the first trailing
1608    // argument must be a valid search filter.
1609    if (! (ldapURLFile.isPresent() || filter.isPresent() ||
1610           filterFile.isPresent()))
1611    {
1612      if (trailingArgs.isEmpty())
1613      {
1614        throw new ArgumentException(ERR_LDAPSEARCH_NO_TRAILING_ARGS.get(
1615             filterFile.getIdentifierString(),
1616             ldapURLFile.getIdentifierString()));
1617      }
1618
1619      try
1620      {
1621        Filter.create(trailingArgs.get(0));
1622      }
1623      catch (final Exception e)
1624      {
1625        Debug.debugException(e);
1626        throw new ArgumentException(
1627             ERR_LDAPSEARCH_FIRST_TRAILING_ARG_NOT_FILTER.get(
1628                  trailingArgs.get(0)),
1629             e);
1630      }
1631    }
1632
1633
1634    // There should never be a case in which a trailing argument starts with a
1635    // dash, and it's probably an attempt to use a named argument but that was
1636    // inadvertently put after the filter.  Warn about the problem, but don't
1637    // fail.
1638    for (final String s : trailingArgs)
1639    {
1640      if (s.startsWith("-"))
1641      {
1642        commentToErr(WARN_LDAPSEARCH_TRAILING_ARG_STARTS_WITH_DASH.get(s));
1643        break;
1644      }
1645    }
1646
1647
1648    // If any matched values filters are specified, then validate them and
1649    // pre-create the matched values request control.
1650    if (matchedValuesFilter.isPresent())
1651    {
1652      final List<Filter> filterList = matchedValuesFilter.getValues();
1653      final MatchedValuesFilter[] matchedValuesFilters =
1654           new MatchedValuesFilter[filterList.size()];
1655      for (int i=0; i < matchedValuesFilters.length; i++)
1656      {
1657        try
1658        {
1659          matchedValuesFilters[i] =
1660               MatchedValuesFilter.create(filterList.get(i));
1661        }
1662        catch (final Exception e)
1663        {
1664          Debug.debugException(e);
1665          throw new ArgumentException(
1666               ERR_LDAPSEARCH_INVALID_MATCHED_VALUES_FILTER.get(
1667                    filterList.get(i).toString()),
1668               e);
1669        }
1670      }
1671
1672      matchedValuesRequestControl =
1673           new MatchedValuesRequestControl(true, matchedValuesFilters);
1674    }
1675
1676
1677    // If we should use the matching entry count request control, then validate
1678    // the argument value and pre-create the control.
1679    if (matchingEntryCountControl.isPresent())
1680    {
1681      final MatchingEntryCountRequestControlProperties properties =
1682           new MatchingEntryCountRequestControlProperties();
1683
1684      Integer examineCount                 = null;
1685      try
1686      {
1687        for (final String element :
1688             matchingEntryCountControl.getValue().toLowerCase().split(":"))
1689        {
1690          if (element.startsWith("examinecount="))
1691          {
1692            examineCount = Integer.parseInt(element.substring(13));
1693          }
1694          else if (element.equals("allowunindexed"))
1695          {
1696            properties.setProcessSearchIfUnindexed(true);
1697          }
1698          else if (element.equals("alwaysexamine"))
1699          {
1700            properties.setAlwaysExamineCandidates(true);
1701          }
1702          else if (element.equals("skipresolvingexplodedindexes"))
1703          {
1704            properties.setSkipResolvingExplodedIndexes(true);
1705          }
1706          else if (element.startsWith("fastshortcircuitthreshold="))
1707          {
1708            properties.setFastShortCircuitThreshold(
1709                 Long.parseLong(element.substring(26)));
1710          }
1711          else if (element.startsWith("slowshortcircuitthreshold="))
1712          {
1713            properties.setSlowShortCircuitThreshold(
1714                 Long.parseLong(element.substring(26)));
1715          }
1716          else if (element.equals("extendedresponsedata"))
1717          {
1718            properties.setIncludeExtendedResponseData(true);
1719          }
1720          else if (element.equals("debug"))
1721          {
1722            properties.setIncludeDebugInfo(true);
1723          }
1724          else
1725          {
1726            throw new ArgumentException(
1727                 ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get(
1728                      matchingEntryCountControl.getIdentifierString()));
1729          }
1730        }
1731      }
1732      catch (final ArgumentException ae)
1733      {
1734        Debug.debugException(ae);
1735        throw ae;
1736      }
1737      catch (final Exception e)
1738      {
1739        Debug.debugException(e);
1740        throw new ArgumentException(
1741             ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get(
1742                  matchingEntryCountControl.getIdentifierString()),
1743             e);
1744      }
1745
1746      if (examineCount == null)
1747      {
1748        throw new ArgumentException(
1749             ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get(
1750                  matchingEntryCountControl.getIdentifierString()));
1751      }
1752      else
1753      {
1754        properties.setMaxCandidatesToExamine(examineCount);
1755      }
1756
1757      matchingEntryCountRequestControl =
1758           new MatchingEntryCountRequestControl(true, properties);
1759    }
1760
1761
1762    // If we should include the override search limits request control, then
1763    // validate the provided values.
1764    if (overrideSearchLimit.isPresent())
1765    {
1766      final LinkedHashMap<String,String> properties =
1767           new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1768      for (final String value : overrideSearchLimit.getValues())
1769      {
1770        final int equalPos = value.indexOf('=');
1771        if (equalPos < 0)
1772        {
1773          throw new ArgumentException(
1774               ERR_LDAPSEARCH_OVERRIDE_LIMIT_NO_EQUAL.get(
1775                    overrideSearchLimit.getIdentifierString()));
1776        }
1777        else if (equalPos == 0)
1778        {
1779          throw new ArgumentException(
1780               ERR_LDAPSEARCH_OVERRIDE_LIMIT_EMPTY_PROPERTY_NAME.get(
1781                    overrideSearchLimit.getIdentifierString()));
1782        }
1783
1784        final String propertyName = value.substring(0, equalPos);
1785        if (properties.containsKey(propertyName))
1786        {
1787          throw new ArgumentException(
1788               ERR_LDAPSEARCH_OVERRIDE_LIMIT_DUPLICATE_PROPERTY_NAME.get(
1789                    overrideSearchLimit.getIdentifierString(), propertyName));
1790        }
1791
1792        if (equalPos == (value.length() - 1))
1793        {
1794          throw new ArgumentException(
1795               ERR_LDAPSEARCH_OVERRIDE_LIMIT_EMPTY_PROPERTY_VALUE.get(
1796                    overrideSearchLimit.getIdentifierString(), propertyName));
1797        }
1798
1799        properties.put(propertyName, value.substring(equalPos+1));
1800      }
1801
1802      overrideSearchLimitsRequestControl =
1803           new OverrideSearchLimitsRequestControl(properties, false);
1804    }
1805
1806
1807    // If we should use the persistent search request control, then validate
1808    // the argument value and pre-create the control.
1809    if (persistentSearch.isPresent())
1810    {
1811      boolean changesOnly = true;
1812      boolean returnECs   = true;
1813      EnumSet<PersistentSearchChangeType> changeTypes =
1814           EnumSet.allOf(PersistentSearchChangeType.class);
1815      try
1816      {
1817        final String[] elements =
1818             persistentSearch.getValue().toLowerCase().split(":");
1819        if (elements.length == 0)
1820        {
1821          throw new ArgumentException(
1822               ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1823                    persistentSearch.getIdentifierString()));
1824        }
1825
1826        final String header = StaticUtils.toLowerCase(elements[0]);
1827        if (! (header.equals("ps") || header.equals("persist") ||
1828             header.equals("persistent") || header.equals("psearch") ||
1829             header.equals("persistentsearch")))
1830        {
1831          throw new ArgumentException(
1832               ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1833                    persistentSearch.getIdentifierString()));
1834        }
1835
1836        if (elements.length > 1)
1837        {
1838          final String ctString = StaticUtils.toLowerCase(elements[1]);
1839          if (ctString.equals("any"))
1840          {
1841            changeTypes = EnumSet.allOf(PersistentSearchChangeType.class);
1842          }
1843          else
1844          {
1845            changeTypes.clear();
1846            for (final String t : ctString.split(","))
1847            {
1848              if (t.equals("add"))
1849              {
1850                changeTypes.add(PersistentSearchChangeType.ADD);
1851              }
1852              else if (t.equals("del") || t.equals("delete"))
1853              {
1854                changeTypes.add(PersistentSearchChangeType.DELETE);
1855              }
1856              else if (t.equals("mod") || t.equals("modify"))
1857              {
1858                changeTypes.add(PersistentSearchChangeType.MODIFY);
1859              }
1860              else if (t.equals("moddn") || t.equals("modrdn") ||
1861                   t.equals("modifydn") || t.equals("modifyrdn"))
1862              {
1863                changeTypes.add(PersistentSearchChangeType.MODIFY_DN);
1864              }
1865              else
1866              {
1867                throw new ArgumentException(
1868                     ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1869                          persistentSearch.getIdentifierString()));
1870              }
1871            }
1872          }
1873        }
1874
1875        if (elements.length > 2)
1876        {
1877          if (elements[2].equalsIgnoreCase("true") || elements[2].equals("1"))
1878          {
1879            changesOnly = true;
1880          }
1881          else if (elements[2].equalsIgnoreCase("false") ||
1882               elements[2].equals("0"))
1883          {
1884            changesOnly = false;
1885          }
1886          else
1887          {
1888            throw new ArgumentException(
1889                 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1890                      persistentSearch.getIdentifierString()));
1891          }
1892        }
1893
1894        if (elements.length > 3)
1895        {
1896          if (elements[3].equalsIgnoreCase("true") || elements[3].equals("1"))
1897          {
1898            returnECs = true;
1899          }
1900          else if (elements[3].equalsIgnoreCase("false") ||
1901               elements[3].equals("0"))
1902          {
1903            returnECs = false;
1904          }
1905          else
1906          {
1907            throw new ArgumentException(
1908                 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1909                      persistentSearch.getIdentifierString()));
1910          }
1911        }
1912      }
1913      catch (final ArgumentException ae)
1914      {
1915        Debug.debugException(ae);
1916        throw ae;
1917      }
1918      catch (final Exception e)
1919      {
1920        Debug.debugException(e);
1921        throw new ArgumentException(
1922             ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1923                  persistentSearch.getIdentifierString()),
1924             e);
1925      }
1926
1927      persistentSearchRequestControl = new PersistentSearchRequestControl(
1928           changeTypes, changesOnly, returnECs, true);
1929    }
1930
1931
1932    // If we should use the server-side sort request control, then validate the
1933    // sort order and pre-create the control.
1934    if (sortOrder.isPresent())
1935    {
1936      final ArrayList<SortKey> sortKeyList = new ArrayList<>(5);
1937      final StringTokenizer tokenizer =
1938           new StringTokenizer(sortOrder.getValue(), ", ");
1939      while (tokenizer.hasMoreTokens())
1940      {
1941        final String token = tokenizer.nextToken();
1942
1943        final boolean ascending;
1944        String attributeName;
1945        if (token.startsWith("-"))
1946        {
1947          ascending = false;
1948          attributeName = token.substring(1);
1949        }
1950        else if (token.startsWith("+"))
1951        {
1952          ascending = true;
1953          attributeName = token.substring(1);
1954        }
1955        else
1956        {
1957          ascending = true;
1958          attributeName = token;
1959        }
1960
1961        final String matchingRuleID;
1962        final int colonPos = attributeName.indexOf(':');
1963        if (colonPos >= 0)
1964        {
1965          matchingRuleID = attributeName.substring(colonPos+1);
1966          attributeName = attributeName.substring(0, colonPos);
1967        }
1968        else
1969        {
1970          matchingRuleID = null;
1971        }
1972
1973        final StringBuilder invalidReason = new StringBuilder();
1974        if (! PersistUtils.isValidLDAPName(attributeName, false, invalidReason))
1975        {
1976          throw new ArgumentException(
1977               ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get(
1978                    sortOrder.getIdentifierString()));
1979        }
1980
1981        sortKeyList.add(
1982             new SortKey(attributeName, matchingRuleID, (! ascending)));
1983      }
1984
1985      if (sortKeyList.isEmpty())
1986      {
1987        throw new ArgumentException(
1988             ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get(
1989                  sortOrder.getIdentifierString()));
1990      }
1991
1992      final SortKey[] sortKeyArray = new SortKey[sortKeyList.size()];
1993      sortKeyList.toArray(sortKeyArray);
1994
1995      sortRequestControl = new ServerSideSortRequestControl(sortKeyArray);
1996    }
1997
1998
1999    // If we should use the virtual list view request control, then validate the
2000    // argument value and pre-create the control.
2001    if (virtualListView.isPresent())
2002    {
2003      try
2004      {
2005        final String[] elements = virtualListView.getValue().split(":");
2006        if (elements.length == 4)
2007        {
2008          vlvRequestControl = new VirtualListViewRequestControl(
2009               Integer.parseInt(elements[2]), Integer.parseInt(elements[0]),
2010               Integer.parseInt(elements[1]), Integer.parseInt(elements[3]),
2011               null);
2012        }
2013        else if (elements.length == 3)
2014        {
2015          vlvRequestControl = new VirtualListViewRequestControl(elements[2],
2016               Integer.parseInt(elements[0]), Integer.parseInt(elements[1]),
2017               null);
2018        }
2019        else
2020        {
2021          throw new ArgumentException(
2022               ERR_LDAPSEARCH_VLV_INVALID_VALUE.get(
2023                    virtualListView.getIdentifierString()));
2024        }
2025      }
2026      catch (final ArgumentException ae)
2027      {
2028        Debug.debugException(ae);
2029        throw ae;
2030      }
2031      catch (final Exception e)
2032      {
2033        Debug.debugException(e);
2034        throw new ArgumentException(
2035             ERR_LDAPSEARCH_VLV_INVALID_VALUE.get(
2036                  virtualListView.getIdentifierString()),
2037             e);
2038      }
2039    }
2040
2041
2042    // If we should use the LDAP join request control, then validate and
2043    // pre-create that control.
2044    if (joinRule.isPresent())
2045    {
2046      final JoinRule rule;
2047      try
2048      {
2049        final String[] elements = joinRule.getValue().toLowerCase().split(":");
2050        final String ruleName = StaticUtils.toLowerCase(elements[0]);
2051        if (ruleName.equals("dn"))
2052        {
2053          rule = JoinRule.createDNJoin(elements[1]);
2054        }
2055        else if (ruleName.equals("reverse-dn") || ruleName.equals("reversedn"))
2056        {
2057          rule = JoinRule.createReverseDNJoin(elements[1]);
2058        }
2059        else if (ruleName.equals("equals") || ruleName.equals("equality"))
2060        {
2061          rule = JoinRule.createEqualityJoin(elements[1], elements[2], false);
2062        }
2063        else if (ruleName.equals("contains") || ruleName.equals("substring"))
2064        {
2065          rule = JoinRule.createContainsJoin(elements[1], elements[2], false);
2066        }
2067        else
2068        {
2069          throw new ArgumentException(
2070               ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get(
2071                    joinRule.getIdentifierString()));
2072        }
2073      }
2074      catch (final ArgumentException ae)
2075      {
2076        Debug.debugException(ae);
2077        throw ae;
2078      }
2079      catch (final Exception e)
2080      {
2081        Debug.debugException(e);
2082        throw new ArgumentException(
2083             ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get(
2084                  joinRule.getIdentifierString()),
2085             e);
2086      }
2087
2088      final JoinBaseDN joinBase;
2089      if (joinBaseDN.isPresent())
2090      {
2091        final String s = StaticUtils.toLowerCase(joinBaseDN.getValue());
2092        if (s.equals("search-base") || s.equals("search-base-dn"))
2093        {
2094          joinBase = JoinBaseDN.createUseSearchBaseDN();
2095        }
2096        else if (s.equals("source-entry-dn") || s.equals("source-dn"))
2097        {
2098          joinBase = JoinBaseDN.createUseSourceEntryDN();
2099        }
2100        else
2101        {
2102          try
2103          {
2104            final DN dn = new DN(joinBaseDN.getValue());
2105            joinBase = JoinBaseDN.createUseCustomBaseDN(joinBaseDN.getValue());
2106          }
2107          catch (final Exception e)
2108          {
2109            Debug.debugException(e);
2110            throw new ArgumentException(
2111                 ERR_LDAPSEARCH_JOIN_BASE_DN_INVALID_VALUE.get(
2112                      joinBaseDN.getIdentifierString()),
2113                 e);
2114          }
2115        }
2116      }
2117      else
2118      {
2119        joinBase = JoinBaseDN.createUseSearchBaseDN();
2120      }
2121
2122      final String[] joinAttrs;
2123      if (joinRequestedAttribute.isPresent())
2124      {
2125        final List<String> valueList = joinRequestedAttribute.getValues();
2126        joinAttrs = new String[valueList.size()];
2127        valueList.toArray(joinAttrs);
2128      }
2129      else
2130      {
2131        joinAttrs = null;
2132      }
2133
2134      joinRequestControl = new JoinRequestControl(new JoinRequestValue(rule,
2135           joinBase, joinScope.getValue(), DereferencePolicy.NEVER,
2136           joinSizeLimit.getValue(), joinFilter.getValue(), joinAttrs,
2137           joinRequireMatch.isPresent(), null));
2138    }
2139
2140
2141    // If we should use the route to backend set request control, then validate
2142    // and pre-create those controls.
2143    if (routeToBackendSet.isPresent())
2144    {
2145      final List<String> values = routeToBackendSet.getValues();
2146      final Map<String,List<String>> idsByRP = new LinkedHashMap<>(
2147           StaticUtils.computeMapCapacity(values.size()));
2148      for (final String value : values)
2149      {
2150        final int colonPos = value.indexOf(':');
2151        if (colonPos <= 0)
2152        {
2153          throw new ArgumentException(
2154               ERR_LDAPSEARCH_ROUTE_TO_BACKEND_SET_INVALID_FORMAT.get(value,
2155                    routeToBackendSet.getIdentifierString()));
2156        }
2157
2158        final String rpID = value.substring(0, colonPos);
2159        final String bsID = value.substring(colonPos+1);
2160
2161        List<String> idsForRP = idsByRP.get(rpID);
2162        if (idsForRP == null)
2163        {
2164          idsForRP = new ArrayList<>(values.size());
2165          idsByRP.put(rpID, idsForRP);
2166        }
2167        idsForRP.add(bsID);
2168      }
2169
2170      for (final Map.Entry<String,List<String>> e : idsByRP.entrySet())
2171      {
2172        final String rpID = e.getKey();
2173        final List<String> bsIDs = e.getValue();
2174        routeToBackendSetRequestControls.add(
2175             RouteToBackendSetRequestControl.createAbsoluteRoutingRequest(true,
2176                  rpID, bsIDs));
2177      }
2178    }
2179
2180
2181    // Parse the dereference policy.
2182    final String derefStr =
2183         StaticUtils.toLowerCase(dereferencePolicy.getValue());
2184    if (derefStr.equals("always"))
2185    {
2186      derefPolicy = DereferencePolicy.ALWAYS;
2187    }
2188    else if (derefStr.equals("search"))
2189    {
2190      derefPolicy = DereferencePolicy.SEARCHING;
2191    }
2192    else if (derefStr.equals("find"))
2193    {
2194      derefPolicy = DereferencePolicy.FINDING;
2195    }
2196    else
2197    {
2198      derefPolicy = DereferencePolicy.NEVER;
2199    }
2200
2201
2202    // See if any entry transformations need to be applied.
2203    final ArrayList<EntryTransformation> transformations = new ArrayList<>(5);
2204    if (excludeAttribute.isPresent())
2205    {
2206      transformations.add(new ExcludeAttributeTransformation(null,
2207           excludeAttribute.getValues()));
2208    }
2209
2210    if (redactAttribute.isPresent())
2211    {
2212      transformations.add(new RedactAttributeTransformation(null, true,
2213           (! hideRedactedValueCount.isPresent()),
2214           redactAttribute.getValues()));
2215    }
2216
2217    if (scrambleAttribute.isPresent())
2218    {
2219      final Long randomSeed;
2220      if (scrambleRandomSeed.isPresent())
2221      {
2222        randomSeed = scrambleRandomSeed.getValue().longValue();
2223      }
2224      else
2225      {
2226        randomSeed = null;
2227      }
2228
2229      transformations.add(new ScrambleAttributeTransformation(null, randomSeed,
2230           true, scrambleAttribute.getValues(), scrambleJSONField.getValues()));
2231    }
2232
2233    if (renameAttributeFrom.isPresent())
2234    {
2235      if (renameAttributeFrom.getNumOccurrences() !=
2236          renameAttributeTo.getNumOccurrences())
2237      {
2238        throw new ArgumentException(
2239             ERR_LDAPSEARCH_RENAME_ATTRIBUTE_MISMATCH.get());
2240      }
2241
2242      final Iterator<String> sourceIterator =
2243           renameAttributeFrom.getValues().iterator();
2244      final Iterator<String> targetIterator =
2245           renameAttributeTo.getValues().iterator();
2246      while (sourceIterator.hasNext())
2247      {
2248        transformations.add(new RenameAttributeTransformation(null,
2249             sourceIterator.next(), targetIterator.next(), true));
2250      }
2251    }
2252
2253    if (moveSubtreeFrom.isPresent())
2254    {
2255      if (moveSubtreeFrom.getNumOccurrences() !=
2256          moveSubtreeTo.getNumOccurrences())
2257      {
2258        throw new ArgumentException(ERR_LDAPSEARCH_MOVE_SUBTREE_MISMATCH.get());
2259      }
2260
2261      final Iterator<DN> sourceIterator =
2262           moveSubtreeFrom.getValues().iterator();
2263      final Iterator<DN> targetIterator = moveSubtreeTo.getValues().iterator();
2264      while (sourceIterator.hasNext())
2265      {
2266        transformations.add(new MoveSubtreeTransformation(sourceIterator.next(),
2267             targetIterator.next()));
2268      }
2269    }
2270
2271    if (! transformations.isEmpty())
2272    {
2273      entryTransformations = transformations;
2274    }
2275
2276
2277    // Create the result writer.
2278    final String outputFormatStr =
2279         StaticUtils.toLowerCase(outputFormat.getValue());
2280    if (outputFormatStr.equals("json"))
2281    {
2282      resultWriter = new JSONLDAPResultWriter(getOutStream());
2283    }
2284    else if (outputFormatStr.equals("csv") ||
2285             outputFormatStr.equals("multi-valued-csv") ||
2286             outputFormatStr.equals("tab-delimited") ||
2287             outputFormatStr.equals("multi-valued-tab-delimited"))
2288    {
2289      // These output formats cannot be used with the --ldapURLFile argument.
2290      if (ldapURLFile.isPresent())
2291      {
2292        throw new ArgumentException(
2293             ERR_LDAPSEARCH_OUTPUT_FORMAT_NOT_SUPPORTED_WITH_URLS.get(
2294                  outputFormat.getValue(), ldapURLFile.getIdentifierString()));
2295      }
2296
2297      // These output formats require the requested attributes to be specified
2298      // via the --requestedAttribute argument rather than as unnamed trailing
2299      // arguments.
2300      final List<String> requestedAttributes = requestedAttribute.getValues();
2301      if ((requestedAttributes == null) || requestedAttributes.isEmpty())
2302      {
2303        throw new ArgumentException(
2304             ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2305                  outputFormat.getValue(),
2306                  requestedAttribute.getIdentifierString()));
2307      }
2308
2309      switch (trailingArgs.size())
2310      {
2311        case 0:
2312          // This is fine.
2313          break;
2314
2315        case 1:
2316          // Make sure that the trailing argument is a filter rather than a
2317          // requested attribute.  It's sufficient to ensure that neither the
2318          // filter nor filterFile argument was provided.
2319          if (filter.isPresent() || filterFile.isPresent())
2320          {
2321            throw new ArgumentException(
2322                 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2323                      outputFormat.getValue(),
2324                      requestedAttribute.getIdentifierString()));
2325          }
2326          break;
2327
2328        default:
2329          throw new ArgumentException(
2330               ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2331                    outputFormat.getValue(),
2332                    requestedAttribute.getIdentifierString()));
2333      }
2334
2335      final OutputFormat format;
2336      final boolean includeAllValues;
2337      switch (outputFormatStr)
2338      {
2339        case "multi-valued-csv":
2340          format = OutputFormat.CSV;
2341          includeAllValues = true;
2342          break;
2343        case "tab-delimited":
2344          format = OutputFormat.TAB_DELIMITED_TEXT;
2345          includeAllValues = false;
2346          break;
2347        case "multi-valued-tab-delimited":
2348          format = OutputFormat.TAB_DELIMITED_TEXT;
2349          includeAllValues = true;
2350          break;
2351        case "csv":
2352        default:
2353          format = OutputFormat.CSV;
2354          includeAllValues = false;
2355          break;
2356      }
2357
2358
2359      resultWriter = new ColumnBasedLDAPResultWriter(getOutStream(),
2360           format, requestedAttributes, WRAP_COLUMN, includeAllValues);
2361    }
2362    else if (outputFormatStr.equals("dns-only"))
2363    {
2364      resultWriter = new DNsOnlyLDAPResultWriter(getOutStream());
2365    }
2366    else if (outputFormatStr.equals("values-only"))
2367    {
2368      resultWriter = new ValuesOnlyLDAPResultWriter(getOutStream());
2369    }
2370    else
2371    {
2372      resultWriter = new LDIFLDAPResultWriter(getOutStream(), WRAP_COLUMN);
2373    }
2374  }
2375
2376
2377
2378  /**
2379   * {@inheritDoc}
2380   */
2381  @Override()
2382  @NotNull()
2383  public LDAPConnectionOptions getConnectionOptions()
2384  {
2385    final LDAPConnectionOptions options = new LDAPConnectionOptions();
2386
2387    options.setUseSynchronousMode(true);
2388    options.setFollowReferrals(followReferrals.isPresent());
2389    options.setUnsolicitedNotificationHandler(this);
2390    options.setResponseTimeoutMillis(0L);
2391
2392    return options;
2393  }
2394
2395
2396
2397  /**
2398   * {@inheritDoc}
2399   */
2400  @Override()
2401  @NotNull()
2402  public ResultCode doToolProcessing()
2403  {
2404    // If we should encrypt the output, then get the encryption passphrase.
2405    if (encryptOutput.isPresent())
2406    {
2407      if (encryptionPassphraseFile.isPresent())
2408      {
2409        try
2410        {
2411          encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile(
2412               encryptionPassphraseFile.getValue());
2413        }
2414        catch (final LDAPException e)
2415        {
2416          Debug.debugException(e);
2417          wrapErr(0, WRAP_COLUMN, e.getMessage());
2418          return e.getResultCode();
2419        }
2420      }
2421      else
2422      {
2423        try
2424        {
2425          encryptionPassphrase = ToolUtils.promptForEncryptionPassphrase(false,
2426               true, getOut(), getErr());
2427        }
2428        catch (final LDAPException e)
2429        {
2430          Debug.debugException(e);
2431          wrapErr(0, WRAP_COLUMN, e.getMessage());
2432          return e.getResultCode();
2433        }
2434      }
2435    }
2436
2437
2438    // If we should use an output file, then set that up now.  Otherwise, write
2439    // the header to standard output.
2440    if (outputFile.isPresent())
2441    {
2442      if (! separateOutputFilePerSearch.isPresent())
2443      {
2444        try
2445        {
2446          OutputStream s = new FileOutputStream(outputFile.getValue());
2447
2448          if (encryptOutput.isPresent())
2449          {
2450            s = new PassphraseEncryptedOutputStream(encryptionPassphrase, s);
2451          }
2452
2453          if (compressOutput.isPresent())
2454          {
2455            s = new GZIPOutputStream(s);
2456          }
2457
2458          if (teeResultsToStandardOut.isPresent())
2459          {
2460            outStream = new PrintStream(new TeeOutputStream(s, getOut()));
2461          }
2462          else
2463          {
2464            outStream = new PrintStream(s);
2465          }
2466          resultWriter.updateOutputStream(outStream);
2467          errStream = outStream;
2468        }
2469        catch (final Exception e)
2470        {
2471          Debug.debugException(e);
2472          wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get(
2473               outputFile.getValue().getAbsolutePath(),
2474               StaticUtils.getExceptionMessage(e)));
2475          return ResultCode.LOCAL_ERROR;
2476        }
2477
2478        resultWriter.writeHeader();
2479      }
2480    }
2481    else
2482    {
2483      resultWriter.writeHeader();
2484    }
2485
2486
2487    // Examine the arguments to determine the sets of controls to use for each
2488    // type of request.
2489    final List<Control> searchControls;
2490    try
2491    {
2492      searchControls = getSearchControls();
2493    }
2494    catch (final LDAPException e)
2495    {
2496      Debug.debugException(e);
2497      wrapErr(0, WRAP_COLUMN, e.getMessage());
2498      return e.getResultCode();
2499    }
2500
2501
2502    // If appropriate, ensure that any search result entries that include
2503    // base64-encoded attribute values will also include comments that attempt
2504    // to provide a human-readable representation of that value.
2505    final boolean originalCommentAboutBase64EncodedValues =
2506         LDIFWriter.commentAboutBase64EncodedValues();
2507    LDIFWriter.setCommentAboutBase64EncodedValues(
2508         ! suppressBase64EncodedValueComments.isPresent());
2509
2510
2511    LDAPConnectionPool pool = null;
2512    try
2513    {
2514      // Create a connection pool that will be used to communicate with the
2515      // directory server.
2516      if (! dryRun.isPresent())
2517      {
2518        try
2519        {
2520          final StartAdministrativeSessionPostConnectProcessor p;
2521          if (useAdministrativeSession.isPresent())
2522          {
2523            p = new StartAdministrativeSessionPostConnectProcessor(
2524                 new StartAdministrativeSessionExtendedRequest(getToolName(),
2525                      true));
2526          }
2527          else
2528          {
2529            p = null;
2530          }
2531
2532          pool = getConnectionPool(1, 1, 0, p, null, true,
2533               new ReportBindResultLDAPConnectionPoolHealthCheck(this, true,
2534                    false));
2535        }
2536        catch (final LDAPException le)
2537        {
2538          // This shouldn't happen since the pool won't throw an exception if an
2539          // attempt to create an initial connection fails.
2540          Debug.debugException(le);
2541          commentToErr(ERR_LDAPSEARCH_CANNOT_CREATE_CONNECTION_POOL.get(
2542               StaticUtils.getExceptionMessage(le)));
2543          return le.getResultCode();
2544        }
2545
2546        if (retryFailedOperations.isPresent())
2547        {
2548          pool.setRetryFailedOperationsDueToInvalidConnections(true);
2549        }
2550      }
2551
2552
2553      // If appropriate, create a rate limiter.
2554      final FixedRateBarrier rateLimiter;
2555      if (ratePerSecond.isPresent())
2556      {
2557        rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue());
2558      }
2559      else
2560      {
2561        rateLimiter = null;
2562      }
2563
2564
2565      // If one or more LDAP URL files are provided, then construct search
2566      // requests from those URLs.
2567      if (ldapURLFile.isPresent())
2568      {
2569        return searchWithLDAPURLs(pool, rateLimiter, searchControls);
2570      }
2571
2572
2573      // Get the set of requested attributes, as a combination of the
2574      // requestedAttribute argument values and any trailing arguments.
2575      final ArrayList<String> attrList = new ArrayList<>(10);
2576      if (requestedAttribute.isPresent())
2577      {
2578        attrList.addAll(requestedAttribute.getValues());
2579      }
2580
2581      final List<String> trailingArgs = parser.getTrailingArguments();
2582      if (! trailingArgs.isEmpty())
2583      {
2584        final Iterator<String> trailingArgIterator = trailingArgs.iterator();
2585        if (! (filter.isPresent() || filterFile.isPresent()))
2586        {
2587          trailingArgIterator.next();
2588        }
2589
2590        while (trailingArgIterator.hasNext())
2591        {
2592          attrList.add(trailingArgIterator.next());
2593        }
2594      }
2595
2596      final String[] attributes = new String[attrList.size()];
2597      attrList.toArray(attributes);
2598
2599
2600      // If either or both the filter or filterFile arguments are provided, then
2601      // use them to get the filters to process.  Otherwise, the first trailing
2602      // argument should be a filter.
2603      ResultCode resultCode = ResultCode.SUCCESS;
2604      if (filter.isPresent() || filterFile.isPresent())
2605      {
2606        if (filter.isPresent())
2607        {
2608          for (final Filter f : filter.getValues())
2609          {
2610            final ResultCode rc = searchWithFilter(pool, f, attributes,
2611                 rateLimiter, searchControls);
2612            if (rc != ResultCode.SUCCESS)
2613            {
2614              if (resultCode == ResultCode.SUCCESS)
2615              {
2616                resultCode = rc;
2617              }
2618
2619              if (! continueOnError.isPresent())
2620              {
2621                return resultCode;
2622              }
2623            }
2624          }
2625        }
2626
2627        if (filterFile.isPresent())
2628        {
2629          final ResultCode rc = searchWithFilterFile(pool, attributes,
2630               rateLimiter, searchControls);
2631          if (rc != ResultCode.SUCCESS)
2632          {
2633            if (resultCode == ResultCode.SUCCESS)
2634            {
2635              resultCode = rc;
2636            }
2637
2638            if (! continueOnError.isPresent())
2639            {
2640              return resultCode;
2641            }
2642          }
2643        }
2644      }
2645      else
2646      {
2647        final Filter f;
2648        try
2649        {
2650          final String filterStr =
2651               parser.getTrailingArguments().iterator().next();
2652          f = Filter.create(filterStr);
2653        }
2654        catch (final LDAPException le)
2655        {
2656          // This should never happen.
2657          Debug.debugException(le);
2658          displayResult(le.toLDAPResult());
2659          return le.getResultCode();
2660        }
2661
2662        resultCode =
2663             searchWithFilter(pool, f, attributes, rateLimiter, searchControls);
2664      }
2665
2666      return resultCode;
2667    }
2668    finally
2669    {
2670      if (pool != null)
2671      {
2672        try
2673        {
2674          pool.close();
2675        }
2676        catch (final Exception e)
2677        {
2678          Debug.debugException(e);
2679        }
2680      }
2681
2682      if (outStream != null)
2683      {
2684        try
2685        {
2686          outStream.close();
2687          outStream = null;
2688        }
2689        catch (final Exception e)
2690        {
2691          Debug.debugException(e);
2692        }
2693      }
2694
2695      if (errStream != null)
2696      {
2697        try
2698        {
2699          errStream.close();
2700          errStream = null;
2701        }
2702        catch (final Exception e)
2703        {
2704          Debug.debugException(e);
2705        }
2706      }
2707
2708      LDIFWriter.setCommentAboutBase64EncodedValues(
2709           originalCommentAboutBase64EncodedValues);
2710    }
2711  }
2712
2713
2714
2715  /**
2716   * Processes a set of searches using LDAP URLs read from one or more files.
2717   *
2718   * @param  pool            The connection pool to use to communicate with the
2719   *                         directory server.
2720   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2721   *                         request rate limiting.
2722   * @param  searchControls  The set of controls to include in search requests.
2723   *
2724   * @return  A result code indicating the result of the processing.
2725   */
2726  @NotNull()
2727  private ResultCode searchWithLDAPURLs(@NotNull final LDAPConnectionPool pool,
2728               @Nullable final FixedRateBarrier rateLimiter,
2729               @NotNull final List<Control> searchControls)
2730  {
2731    ResultCode resultCode = ResultCode.SUCCESS;
2732    for (final File f : ldapURLFile.getValues())
2733    {
2734      BufferedReader reader = null;
2735
2736      try
2737      {
2738        reader = new BufferedReader(new FileReader(f));
2739        while (true)
2740        {
2741          final String line = reader.readLine();
2742          if (line == null)
2743          {
2744            break;
2745          }
2746
2747          if ((line.length() == 0) || line.startsWith("#"))
2748          {
2749            continue;
2750          }
2751
2752          final LDAPURL url;
2753          try
2754          {
2755            url = new LDAPURL(line);
2756          }
2757          catch (final LDAPException le)
2758          {
2759            Debug.debugException(le);
2760
2761            commentToErr(ERR_LDAPSEARCH_MALFORMED_LDAP_URL.get(
2762                 f.getAbsolutePath(), line));
2763            if (resultCode == ResultCode.SUCCESS)
2764            {
2765              resultCode = le.getResultCode();
2766            }
2767
2768            if (continueOnError.isPresent())
2769            {
2770              continue;
2771            }
2772            else
2773            {
2774              return resultCode;
2775            }
2776          }
2777
2778          final SearchRequest searchRequest = new SearchRequest(
2779               new LDAPSearchListener(resultWriter, entryTransformations),
2780               url.getBaseDN().toString(), url.getScope(), derefPolicy,
2781               sizeLimit.getValue(), timeLimitSeconds.getValue(),
2782               typesOnly.isPresent(), url.getFilter(), url.getAttributes());
2783          final ResultCode rc =
2784               doSearch(pool, searchRequest, rateLimiter, searchControls);
2785          if (rc != ResultCode.SUCCESS)
2786          {
2787            if (resultCode == ResultCode.SUCCESS)
2788            {
2789              resultCode = rc;
2790            }
2791
2792            if (! continueOnError.isPresent())
2793            {
2794              return resultCode;
2795            }
2796          }
2797        }
2798      }
2799      catch (final IOException ioe)
2800      {
2801        commentToErr(ERR_LDAPSEARCH_CANNOT_READ_LDAP_URL_FILE.get(
2802             f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe)));
2803        return ResultCode.LOCAL_ERROR;
2804      }
2805      finally
2806      {
2807        if (reader != null)
2808        {
2809          try
2810          {
2811            reader.close();
2812          }
2813          catch (final Exception e)
2814          {
2815            Debug.debugException(e);
2816          }
2817        }
2818      }
2819    }
2820
2821    return resultCode;
2822  }
2823
2824
2825
2826  /**
2827   * Processes a set of searches using filters read from one or more files.
2828   *
2829   * @param  pool            The connection pool to use to communicate with the
2830   *                         directory server.
2831   * @param  attributes      The set of attributes to request that the server
2832   *                         include in matching entries.
2833   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2834   *                         request rate limiting.
2835   * @param  searchControls  The set of controls to include in search requests.
2836   *
2837   * @return  A result code indicating the result of the processing.
2838   */
2839  @NotNull()
2840  private ResultCode searchWithFilterFile(
2841               @NotNull final LDAPConnectionPool pool,
2842               @NotNull final String[] attributes,
2843               @Nullable final FixedRateBarrier rateLimiter,
2844               @NotNull final List<Control> searchControls)
2845  {
2846    ResultCode resultCode = ResultCode.SUCCESS;
2847    for (final File f : filterFile.getValues())
2848    {
2849      FilterFileReader reader = null;
2850
2851      try
2852      {
2853        reader = new FilterFileReader(f);
2854        while (true)
2855        {
2856          final Filter searchFilter;
2857          try
2858          {
2859            searchFilter = reader.readFilter();
2860          }
2861          catch (final LDAPException le)
2862          {
2863            Debug.debugException(le);
2864            commentToErr(ERR_LDAPSEARCH_MALFORMED_FILTER.get(
2865                 f.getAbsolutePath(), le.getMessage()));
2866            if (resultCode == ResultCode.SUCCESS)
2867            {
2868              resultCode = le.getResultCode();
2869            }
2870
2871            if (continueOnError.isPresent())
2872            {
2873              continue;
2874            }
2875            else
2876            {
2877              return resultCode;
2878            }
2879          }
2880
2881          if (searchFilter == null)
2882          {
2883            break;
2884          }
2885
2886          final ResultCode rc = searchWithFilter(pool, searchFilter, attributes,
2887               rateLimiter, searchControls);
2888          if (rc != ResultCode.SUCCESS)
2889          {
2890            if (resultCode == ResultCode.SUCCESS)
2891            {
2892              resultCode = rc;
2893            }
2894
2895            if (! continueOnError.isPresent())
2896            {
2897              return resultCode;
2898            }
2899          }
2900        }
2901      }
2902      catch (final IOException ioe)
2903      {
2904        Debug.debugException(ioe);
2905        commentToErr(ERR_LDAPSEARCH_CANNOT_READ_FILTER_FILE.get(
2906             f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe)));
2907        return ResultCode.LOCAL_ERROR;
2908      }
2909      finally
2910      {
2911        if (reader != null)
2912        {
2913          try
2914          {
2915            reader.close();
2916          }
2917          catch (final Exception e)
2918          {
2919            Debug.debugException(e);
2920          }
2921        }
2922      }
2923    }
2924
2925    return resultCode;
2926  }
2927
2928
2929
2930  /**
2931   * Processes a search using the provided filter.
2932   *
2933   * @param  pool            The connection pool to use to communicate with the
2934   *                         directory server.
2935   * @param  filter          The filter to use for the search.
2936   * @param  attributes      The set of attributes to request that the server
2937   *                         include in matching entries.
2938   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2939   *                         request rate limiting.
2940   * @param  searchControls  The set of controls to include in search requests.
2941   *
2942   * @return  A result code indicating the result of the processing.
2943   */
2944  @NotNull()
2945  private ResultCode searchWithFilter(@NotNull final LDAPConnectionPool pool,
2946               @NotNull final Filter filter,
2947               @NotNull final String[] attributes,
2948               @Nullable final FixedRateBarrier rateLimiter,
2949               @NotNull final List<Control> searchControls)
2950  {
2951    final String baseDNString;
2952    if (baseDN.isPresent())
2953    {
2954      baseDNString = baseDN.getStringValue();
2955    }
2956    else
2957    {
2958      baseDNString = "";
2959    }
2960
2961    final SearchRequest searchRequest = new SearchRequest(
2962         new LDAPSearchListener(resultWriter, entryTransformations),
2963         baseDNString, scope.getValue(), derefPolicy, sizeLimit.getValue(),
2964         timeLimitSeconds.getValue(), typesOnly.isPresent(), filter,
2965         attributes);
2966    return doSearch(pool, searchRequest, rateLimiter, searchControls);
2967  }
2968
2969
2970
2971  /**
2972   * Processes a search with the provided information.
2973   *
2974   * @param  pool            The connection pool to use to communicate with the
2975   *                         directory server.
2976   * @param  searchRequest   The search request to process.
2977   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2978   *                         request rate limiting.
2979   * @param  searchControls  The set of controls to include in search requests.
2980   *
2981   * @return  A result code indicating the result of the processing.
2982   */
2983  @NotNull()
2984  private ResultCode doSearch(@NotNull final LDAPConnectionPool pool,
2985                              @NotNull final SearchRequest searchRequest,
2986                              @Nullable final FixedRateBarrier rateLimiter,
2987                              @NotNull final List<Control> searchControls)
2988  {
2989    if (separateOutputFilePerSearch.isPresent())
2990    {
2991      try
2992      {
2993        final String path = outputFile.getValue().getAbsolutePath() + '.' +
2994             outputFileCounter.getAndIncrement();
2995
2996        OutputStream s = new FileOutputStream(path);
2997
2998        if (encryptOutput.isPresent())
2999        {
3000          s = new PassphraseEncryptedOutputStream(encryptionPassphrase, s);
3001        }
3002
3003        if (compressOutput.isPresent())
3004        {
3005          s = new GZIPOutputStream(s);
3006        }
3007
3008        if (teeResultsToStandardOut.isPresent())
3009        {
3010          outStream = new PrintStream(new TeeOutputStream(s, getOut()));
3011        }
3012        else
3013        {
3014          outStream = new PrintStream(s);
3015        }
3016        resultWriter.updateOutputStream(outStream);
3017        errStream = outStream;
3018      }
3019      catch (final Exception e)
3020      {
3021        Debug.debugException(e);
3022        wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get(
3023             outputFile.getValue().getAbsolutePath(),
3024             StaticUtils.getExceptionMessage(e)));
3025        return ResultCode.LOCAL_ERROR;
3026      }
3027
3028      resultWriter.writeHeader();
3029    }
3030
3031    try
3032    {
3033      if (rateLimiter != null)
3034      {
3035        rateLimiter.await();
3036      }
3037
3038
3039      ASN1OctetString pagedResultsCookie = null;
3040      boolean multiplePages = false;
3041      long totalEntries = 0;
3042      long totalReferences = 0;
3043
3044      SearchResult searchResult;
3045      try
3046      {
3047        while (true)
3048        {
3049          searchRequest.setControls(searchControls);
3050          if (simplePageSize.isPresent())
3051          {
3052            searchRequest.addControl(new SimplePagedResultsControl(
3053                 simplePageSize.getValue(), pagedResultsCookie));
3054          }
3055
3056          if (dryRun.isPresent())
3057          {
3058            searchResult = new SearchResult(-1, ResultCode.SUCCESS,
3059                 INFO_LDAPSEARCH_DRY_RUN_REQUEST_NOT_SENT.get(
3060                      dryRun.getIdentifierString(),
3061                      String.valueOf(searchRequest)),
3062                 null, null, 0, 0, null);
3063            break;
3064          }
3065          else
3066          {
3067            if (! terse.isPresent())
3068            {
3069              if (verbose.isPresent() || persistentSearch.isPresent() ||
3070                  filterFile.isPresent() || ldapURLFile.isPresent() ||
3071                  (filter.isPresent() && (filter.getNumOccurrences() > 1)))
3072              {
3073                commentToOut(INFO_LDAPSEARCH_SENDING_SEARCH_REQUEST.get(
3074                     String.valueOf(searchRequest)));
3075              }
3076            }
3077            searchResult = pool.search(searchRequest);
3078          }
3079
3080          searchResult = handleJSONEncodedResponseControls(searchResult);
3081
3082          if (searchResult.getEntryCount() > 0)
3083          {
3084            totalEntries += searchResult.getEntryCount();
3085          }
3086
3087          if (searchResult.getReferenceCount() > 0)
3088          {
3089            totalReferences += searchResult.getReferenceCount();
3090          }
3091
3092          if (simplePageSize.isPresent())
3093          {
3094            final SimplePagedResultsControl pagedResultsControl;
3095            try
3096            {
3097              pagedResultsControl = SimplePagedResultsControl.get(searchResult);
3098              if (pagedResultsControl == null)
3099              {
3100                throw new LDAPSearchException(new SearchResult(
3101                     searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND,
3102                     ERR_LDAPSEARCH_MISSING_PAGED_RESULTS_RESPONSE_CONTROL.
3103                          get(),
3104                     searchResult.getMatchedDN(),
3105                     searchResult.getReferralURLs(),
3106                     searchResult.getSearchEntries(),
3107                     searchResult.getSearchReferences(),
3108                     searchResult.getEntryCount(),
3109                     searchResult.getReferenceCount(),
3110                     searchResult.getResponseControls()));
3111              }
3112
3113              if (pagedResultsControl.moreResultsToReturn())
3114              {
3115                if (verbose.isPresent())
3116                {
3117                  commentToOut(
3118                       INFO_LDAPSEARCH_INTERMEDIATE_PAGED_SEARCH_RESULT.get());
3119                  displayResult(searchResult);
3120                }
3121
3122                multiplePages = true;
3123                pagedResultsCookie = pagedResultsControl.getCookie();
3124              }
3125              else
3126              {
3127                break;
3128              }
3129            }
3130            catch (final LDAPException le)
3131            {
3132              Debug.debugException(le);
3133              throw new LDAPSearchException(new SearchResult(
3134                   searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND,
3135                   ERR_LDAPSEARCH_CANNOT_DECODE_PAGED_RESULTS_RESPONSE_CONTROL.
3136                        get(StaticUtils.getExceptionMessage(le)),
3137                   searchResult.getMatchedDN(), searchResult.getReferralURLs(),
3138                   searchResult.getSearchEntries(),
3139                   searchResult.getSearchReferences(),
3140                   searchResult.getEntryCount(),
3141                   searchResult.getReferenceCount(),
3142                   searchResult.getResponseControls()));
3143            }
3144          }
3145          else
3146          {
3147            break;
3148          }
3149        }
3150      }
3151      catch (final LDAPSearchException lse)
3152      {
3153        Debug.debugException(lse);
3154        searchResult = lse.toLDAPResult();
3155
3156        if (searchResult.getEntryCount() > 0)
3157        {
3158          totalEntries += searchResult.getEntryCount();
3159        }
3160
3161        if (searchResult.getReferenceCount() > 0)
3162        {
3163          totalReferences += searchResult.getReferenceCount();
3164        }
3165      }
3166
3167      if ((searchResult.getResultCode() != ResultCode.SUCCESS) ||
3168          (searchResult.getDiagnosticMessage() != null) ||
3169          (! terse.isPresent()))
3170      {
3171        displayResult(searchResult);
3172      }
3173
3174      if (multiplePages && (! terse.isPresent()))
3175      {
3176        commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_ENTRIES.get(totalEntries));
3177
3178        if (totalReferences > 0)
3179        {
3180          commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_REFERENCES.get(
3181               totalReferences));
3182        }
3183      }
3184
3185      if (countEntries.isPresent())
3186      {
3187        return ResultCode.valueOf((int) Math.min(totalEntries, 255));
3188      }
3189      else if (requireMatch.isPresent() && (totalEntries == 0))
3190      {
3191        return ResultCode.NO_RESULTS_RETURNED;
3192      }
3193      else
3194      {
3195        return searchResult.getResultCode();
3196      }
3197    }
3198    finally
3199    {
3200      if (separateOutputFilePerSearch.isPresent())
3201      {
3202        try
3203        {
3204          outStream.close();
3205        }
3206        catch (final Exception e)
3207        {
3208          Debug.debugException(e);
3209        }
3210
3211        outStream = null;
3212        errStream = null;
3213      }
3214    }
3215  }
3216
3217
3218
3219  /**
3220   * Retrieves a list of the controls that should be used when processing search
3221   * operations.
3222   *
3223   * @return  A list of the controls that should be used when processing search
3224   *          operations.
3225   *
3226   * @throws  LDAPException  If a problem is encountered while generating the
3227   *                         controls for a search request.
3228   */
3229  @NotNull()
3230  private List<Control> getSearchControls()
3231          throws LDAPException
3232  {
3233    final ArrayList<Control> controls = new ArrayList<>(10);
3234
3235    if (searchControl.isPresent())
3236    {
3237      controls.addAll(searchControl.getValues());
3238    }
3239
3240    if (joinRequestControl != null)
3241    {
3242      controls.add(joinRequestControl);
3243    }
3244
3245    if (matchedValuesRequestControl != null)
3246    {
3247      controls.add(matchedValuesRequestControl);
3248    }
3249
3250    if (matchingEntryCountRequestControl != null)
3251    {
3252      controls.add(matchingEntryCountRequestControl);
3253    }
3254
3255    if (overrideSearchLimitsRequestControl != null)
3256    {
3257      controls.add(overrideSearchLimitsRequestControl);
3258    }
3259
3260    if (persistentSearchRequestControl != null)
3261    {
3262      controls.add(persistentSearchRequestControl);
3263    }
3264
3265    if (sortRequestControl != null)
3266    {
3267      controls.add(sortRequestControl);
3268    }
3269
3270    if (vlvRequestControl != null)
3271    {
3272      controls.add(vlvRequestControl);
3273    }
3274
3275    controls.addAll(routeToBackendSetRequestControls);
3276
3277    if (accessLogField.isPresent())
3278    {
3279      final Map<String,JSONValue> fields = new LinkedHashMap<>();
3280      for (final String nameValueStr : accessLogField.getValues())
3281      {
3282        final int colonPos = nameValueStr.indexOf(':');
3283        if (colonPos < 0)
3284        {
3285          throw new LDAPException(ResultCode.PARAM_ERROR,
3286               ERR_LDAPSEARCH_ACCESS_LOG_FIELD_NO_COLON.get(
3287                    accessLogField.getIdentifierString(), nameValueStr));
3288        }
3289
3290        final String fieldName = nameValueStr.substring(0, colonPos);
3291        if (fields.containsKey(fieldName))
3292        {
3293          throw new LDAPException(ResultCode.PARAM_ERROR,
3294               ERR_LDAPSEARCH_ACCESS_LOG_FIELD_DUPLICATE_FIELD.get(
3295                    accessLogField.getIdentifierString(), fieldName));
3296        }
3297
3298        final String valueStr = nameValueStr.substring(colonPos + 1);
3299        if (valueStr.equalsIgnoreCase("true"))
3300        {
3301          fields.put(fieldName, JSONBoolean.TRUE);
3302        }
3303        else if (valueStr.equalsIgnoreCase("false"))
3304        {
3305          fields.put(fieldName, JSONBoolean.FALSE);
3306        }
3307        else
3308        {
3309          try
3310          {
3311            final BigDecimal d = new BigDecimal(valueStr);
3312            fields.put(fieldName, new JSONNumber(d));
3313          }
3314          catch (final Exception e)
3315          {
3316            Debug.debugException(e);
3317            fields.put(fieldName, new JSONString(valueStr));
3318          }
3319        }
3320      }
3321
3322      controls.add(new AccessLogFieldRequestControl(false,
3323           new JSONObject(fields)));
3324    }
3325
3326    if (accountUsable.isPresent())
3327    {
3328      controls.add(new AccountUsableRequestControl(true));
3329    }
3330
3331    if (getBackendSetID.isPresent())
3332    {
3333      controls.add(new GetBackendSetIDRequestControl(false));
3334    }
3335
3336    if (getServerID.isPresent())
3337    {
3338      controls.add(new GetServerIDRequestControl(false));
3339    }
3340
3341    if (includeReplicationConflictEntries.isPresent())
3342    {
3343      controls.add(new ReturnConflictEntriesRequestControl(true));
3344    }
3345
3346    if (includeSoftDeletedEntries.isPresent())
3347    {
3348      final String valueStr =
3349           StaticUtils.toLowerCase(includeSoftDeletedEntries.getValue());
3350      if (valueStr.equals("with-non-deleted-entries"))
3351      {
3352        controls.add(new SoftDeletedEntryAccessRequestControl(true, true,
3353             false));
3354      }
3355      else if (valueStr.equals("without-non-deleted-entries"))
3356      {
3357        controls.add(new SoftDeletedEntryAccessRequestControl(true, false,
3358             false));
3359      }
3360      else
3361      {
3362        controls.add(new SoftDeletedEntryAccessRequestControl(true, false,
3363             true));
3364      }
3365    }
3366
3367    if (draftLDUPSubentries.isPresent())
3368    {
3369      controls.add(new DraftLDUPSubentriesRequestControl(true));
3370    }
3371
3372    if (rfc3672Subentries.isPresent())
3373    {
3374      controls.add(new RFC3672SubentriesRequestControl(
3375           rfc3672Subentries.getValue()));
3376    }
3377
3378    if (manageDsaIT.isPresent())
3379    {
3380      controls.add(new ManageDsaITRequestControl(true));
3381    }
3382
3383    if (realAttributesOnly.isPresent())
3384    {
3385      controls.add(new RealAttributesOnlyRequestControl(true));
3386    }
3387
3388    if (routeToServer.isPresent())
3389    {
3390      controls.add(new RouteToServerRequestControl(false,
3391           routeToServer.getValue(), false, false, false));
3392    }
3393
3394    if (virtualAttributesOnly.isPresent())
3395    {
3396      controls.add(new VirtualAttributesOnlyRequestControl(true));
3397    }
3398
3399    if (excludeBranch.isPresent())
3400    {
3401      final ArrayList<String> dns =
3402           new ArrayList<>(excludeBranch.getValues().size());
3403      for (final DN dn : excludeBranch.getValues())
3404      {
3405        dns.add(dn.toString());
3406      }
3407      controls.add(new ExcludeBranchRequestControl(true, dns));
3408    }
3409
3410    if (assertionFilter.isPresent())
3411    {
3412      controls.add(new AssertionRequestControl(
3413           assertionFilter.getValue(), true));
3414    }
3415
3416    if (getEffectiveRightsAuthzID.isPresent())
3417    {
3418      final String[] attributes;
3419      if (getEffectiveRightsAttribute.isPresent())
3420      {
3421        attributes = new String[getEffectiveRightsAttribute.getValues().size()];
3422        for (int i=0; i < attributes.length; i++)
3423        {
3424          attributes[i] = getEffectiveRightsAttribute.getValues().get(i);
3425        }
3426      }
3427      else
3428      {
3429        attributes = StaticUtils.NO_STRINGS;
3430      }
3431
3432      controls.add(new GetEffectiveRightsRequestControl(true,
3433           getEffectiveRightsAuthzID.getValue(), attributes));
3434    }
3435
3436    if (operationPurpose.isPresent())
3437    {
3438      controls.add(new OperationPurposeRequestControl(true, "ldapsearch",
3439           Version.NUMERIC_VERSION_STRING, "LDAPSearch.getSearchControls",
3440           operationPurpose.getValue()));
3441    }
3442
3443    if (proxyAs.isPresent())
3444    {
3445      controls.add(new ProxiedAuthorizationV2RequestControl(
3446           proxyAs.getValue()));
3447    }
3448
3449    if (proxyV1As.isPresent())
3450    {
3451      controls.add(new ProxiedAuthorizationV1RequestControl(
3452           proxyV1As.getValue()));
3453    }
3454
3455    if (suppressOperationalAttributeUpdates.isPresent())
3456    {
3457      final EnumSet<SuppressType> suppressTypes =
3458           EnumSet.noneOf(SuppressType.class);
3459      for (final String s : suppressOperationalAttributeUpdates.getValues())
3460      {
3461        if (s.equalsIgnoreCase("last-access-time"))
3462        {
3463          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
3464        }
3465        else if (s.equalsIgnoreCase("last-login-time"))
3466        {
3467          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
3468        }
3469        else if (s.equalsIgnoreCase("last-login-ip"))
3470        {
3471          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
3472        }
3473      }
3474
3475      controls.add(new SuppressOperationalAttributeUpdateRequestControl(
3476           suppressTypes));
3477    }
3478
3479    if (rejectUnindexedSearch.isPresent())
3480    {
3481      controls.add(new RejectUnindexedSearchRequestControl());
3482    }
3483
3484    if (permitUnindexedSearch.isPresent())
3485    {
3486      controls.add(new PermitUnindexedSearchRequestControl());
3487    }
3488
3489    if (useJSONFormattedRequestControls.isPresent())
3490    {
3491      final JSONFormattedRequestControl jsonFormattedRequestControl =
3492           JSONFormattedRequestControl.createWithControls(true, controls);
3493      controls.clear();
3494      controls.add(jsonFormattedRequestControl);
3495    }
3496
3497    return controls;
3498  }
3499
3500
3501
3502  /**
3503   * Displays information about the provided result, including special
3504   * processing for a number of supported response controls.
3505   *
3506   * @param  result  The result to examine.
3507   */
3508  private void displayResult(@NotNull final LDAPResult result)
3509  {
3510    resultWriter.writeResult(result);
3511  }
3512
3513
3514
3515  /**
3516   * Writes the provided message to the output stream.
3517   *
3518   * @param  message  The message to be written.
3519   */
3520  void writeOut(@NotNull final String message)
3521  {
3522    if (outStream == null)
3523    {
3524      out(message);
3525    }
3526    else
3527    {
3528      outStream.println(message);
3529    }
3530  }
3531
3532
3533
3534  /**
3535   * Writes the provided message to the error stream.
3536   *
3537   * @param  message  The message to be written.
3538   */
3539  private void writeErr(@NotNull final String message)
3540  {
3541    if (errStream == null)
3542    {
3543      err(message);
3544    }
3545    else
3546    {
3547      errStream.println(message);
3548    }
3549  }
3550
3551
3552
3553  /**
3554   * Writes a line-wrapped, commented version of the provided message to
3555   * standard output.
3556   *
3557   * @param  message  The message to be written.
3558   */
3559  private void commentToOut(@NotNull final String message)
3560  {
3561    if (terse.isPresent())
3562    {
3563      return;
3564    }
3565
3566    for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2)))
3567    {
3568      writeOut("# " + line);
3569    }
3570  }
3571
3572
3573
3574  /**
3575   * Writes a line-wrapped, commented version of the provided message to
3576   * standard error.
3577   *
3578   * @param  message  The message to be written.
3579   */
3580  private void commentToErr(@NotNull final String message)
3581  {
3582    for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2)))
3583    {
3584      writeErr("# " + line);
3585    }
3586  }
3587
3588
3589
3590  /**
3591   * Retrieves the tool's output stream.
3592   *
3593   * @return  The tool's output stream.
3594   */
3595  @NotNull()
3596  PrintStream getOutStream()
3597  {
3598    if (outStream == null)
3599    {
3600      return getOut();
3601    }
3602    else
3603    {
3604      return outStream;
3605    }
3606  }
3607
3608
3609
3610  /**
3611   * Retrieves the tool's error stream.
3612   *
3613   * @return  The tool's error stream.
3614   */
3615  @NotNull()
3616  PrintStream getErrStream()
3617  {
3618    if (errStream == null)
3619    {
3620      return getErr();
3621    }
3622    else
3623    {
3624      return errStream;
3625    }
3626  }
3627
3628
3629
3630  /**
3631   * Sets the output handler that should be used by this tool  This is primarily
3632   * intended for testing purposes.
3633   *
3634   * @param  resultWriter  The result writer that should be used by this tool.
3635   */
3636  void setResultWriter(@NotNull final LDAPResultWriter resultWriter)
3637  {
3638    this.resultWriter = resultWriter;
3639  }
3640
3641
3642
3643  /**
3644   * {@inheritDoc}
3645   */
3646  @Override()
3647  public void handleUnsolicitedNotification(
3648                   @NotNull final LDAPConnection connection,
3649                   @NotNull final ExtendedResult notification)
3650  {
3651    resultWriter.writeUnsolicitedNotification(connection, notification);
3652  }
3653
3654
3655
3656  /**
3657   * Examines the provided search result to see if it includes a JSONf-formatted
3658   * response control.  If so, then its embedded controls will be extracted and
3659   * a new search result will be returned with those extracted controls instead
3660   * of the JSON-formatted response control.  Otherwise, the provided search
3661   * result will be returned.
3662   *
3663   * @param  searchResult  The search result to be handled.  It must not be
3664   *                       {@code null}.
3665   *
3666   * @return  A new search result with the controls extracted from a
3667   *          JSON-formatted response control, or the original search result if
3668   *          it did not include a JSON-formatted response control.
3669   */
3670  @NotNull()
3671  static SearchResult handleJSONEncodedResponseControls(
3672              @NotNull final SearchResult searchResult)
3673  {
3674    try
3675    {
3676      final JSONFormattedResponseControl jsonFormattedResponseControl =
3677           JSONFormattedResponseControl.get(searchResult);
3678      if (jsonFormattedResponseControl == null)
3679      {
3680        return searchResult;
3681      }
3682
3683      final JSONFormattedControlDecodeBehavior decodeBehavior =
3684           new JSONFormattedControlDecodeBehavior();
3685      decodeBehavior.setThrowOnUnparsableObject(false);
3686      decodeBehavior.setThrowOnInvalidCriticalControl(false);
3687      decodeBehavior.setThrowOnInvalidNonCriticalControl(false);
3688      decodeBehavior.setThrowOnInvalidNonCriticalControl(false);
3689      decodeBehavior.setAllowEmbeddedJSONFormattedControl(true);
3690      decodeBehavior.setStrict(false);
3691
3692      final List<Control> decodedControls =
3693           jsonFormattedResponseControl.decodeEmbeddedControls(
3694                decodeBehavior, null);
3695
3696      return new SearchResult(searchResult.getMessageID(),
3697           searchResult.getResultCode(),
3698           searchResult.getDiagnosticMessage(),
3699           searchResult.getMatchedDN(),
3700           searchResult.getReferralURLs(),
3701           searchResult.getSearchEntries(),
3702           searchResult.getSearchReferences(),
3703           searchResult.getEntryCount(),
3704           searchResult.getReferenceCount(),
3705           StaticUtils.toArray(decodedControls, Control.class));
3706    }
3707    catch (final LDAPException e)
3708    {
3709      Debug.debugException(e);
3710      return searchResult;
3711    }
3712  }
3713
3714
3715
3716  /**
3717   * {@inheritDoc}
3718   */
3719  @Override()
3720  @NotNull()
3721  public LinkedHashMap<String[],String> getExampleUsages()
3722  {
3723    final LinkedHashMap<String[],String> examples =
3724         new LinkedHashMap<>(StaticUtils.computeMapCapacity(5));
3725
3726    String[] args =
3727    {
3728      "--hostname", "directory.example.com",
3729      "--port", "389",
3730      "--bindDN", "uid=jdoe,ou=People,dc=example,dc=com",
3731      "--bindPassword", "password",
3732      "--baseDN", "ou=People,dc=example,dc=com",
3733      "--scope", "sub",
3734      "(uid=jqpublic)",
3735      "givenName",
3736      "sn",
3737      "mail"
3738    };
3739    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_1.get());
3740
3741
3742    args = new String[]
3743    {
3744      "--hostname", "directory.example.com",
3745      "--port", "636",
3746      "--useSSL",
3747      "--saslOption", "mech=PLAIN",
3748      "--saslOption", "authID=u:jdoe",
3749      "--bindPasswordFile", "/path/to/password/file",
3750      "--baseDN", "ou=People,dc=example,dc=com",
3751      "--scope", "sub",
3752      "--filterFile", "/path/to/filter/file",
3753      "--outputFile", "/path/to/base/output/file",
3754      "--separateOutputFilePerSearch",
3755      "--requestedAttribute", "*",
3756      "--requestedAttribute", "+"
3757    };
3758    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_2.get());
3759
3760
3761    args = new String[]
3762    {
3763      "--hostname", "directory.example.com",
3764      "--port", "389",
3765      "--useStartTLS",
3766      "--trustStorePath", "/path/to/truststore/file",
3767      "--baseDN", "",
3768      "--scope", "base",
3769      "--outputFile", "/path/to/output/file",
3770      "--teeResultsToStandardOut",
3771      "(objectClass=*)",
3772      "*",
3773      "+"
3774    };
3775    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_3.get());
3776
3777
3778    args = new String[]
3779    {
3780      "--hostname", "directory.example.com",
3781      "--port", "389",
3782      "--bindDN", "uid=admin,dc=example,dc=com",
3783      "--baseDN", "dc=example,dc=com",
3784      "--scope", "sub",
3785      "--outputFile", "/path/to/output/file",
3786      "--simplePageSize", "100",
3787      "(objectClass=*)",
3788      "*",
3789      "+"
3790    };
3791    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_4.get());
3792
3793
3794    args = new String[]
3795    {
3796      "--hostname", "directory.example.com",
3797      "--port", "389",
3798      "--bindDN", "uid=admin,dc=example,dc=com",
3799      "--baseDN", "dc=example,dc=com",
3800      "--scope", "sub",
3801      "(&(givenName=John)(sn=Doe))",
3802      "debugsearchindex"
3803    };
3804    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_5.get());
3805
3806    return examples;
3807  }
3808}