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