001/*
002 * Copyright 2016-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2019 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.ByteArrayInputStream;
026import java.io.File;
027import java.io.InputStream;
028import java.io.IOException;
029import java.io.OutputStream;
030import java.util.ArrayList;
031import java.util.EnumSet;
032import java.util.HashSet;
033import java.util.LinkedHashMap;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037import java.util.SortedMap;
038import java.util.StringTokenizer;
039import java.util.concurrent.TimeUnit;
040import java.util.concurrent.atomic.AtomicBoolean;
041
042import com.unboundid.asn1.ASN1OctetString;
043import com.unboundid.ldap.sdk.AddRequest;
044import com.unboundid.ldap.sdk.Control;
045import com.unboundid.ldap.sdk.DeleteRequest;
046import com.unboundid.ldap.sdk.DN;
047import com.unboundid.ldap.sdk.Entry;
048import com.unboundid.ldap.sdk.ExtendedResult;
049import com.unboundid.ldap.sdk.Filter;
050import com.unboundid.ldap.sdk.LDAPConnectionOptions;
051import com.unboundid.ldap.sdk.LDAPConnection;
052import com.unboundid.ldap.sdk.LDAPConnectionPool;
053import com.unboundid.ldap.sdk.LDAPException;
054import com.unboundid.ldap.sdk.LDAPRequest;
055import com.unboundid.ldap.sdk.LDAPResult;
056import com.unboundid.ldap.sdk.LDAPSearchException;
057import com.unboundid.ldap.sdk.Modification;
058import com.unboundid.ldap.sdk.ModifyRequest;
059import com.unboundid.ldap.sdk.ModifyDNRequest;
060import com.unboundid.ldap.sdk.ResultCode;
061import com.unboundid.ldap.sdk.SearchRequest;
062import com.unboundid.ldap.sdk.SearchResult;
063import com.unboundid.ldap.sdk.SearchScope;
064import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler;
065import com.unboundid.ldap.sdk.Version;
066import com.unboundid.ldap.sdk.controls.AssertionRequestControl;
067import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
068import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl;
069import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl;
070import com.unboundid.ldap.sdk.controls.PostReadRequestControl;
071import com.unboundid.ldap.sdk.controls.PreReadRequestControl;
072import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl;
073import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
074import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
075import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl;
076import com.unboundid.ldap.sdk.controls.TransactionSpecificationRequestControl;
077import com.unboundid.ldap.sdk.extensions.StartTransactionExtendedRequest;
078import com.unboundid.ldap.sdk.extensions.StartTransactionExtendedResult;
079import com.unboundid.ldap.sdk.extensions.EndTransactionExtendedRequest;
080import com.unboundid.ldap.sdk.unboundidds.controls.AssuredReplicationLocalLevel;
081import com.unboundid.ldap.sdk.unboundidds.controls.
082            AssuredReplicationRequestControl;
083import com.unboundid.ldap.sdk.unboundidds.controls.
084            AssuredReplicationRemoteLevel;
085import com.unboundid.ldap.sdk.unboundidds.controls.
086            GeneratePasswordRequestControl;
087import com.unboundid.ldap.sdk.unboundidds.controls.
088            GetAuthorizationEntryRequestControl;
089import com.unboundid.ldap.sdk.unboundidds.controls.
090            GetBackendSetIDRequestControl;
091import com.unboundid.ldap.sdk.unboundidds.controls.
092            GetUserResourceLimitsRequestControl;
093import com.unboundid.ldap.sdk.unboundidds.controls.GetServerIDRequestControl;
094import com.unboundid.ldap.sdk.unboundidds.controls.HardDeleteRequestControl;
095import com.unboundid.ldap.sdk.unboundidds.controls.
096            IgnoreNoUserModificationRequestControl;
097import com.unboundid.ldap.sdk.unboundidds.controls.
098            NameWithEntryUUIDRequestControl;
099import com.unboundid.ldap.sdk.unboundidds.controls.NoOpRequestControl;
100import com.unboundid.ldap.sdk.unboundidds.controls.
101            OperationPurposeRequestControl;
102import com.unboundid.ldap.sdk.unboundidds.controls.PasswordPolicyRequestControl;
103import com.unboundid.ldap.sdk.unboundidds.controls.
104            PasswordUpdateBehaviorRequestControl;
105import com.unboundid.ldap.sdk.unboundidds.controls.
106            PasswordUpdateBehaviorRequestControlProperties;
107import com.unboundid.ldap.sdk.unboundidds.controls.
108            PasswordValidationDetailsRequestControl;
109import com.unboundid.ldap.sdk.unboundidds.controls.PurgePasswordRequestControl;
110import com.unboundid.ldap.sdk.unboundidds.controls.
111            ReplicationRepairRequestControl;
112import com.unboundid.ldap.sdk.unboundidds.controls.RetirePasswordRequestControl;
113import com.unboundid.ldap.sdk.unboundidds.controls.
114            RouteToBackendSetRequestControl;
115import com.unboundid.ldap.sdk.unboundidds.controls.RouteToServerRequestControl;
116import com.unboundid.ldap.sdk.unboundidds.controls.SoftDeleteRequestControl;
117import com.unboundid.ldap.sdk.unboundidds.controls.
118            SuppressOperationalAttributeUpdateRequestControl;
119import com.unboundid.ldap.sdk.unboundidds.controls.
120            SuppressReferentialIntegrityUpdatesRequestControl;
121import com.unboundid.ldap.sdk.unboundidds.controls.UniquenessMultipleAttributeBehavior;
122import com.unboundid.ldap.sdk.unboundidds.controls.UniquenessRequestControl;
123import com.unboundid.ldap.sdk.unboundidds.controls.
124            UniquenessRequestControlProperties;
125import com.unboundid.ldap.sdk.unboundidds.controls.SuppressType;
126import com.unboundid.ldap.sdk.unboundidds.controls.UndeleteRequestControl;
127import com.unboundid.ldap.sdk.unboundidds.controls.UniquenessValidationLevel;
128import com.unboundid.ldap.sdk.unboundidds.extensions.MultiUpdateErrorBehavior;
129import com.unboundid.ldap.sdk.unboundidds.extensions.MultiUpdateExtendedRequest;
130import com.unboundid.ldap.sdk.unboundidds.extensions.
131            StartAdministrativeSessionExtendedRequest;
132import com.unboundid.ldap.sdk.unboundidds.extensions.
133            StartAdministrativeSessionPostConnectProcessor;
134import com.unboundid.ldif.LDIFAddChangeRecord;
135import com.unboundid.ldif.LDIFChangeRecord;
136import com.unboundid.ldif.LDIFDeleteChangeRecord;
137import com.unboundid.ldif.LDIFException;
138import com.unboundid.ldif.LDIFModifyChangeRecord;
139import com.unboundid.ldif.LDIFModifyDNChangeRecord;
140import com.unboundid.ldif.LDIFReader;
141import com.unboundid.ldif.LDIFWriter;
142import com.unboundid.ldif.TrailingSpaceBehavior;
143import com.unboundid.util.Debug;
144import com.unboundid.util.DNFileReader;
145import com.unboundid.util.FilterFileReader;
146import com.unboundid.util.FixedRateBarrier;
147import com.unboundid.util.LDAPCommandLineTool;
148import com.unboundid.util.StaticUtils;
149import com.unboundid.util.SubtreeDeleter;
150import com.unboundid.util.SubtreeDeleterResult;
151import com.unboundid.util.ThreadSafety;
152import com.unboundid.util.ThreadSafetyLevel;
153import com.unboundid.util.args.ArgumentException;
154import com.unboundid.util.args.ArgumentParser;
155import com.unboundid.util.args.BooleanArgument;
156import com.unboundid.util.args.ControlArgument;
157import com.unboundid.util.args.DNArgument;
158import com.unboundid.util.args.DurationArgument;
159import com.unboundid.util.args.FileArgument;
160import com.unboundid.util.args.FilterArgument;
161import com.unboundid.util.args.IntegerArgument;
162import com.unboundid.util.args.StringArgument;
163
164import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;
165
166
167
168/**
169 * This class provides an implementation of an LDAP command-line tool that may
170 * be used to apply changes to a directory server.  The changes to apply (which
171 * may include add, delete, modify, and modify DN operations) will be read in
172 * LDIF form, either from standard input or a specified file or set of files.
173 * This is a much more full-featured tool than the
174 * {@link com.unboundid.ldap.sdk.examples.LDAPModify} tool
175 * <BR>
176 * <BLOCKQUOTE>
177 *   <B>NOTE:</B>  This class, and other classes within the
178 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
179 *   supported for use against Ping Identity, UnboundID, and
180 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
181 *   for proprietary functionality or for external specifications that are not
182 *   considered stable or mature enough to be guaranteed to work in an
183 *   interoperable way with other types of LDAP servers.
184 * </BLOCKQUOTE>
185 */
186@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
187public final class LDAPModify
188       extends LDAPCommandLineTool
189       implements UnsolicitedNotificationHandler
190{
191  /**
192   * The column at which output should be wrapped.
193   */
194  private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
195
196
197
198  /**
199   * The name of the attribute type used to specify a password in the
200   * authentication password syntax as described in RFC 3112.
201   */
202  private static final String ATTR_AUTH_PASSWORD = "authPassword";
203
204
205
206  /**
207   * The name of the attribute type used to specify the DN of the soft-deleted
208   * entry to be restored via an undelete operation.
209   */
210  private static final String ATTR_UNDELETE_FROM_DN = "ds-undelete-from-dn";
211
212
213
214  /**
215   * The name of the attribute type used to specify a password in the
216   * userPassword syntax.
217   */
218  private static final String ATTR_USER_PASSWORD = "userPassword";
219
220
221
222  /**
223   * The long identifier for the argument used to specify the desired assured
224   * replication local level.
225   */
226  private static final String ARG_ASSURED_REPLICATION_LOCAL_LEVEL =
227       "assuredReplicationLocalLevel";
228
229
230
231  /**
232   * The long identifier for the argument used to specify the desired assured
233   * replication remote level.
234   */
235  private static final String ARG_ASSURED_REPLICATION_REMOTE_LEVEL =
236       "assuredReplicationRemoteLevel";
237
238
239
240  /**
241   * The long identifier for the argument used to specify the desired assured
242   * timeout.
243   */
244  private static final String ARG_ASSURED_REPLICATION_TIMEOUT =
245       "assuredReplicationTimeout";
246
247
248
249  /**
250   * The long identifier for the argument used to specify the path to an LDIF
251   * file containing changes to apply.
252   */
253  private static final String ARG_LDIF_FILE = "ldifFile";
254
255
256
257  /**
258   * The long identifier for the argument used to specify the simple paged
259   * results page size to use when modifying entries that match a provided
260   * filter.
261   */
262  private static final String ARG_SEARCH_PAGE_SIZE = "searchPageSize";
263
264
265
266  // The set of arguments supported by this program.
267  private BooleanArgument allowUndelete = null;
268  private BooleanArgument assuredReplication = null;
269  private BooleanArgument authorizationIdentity = null;
270  private BooleanArgument clientSideSubtreeDelete = null;
271  private BooleanArgument continueOnError = null;
272  private BooleanArgument defaultAdd = null;
273  private BooleanArgument dryRun = null;
274  private BooleanArgument followReferrals = null;
275  private BooleanArgument generatePassword = null;
276  private BooleanArgument getBackendSetID = null;
277  private BooleanArgument getServerID = null;
278  private BooleanArgument getUserResourceLimits = null;
279  private BooleanArgument hardDelete = null;
280  private BooleanArgument ignoreNoUserModification = null;
281  private BooleanArgument manageDsaIT = null;
282  private BooleanArgument nameWithEntryUUID = null;
283  private BooleanArgument noOperation = null;
284  private BooleanArgument passwordValidationDetails = null;
285  private BooleanArgument permissiveModify = null;
286  private BooleanArgument purgeCurrentPassword = null;
287  private BooleanArgument replicationRepair = null;
288  private BooleanArgument retireCurrentPassword = null;
289  private BooleanArgument retryFailedOperations = null;
290  private BooleanArgument softDelete = null;
291  private BooleanArgument stripTrailingSpaces = null;
292  private BooleanArgument serverSideSubtreeDelete = null;
293  private BooleanArgument suppressReferentialIntegrityUpdates = null;
294  private BooleanArgument useAdministrativeSession = null;
295  private BooleanArgument usePasswordPolicyControl = null;
296  private BooleanArgument useTransaction = null;
297  private BooleanArgument verbose = null;
298  private ControlArgument addControl = null;
299  private ControlArgument bindControl = null;
300  private ControlArgument deleteControl = null;
301  private ControlArgument modifyControl = null;
302  private ControlArgument modifyDNControl = null;
303  private ControlArgument operationControl = null;
304  private DNArgument modifyEntryWithDN = null;
305  private DNArgument proxyV1As = null;
306  private DNArgument uniquenessBaseDN = null;
307  private DurationArgument assuredReplicationTimeout = null;
308  private FileArgument encryptionPassphraseFile = null;
309  private FileArgument ldifFile = null;
310  private FileArgument modifyEntriesMatchingFiltersFromFile = null;
311  private FileArgument modifyEntriesWithDNsFromFile = null;
312  private FileArgument rejectFile = null;
313  private FilterArgument assertionFilter = null;
314  private FilterArgument modifyEntriesMatchingFilter = null;
315  private FilterArgument uniquenessFilter = null;
316  private IntegerArgument ratePerSecond = null;
317  private IntegerArgument searchPageSize = null;
318  private StringArgument assuredReplicationLocalLevel = null;
319  private StringArgument assuredReplicationRemoteLevel = null;
320  private StringArgument characterSet = null;
321  private StringArgument getAuthorizationEntryAttribute = null;
322  private StringArgument multiUpdateErrorBehavior = null;
323  private StringArgument operationPurpose = null;
324  private StringArgument passwordUpdateBehavior = null;
325  private StringArgument postReadAttribute = null;
326  private StringArgument preReadAttribute = null;
327  private StringArgument proxyAs = null;
328  private StringArgument routeToBackendSet = null;
329  private StringArgument routeToServer = null;
330  private StringArgument suppressOperationalAttributeUpdates = null;
331  private StringArgument uniquenessAttribute = null;
332  private StringArgument uniquenessMultipleAttributeBehavior = null;
333  private StringArgument uniquenessPostCommitValidationLevel = null;
334  private StringArgument uniquenessPreCommitValidationLevel = null;
335
336  // Indicates whether we've written anything to the reject writer yet.
337  private final AtomicBoolean rejectWritten;
338
339  // The input stream from to use for standard input.
340  private final InputStream in;
341
342  // The route to backend set request controls to include in write requests.
343  private final List<RouteToBackendSetRequestControl>
344       routeToBackendSetRequestControls = new ArrayList<>(10);
345
346
347
348  /**
349   * Runs this tool with the provided command-line arguments.  It will use the
350   * JVM-default streams for standard input, output, and error.
351   *
352   * @param  args  The command-line arguments to provide to this program.
353   */
354  public static void main(final String... args)
355  {
356    final ResultCode resultCode = main(System.in, System.out, System.err, args);
357    if (resultCode != ResultCode.SUCCESS)
358    {
359      System.exit(Math.min(resultCode.intValue(), 255));
360    }
361  }
362
363
364
365  /**
366   * Runs this tool with the provided streams and command-line arguments.
367   *
368   * @param  in    The input stream to use for standard input.  If this is
369   *               {@code null}, then no standard input will be used.
370   * @param  out   The output stream to use for standard output.  If this is
371   *               {@code null}, then standard output will be suppressed.
372   * @param  err   The output stream to use for standard error.  If this is
373   *               {@code null}, then standard error will be suppressed.
374   * @param  args  The command-line arguments provided to this program.
375   *
376   * @return  The result code obtained when running the tool.  Any result code
377   *          other than {@link ResultCode#SUCCESS} indicates an error.
378   */
379  public static ResultCode main(final InputStream in, final OutputStream out,
380                                final OutputStream err, final String... args)
381  {
382    final LDAPModify tool = new LDAPModify(in, out, err);
383    return tool.runTool(args);
384  }
385
386
387
388  /**
389   * Creates a new instance of this tool with the provided streams.  Standard
390   * input will not be available.
391   *
392   * @param  out  The output stream to use for standard output.  If this is
393   *              {@code null}, then standard output will be suppressed.
394   * @param  err  The output stream to use for standard error.  If this is
395   *              {@code null}, then standard error will be suppressed.
396   */
397  public LDAPModify(final OutputStream out, final OutputStream err)
398  {
399    this(null, out, err);
400  }
401
402
403
404  /**
405   * Creates a new instance of this tool with the provided streams.
406   *
407   * @param  in   The input stream to use for standard input.  If this is
408   *              {@code null}, then no standard input will be used.
409   * @param  out  The output stream to use for standard output.  If this is
410   *              {@code null}, then standard output will be suppressed.
411   * @param  err  The output stream to use for standard error.  If this is
412   *              {@code null}, then standard error will be suppressed.
413   */
414  public LDAPModify(final InputStream in, final OutputStream out,
415                    final OutputStream err)
416  {
417    super(out, err);
418
419    if (in == null)
420    {
421      this.in = new ByteArrayInputStream(StaticUtils.NO_BYTES);
422    }
423    else
424    {
425      this.in = in;
426    }
427
428
429    rejectWritten = new AtomicBoolean(false);
430  }
431
432
433
434  /**
435   * {@inheritDoc}
436   */
437  @Override()
438  public String getToolName()
439  {
440    return "ldapmodify";
441  }
442
443
444
445  /**
446   * {@inheritDoc}
447   */
448  @Override()
449  public String getToolDescription()
450  {
451    return INFO_LDAPMODIFY_TOOL_DESCRIPTION.get(ARG_LDIF_FILE);
452  }
453
454
455
456  /**
457   * {@inheritDoc}
458   */
459  @Override()
460  public String getToolVersion()
461  {
462    return Version.NUMERIC_VERSION_STRING;
463  }
464
465
466
467  /**
468   * {@inheritDoc}
469   */
470  @Override()
471  public boolean supportsInteractiveMode()
472  {
473    return true;
474  }
475
476
477
478  /**
479   * {@inheritDoc}
480   */
481  @Override()
482  public boolean defaultsToInteractiveMode()
483  {
484    return true;
485  }
486
487
488
489  /**
490   * {@inheritDoc}
491   */
492  @Override()
493  public boolean supportsPropertiesFile()
494  {
495    return true;
496  }
497
498
499
500  /**
501   * {@inheritDoc}
502   */
503  @Override()
504  public boolean supportsOutputFile()
505  {
506    return true;
507  }
508
509
510
511  /**
512   * {@inheritDoc}
513   */
514  @Override()
515  protected boolean defaultToPromptForBindPassword()
516  {
517    return true;
518  }
519
520
521
522  /**
523   * {@inheritDoc}
524   */
525  @Override()
526  protected boolean includeAlternateLongIdentifiers()
527  {
528    return true;
529  }
530
531
532
533  /**
534   * {@inheritDoc}
535   */
536  @Override()
537  protected boolean supportsSSLDebugging()
538  {
539    return true;
540  }
541
542
543
544  /**
545   * {@inheritDoc}
546   */
547  @Override()
548  protected boolean logToolInvocationByDefault()
549  {
550    return true;
551  }
552
553
554
555  /**
556   * {@inheritDoc}
557   */
558  @Override()
559  public void addNonLDAPArguments(final ArgumentParser parser)
560         throws ArgumentException
561  {
562    ldifFile = new FileArgument('f', ARG_LDIF_FILE, false, -1, null,
563         INFO_LDAPMODIFY_ARG_DESCRIPTION_LDIF_FILE.get(), true, true, true,
564         false);
565    ldifFile.addLongIdentifier("filename", true);
566    ldifFile.addLongIdentifier("ldif-file", true);
567    ldifFile.addLongIdentifier("file-name", true);
568    ldifFile.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
569    parser.addArgument(ldifFile);
570
571
572    encryptionPassphraseFile = new FileArgument(null,
573         "encryptionPassphraseFile", false, 1, null,
574         INFO_LDAPMODIFY_ARG_DESCRIPTION_ENCRYPTION_PW_FILE.get(), true, true,
575         true, false);
576    encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file",
577         true);
578    encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true);
579    encryptionPassphraseFile.addLongIdentifier("encryption-password-file",
580         true);
581    encryptionPassphraseFile.setArgumentGroupName(
582         INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
583    parser.addArgument(encryptionPassphraseFile);
584
585
586    characterSet = new StringArgument('i', "characterSet", false, 1,
587         INFO_LDAPMODIFY_PLACEHOLDER_CHARSET.get(),
588         INFO_LDAPMODIFY_ARG_DESCRIPTION_CHARACTER_SET.get(), "UTF-8");
589    characterSet.addLongIdentifier("encoding", true);
590    characterSet.addLongIdentifier("character-set", true);
591    characterSet.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
592    parser.addArgument(characterSet);
593
594
595    rejectFile = new FileArgument('R', "rejectFile", false, 1, null,
596         INFO_LDAPMODIFY_ARG_DESCRIPTION_REJECT_FILE.get(), false, true, true,
597         false);
598    rejectFile.addLongIdentifier("reject-file", true);
599    rejectFile.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
600    parser.addArgument(rejectFile);
601
602
603    verbose = new BooleanArgument('v', "verbose", 1,
604         INFO_LDAPMODIFY_ARG_DESCRIPTION_VERBOSE.get());
605    verbose.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
606    parser.addArgument(verbose);
607
608
609    modifyEntriesMatchingFilter = new FilterArgument(null,
610         "modifyEntriesMatchingFilter", false, 0, null,
611         INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_ENTRIES_MATCHING_FILTER.get(
612              ARG_SEARCH_PAGE_SIZE));
613    modifyEntriesMatchingFilter.addLongIdentifier(
614         "modify-entries-matching-filter", true);
615    modifyEntriesMatchingFilter.setArgumentGroupName(
616         INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
617    parser.addArgument(modifyEntriesMatchingFilter);
618
619
620    modifyEntriesMatchingFiltersFromFile = new FileArgument(null,
621         "modifyEntriesMatchingFiltersFromFile", false, 0, null,
622         INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_FILTER_FILE.get(
623              ARG_SEARCH_PAGE_SIZE), true, false, true, false);
624    modifyEntriesMatchingFiltersFromFile.addLongIdentifier(
625         "modify-entries-matching-filters-from-file", true);
626    modifyEntriesMatchingFiltersFromFile.setArgumentGroupName(
627         INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
628    parser.addArgument(modifyEntriesMatchingFiltersFromFile);
629
630
631    modifyEntryWithDN = new DNArgument(null, "modifyEntryWithDN", false, 0,
632         null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_ENTRY_DN.get());
633    modifyEntryWithDN.addLongIdentifier("modify-entry-with-dn", true);
634    modifyEntryWithDN.setArgumentGroupName(
635         INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
636    parser.addArgument(modifyEntryWithDN);
637
638
639    modifyEntriesWithDNsFromFile = new FileArgument(null,
640         "modifyEntriesWithDNsFromFile", false, 0,
641         null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_DN_FILE.get(), true,
642         false, true, false);
643    modifyEntriesWithDNsFromFile.addLongIdentifier(
644         "modify-entries-with-dns-from-file", true);
645    modifyEntriesWithDNsFromFile.setArgumentGroupName(
646         INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
647    parser.addArgument(modifyEntriesWithDNsFromFile);
648
649
650    searchPageSize = new IntegerArgument(null, ARG_SEARCH_PAGE_SIZE, false, 1,
651         null,
652         INFO_LDAPMODIFY_ARG_DESCRIPTION_SEARCH_PAGE_SIZE.get(
653              modifyEntriesMatchingFilter.getIdentifierString(),
654              modifyEntriesMatchingFiltersFromFile.getIdentifierString()),
655         1, Integer.MAX_VALUE);
656    searchPageSize.addLongIdentifier("search-page-size", true);
657    searchPageSize.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
658    parser.addArgument(searchPageSize);
659
660
661    retryFailedOperations = new BooleanArgument(null, "retryFailedOperations",
662         1, INFO_LDAPMODIFY_ARG_DESCRIPTION_RETRY_FAILED_OPERATIONS.get());
663    retryFailedOperations.addLongIdentifier("retry-failed-operations", true);
664    retryFailedOperations.setArgumentGroupName(
665         INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
666    parser.addArgument(retryFailedOperations);
667
668
669    dryRun = new BooleanArgument('n', "dryRun", 1,
670         INFO_LDAPMODIFY_ARG_DESCRIPTION_DRY_RUN.get());
671    dryRun.addLongIdentifier("dry-run", true);
672    dryRun.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
673    parser.addArgument(dryRun);
674
675
676    defaultAdd = new BooleanArgument('a', "defaultAdd", 1,
677         INFO_LDAPMODIFY_ARG_DESCRIPTION_DEFAULT_ADD.get());
678    defaultAdd.addLongIdentifier("default-add", true);
679    defaultAdd.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
680    parser.addArgument(defaultAdd);
681
682
683    continueOnError = new BooleanArgument('c', "continueOnError", 1,
684         INFO_LDAPMODIFY_ARG_DESCRIPTION_CONTINUE_ON_ERROR.get());
685    continueOnError.addLongIdentifier("continue-on-error", true);
686    continueOnError.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
687    parser.addArgument(continueOnError);
688
689
690    stripTrailingSpaces = new BooleanArgument(null, "stripTrailingSpaces", 1,
691         INFO_LDAPMODIFY_ARG_DESCRIPTION_STRIP_TRAILING_SPACES.get());
692    stripTrailingSpaces.addLongIdentifier("strip-trailing-spaces", true);
693    stripTrailingSpaces.setArgumentGroupName(
694         INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
695    parser.addArgument(stripTrailingSpaces);
696
697
698
699    followReferrals = new BooleanArgument(null, "followReferrals", 1,
700         INFO_LDAPMODIFY_ARG_DESCRIPTION_FOLLOW_REFERRALS.get());
701    followReferrals.addLongIdentifier("follow-referrals", true);
702    followReferrals.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
703    parser.addArgument(followReferrals);
704
705
706    proxyAs = new StringArgument('Y', "proxyAs", false, 1,
707         INFO_PLACEHOLDER_AUTHZID.get(),
708         INFO_LDAPMODIFY_ARG_DESCRIPTION_PROXY_AS.get());
709    proxyAs.addLongIdentifier("proxyV2As", true);
710    proxyAs.addLongIdentifier("proxy-as", true);
711    proxyAs.addLongIdentifier("proxy-v2-as", true);
712    proxyAs.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
713    parser.addArgument(proxyAs);
714
715    proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null,
716         INFO_LDAPMODIFY_ARG_DESCRIPTION_PROXY_V1_AS.get());
717    proxyV1As.addLongIdentifier("proxy-v1-as", true);
718    proxyV1As.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
719    parser.addArgument(proxyV1As);
720
721
722    useAdministrativeSession = new BooleanArgument(null,
723         "useAdministrativeSession", 1,
724         INFO_LDAPMODIFY_ARG_DESCRIPTION_USE_ADMIN_SESSION.get());
725    useAdministrativeSession.addLongIdentifier("use-administrative-session",
726         true);
727    useAdministrativeSession.setArgumentGroupName(
728         INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
729    parser.addArgument(useAdministrativeSession);
730
731
732    operationPurpose = new StringArgument(null, "operationPurpose", false, 1,
733         INFO_PLACEHOLDER_PURPOSE.get(),
734         INFO_LDAPMODIFY_ARG_DESCRIPTION_OPERATION_PURPOSE.get());
735    operationPurpose.addLongIdentifier("operation-purpose", true);
736    operationPurpose.setArgumentGroupName(
737         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
738    parser.addArgument(operationPurpose);
739
740
741    manageDsaIT = new BooleanArgument(null, "useManageDsaIT", 1,
742         INFO_LDAPMODIFY_ARG_DESCRIPTION_MANAGE_DSA_IT.get());
743    manageDsaIT.addLongIdentifier("manageDsaIT", true);
744    manageDsaIT.addLongIdentifier("use-manage-dsa-it", true);
745    manageDsaIT.addLongIdentifier("manage-dsa-it", true);
746    manageDsaIT.setArgumentGroupName(
747         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
748    parser.addArgument(manageDsaIT);
749
750
751    useTransaction = new BooleanArgument(null, "useTransaction", 1,
752         INFO_LDAPMODIFY_ARG_DESCRIPTION_USE_TRANSACTION.get());
753    useTransaction.addLongIdentifier("use-transaction", true);
754    useTransaction.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
755    parser.addArgument(useTransaction);
756
757
758    final Set<String> multiUpdateErrorBehaviorAllowedValues =
759         StaticUtils.setOf("atomic", "abort-on-error", "continue-on-error");
760    multiUpdateErrorBehavior = new StringArgument(null,
761         "multiUpdateErrorBehavior", false, 1,
762         "{atomic|abort-on-error|continue-on-error}",
763         INFO_LDAPMODIFY_ARG_DESCRIPTION_MULTI_UPDATE_ERROR_BEHAVIOR.get(),
764         multiUpdateErrorBehaviorAllowedValues);
765    multiUpdateErrorBehavior.addLongIdentifier("multi-update-error-behavior",
766         true);
767    multiUpdateErrorBehavior.setArgumentGroupName(
768         INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
769    parser.addArgument(multiUpdateErrorBehavior);
770
771
772    assertionFilter = new FilterArgument(null, "assertionFilter", false, 1,
773         INFO_PLACEHOLDER_FILTER.get(),
774         INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSERTION_FILTER.get());
775    assertionFilter.addLongIdentifier("assertion-filter", true);
776    assertionFilter.setArgumentGroupName(
777         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
778    parser.addArgument(assertionFilter);
779
780
781    authorizationIdentity = new BooleanArgument('E',
782         "authorizationIdentity", 1,
783         INFO_LDAPMODIFY_ARG_DESCRIPTION_AUTHZ_IDENTITY.get());
784    authorizationIdentity.addLongIdentifier("reportAuthzID", true);
785    authorizationIdentity.addLongIdentifier("authorization-identity", true);
786    authorizationIdentity.addLongIdentifier("report-authzID", true);
787    authorizationIdentity.addLongIdentifier("report-authz-id", true);
788    authorizationIdentity.setArgumentGroupName(
789         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
790    parser.addArgument(authorizationIdentity);
791
792
793    generatePassword = new BooleanArgument(null, "generatePassword", 1,
794         INFO_LDAPMODIFY_ARG_DESCRIPTION_GENERATE_PASSWORD.get());
795    generatePassword.addLongIdentifier("generatePW", true);
796    generatePassword.addLongIdentifier("generate-password", true);
797    generatePassword.addLongIdentifier("generate-pw", true);
798    generatePassword.setArgumentGroupName(
799         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
800    parser.addArgument(generatePassword);
801
802
803    getAuthorizationEntryAttribute = new StringArgument(null,
804         "getAuthorizationEntryAttribute", false, 0,
805         INFO_PLACEHOLDER_ATTR.get(),
806         INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_AUTHZ_ENTRY_ATTR.get());
807    getAuthorizationEntryAttribute.addLongIdentifier(
808         "get-authorization-entry-attribute", true);
809    getAuthorizationEntryAttribute.setArgumentGroupName(
810         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
811    parser.addArgument(getAuthorizationEntryAttribute);
812
813
814    getBackendSetID = new BooleanArgument(null, "getBackendSetID",
815         1, INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_BACKEND_SET_ID.get());
816    getBackendSetID.addLongIdentifier("get-backend-set-id", true);
817    getBackendSetID.setArgumentGroupName(
818         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
819    parser.addArgument(getBackendSetID);
820
821
822    getServerID = new BooleanArgument(null, "getServerID",
823         1, INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_SERVER_ID.get());
824    getServerID.addLongIdentifier("get-server-id", true);
825    getServerID.setArgumentGroupName(
826         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
827    parser.addArgument(getServerID);
828
829
830    getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits",
831         1, INFO_LDAPMODIFY_ARG_DESCRIPTION_GET_USER_RESOURCE_LIMITS.get());
832    getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true);
833    getUserResourceLimits.setArgumentGroupName(
834         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
835    parser.addArgument(getUserResourceLimits);
836
837
838    ignoreNoUserModification = new BooleanArgument(null,
839         "ignoreNoUserModification", 1,
840         INFO_LDAPMODIFY_ARG_DESCRIPTION_IGNORE_NO_USER_MOD.get());
841    ignoreNoUserModification.addLongIdentifier("ignore-no-user-modification",
842         true);
843    ignoreNoUserModification.setArgumentGroupName(
844         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
845    parser.addArgument(ignoreNoUserModification);
846
847
848    preReadAttribute = new StringArgument(null, "preReadAttribute", false, -1,
849         INFO_PLACEHOLDER_ATTR.get(),
850         INFO_LDAPMODIFY_ARG_DESCRIPTION_PRE_READ_ATTRIBUTE.get());
851    preReadAttribute.addLongIdentifier("preReadAttributes", true);
852    preReadAttribute.addLongIdentifier("pre-read-attribute", true);
853    preReadAttribute.addLongIdentifier("pre-read-attributes", true);
854    preReadAttribute.setArgumentGroupName(
855         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
856    parser.addArgument(preReadAttribute);
857
858
859    postReadAttribute = new StringArgument(null, "postReadAttribute", false,
860         -1, INFO_PLACEHOLDER_ATTR.get(),
861         INFO_LDAPMODIFY_ARG_DESCRIPTION_POST_READ_ATTRIBUTE.get());
862    postReadAttribute.addLongIdentifier("postReadAttributes", true);
863    postReadAttribute.addLongIdentifier("post-read-attribute", true);
864    postReadAttribute.addLongIdentifier("post-read-attributes", true);
865    postReadAttribute.setArgumentGroupName(
866         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
867    parser.addArgument(postReadAttribute);
868
869
870    routeToBackendSet = new StringArgument(null, "routeToBackendSet",
871         false, 0,
872         INFO_LDAPMODIFY_ARG_PLACEHOLDER_ROUTE_TO_BACKEND_SET.get(),
873         INFO_LDAPMODIFY_ARG_DESCRIPTION_ROUTE_TO_BACKEND_SET.get());
874    routeToBackendSet.addLongIdentifier("route-to-backend-set", true);
875    routeToBackendSet.setArgumentGroupName(
876         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
877    parser.addArgument(routeToBackendSet);
878
879
880    routeToServer = new StringArgument(null, "routeToServer", false, 1,
881         INFO_LDAPMODIFY_ARG_PLACEHOLDER_ROUTE_TO_SERVER.get(),
882         INFO_LDAPMODIFY_ARG_DESCRIPTION_ROUTE_TO_SERVER.get());
883    routeToServer.addLongIdentifier("route-to-server", true);
884    routeToServer.setArgumentGroupName(
885         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
886    parser.addArgument(routeToServer);
887
888
889    assuredReplication = new BooleanArgument(null, "useAssuredReplication", 1,
890         INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPLICATION.get(
891              ARG_ASSURED_REPLICATION_LOCAL_LEVEL,
892              ARG_ASSURED_REPLICATION_REMOTE_LEVEL,
893              ARG_ASSURED_REPLICATION_TIMEOUT));
894    assuredReplication.addLongIdentifier("assuredReplication", true);
895    assuredReplication.addLongIdentifier("use-assured-replication", true);
896    assuredReplication.addLongIdentifier("assured-replication", true);
897    assuredReplication.setArgumentGroupName(
898         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
899    parser.addArgument(assuredReplication);
900
901
902    final Set<String> assuredReplicationLocalLevelAllowedValues =
903         StaticUtils.setOf("none", "received-any-server",
904              "processed-all-servers");
905    assuredReplicationLocalLevel = new StringArgument(null,
906         ARG_ASSURED_REPLICATION_LOCAL_LEVEL, false, 1,
907         INFO_PLACEHOLDER_LEVEL.get(),
908         INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_LOCAL_LEVEL.get(
909              assuredReplication.getIdentifierString()),
910         assuredReplicationLocalLevelAllowedValues);
911    assuredReplicationLocalLevel.addLongIdentifier(
912         "assured-replication-local-level", true);
913    assuredReplicationLocalLevel.setArgumentGroupName(
914         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
915    parser.addArgument(assuredReplicationLocalLevel);
916
917
918    final Set<String> assuredReplicationRemoteLevelAllowedValues =
919         StaticUtils.setOf("none", "received-any-remote-location",
920              "received-all-remote-locations", "processed-all-remote-servers");
921    assuredReplicationRemoteLevel = new StringArgument(null,
922         ARG_ASSURED_REPLICATION_REMOTE_LEVEL, false, 1,
923         INFO_PLACEHOLDER_LEVEL.get(),
924         INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_REMOTE_LEVEL.get(
925              assuredReplication.getIdentifierString()),
926         assuredReplicationRemoteLevelAllowedValues);
927    assuredReplicationRemoteLevel.addLongIdentifier(
928         "assured-replication-remote-level", true);
929    assuredReplicationRemoteLevel.setArgumentGroupName(
930         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
931    parser.addArgument(assuredReplicationRemoteLevel);
932
933
934    assuredReplicationTimeout = new DurationArgument(null,
935         ARG_ASSURED_REPLICATION_TIMEOUT, false, INFO_PLACEHOLDER_TIMEOUT.get(),
936         INFO_LDAPMODIFY_ARG_DESCRIPTION_ASSURED_REPL_TIMEOUT.get(
937              assuredReplication.getIdentifierString()));
938    assuredReplicationTimeout.setArgumentGroupName(
939         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
940    parser.addArgument(assuredReplicationTimeout);
941
942
943    replicationRepair = new BooleanArgument(null, "replicationRepair",
944         1, INFO_LDAPMODIFY_ARG_DESCRIPTION_REPLICATION_REPAIR.get());
945    replicationRepair.addLongIdentifier("replication-repair", true);
946    replicationRepair.setArgumentGroupName(
947         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
948    parser.addArgument(replicationRepair);
949
950
951    nameWithEntryUUID = new BooleanArgument(null, "nameWithEntryUUID", 1,
952         INFO_LDAPMODIFY_ARG_DESCRIPTION_NAME_WITH_ENTRY_UUID.get());
953    nameWithEntryUUID.addLongIdentifier("name-with-entryUUID", true);
954    nameWithEntryUUID.addLongIdentifier("name-with-entry-uuid", true);
955    nameWithEntryUUID.setArgumentGroupName(
956         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
957    parser.addArgument(nameWithEntryUUID);
958
959
960    noOperation = new BooleanArgument(null, "noOperation", 1,
961         INFO_LDAPMODIFY_ARG_DESCRIPTION_NO_OPERATION.get());
962    noOperation.addLongIdentifier("noOp", true);
963    noOperation.addLongIdentifier("no-operation", true);
964    noOperation.addLongIdentifier("no-op", true);
965    noOperation.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
966    parser.addArgument(noOperation);
967
968
969    passwordUpdateBehavior = new StringArgument(null,
970         "passwordUpdateBehavior", false, 0,
971         INFO_LDAPMODIFY_PLACEHOLDER_NAME_EQUALS_VALUE.get(),
972         INFO_LDAPMODIFY_ARG_DESCRIPTION_PW_UPDATE_BEHAVIOR.get());
973    passwordUpdateBehavior.addLongIdentifier("password-update-behavior", true);
974    passwordUpdateBehavior.setArgumentGroupName(
975         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
976    parser.addArgument(passwordUpdateBehavior);
977
978    passwordValidationDetails = new BooleanArgument(null,
979         "getPasswordValidationDetails", 1,
980         INFO_LDAPMODIFY_ARG_DESCRIPTION_PASSWORD_VALIDATION_DETAILS.get(
981              ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD));
982    passwordValidationDetails.addLongIdentifier("passwordValidationDetails",
983         true);
984    passwordValidationDetails.addLongIdentifier(
985         "get-password-validation-details", true);
986    passwordValidationDetails.addLongIdentifier("password-validation-details",
987         true);
988    passwordValidationDetails.setArgumentGroupName(
989         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
990    parser.addArgument(passwordValidationDetails);
991
992
993    permissiveModify = new BooleanArgument(null, "permissiveModify",
994         1, INFO_LDAPMODIFY_ARG_DESCRIPTION_PERMISSIVE_MODIFY.get());
995    permissiveModify.addLongIdentifier("permissive-modify", true);
996    permissiveModify.setArgumentGroupName(
997         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
998    parser.addArgument(permissiveModify);
999
1000
1001    clientSideSubtreeDelete = new BooleanArgument(null,
1002         "clientSideSubtreeDelete", 1,
1003         INFO_LDAPMODIFY_ARG_DESCRIPTION_CLIENT_SIDE_SUBTREE_DELETE.get());
1004    clientSideSubtreeDelete.addLongIdentifier("client-side-subtree-delete",
1005         true);
1006    clientSideSubtreeDelete.setArgumentGroupName(
1007         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1008    parser.addArgument(clientSideSubtreeDelete);
1009
1010
1011    serverSideSubtreeDelete = new BooleanArgument(null,
1012         "serverSideSubtreeDelete", 1,
1013         INFO_LDAPMODIFY_ARG_DESCRIPTION_SERVER_SIDE_SUBTREE_DELETE.get());
1014    serverSideSubtreeDelete.addLongIdentifier("server-side-subtree-delete",
1015         true);
1016    serverSideSubtreeDelete.addLongIdentifier("subtreeDelete", true);
1017    serverSideSubtreeDelete.addLongIdentifier("subtree-delete", true);
1018    serverSideSubtreeDelete.addLongIdentifier("subtreeDeleteControl", true);
1019    serverSideSubtreeDelete.addLongIdentifier("subtree-delete-control", true);
1020    serverSideSubtreeDelete.addLongIdentifier("useSubtreeDeleteControl", true);
1021    serverSideSubtreeDelete.addLongIdentifier("use-subtree-delete-control",
1022         true);
1023    serverSideSubtreeDelete.setArgumentGroupName(
1024         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1025    parser.addArgument(serverSideSubtreeDelete);
1026
1027
1028    softDelete = new BooleanArgument('s', "softDelete", 1,
1029         INFO_LDAPMODIFY_ARG_DESCRIPTION_SOFT_DELETE.get());
1030    softDelete.addLongIdentifier("useSoftDelete", true);
1031    softDelete.addLongIdentifier("soft-delete", true);
1032    softDelete.addLongIdentifier("use-soft-delete", true);
1033    softDelete.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1034    parser.addArgument(softDelete);
1035
1036
1037    hardDelete = new BooleanArgument(null, "hardDelete", 1,
1038         INFO_LDAPMODIFY_ARG_DESCRIPTION_HARD_DELETE.get());
1039    hardDelete.addLongIdentifier("hard-delete", true);
1040    hardDelete.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1041    parser.addArgument(hardDelete);
1042
1043
1044    allowUndelete = new BooleanArgument(null, "allowUndelete", 1,
1045         INFO_LDAPMODIFY_ARG_DESCRIPTION_ALLOW_UNDELETE.get(
1046              ATTR_UNDELETE_FROM_DN));
1047    allowUndelete.addLongIdentifier("allow-undelete", true);
1048    allowUndelete.setArgumentGroupName(
1049         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1050    parser.addArgument(allowUndelete);
1051
1052
1053    retireCurrentPassword = new BooleanArgument(null, "retireCurrentPassword",
1054         1,
1055         INFO_LDAPMODIFY_ARG_DESCRIPTION_RETIRE_CURRENT_PASSWORD.get(
1056              ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD));
1057    retireCurrentPassword.addLongIdentifier("retire-current-password", true);
1058    retireCurrentPassword.setArgumentGroupName(
1059         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1060    parser.addArgument(retireCurrentPassword);
1061
1062
1063    purgeCurrentPassword = new BooleanArgument(null, "purgeCurrentPassword", 1,
1064         INFO_LDAPMODIFY_ARG_DESCRIPTION_PURGE_CURRENT_PASSWORD.get(
1065              ATTR_USER_PASSWORD, ATTR_AUTH_PASSWORD));
1066    purgeCurrentPassword.addLongIdentifier("purge-current-password", true);
1067    purgeCurrentPassword.setArgumentGroupName(
1068         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1069    parser.addArgument(purgeCurrentPassword);
1070
1071
1072    final Set<String> suppressOperationalAttributeUpdatesAllowedValues =
1073         StaticUtils.setOf("last-access-time", "last-login-time",
1074              "last-login-ip", "lastmod");
1075    suppressOperationalAttributeUpdates = new StringArgument(null,
1076         "suppressOperationalAttributeUpdates", false, -1,
1077         INFO_PLACEHOLDER_ATTR.get(),
1078         INFO_LDAPMODIFY_ARG_DESCRIPTION_SUPPRESS_OP_ATTR_UPDATES.get(),
1079         suppressOperationalAttributeUpdatesAllowedValues);
1080    suppressOperationalAttributeUpdates.addLongIdentifier(
1081         "suppress-operational-attribute-updates", true);
1082    suppressOperationalAttributeUpdates.setArgumentGroupName(
1083         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1084    parser.addArgument(suppressOperationalAttributeUpdates);
1085
1086
1087    suppressReferentialIntegrityUpdates = new BooleanArgument(null,
1088         "suppressReferentialIntegrityUpdates", 1,
1089         INFO_LDAPMODIFY_ARG_DESCRIPTION_SUPPRESS_REFERINT_UPDATES.get());
1090    suppressReferentialIntegrityUpdates.addLongIdentifier(
1091         "suppress-referential-integrity-updates", true);
1092    suppressReferentialIntegrityUpdates.setArgumentGroupName(
1093         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1094    parser.addArgument(suppressReferentialIntegrityUpdates);
1095
1096
1097    usePasswordPolicyControl = new BooleanArgument(null,
1098         "usePasswordPolicyControl", 1,
1099         INFO_LDAPMODIFY_ARG_DESCRIPTION_PASSWORD_POLICY.get());
1100    usePasswordPolicyControl.addLongIdentifier("use-password-policy-control",
1101         true);
1102    usePasswordPolicyControl.setArgumentGroupName(
1103         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1104    parser.addArgument(usePasswordPolicyControl);
1105
1106
1107    uniquenessAttribute = new StringArgument(null, "uniquenessAttribute", false,
1108         0, INFO_PLACEHOLDER_ATTR.get(),
1109        INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_ATTR.get());
1110    uniquenessAttribute.addLongIdentifier("uniquenessAttributeType", true);
1111    uniquenessAttribute.addLongIdentifier("uniqueAttribute", true);
1112    uniquenessAttribute.addLongIdentifier("uniqueAttributeType", true);
1113    uniquenessAttribute.addLongIdentifier("uniqueness-attribute", true);
1114    uniquenessAttribute.addLongIdentifier("uniqueness-attribute-type", true);
1115    uniquenessAttribute.addLongIdentifier("unique-attribute", true);
1116    uniquenessAttribute.addLongIdentifier("unique-attribute-type", true);
1117    uniquenessAttribute.setArgumentGroupName(
1118         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1119    parser.addArgument(uniquenessAttribute);
1120
1121
1122    uniquenessFilter = new FilterArgument(null, "uniquenessFilter", false, 1,
1123         null, INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_FILTER.get());
1124    uniquenessFilter.addLongIdentifier("uniqueness-filter", true);
1125    uniquenessFilter.setArgumentGroupName(
1126         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1127    parser.addArgument(uniquenessFilter);
1128
1129
1130    uniquenessBaseDN = new DNArgument(null, "uniquenessBaseDN", false, 1, null,
1131         INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_BASE_DN.get());
1132    uniquenessBaseDN.addLongIdentifier("uniqueness-base-dn", true);
1133    uniquenessBaseDN.setArgumentGroupName(
1134         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1135    parser.addArgument(uniquenessBaseDN);
1136    parser.addDependentArgumentSet(uniquenessBaseDN, uniquenessAttribute,
1137         uniquenessFilter);
1138
1139
1140    final Set<String> mabValues = StaticUtils.setOf(
1141         "unique-within-each-attribute",
1142         "unique-across-all-attributes-including-in-same-entry",
1143         "unique-across-all-attributes-except-in-same-entry",
1144         "unique-in-combination");
1145    uniquenessMultipleAttributeBehavior = new StringArgument(null,
1146         "uniquenessMultipleAttributeBehavior", false, 1,
1147         INFO_LDAPMODIFY_PLACEHOLDER_BEHAVIOR.get(),
1148         INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_MULTIPLE_ATTRIBUTE_BEHAVIOR.
1149              get(),
1150         mabValues);
1151    uniquenessMultipleAttributeBehavior.addLongIdentifier(
1152         "uniqueness-multiple-attribute-behavior", true);
1153    uniquenessMultipleAttributeBehavior.setArgumentGroupName(
1154         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1155    parser.addArgument(uniquenessMultipleAttributeBehavior);
1156    parser.addDependentArgumentSet(uniquenessMultipleAttributeBehavior,
1157         uniquenessAttribute);
1158
1159
1160    final Set<String> vlValues = StaticUtils.setOf("none", "all-subtree-views",
1161         "all-backend-sets", "all-available-backend-servers");
1162    uniquenessPreCommitValidationLevel = new StringArgument(null,
1163         "uniquenessPreCommitValidationLevel", false, 1,
1164         INFO_LDAPMODIFY_PLACEHOLDER_LEVEL.get(),
1165         INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_PRE_COMMIT_LEVEL.get(),
1166         vlValues);
1167    uniquenessPreCommitValidationLevel.addLongIdentifier(
1168         "uniqueness-pre-commit-validation-level", true);
1169    uniquenessPreCommitValidationLevel.setArgumentGroupName(
1170         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1171    parser.addArgument(uniquenessPreCommitValidationLevel);
1172    parser.addDependentArgumentSet(uniquenessPreCommitValidationLevel,
1173         uniquenessAttribute, uniquenessFilter);
1174
1175
1176    uniquenessPostCommitValidationLevel = new StringArgument(null,
1177         "uniquenessPostCommitValidationLevel", false, 1,
1178         INFO_LDAPMODIFY_PLACEHOLDER_LEVEL.get(),
1179         INFO_LDAPMODIFY_ARG_DESCRIPTION_UNIQUE_POST_COMMIT_LEVEL.get(),
1180         vlValues);
1181    uniquenessPostCommitValidationLevel.addLongIdentifier(
1182         "uniqueness-post-commit-validation-level", true);
1183    uniquenessPostCommitValidationLevel.setArgumentGroupName(
1184         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1185    parser.addArgument(uniquenessPostCommitValidationLevel);
1186    parser.addDependentArgumentSet(uniquenessPostCommitValidationLevel,
1187         uniquenessAttribute, uniquenessFilter);
1188
1189    operationControl = new ControlArgument('J', "control", false, 0, null,
1190         INFO_LDAPMODIFY_ARG_DESCRIPTION_OP_CONTROL.get());
1191    operationControl.setArgumentGroupName(
1192         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1193    parser.addArgument(operationControl);
1194
1195
1196    addControl = new ControlArgument(null, "addControl", false, 0, null,
1197         INFO_LDAPMODIFY_ARG_DESCRIPTION_ADD_CONTROL.get());
1198    addControl.addLongIdentifier("add-control", true);
1199    addControl.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1200    parser.addArgument(addControl);
1201
1202
1203    bindControl = new ControlArgument(null, "bindControl", false, 0, null,
1204         INFO_LDAPMODIFY_ARG_DESCRIPTION_BIND_CONTROL.get());
1205    bindControl.addLongIdentifier("bind-control", true);
1206    bindControl.setArgumentGroupName(
1207         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1208    parser.addArgument(bindControl);
1209
1210
1211    deleteControl = new ControlArgument(null, "deleteControl", false, 0, null,
1212         INFO_LDAPMODIFY_ARG_DESCRIPTION_DELETE_CONTROL.get());
1213    deleteControl.addLongIdentifier("delete-control", true);
1214    deleteControl.setArgumentGroupName(
1215         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1216    parser.addArgument(deleteControl);
1217
1218
1219    modifyControl = new ControlArgument(null, "modifyControl", false, 0, null,
1220         INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_CONTROL.get());
1221    modifyControl.addLongIdentifier("modify-control", true);
1222    modifyControl.setArgumentGroupName(
1223         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1224    parser.addArgument(modifyControl);
1225
1226
1227    modifyDNControl = new ControlArgument(null, "modifyDNControl", false, 0,
1228         null, INFO_LDAPMODIFY_ARG_DESCRIPTION_MODIFY_DN_CONTROL.get());
1229    modifyDNControl.addLongIdentifier("modify-dn-control", true);
1230    modifyDNControl.setArgumentGroupName(
1231         INFO_LDAPMODIFY_ARG_GROUP_CONTROLS.get());
1232    parser.addArgument(modifyDNControl);
1233
1234
1235    ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1,
1236         INFO_PLACEHOLDER_NUM.get(),
1237         INFO_LDAPMODIFY_ARG_DESCRIPTION_RATE_PER_SECOND.get(), 1,
1238         Integer.MAX_VALUE);
1239    ratePerSecond.addLongIdentifier("rate-per-second", true);
1240    ratePerSecond.setArgumentGroupName(INFO_LDAPMODIFY_ARG_GROUP_OPS.get());
1241    parser.addArgument(ratePerSecond);
1242
1243
1244    // The "--scriptFriendly" argument is provided for compatibility with legacy
1245    // ldapmodify tools, but is not actually used by this tool.
1246    final BooleanArgument scriptFriendly = new BooleanArgument(null,
1247         "scriptFriendly", 1,
1248         INFO_LDAPMODIFY_ARG_DESCRIPTION_SCRIPT_FRIENDLY.get());
1249    scriptFriendly.addLongIdentifier("script-friendly", true);
1250    scriptFriendly.setArgumentGroupName(
1251         INFO_LDAPMODIFY_ARG_GROUP_DATA.get());
1252    scriptFriendly.setHidden(true);
1253    parser.addArgument(scriptFriendly);
1254
1255
1256    // The "-V" / "--ldapVersion" argument is provided for compatibility with
1257    // legacy ldapmodify tools, but is not actually used by this tool.
1258    final IntegerArgument ldapVersion = new IntegerArgument('V', "ldapVersion",
1259         false, 1, null, INFO_LDAPMODIFY_ARG_DESCRIPTION_LDAP_VERSION.get());
1260    ldapVersion.addLongIdentifier("ldap-version", true);
1261    ldapVersion.setHidden(true);
1262    parser.addArgument(ldapVersion);
1263
1264
1265    // A few assured replication arguments will only be allowed if assured
1266    // replication is to be used.
1267    parser.addDependentArgumentSet(assuredReplicationLocalLevel,
1268         assuredReplication);
1269    parser.addDependentArgumentSet(assuredReplicationRemoteLevel,
1270         assuredReplication);
1271    parser.addDependentArgumentSet(assuredReplicationTimeout,
1272         assuredReplication);
1273
1274    // Transactions will be incompatible with a lot of settings.
1275    parser.addExclusiveArgumentSet(useTransaction, multiUpdateErrorBehavior);
1276    parser.addExclusiveArgumentSet(useTransaction, rejectFile);
1277    parser.addExclusiveArgumentSet(useTransaction, retryFailedOperations);
1278    parser.addExclusiveArgumentSet(useTransaction, continueOnError);
1279    parser.addExclusiveArgumentSet(useTransaction, dryRun);
1280    parser.addExclusiveArgumentSet(useTransaction, followReferrals);
1281    parser.addExclusiveArgumentSet(useTransaction, nameWithEntryUUID);
1282    parser.addExclusiveArgumentSet(useTransaction, noOperation);
1283    parser.addExclusiveArgumentSet(useTransaction, modifyEntriesMatchingFilter);
1284    parser.addExclusiveArgumentSet(useTransaction,
1285         modifyEntriesMatchingFiltersFromFile);
1286    parser.addExclusiveArgumentSet(useTransaction, modifyEntryWithDN);
1287    parser.addExclusiveArgumentSet(useTransaction,
1288         modifyEntriesWithDNsFromFile);
1289    parser.addExclusiveArgumentSet(useTransaction,
1290         clientSideSubtreeDelete);
1291
1292    // Multi-update is incompatible with a lot of settings.
1293    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, ratePerSecond);
1294    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, rejectFile);
1295    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1296         retryFailedOperations);
1297    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, continueOnError);
1298    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, dryRun);
1299    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, followReferrals);
1300    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, nameWithEntryUUID);
1301    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, noOperation);
1302    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1303         modifyEntriesMatchingFilter);
1304    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1305         modifyEntriesMatchingFiltersFromFile);
1306    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, modifyEntryWithDN);
1307    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1308         modifyEntriesWithDNsFromFile);
1309    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1310         clientSideSubtreeDelete);
1311
1312    // Client-side and server-side subtree deletes cannot be used together.
1313    parser.addExclusiveArgumentSet(clientSideSubtreeDelete,
1314         serverSideSubtreeDelete);
1315
1316    // Soft delete cannot be used with either hard delete or subtree delete.
1317    parser.addExclusiveArgumentSet(softDelete, hardDelete);
1318    parser.addExclusiveArgumentSet(softDelete, clientSideSubtreeDelete);
1319    parser.addExclusiveArgumentSet(softDelete, serverSideSubtreeDelete);
1320
1321    // Client-side subtree delete cannot be used in conjunction with a few
1322    // other settings.
1323    parser.addExclusiveArgumentSet(clientSideSubtreeDelete, followReferrals);
1324    parser.addExclusiveArgumentSet(clientSideSubtreeDelete, preReadAttribute);
1325    parser.addExclusiveArgumentSet(clientSideSubtreeDelete, getBackendSetID);
1326    parser.addExclusiveArgumentSet(clientSideSubtreeDelete, getServerID);
1327    parser.addExclusiveArgumentSet(clientSideSubtreeDelete, noOperation);
1328    parser.addExclusiveArgumentSet(clientSideSubtreeDelete, dryRun);
1329
1330    // Password retiring and purging can't be used together.
1331    parser.addExclusiveArgumentSet(retireCurrentPassword, purgeCurrentPassword);
1332
1333    // Referral following cannot be used in conjunction with the manageDsaIT
1334    // control.
1335    parser.addExclusiveArgumentSet(followReferrals, manageDsaIT);
1336
1337    // The proxyAs and proxyV1As arguments cannot be used together.
1338    parser.addExclusiveArgumentSet(proxyAs, proxyV1As);
1339
1340    // The modifyEntriesMatchingFilter argument is incompatible with a lot of
1341    // settings, since it can only be used for modify operations.
1342    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, allowUndelete);
1343    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, defaultAdd);
1344    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, dryRun);
1345    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, hardDelete);
1346    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1347         ignoreNoUserModification);
1348    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1349         nameWithEntryUUID);
1350    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, softDelete);
1351    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1352         clientSideSubtreeDelete);
1353    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1354         serverSideSubtreeDelete);
1355    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1356         suppressReferentialIntegrityUpdates);
1357    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, addControl);
1358    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, deleteControl);
1359    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1360         modifyDNControl);
1361
1362    // The modifyEntriesMatchingFilterFromFile argument is incompatible with a
1363    // lot of settings, since it can only be used for modify operations.
1364    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1365         allowUndelete);
1366    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1367         defaultAdd);
1368    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1369         dryRun);
1370    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1371         hardDelete);
1372    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1373         ignoreNoUserModification);
1374    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1375         nameWithEntryUUID);
1376    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1377         softDelete);
1378    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1379         clientSideSubtreeDelete);
1380    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1381         serverSideSubtreeDelete);
1382    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1383         suppressReferentialIntegrityUpdates);
1384    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1385         addControl);
1386    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1387         deleteControl);
1388    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1389         modifyDNControl);
1390
1391    // The modifyEntryWithDN argument is incompatible with a lot of
1392    // settings, since it can only be used for modify operations.
1393    parser.addExclusiveArgumentSet(modifyEntryWithDN, allowUndelete);
1394    parser.addExclusiveArgumentSet(modifyEntryWithDN, defaultAdd);
1395    parser.addExclusiveArgumentSet(modifyEntryWithDN, dryRun);
1396    parser.addExclusiveArgumentSet(modifyEntryWithDN, hardDelete);
1397    parser.addExclusiveArgumentSet(modifyEntryWithDN, ignoreNoUserModification);
1398    parser.addExclusiveArgumentSet(modifyEntryWithDN, nameWithEntryUUID);
1399    parser.addExclusiveArgumentSet(modifyEntryWithDN, softDelete);
1400    parser.addExclusiveArgumentSet(modifyEntryWithDN, clientSideSubtreeDelete);
1401    parser.addExclusiveArgumentSet(modifyEntryWithDN, serverSideSubtreeDelete);
1402    parser.addExclusiveArgumentSet(modifyEntryWithDN,
1403         suppressReferentialIntegrityUpdates);
1404    parser.addExclusiveArgumentSet(modifyEntryWithDN, addControl);
1405    parser.addExclusiveArgumentSet(modifyEntryWithDN, deleteControl);
1406    parser.addExclusiveArgumentSet(modifyEntryWithDN, modifyDNControl);
1407
1408    // The modifyEntriesWithDNsFromFile argument is incompatible with a lot of
1409    // settings, since it can only be used for modify operations.
1410    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, allowUndelete);
1411    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, defaultAdd);
1412    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, dryRun);
1413    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, hardDelete);
1414    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1415         ignoreNoUserModification);
1416    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1417         nameWithEntryUUID);
1418    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, softDelete);
1419    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1420         clientSideSubtreeDelete);
1421    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1422         serverSideSubtreeDelete);
1423    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1424         suppressReferentialIntegrityUpdates);
1425    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, addControl);
1426    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, deleteControl);
1427    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1428         modifyDNControl);
1429  }
1430
1431
1432
1433  /**
1434   * {@inheritDoc}
1435   */
1436  @Override()
1437  public void doExtendedNonLDAPArgumentValidation()
1438         throws ArgumentException
1439  {
1440    // If we should use the route to backend set request control, then validate
1441    // and pre-create those controls.
1442    if (routeToBackendSet.isPresent())
1443    {
1444      final List<String> values = routeToBackendSet.getValues();
1445      final Map<String,List<String>> idsByRP = new LinkedHashMap<>(
1446           StaticUtils.computeMapCapacity(values.size()));
1447      for (final String value : values)
1448      {
1449        final int colonPos = value.indexOf(':');
1450        if (colonPos <= 0)
1451        {
1452          throw new ArgumentException(
1453               ERR_LDAPMODIFY_ROUTE_TO_BACKEND_SET_INVALID_FORMAT.get(value,
1454                    routeToBackendSet.getIdentifierString()));
1455        }
1456
1457        final String rpID = value.substring(0, colonPos);
1458        final String bsID = value.substring(colonPos+1);
1459
1460        List<String> idsForRP = idsByRP.get(rpID);
1461        if (idsForRP == null)
1462        {
1463          idsForRP = new ArrayList<>(values.size());
1464          idsByRP.put(rpID, idsForRP);
1465        }
1466        idsForRP.add(bsID);
1467      }
1468
1469      for (final Map.Entry<String,List<String>> e : idsByRP.entrySet())
1470      {
1471        final String rpID = e.getKey();
1472        final List<String> bsIDs = e.getValue();
1473        routeToBackendSetRequestControls.add(
1474             RouteToBackendSetRequestControl.createAbsoluteRoutingRequest(true,
1475                  rpID, bsIDs));
1476      }
1477    }
1478  }
1479
1480
1481
1482  /**
1483   * {@inheritDoc}
1484   */
1485  @Override()
1486  protected List<Control> getBindControls()
1487  {
1488    final ArrayList<Control> bindControls = new ArrayList<>(10);
1489
1490    if (bindControl.isPresent())
1491    {
1492      bindControls.addAll(bindControl.getValues());
1493    }
1494
1495    if (authorizationIdentity.isPresent())
1496    {
1497      bindControls.add(new AuthorizationIdentityRequestControl(false));
1498    }
1499
1500    if (getAuthorizationEntryAttribute.isPresent())
1501    {
1502      bindControls.add(new GetAuthorizationEntryRequestControl(true, true,
1503           getAuthorizationEntryAttribute.getValues()));
1504    }
1505
1506    if (getUserResourceLimits.isPresent())
1507    {
1508      bindControls.add(new GetUserResourceLimitsRequestControl());
1509    }
1510
1511    if (usePasswordPolicyControl.isPresent())
1512    {
1513      bindControls.add(new PasswordPolicyRequestControl());
1514    }
1515
1516    if (suppressOperationalAttributeUpdates.isPresent())
1517    {
1518      final EnumSet<SuppressType> suppressTypes =
1519           EnumSet.noneOf(SuppressType.class);
1520      for (final String s : suppressOperationalAttributeUpdates.getValues())
1521      {
1522        if (s.equalsIgnoreCase("last-access-time"))
1523        {
1524          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
1525        }
1526        else if (s.equalsIgnoreCase("last-login-time"))
1527        {
1528          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
1529        }
1530        else if (s.equalsIgnoreCase("last-login-ip"))
1531        {
1532          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
1533        }
1534      }
1535
1536      bindControls.add(new SuppressOperationalAttributeUpdateRequestControl(
1537           suppressTypes));
1538    }
1539
1540    return bindControls;
1541  }
1542
1543
1544
1545  /**
1546   * {@inheritDoc}
1547   */
1548  @Override()
1549  protected boolean supportsMultipleServers()
1550  {
1551    // We will support providing information about multiple servers.  This tool
1552    // will not communicate with multiple servers concurrently, but it can
1553    // accept information about multiple servers in the event that a large set
1554    // of changes is to be processed and a server goes down in the middle of
1555    // those changes.  In this case, we can resume processing on a newly-created
1556    // connection, possibly to a different server.
1557    return true;
1558  }
1559
1560
1561
1562  /**
1563   * {@inheritDoc}
1564   */
1565  @Override()
1566  public LDAPConnectionOptions getConnectionOptions()
1567  {
1568    final LDAPConnectionOptions options = new LDAPConnectionOptions();
1569
1570    options.setUseSynchronousMode(true);
1571    options.setFollowReferrals(followReferrals.isPresent());
1572    options.setUnsolicitedNotificationHandler(this);
1573    options.setResponseTimeoutMillis(0L);
1574
1575    return options;
1576  }
1577
1578
1579
1580  /**
1581   * {@inheritDoc}
1582   */
1583  @Override()
1584  public ResultCode doToolProcessing()
1585  {
1586    // Examine the arguments to determine the sets of controls to use for each
1587    // type of request.
1588    final ArrayList<Control> addControls = new ArrayList<>(10);
1589    final ArrayList<Control> deleteControls = new ArrayList<>(10);
1590    final ArrayList<Control> modifyControls = new ArrayList<>(10);
1591    final ArrayList<Control> modifyDNControls = new ArrayList<>(10);
1592    final ArrayList<Control> searchControls = new ArrayList<>(10);
1593    try
1594    {
1595      createRequestControls(addControls, deleteControls, modifyControls,
1596           modifyDNControls, searchControls);
1597    }
1598    catch (final LDAPException le)
1599    {
1600      Debug.debugException(le);
1601      for (final String line :
1602           ResultUtils.formatResult(le, true, 0, WRAP_COLUMN))
1603      {
1604        err(line);
1605      }
1606      return le.getResultCode();
1607    }
1608
1609
1610    // If an encryption passphrase file was specified, then read its value.
1611    String encryptionPassphrase = null;
1612    if (encryptionPassphraseFile.isPresent())
1613    {
1614      try
1615      {
1616        encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile(
1617             encryptionPassphraseFile.getValue());
1618      }
1619      catch (final LDAPException e)
1620      {
1621        Debug.debugException(e);
1622        wrapErr(0, WRAP_COLUMN, e.getMessage());
1623        return e.getResultCode();
1624      }
1625    }
1626
1627
1628    LDAPConnectionPool connectionPool = null;
1629    LDIFReader         ldifReader     = null;
1630    LDIFWriter         rejectWriter   = null;
1631    try
1632    {
1633      // Create a connection pool that will be used to communicate with the
1634      // directory server.  If we should use an administrative session, then
1635      // create a connect processor that will be used to start the session
1636      // before performing the bind.
1637      try
1638      {
1639        final StartAdministrativeSessionPostConnectProcessor p;
1640        if (useAdministrativeSession.isPresent())
1641        {
1642          p = new StartAdministrativeSessionPostConnectProcessor(
1643               new StartAdministrativeSessionExtendedRequest(getToolName(),
1644                    true));
1645        }
1646        else
1647        {
1648          p = null;
1649        }
1650
1651        if (! dryRun.isPresent())
1652        {
1653          connectionPool = getConnectionPool(1, 2, 0, p, null, true,
1654               new ReportBindResultLDAPConnectionPoolHealthCheck(this, true,
1655                    verbose.isPresent()));
1656        }
1657      }
1658      catch (final LDAPException le)
1659      {
1660        Debug.debugException(le);
1661
1662        // Unable to create the connection pool, which means that either the
1663        // connection could not be established or the attempt to authenticate
1664        // the connection failed.  If the bind failed, then the report bind
1665        // result health check should have already reported the bind failure.
1666        // If the failure was something else, then display that failure result.
1667        if (le.getResultCode() != ResultCode.INVALID_CREDENTIALS)
1668        {
1669          for (final String line :
1670               ResultUtils.formatResult(le, true, 0, WRAP_COLUMN))
1671          {
1672            err(line);
1673          }
1674        }
1675        return le.getResultCode();
1676      }
1677
1678      if ((connectionPool != null) && retryFailedOperations.isPresent())
1679      {
1680        connectionPool.setRetryFailedOperationsDueToInvalidConnections(true);
1681      }
1682
1683
1684      // Report that the connection was successfully established.
1685      if (connectionPool != null)
1686      {
1687        try
1688        {
1689          final LDAPConnection connection = connectionPool.getConnection();
1690          final String hostPort = connection.getHostPort();
1691          connectionPool.releaseConnection(connection);
1692          commentToOut(INFO_LDAPMODIFY_CONNECTION_ESTABLISHED.get(hostPort));
1693          out();
1694        }
1695        catch (final LDAPException le)
1696        {
1697          Debug.debugException(le);
1698          // This should never happen.
1699        }
1700      }
1701
1702
1703      // If we should process the operations in a transaction, then start that
1704      // now.
1705      final ASN1OctetString txnID;
1706      if (useTransaction.isPresent())
1707      {
1708        final Control[] startTxnControls;
1709        if (proxyAs.isPresent())
1710        {
1711          // In a transaction, the proxied authorization control must only be
1712          // used in the start transaction request and not in any of the
1713          // subsequent operation requests.
1714          startTxnControls = new Control[]
1715          {
1716            new ProxiedAuthorizationV2RequestControl(proxyAs.getValue())
1717          };
1718        }
1719        else if (proxyV1As.isPresent())
1720        {
1721          // In a transaction, the proxied authorization control must only be
1722          // used in the start transaction request and not in any of the
1723          // subsequent operation requests.
1724          startTxnControls = new Control[]
1725          {
1726            new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue())
1727          };
1728        }
1729        else
1730        {
1731          startTxnControls = StaticUtils.NO_CONTROLS;
1732        }
1733
1734        try
1735        {
1736          final StartTransactionExtendedResult startTxnResult =
1737               (StartTransactionExtendedResult)
1738               connectionPool.processExtendedOperation(
1739                    new StartTransactionExtendedRequest(startTxnControls));
1740          if (startTxnResult.getResultCode() == ResultCode.SUCCESS)
1741          {
1742            txnID = startTxnResult.getTransactionID();
1743
1744            final TransactionSpecificationRequestControl c =
1745                 new TransactionSpecificationRequestControl(txnID);
1746            addControls.add(c);
1747            deleteControls.add(c);
1748            modifyControls.add(c);
1749            modifyDNControls.add(c);
1750
1751            final String txnIDString;
1752            if (StaticUtils.isPrintableString(txnID.getValue()))
1753            {
1754              txnIDString = txnID.stringValue();
1755            }
1756            else
1757            {
1758              final StringBuilder hexBuffer = new StringBuilder();
1759              StaticUtils.toHex(txnID.getValue(), ":", hexBuffer);
1760              txnIDString = hexBuffer.toString();
1761            }
1762
1763            commentToOut(INFO_LDAPMODIFY_STARTED_TXN.get(txnIDString));
1764          }
1765          else
1766          {
1767            commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get(
1768                 startTxnResult.getResultString()));
1769            return startTxnResult.getResultCode();
1770          }
1771        }
1772        catch (final LDAPException le)
1773        {
1774          Debug.debugException(le);
1775          commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get(
1776               StaticUtils.getExceptionMessage(le)));
1777          return le.getResultCode();
1778        }
1779      }
1780      else
1781      {
1782        txnID = null;
1783      }
1784
1785
1786      // Create an LDIF reader that will be used to read the changes to process.
1787      try
1788      {
1789        final InputStream ldifInputStream;
1790        if (ldifFile.isPresent())
1791        {
1792          ldifInputStream = ToolUtils.getInputStreamForLDIFFiles(
1793               ldifFile.getValues(), encryptionPassphrase, getOut(),
1794               getErr()).getFirst();
1795        }
1796        else
1797        {
1798          ldifInputStream = in;
1799        }
1800
1801        ldifReader = new LDIFReader(ldifInputStream, 0, null, null,
1802             characterSet.getValue());
1803      }
1804      catch (final Exception e)
1805      {
1806        commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_LDIF_READER.get(
1807             StaticUtils.getExceptionMessage(e)));
1808        return ResultCode.LOCAL_ERROR;
1809      }
1810
1811      if (stripTrailingSpaces.isPresent())
1812      {
1813        ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.STRIP);
1814      }
1815
1816
1817      // If appropriate, create a reject writer.
1818      if (rejectFile.isPresent())
1819      {
1820        try
1821        {
1822          rejectWriter = new LDIFWriter(rejectFile.getValue());
1823
1824          // Set the maximum allowed wrap column.  This is better than setting a
1825          // wrap column of zero because it will ensure that comments don't get
1826          // wrapped either.
1827          rejectWriter.setWrapColumn(Integer.MAX_VALUE);
1828        }
1829        catch (final Exception e)
1830        {
1831          Debug.debugException(e);
1832          commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_REJECT_WRITER.get(
1833               rejectFile.getValue().getAbsolutePath(),
1834               StaticUtils.getExceptionMessage(e)));
1835          return ResultCode.LOCAL_ERROR;
1836        }
1837      }
1838
1839
1840      // If appropriate, create a rate limiter.
1841      final FixedRateBarrier rateLimiter;
1842      if (ratePerSecond.isPresent())
1843      {
1844        rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue());
1845      }
1846      else
1847      {
1848        rateLimiter = null;
1849      }
1850
1851
1852      // Iterate through the set of changes to process.
1853      boolean commitTransaction = true;
1854      ResultCode resultCode = null;
1855      final ArrayList<LDAPRequest> multiUpdateRequests =
1856           new ArrayList<>(10);
1857      final boolean isBulkModify = modifyEntriesMatchingFilter.isPresent() ||
1858           modifyEntriesMatchingFiltersFromFile.isPresent() ||
1859           modifyEntryWithDN.isPresent() ||
1860           modifyEntriesWithDNsFromFile.isPresent();
1861readChangeRecordLoop:
1862      while (true)
1863      {
1864        // If there is a rate limiter, then use it to sleep if necessary.
1865        if ((rateLimiter != null) && (! isBulkModify))
1866        {
1867          rateLimiter.await();
1868        }
1869
1870
1871        // Read the next LDIF change record.  If we get an error then handle it
1872        // and abort if appropriate.
1873        final LDIFChangeRecord changeRecord;
1874        try
1875        {
1876          changeRecord = ldifReader.readChangeRecord(defaultAdd.isPresent());
1877        }
1878        catch (final IOException ioe)
1879        {
1880          Debug.debugException(ioe);
1881
1882          final String message = ERR_LDAPMODIFY_IO_ERROR_READING_CHANGE.get(
1883               StaticUtils.getExceptionMessage(ioe));
1884          commentToErr(message);
1885          writeRejectedChange(rejectWriter, message, null);
1886          commitTransaction = false;
1887          resultCode = ResultCode.LOCAL_ERROR;
1888          break;
1889        }
1890        catch (final LDIFException le)
1891        {
1892          Debug.debugException(le);
1893
1894          final StringBuilder buffer = new StringBuilder();
1895          if (le.mayContinueReading() && (! useTransaction.isPresent()))
1896          {
1897            buffer.append(
1898                 ERR_LDAPMODIFY_RECOVERABLE_LDIF_ERROR_READING_CHANGE.get(
1899                      le.getLineNumber(), StaticUtils.getExceptionMessage(le)));
1900          }
1901          else
1902          {
1903            buffer.append(
1904                 ERR_LDAPMODIFY_UNRECOVERABLE_LDIF_ERROR_READING_CHANGE.get(
1905                      le.getLineNumber(), StaticUtils.getExceptionMessage(le)));
1906          }
1907
1908          if ((resultCode == null) || (resultCode == ResultCode.SUCCESS))
1909          {
1910            resultCode = ResultCode.LOCAL_ERROR;
1911          }
1912
1913          if ((le.getDataLines() != null) && (! le.getDataLines().isEmpty()))
1914          {
1915            buffer.append(StaticUtils.EOL);
1916            buffer.append(StaticUtils.EOL);
1917            buffer.append(ERR_LDAPMODIFY_INVALID_LINES.get());
1918            buffer.append(StaticUtils.EOL);
1919            for (final String s : le.getDataLines())
1920            {
1921              buffer.append(s);
1922              buffer.append(StaticUtils.EOL);
1923            }
1924          }
1925
1926          final String message = buffer.toString();
1927          commentToErr(message);
1928          writeRejectedChange(rejectWriter, message, null);
1929
1930          if (le.mayContinueReading() && (! useTransaction.isPresent()))
1931          {
1932            continue;
1933          }
1934          else
1935          {
1936            commitTransaction = false;
1937            resultCode = ResultCode.LOCAL_ERROR;
1938            break;
1939          }
1940        }
1941
1942
1943        // If we read a null change record, then there are no more changes to
1944        // process.  Otherwise, treat it appropriately based on the operation
1945        // type.
1946        if (changeRecord == null)
1947        {
1948          break;
1949        }
1950
1951
1952        // If we should modify entries matching a specified filter, then convert
1953        // the change record into a set of modifications.
1954        if (modifyEntriesMatchingFilter.isPresent())
1955        {
1956          for (final Filter filter : modifyEntriesMatchingFilter.getValues())
1957          {
1958            final ResultCode rc = handleModifyMatchingFilter(connectionPool,
1959                 changeRecord,
1960                 modifyEntriesMatchingFilter.getIdentifierString(),
1961                 filter, searchControls, modifyControls, rateLimiter,
1962                 rejectWriter);
1963            if (rc != ResultCode.SUCCESS)
1964            {
1965              if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) ||
1966                   (resultCode == ResultCode.NO_OPERATION))
1967              {
1968                resultCode = rc;
1969              }
1970            }
1971          }
1972        }
1973
1974        if (modifyEntriesMatchingFiltersFromFile.isPresent())
1975        {
1976          for (final File f : modifyEntriesMatchingFiltersFromFile.getValues())
1977          {
1978            final FilterFileReader filterReader;
1979            try
1980            {
1981              filterReader = new FilterFileReader(f);
1982            }
1983            catch (final Exception e)
1984            {
1985              Debug.debugException(e);
1986              commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_FILTER_FILE.get(
1987                   f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
1988              return ResultCode.LOCAL_ERROR;
1989            }
1990
1991            try
1992            {
1993              while (true)
1994              {
1995                final Filter filter;
1996                try
1997                {
1998                  filter = filterReader.readFilter();
1999                }
2000                catch (final IOException ioe)
2001                {
2002                  Debug.debugException(ioe);
2003                  commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_FILTER_FILE.get(
2004                       f.getAbsolutePath(),
2005                       StaticUtils.getExceptionMessage(ioe)));
2006                  return ResultCode.LOCAL_ERROR;
2007                }
2008                catch (final LDAPException le)
2009                {
2010                  Debug.debugException(le);
2011                  commentToErr(le.getMessage());
2012                  if (continueOnError.isPresent())
2013                  {
2014                    if ((resultCode == null) ||
2015                        (resultCode == ResultCode.SUCCESS) ||
2016                        (resultCode == ResultCode.NO_OPERATION))
2017                    {
2018                      resultCode = le.getResultCode();
2019                    }
2020                    continue;
2021                  }
2022                  else
2023                  {
2024                    return le.getResultCode();
2025                  }
2026                }
2027
2028                if (filter == null)
2029                {
2030                  break;
2031                }
2032
2033                final ResultCode rc = handleModifyMatchingFilter(connectionPool,
2034                     changeRecord,
2035                     modifyEntriesMatchingFiltersFromFile.getIdentifierString(),
2036                     filter, searchControls, modifyControls, rateLimiter,
2037                     rejectWriter);
2038                if (rc != ResultCode.SUCCESS)
2039                {
2040                  if ((resultCode == null) ||
2041                      (resultCode == ResultCode.SUCCESS) ||
2042                      (resultCode == ResultCode.NO_OPERATION))
2043                  {
2044                    resultCode = rc;
2045                  }
2046                }
2047              }
2048            }
2049            finally
2050            {
2051              try
2052              {
2053                filterReader.close();
2054              }
2055              catch (final Exception e)
2056              {
2057                Debug.debugException(e);
2058              }
2059            }
2060          }
2061        }
2062
2063        if (modifyEntryWithDN.isPresent())
2064        {
2065          for (final DN dn : modifyEntryWithDN.getValues())
2066          {
2067            final ResultCode rc = handleModifyWithDN(connectionPool,
2068                 changeRecord, modifyEntryWithDN.getIdentifierString(), dn,
2069                 modifyControls, rateLimiter, rejectWriter);
2070            if (rc != ResultCode.SUCCESS)
2071            {
2072              if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) ||
2073                   (resultCode == ResultCode.NO_OPERATION))
2074              {
2075                resultCode = rc;
2076              }
2077            }
2078          }
2079        }
2080
2081        if (modifyEntriesWithDNsFromFile.isPresent())
2082        {
2083          for (final File f : modifyEntriesWithDNsFromFile.getValues())
2084          {
2085            final DNFileReader dnReader;
2086            try
2087            {
2088              dnReader = new DNFileReader(f);
2089            }
2090            catch (final Exception e)
2091            {
2092              Debug.debugException(e);
2093              commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_DN_FILE.get(
2094                   f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
2095              return ResultCode.LOCAL_ERROR;
2096            }
2097
2098            try
2099            {
2100              while (true)
2101              {
2102                final DN dn;
2103                try
2104                {
2105                  dn = dnReader.readDN();
2106                }
2107                catch (final IOException ioe)
2108                {
2109                  Debug.debugException(ioe);
2110                  commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_DN_FILE.get(
2111                       f.getAbsolutePath(),
2112                       StaticUtils.getExceptionMessage(ioe)));
2113                  return ResultCode.LOCAL_ERROR;
2114                }
2115                catch (final LDAPException le)
2116                {
2117                  Debug.debugException(le);
2118                  commentToErr(le.getMessage());
2119                  if (continueOnError.isPresent())
2120                  {
2121                    if ((resultCode == null) ||
2122                        (resultCode == ResultCode.SUCCESS) ||
2123                        (resultCode == ResultCode.NO_OPERATION))
2124                    {
2125                      resultCode = le.getResultCode();
2126                    }
2127                    continue;
2128                  }
2129                  else
2130                  {
2131                    return le.getResultCode();
2132                  }
2133                }
2134
2135                if (dn == null)
2136                {
2137                  break;
2138                }
2139
2140                final ResultCode rc = handleModifyWithDN(connectionPool,
2141                     changeRecord,
2142                     modifyEntriesWithDNsFromFile.getIdentifierString(), dn,
2143                     modifyControls, rateLimiter, rejectWriter);
2144                if (rc != ResultCode.SUCCESS)
2145                {
2146                  if ((resultCode == null) ||
2147                      (resultCode == ResultCode.SUCCESS) ||
2148                      (resultCode == ResultCode.NO_OPERATION))
2149                  {
2150                    resultCode = rc;
2151                  }
2152                }
2153              }
2154            }
2155            finally
2156            {
2157              try
2158              {
2159                dnReader.close();
2160              }
2161              catch (final Exception e)
2162              {
2163                Debug.debugException(e);
2164              }
2165            }
2166          }
2167        }
2168
2169        if (isBulkModify)
2170        {
2171          continue;
2172        }
2173
2174        try
2175        {
2176          final ResultCode rc;
2177          if (changeRecord instanceof LDIFAddChangeRecord)
2178          {
2179            rc = doAdd((LDIFAddChangeRecord) changeRecord, addControls,
2180                 connectionPool, multiUpdateRequests, rejectWriter);
2181          }
2182          else if (changeRecord instanceof LDIFDeleteChangeRecord)
2183          {
2184            rc = doDelete((LDIFDeleteChangeRecord) changeRecord, deleteControls,
2185                 connectionPool, multiUpdateRequests, rejectWriter);
2186          }
2187          else if (changeRecord instanceof LDIFModifyChangeRecord)
2188          {
2189            rc = doModify((LDIFModifyChangeRecord) changeRecord, modifyControls,
2190                 connectionPool, multiUpdateRequests, rejectWriter);
2191          }
2192          else if (changeRecord instanceof LDIFModifyDNChangeRecord)
2193          {
2194            rc = doModifyDN((LDIFModifyDNChangeRecord) changeRecord,
2195                 modifyDNControls, connectionPool, multiUpdateRequests,
2196                 rejectWriter);
2197          }
2198          else
2199          {
2200            // This should never happen.
2201            commentToErr(ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get());
2202            for (final String line : changeRecord.toLDIF())
2203            {
2204              err("#      " + line);
2205            }
2206            throw new LDAPException(ResultCode.PARAM_ERROR,
2207                 ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get() +
2208                      changeRecord.toString());
2209          }
2210
2211          if ((resultCode == null) && (rc != ResultCode.SUCCESS))
2212          {
2213            resultCode = rc;
2214          }
2215        }
2216        catch (final LDAPException le)
2217        {
2218          Debug.debugException(le);
2219
2220          commitTransaction = false;
2221          if (continueOnError.isPresent())
2222          {
2223            if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) ||
2224                 (resultCode == ResultCode.NO_OPERATION))
2225            {
2226              resultCode = le.getResultCode();
2227            }
2228          }
2229          else
2230          {
2231            resultCode = le.getResultCode();
2232            break;
2233          }
2234        }
2235      }
2236
2237
2238      // If the operations are part of a transaction, then commit or abort that
2239      // transaction now.  Otherwise, if they should be part of a multi-update
2240      // operation, then process that now.
2241      if (useTransaction.isPresent())
2242      {
2243        LDAPResult endTxnResult;
2244        final EndTransactionExtendedRequest endTxnRequest =
2245             new EndTransactionExtendedRequest(txnID, commitTransaction);
2246        try
2247        {
2248          endTxnResult = connectionPool.processExtendedOperation(endTxnRequest);
2249        }
2250        catch (final LDAPException le)
2251        {
2252          endTxnResult = le.toLDAPResult();
2253        }
2254
2255        displayResult(endTxnResult, false);
2256        if (((resultCode == null) || (resultCode == ResultCode.SUCCESS)) &&
2257            (endTxnResult.getResultCode() != ResultCode.SUCCESS))
2258        {
2259          resultCode = endTxnResult.getResultCode();
2260        }
2261      }
2262      else if (multiUpdateErrorBehavior.isPresent())
2263      {
2264        final MultiUpdateErrorBehavior errorBehavior;
2265        if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase("atomic"))
2266        {
2267          errorBehavior = MultiUpdateErrorBehavior.ATOMIC;
2268        }
2269        else if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase(
2270                      "abort-on-error"))
2271        {
2272          errorBehavior = MultiUpdateErrorBehavior.ABORT_ON_ERROR;
2273        }
2274        else
2275        {
2276          errorBehavior = MultiUpdateErrorBehavior.CONTINUE_ON_ERROR;
2277        }
2278
2279        final Control[] multiUpdateControls;
2280        if (proxyAs.isPresent())
2281        {
2282          multiUpdateControls = new Control[]
2283          {
2284            new ProxiedAuthorizationV2RequestControl(proxyAs.getValue())
2285          };
2286        }
2287        else if (proxyV1As.isPresent())
2288        {
2289          multiUpdateControls = new Control[]
2290          {
2291            new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue())
2292          };
2293        }
2294        else
2295        {
2296          multiUpdateControls = StaticUtils.NO_CONTROLS;
2297        }
2298
2299        ExtendedResult multiUpdateResult;
2300        try
2301        {
2302          commentToOut(INFO_LDAPMODIFY_SENDING_MULTI_UPDATE_REQUEST.get());
2303          final MultiUpdateExtendedRequest multiUpdateRequest =
2304               new MultiUpdateExtendedRequest(errorBehavior,
2305                    multiUpdateRequests, multiUpdateControls);
2306          multiUpdateResult =
2307               connectionPool.processExtendedOperation(multiUpdateRequest);
2308        }
2309        catch (final LDAPException le)
2310        {
2311          multiUpdateResult = new ExtendedResult(le);
2312        }
2313
2314        displayResult(multiUpdateResult, false);
2315        resultCode = multiUpdateResult.getResultCode();
2316      }
2317
2318
2319      if (resultCode == null)
2320      {
2321        return ResultCode.SUCCESS;
2322      }
2323      else
2324      {
2325        return resultCode;
2326      }
2327    }
2328    finally
2329    {
2330      if (rejectWriter != null)
2331      {
2332        try
2333        {
2334          rejectWriter.close();
2335        }
2336        catch (final Exception e)
2337        {
2338          Debug.debugException(e);
2339        }
2340      }
2341
2342      if (ldifReader != null)
2343      {
2344        try
2345        {
2346          ldifReader.close();
2347        }
2348        catch (final Exception e)
2349        {
2350          Debug.debugException(e);
2351        }
2352      }
2353
2354      if (connectionPool != null)
2355      {
2356        try
2357        {
2358          connectionPool.close();
2359        }
2360        catch (final Exception e)
2361        {
2362          Debug.debugException(e);
2363        }
2364      }
2365    }
2366  }
2367
2368
2369
2370  /**
2371   * Handles the processing for a change record when the tool should modify
2372   * entries matching a given filter.
2373   *
2374   * @param  connectionPool       The connection pool to use to communicate with
2375   *                              the directory server.
2376   * @param  changeRecord         The LDIF change record to be processed.
2377   * @param  argIdentifierString  The identifier string for the argument used to
2378   *                              specify the filter to use to identify the
2379   *                              entries to modify.
2380   * @param  filter               The filter to use to identify the entries to
2381   *                              modify.
2382   * @param  searchControls       The set of controls to include in the search
2383   *                              request.
2384   * @param  modifyControls       The set of controls to include in the modify
2385   *                              requests.
2386   * @param  rateLimiter          The fixed-rate barrier to use for rate
2387   *                              limiting.  It may be {@code null} if no rate
2388   *                              limiting is required.
2389   * @param  rejectWriter         The reject writer to use to record information
2390   *                              about any failed operations.
2391   *
2392   * @return  A result code obtained from processing.
2393   */
2394  private ResultCode handleModifyMatchingFilter(
2395                          final LDAPConnectionPool connectionPool,
2396                          final LDIFChangeRecord changeRecord,
2397                          final String argIdentifierString, final Filter filter,
2398                          final List<Control> searchControls,
2399                          final List<Control> modifyControls,
2400                          final FixedRateBarrier rateLimiter,
2401                          final LDIFWriter rejectWriter)
2402  {
2403    // If the provided change record isn't a modify change record, then that's
2404    // an error.  Reject it.
2405    if (! (changeRecord instanceof LDIFModifyChangeRecord))
2406    {
2407      writeRejectedChange(rejectWriter,
2408           ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString),
2409           changeRecord);
2410      return ResultCode.PARAM_ERROR;
2411    }
2412
2413    final LDIFModifyChangeRecord modifyChangeRecord =
2414         (LDIFModifyChangeRecord) changeRecord;
2415    final HashSet<DN> processedDNs =
2416         new HashSet<>(StaticUtils.computeMapCapacity(100));
2417
2418
2419    // If we need to use the simple paged results control, then we may have to
2420    // issue multiple searches.
2421    ASN1OctetString pagedResultsCookie = null;
2422    long entriesProcessed = 0L;
2423    ResultCode resultCode = ResultCode.SUCCESS;
2424    while (true)
2425    {
2426      // Construct the search request to send.
2427      final LDAPModifySearchListener listener =
2428           new LDAPModifySearchListener(this, modifyChangeRecord, filter,
2429                modifyControls, connectionPool, rateLimiter, rejectWriter,
2430                processedDNs);
2431
2432      final SearchRequest searchRequest =
2433           new SearchRequest(listener, modifyChangeRecord.getDN(),
2434                SearchScope.SUB, filter, SearchRequest.NO_ATTRIBUTES);
2435      searchRequest.setControls(searchControls);
2436      if (searchPageSize.isPresent())
2437      {
2438        searchRequest.addControl(new SimplePagedResultsControl(
2439             searchPageSize.getValue(), pagedResultsCookie));
2440      }
2441
2442
2443      // The connection pool's automatic retry feature can't work for searches
2444      // that return one or more entries before encountering a failure.  To get
2445      // around that, we'll check a connection out of the pool and use it to
2446      // process the search.  If an error occurs that indicates the connection
2447      // is no longer valid, we can replace it with a newly-established
2448      // connection and try again.  The search result listener will ensure that
2449      // no entry gets updated twice.
2450      LDAPConnection connection;
2451      try
2452      {
2453        connection = connectionPool.getConnection();
2454      }
2455      catch (final LDAPException le)
2456      {
2457        Debug.debugException(le);
2458
2459        writeRejectedChange(rejectWriter,
2460             ERR_LDAPMODIFY_CANNOT_GET_SEARCH_CONNECTION.get(
2461                  modifyChangeRecord.getDN(), String.valueOf(filter),
2462                  StaticUtils.getExceptionMessage(le)),
2463             modifyChangeRecord, le.toLDAPResult());
2464        return le.getResultCode();
2465      }
2466
2467      SearchResult searchResult;
2468      boolean connectionValid = false;
2469      try
2470      {
2471        try
2472        {
2473          searchResult = connection.search(searchRequest);
2474        }
2475        catch (final LDAPSearchException lse)
2476        {
2477          searchResult = lse.getSearchResult();
2478        }
2479
2480        if (searchResult.getResultCode() == ResultCode.SUCCESS)
2481        {
2482          connectionValid = true;
2483        }
2484        else if (searchResult.getResultCode().isConnectionUsable())
2485        {
2486          connectionValid = true;
2487          writeRejectedChange(rejectWriter,
2488               ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(),
2489                    String.valueOf(filter)),
2490               modifyChangeRecord, searchResult);
2491          return searchResult.getResultCode();
2492        }
2493        else if (retryFailedOperations.isPresent())
2494        {
2495          try
2496          {
2497            connection = connectionPool.replaceDefunctConnection(connection);
2498          }
2499          catch (final LDAPException le)
2500          {
2501            Debug.debugException(le);
2502            writeRejectedChange(rejectWriter,
2503                 ERR_LDAPMODIFY_SEARCH_FAILED_CANNOT_RECONNECT.get(
2504                      modifyChangeRecord.getDN(), String.valueOf(filter)),
2505                 modifyChangeRecord, searchResult);
2506            return searchResult.getResultCode();
2507          }
2508
2509          try
2510          {
2511            searchResult = connection.search(searchRequest);
2512          }
2513          catch (final LDAPSearchException lse)
2514          {
2515            Debug.debugException(lse);
2516            searchResult = lse.getSearchResult();
2517          }
2518
2519          if (searchResult.getResultCode() == ResultCode.SUCCESS)
2520          {
2521            connectionValid = true;
2522          }
2523          else
2524          {
2525            connectionValid = searchResult.getResultCode().isConnectionUsable();
2526            writeRejectedChange(rejectWriter,
2527                 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(),
2528                      String.valueOf(filter)),
2529                 modifyChangeRecord, searchResult);
2530            return searchResult.getResultCode();
2531          }
2532        }
2533        else
2534        {
2535          writeRejectedChange(rejectWriter,
2536               ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(),
2537                    String.valueOf(filter)),
2538               modifyChangeRecord, searchResult);
2539          return searchResult.getResultCode();
2540        }
2541      }
2542      finally
2543      {
2544        if (connectionValid)
2545        {
2546          connectionPool.releaseConnection(connection);
2547        }
2548        else
2549        {
2550          connectionPool.releaseDefunctConnection(connection);
2551        }
2552      }
2553
2554
2555      // If we've gotten here, then the search was successful.  Check to see if
2556      // any of the modifications failed, and if so then update the result code
2557      // accordingly.
2558      if ((resultCode == ResultCode.SUCCESS) &&
2559          (listener.getResultCode() != ResultCode.SUCCESS))
2560      {
2561        resultCode = listener.getResultCode();
2562      }
2563
2564
2565      // If the search used the simple paged results control then we may need to
2566      // repeat the search to get the next page.
2567      entriesProcessed += searchResult.getEntryCount();
2568      if (searchPageSize.isPresent())
2569      {
2570        final SimplePagedResultsControl responseControl;
2571        try
2572        {
2573          responseControl = SimplePagedResultsControl.get(searchResult);
2574        }
2575        catch (final LDAPException le)
2576        {
2577          Debug.debugException(le);
2578          writeRejectedChange(rejectWriter,
2579               ERR_LDAPMODIFY_CANNOT_DECODE_PAGED_RESULTS_CONTROL.get(
2580                    modifyChangeRecord.getDN(), String.valueOf(filter)),
2581               modifyChangeRecord, le.toLDAPResult());
2582          return le.getResultCode();
2583        }
2584
2585        if (responseControl == null)
2586        {
2587          writeRejectedChange(rejectWriter,
2588               ERR_LDAPMODIFY_MISSING_PAGED_RESULTS_RESPONSE.get(
2589                    modifyChangeRecord.getDN(), String.valueOf(filter)),
2590               modifyChangeRecord);
2591          return ResultCode.CONTROL_NOT_FOUND;
2592        }
2593        else
2594        {
2595          pagedResultsCookie = responseControl.getCookie();
2596          if (responseControl.moreResultsToReturn())
2597          {
2598            if (verbose.isPresent())
2599            {
2600              commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED_MORE_PAGES.get(
2601                   modifyChangeRecord.getDN(), String.valueOf(filter),
2602                   entriesProcessed));
2603              for (final String resultLine :
2604                   ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN))
2605              {
2606                out(resultLine);
2607              }
2608              out();
2609            }
2610          }
2611          else
2612          {
2613            commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get(
2614                 entriesProcessed, modifyChangeRecord.getDN(),
2615                 String.valueOf(filter)));
2616            if (verbose.isPresent())
2617            {
2618              for (final String resultLine :
2619                   ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN))
2620              {
2621                out(resultLine);
2622              }
2623            }
2624
2625            out();
2626            return resultCode;
2627          }
2628        }
2629      }
2630      else
2631      {
2632        commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get(
2633             entriesProcessed, modifyChangeRecord.getDN(),
2634             String.valueOf(filter)));
2635        if (verbose.isPresent())
2636        {
2637          for (final String resultLine :
2638               ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN))
2639          {
2640            out(resultLine);
2641          }
2642        }
2643
2644        out();
2645        return resultCode;
2646      }
2647    }
2648  }
2649
2650
2651
2652  /**
2653   * Handles the processing for a change record when the tool should modify an
2654   * entry with a given DN instead of the DN contained in the change record.
2655   *
2656   * @param  connectionPool       The connection pool to use to communicate with
2657   *                              the directory server.
2658   * @param  changeRecord         The LDIF change record to be processed.
2659   * @param  argIdentifierString  The identifier string for the argument used to
2660   *                              specify the DN of the entry to modify.
2661   * @param  dn                   The DN of the entry to modify.
2662   * @param  modifyControls       The set of controls to include in the modify
2663   *                              requests.
2664   * @param  rateLimiter          The fixed-rate barrier to use for rate
2665   *                              limiting.  It may be {@code null} if no rate
2666   *                              limiting is required.
2667   * @param  rejectWriter         The reject writer to use to record information
2668   *                              about any failed operations.
2669   *
2670   * @return  A result code obtained from processing.
2671   */
2672  private ResultCode handleModifyWithDN(
2673                          final LDAPConnectionPool connectionPool,
2674                          final LDIFChangeRecord changeRecord,
2675                          final String argIdentifierString, final DN dn,
2676                          final List<Control> modifyControls,
2677                          final FixedRateBarrier rateLimiter,
2678                          final LDIFWriter rejectWriter)
2679  {
2680    // If the provided change record isn't a modify change record, then that's
2681    // an error.  Reject it.
2682    if (! (changeRecord instanceof LDIFModifyChangeRecord))
2683    {
2684      writeRejectedChange(rejectWriter,
2685           ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString),
2686           changeRecord);
2687      return ResultCode.PARAM_ERROR;
2688    }
2689
2690
2691    // Create a new modify change record with the provided DN instead of the
2692    // original DN.
2693    final LDIFModifyChangeRecord originalChangeRecord =
2694         (LDIFModifyChangeRecord) changeRecord;
2695    final LDIFModifyChangeRecord updatedChangeRecord =
2696         new LDIFModifyChangeRecord(dn.toString(),
2697              originalChangeRecord.getModifications(),
2698              originalChangeRecord.getControls());
2699
2700    if (rateLimiter != null)
2701    {
2702      rateLimiter.await();
2703    }
2704
2705    try
2706    {
2707      return doModify(updatedChangeRecord, modifyControls, connectionPool, null,
2708           rejectWriter);
2709    }
2710    catch (final LDAPException le)
2711    {
2712      Debug.debugException(le);
2713      return le.getResultCode();
2714    }
2715  }
2716
2717
2718
2719  /**
2720   * Populates lists of request controls that should be included in requests
2721   * of various types.
2722   *
2723   * @param  addControls       The list of controls to include in add requests.
2724   * @param  deleteControls    The list of controls to include in delete
2725   *                           requests.
2726   * @param  modifyControls    The list of controls to include in modify
2727   *                           requests.
2728   * @param  modifyDNControls  The list of controls to include in modify DN
2729   *                           requests.
2730   * @param  searchControls    The list of controls to include in search
2731   *                           requests.
2732   *
2733   * @throws  LDAPException  If a problem is encountered while creating any of
2734   *                         the requested controls.
2735   */
2736  private void createRequestControls(final List<Control> addControls,
2737                                     final List<Control> deleteControls,
2738                                     final List<Control> modifyControls,
2739                                     final List<Control> modifyDNControls,
2740                                     final List<Control> searchControls)
2741          throws LDAPException
2742  {
2743    if (addControl.isPresent())
2744    {
2745      addControls.addAll(addControl.getValues());
2746    }
2747
2748    if (deleteControl.isPresent())
2749    {
2750      deleteControls.addAll(deleteControl.getValues());
2751    }
2752
2753    if (modifyControl.isPresent())
2754    {
2755      modifyControls.addAll(modifyControl.getValues());
2756    }
2757
2758    if (modifyDNControl.isPresent())
2759    {
2760      modifyDNControls.addAll(modifyDNControl.getValues());
2761    }
2762
2763    if (operationControl.isPresent())
2764    {
2765      addControls.addAll(operationControl.getValues());
2766      deleteControls.addAll(operationControl.getValues());
2767      modifyControls.addAll(operationControl.getValues());
2768      modifyDNControls.addAll(operationControl.getValues());
2769    }
2770
2771    addControls.addAll(routeToBackendSetRequestControls);
2772    deleteControls.addAll(routeToBackendSetRequestControls);
2773    modifyControls.addAll(routeToBackendSetRequestControls);
2774    modifyDNControls.addAll(routeToBackendSetRequestControls);
2775
2776    if (noOperation.isPresent())
2777    {
2778      final NoOpRequestControl c = new NoOpRequestControl();
2779      addControls.add(c);
2780      deleteControls.add(c);
2781      modifyControls.add(c);
2782      modifyDNControls.add(c);
2783    }
2784
2785    if (generatePassword.isPresent())
2786    {
2787      addControls.add(new GeneratePasswordRequestControl());
2788    }
2789
2790    if (getBackendSetID.isPresent())
2791    {
2792      final GetBackendSetIDRequestControl c =
2793           new GetBackendSetIDRequestControl(false);
2794      addControls.add(c);
2795      deleteControls.add(c);
2796      modifyControls.add(c);
2797      modifyDNControls.add(c);
2798    }
2799
2800    if (getServerID.isPresent())
2801    {
2802      final GetServerIDRequestControl c =
2803           new GetServerIDRequestControl(false);
2804      addControls.add(c);
2805      deleteControls.add(c);
2806      modifyControls.add(c);
2807      modifyDNControls.add(c);
2808    }
2809
2810    if (ignoreNoUserModification.isPresent())
2811    {
2812      addControls.add(new IgnoreNoUserModificationRequestControl(false));
2813      modifyControls.add(new IgnoreNoUserModificationRequestControl(false));
2814    }
2815
2816    if (nameWithEntryUUID.isPresent())
2817    {
2818      addControls.add(new NameWithEntryUUIDRequestControl(true));
2819    }
2820
2821    if (permissiveModify.isPresent())
2822    {
2823      modifyControls.add(new PermissiveModifyRequestControl(false));
2824    }
2825
2826    if (routeToServer.isPresent())
2827    {
2828      final RouteToServerRequestControl c =
2829           new RouteToServerRequestControl(false,
2830           routeToServer.getValue(), false, false, false);
2831      addControls.add(c);
2832      deleteControls.add(c);
2833      modifyControls.add(c);
2834      modifyDNControls.add(c);
2835    }
2836
2837    if (suppressReferentialIntegrityUpdates.isPresent())
2838    {
2839      final SuppressReferentialIntegrityUpdatesRequestControl c =
2840           new SuppressReferentialIntegrityUpdatesRequestControl(true);
2841      deleteControls.add(c);
2842      modifyDNControls.add(c);
2843    }
2844
2845    if (suppressOperationalAttributeUpdates.isPresent())
2846    {
2847      final EnumSet<SuppressType> suppressTypes =
2848           EnumSet.noneOf(SuppressType.class);
2849      for (final String s : suppressOperationalAttributeUpdates.getValues())
2850      {
2851        if (s.equalsIgnoreCase("last-access-time"))
2852        {
2853          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
2854        }
2855        else if (s.equalsIgnoreCase("last-login-time"))
2856        {
2857          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
2858        }
2859        else if (s.equalsIgnoreCase("last-login-ip"))
2860        {
2861          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
2862        }
2863        else if (s.equalsIgnoreCase("lastmod"))
2864        {
2865          suppressTypes.add(SuppressType.LASTMOD);
2866        }
2867      }
2868
2869      final SuppressOperationalAttributeUpdateRequestControl c =
2870           new SuppressOperationalAttributeUpdateRequestControl(suppressTypes);
2871      addControls.add(c);
2872      deleteControls.add(c);
2873      modifyControls.add(c);
2874      modifyDNControls.add(c);
2875    }
2876
2877    if (usePasswordPolicyControl.isPresent())
2878    {
2879      final PasswordPolicyRequestControl c = new PasswordPolicyRequestControl();
2880      addControls.add(c);
2881      modifyControls.add(c);
2882    }
2883
2884    if (assuredReplication.isPresent())
2885    {
2886      AssuredReplicationLocalLevel localLevel = null;
2887      if (assuredReplicationLocalLevel.isPresent())
2888      {
2889        final String level = assuredReplicationLocalLevel.getValue();
2890        if (level.equalsIgnoreCase("none"))
2891        {
2892          localLevel = AssuredReplicationLocalLevel.NONE;
2893        }
2894        else if (level.equalsIgnoreCase("received-any-server"))
2895        {
2896          localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER;
2897        }
2898        else if (level.equalsIgnoreCase("processed-all-servers"))
2899        {
2900          localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS;
2901        }
2902      }
2903
2904      AssuredReplicationRemoteLevel remoteLevel = null;
2905      if (assuredReplicationRemoteLevel.isPresent())
2906      {
2907        final String level = assuredReplicationRemoteLevel.getValue();
2908        if (level.equalsIgnoreCase("none"))
2909        {
2910          remoteLevel = AssuredReplicationRemoteLevel.NONE;
2911        }
2912        else if (level.equalsIgnoreCase("received-any-remote-location"))
2913        {
2914          remoteLevel =
2915               AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION;
2916        }
2917        else if (level.equalsIgnoreCase("received-all-remote-locations"))
2918        {
2919          remoteLevel =
2920               AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS;
2921        }
2922        else if (level.equalsIgnoreCase("processed-all-remote-servers"))
2923        {
2924          remoteLevel =
2925               AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS;
2926        }
2927      }
2928
2929      Long timeoutMillis = null;
2930      if (assuredReplicationTimeout.isPresent())
2931      {
2932        timeoutMillis =
2933             assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS);
2934      }
2935
2936      final AssuredReplicationRequestControl c =
2937           new AssuredReplicationRequestControl(true, localLevel, localLevel,
2938                remoteLevel, remoteLevel, timeoutMillis, false);
2939      addControls.add(c);
2940      deleteControls.add(c);
2941      modifyControls.add(c);
2942      modifyDNControls.add(c);
2943    }
2944
2945    if (hardDelete.isPresent() && (! clientSideSubtreeDelete.isPresent()))
2946    {
2947      deleteControls.add(new HardDeleteRequestControl(true));
2948    }
2949
2950    if (replicationRepair.isPresent())
2951    {
2952      final ReplicationRepairRequestControl c =
2953           new ReplicationRepairRequestControl();
2954      addControls.add(c);
2955      deleteControls.add(c);
2956      modifyControls.add(c);
2957      modifyDNControls.add(c);
2958    }
2959
2960    if (softDelete.isPresent())
2961    {
2962      deleteControls.add(new SoftDeleteRequestControl(true, true));
2963    }
2964
2965    if (serverSideSubtreeDelete.isPresent())
2966    {
2967      deleteControls.add(new SubtreeDeleteRequestControl());
2968    }
2969
2970    if (assertionFilter.isPresent())
2971    {
2972      final AssertionRequestControl c = new AssertionRequestControl(
2973           assertionFilter.getValue(), true);
2974      addControls.add(c);
2975      deleteControls.add(c);
2976      modifyControls.add(c);
2977      modifyDNControls.add(c);
2978    }
2979
2980    if (operationPurpose.isPresent())
2981    {
2982      final OperationPurposeRequestControl c =
2983           new OperationPurposeRequestControl(false, "ldapmodify",
2984                Version.NUMERIC_VERSION_STRING,
2985                LDAPModify.class.getName() + ".createRequestControls",
2986                operationPurpose.getValue());
2987      addControls.add(c);
2988      deleteControls.add(c);
2989      modifyControls.add(c);
2990      modifyDNControls.add(c);
2991    }
2992
2993    if (manageDsaIT.isPresent())
2994    {
2995      final ManageDsaITRequestControl c = new ManageDsaITRequestControl(true);
2996      addControls.add(c);
2997      if (! clientSideSubtreeDelete.isPresent())
2998      {
2999        deleteControls.add(c);
3000      }
3001      modifyControls.add(c);
3002      modifyDNControls.add(c);
3003    }
3004
3005    if (passwordUpdateBehavior.isPresent())
3006    {
3007      final PasswordUpdateBehaviorRequestControl c =
3008           createPasswordUpdateBehaviorRequestControl(
3009                passwordUpdateBehavior.getIdentifierString(),
3010                passwordUpdateBehavior.getValues());
3011      addControls.add(c);
3012      modifyControls.add(c);
3013    }
3014
3015    if (preReadAttribute.isPresent())
3016    {
3017      final ArrayList<String> attrList = new ArrayList<>(10);
3018      for (final String value : preReadAttribute.getValues())
3019      {
3020        final StringTokenizer tokenizer = new StringTokenizer(value, ", ");
3021        while (tokenizer.hasMoreTokens())
3022        {
3023          attrList.add(tokenizer.nextToken());
3024        }
3025      }
3026
3027      final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS);
3028      final PreReadRequestControl c = new PreReadRequestControl(attrArray);
3029      deleteControls.add(c);
3030      modifyControls.add(c);
3031      modifyDNControls.add(c);
3032    }
3033
3034    if (postReadAttribute.isPresent())
3035    {
3036      final ArrayList<String> attrList = new ArrayList<>(10);
3037      for (final String value : postReadAttribute.getValues())
3038      {
3039        final StringTokenizer tokenizer = new StringTokenizer(value, ", ");
3040        while (tokenizer.hasMoreTokens())
3041        {
3042          attrList.add(tokenizer.nextToken());
3043        }
3044      }
3045
3046      final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS);
3047      final PostReadRequestControl c = new PostReadRequestControl(attrArray);
3048      addControls.add(c);
3049      modifyControls.add(c);
3050      modifyDNControls.add(c);
3051    }
3052
3053    if (proxyAs.isPresent() && (! useTransaction.isPresent()) &&
3054        (! multiUpdateErrorBehavior.isPresent()))
3055    {
3056      final ProxiedAuthorizationV2RequestControl c =
3057           new ProxiedAuthorizationV2RequestControl(proxyAs.getValue());
3058      addControls.add(c);
3059      deleteControls.add(c);
3060      modifyControls.add(c);
3061      modifyDNControls.add(c);
3062      searchControls.add(c);
3063    }
3064
3065    if (proxyV1As.isPresent() && (! useTransaction.isPresent()) &&
3066        (! multiUpdateErrorBehavior.isPresent()))
3067    {
3068      final ProxiedAuthorizationV1RequestControl c =
3069           new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue());
3070      addControls.add(c);
3071      deleteControls.add(c);
3072      modifyControls.add(c);
3073      modifyDNControls.add(c);
3074      searchControls.add(c);
3075    }
3076
3077    if (uniquenessAttribute.isPresent() || uniquenessFilter.isPresent())
3078    {
3079      final UniquenessRequestControlProperties uniquenessProperties;
3080      if (uniquenessAttribute.isPresent())
3081      {
3082        uniquenessProperties = new UniquenessRequestControlProperties(
3083             uniquenessAttribute.getValues());
3084        if (uniquenessFilter.isPresent())
3085        {
3086          uniquenessProperties.setFilter(uniquenessFilter.getValue());
3087        }
3088      }
3089      else
3090      {
3091        uniquenessProperties = new UniquenessRequestControlProperties(
3092             uniquenessFilter.getValue());
3093      }
3094
3095      if (uniquenessBaseDN.isPresent())
3096      {
3097        uniquenessProperties.setBaseDN(uniquenessBaseDN.getStringValue());
3098      }
3099
3100      if (uniquenessMultipleAttributeBehavior.isPresent())
3101      {
3102        final String value =
3103             uniquenessMultipleAttributeBehavior.getValue().toLowerCase();
3104        switch (value)
3105        {
3106          case "unique-within-each-attribute":
3107            uniquenessProperties.setMultipleAttributeBehavior(
3108                 UniquenessMultipleAttributeBehavior.
3109                      UNIQUE_WITHIN_EACH_ATTRIBUTE);
3110            break;
3111          case "unique-across-all-attributes-including-in-same-entry":
3112            uniquenessProperties.setMultipleAttributeBehavior(
3113                 UniquenessMultipleAttributeBehavior.
3114                      UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_IN_SAME_ENTRY);
3115            break;
3116          case "unique-across-all-attributes-except-in-same-entry":
3117            uniquenessProperties.setMultipleAttributeBehavior(
3118                 UniquenessMultipleAttributeBehavior.
3119                      UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_IN_SAME_ENTRY);
3120            break;
3121          case "unique-in-combination":
3122            uniquenessProperties.setMultipleAttributeBehavior(
3123                 UniquenessMultipleAttributeBehavior.UNIQUE_IN_COMBINATION);
3124            break;
3125        }
3126      }
3127
3128      if (uniquenessPreCommitValidationLevel.isPresent())
3129      {
3130        final String value =
3131             uniquenessPreCommitValidationLevel.getValue().toLowerCase();
3132        switch (value)
3133        {
3134          case "none":
3135            uniquenessProperties.setPreCommitValidationLevel(
3136                 UniquenessValidationLevel.NONE);
3137            break;
3138          case "all-subtree-views":
3139            uniquenessProperties.setPreCommitValidationLevel(
3140                 UniquenessValidationLevel.ALL_SUBTREE_VIEWS);
3141            break;
3142          case "all-backend-sets":
3143            uniquenessProperties.setPreCommitValidationLevel(
3144                 UniquenessValidationLevel.ALL_BACKEND_SETS);
3145            break;
3146          case "all-available-backend-servers":
3147            uniquenessProperties.setPreCommitValidationLevel(
3148                 UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS);
3149            break;
3150        }
3151      }
3152
3153      if (uniquenessPostCommitValidationLevel.isPresent())
3154      {
3155        final String value =
3156             uniquenessPostCommitValidationLevel.getValue().toLowerCase();
3157        switch (value)
3158        {
3159          case "none":
3160            uniquenessProperties.setPostCommitValidationLevel(
3161                 UniquenessValidationLevel.NONE);
3162            break;
3163          case "all-subtree-views":
3164            uniquenessProperties.setPostCommitValidationLevel(
3165                 UniquenessValidationLevel.ALL_SUBTREE_VIEWS);
3166            break;
3167          case "all-backend-sets":
3168            uniquenessProperties.setPostCommitValidationLevel(
3169                 UniquenessValidationLevel.ALL_BACKEND_SETS);
3170            break;
3171          case "all-available-backend-servers":
3172            uniquenessProperties.setPostCommitValidationLevel(
3173                 UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS);
3174            break;
3175        }
3176      }
3177
3178      final UniquenessRequestControl c =
3179           new UniquenessRequestControl(true, null, uniquenessProperties);
3180      addControls.add(c);
3181      modifyControls.add(c);
3182      modifyDNControls.add(c);
3183    }
3184  }
3185
3186
3187
3188  /**
3189   * Creates the password update behavior request control that should be
3190   * included in add and modify requests.
3191   *
3192   * @param  argIdentifier  The identifier string for the argument used to
3193   *                        configure the password update behavior request
3194   *                        control.
3195   * @param  argValues      The set of values for the password update behavior
3196   *                        request control.
3197   *
3198   * @return  The password update behavior request control that was created.
3199   *
3200   * @throws  LDAPException  If a problem is encountered while creating the
3201   *                         control.
3202   */
3203  static PasswordUpdateBehaviorRequestControl
3204              createPasswordUpdateBehaviorRequestControl(
3205                   final String argIdentifier, final List<String> argValues)
3206       throws LDAPException
3207  {
3208    final PasswordUpdateBehaviorRequestControlProperties properties =
3209         new PasswordUpdateBehaviorRequestControlProperties();
3210
3211    for (final String argValue : argValues)
3212    {
3213      int delimiterPos = argValue.indexOf('=');
3214      if (delimiterPos < 0)
3215      {
3216        delimiterPos = argValue.indexOf(':');
3217      }
3218
3219      if ((delimiterPos <= 0) || (delimiterPos >= (argValue.length() - 1)))
3220      {
3221        throw new LDAPException(ResultCode.PARAM_ERROR,
3222             ERR_LDAPMODIFY_MALFORMED_PW_UPDATE_BEHAVIOR.get(argValue,
3223                  argIdentifier));
3224      }
3225
3226      final String name = argValue.substring(0, delimiterPos).trim();
3227      final String value = argValue.substring(delimiterPos+1).trim();
3228      if (name.equalsIgnoreCase("is-self-change") ||
3229           name.equalsIgnoreCase("self-change") ||
3230           name.equalsIgnoreCase("isSelfChange") ||
3231           name.equalsIgnoreCase("selfChange"))
3232      {
3233        properties.setIsSelfChange(parseBooleanValue(name, value));
3234      }
3235      else if (name.equalsIgnoreCase("allow-pre-encoded-password") ||
3236           name.equalsIgnoreCase("allow-pre-encoded-passwords") ||
3237           name.equalsIgnoreCase("allow-pre-encoded") ||
3238           name.equalsIgnoreCase("allowPreEncodedPassword") ||
3239           name.equalsIgnoreCase("allowPreEncodedPasswords") ||
3240           name.equalsIgnoreCase("allowPreEncoded"))
3241      {
3242        properties.setAllowPreEncodedPassword(parseBooleanValue(name, value));
3243      }
3244      else if (name.equalsIgnoreCase("skip-password-validation") ||
3245           name.equalsIgnoreCase("skip-password-validators") ||
3246           name.equalsIgnoreCase("skip-validation") ||
3247           name.equalsIgnoreCase("skip-validators") ||
3248           name.equalsIgnoreCase("skipPasswordValidation") ||
3249           name.equalsIgnoreCase("skipPasswordValidators") ||
3250           name.equalsIgnoreCase("skipValidation") ||
3251           name.equalsIgnoreCase("skipValidators"))
3252      {
3253        properties.setSkipPasswordValidation(parseBooleanValue(name, value));
3254      }
3255      else if (name.equalsIgnoreCase("ignore-password-history") ||
3256           name.equalsIgnoreCase("skip-password-history") ||
3257           name.equalsIgnoreCase("ignore-history") ||
3258           name.equalsIgnoreCase("skip-history") ||
3259           name.equalsIgnoreCase("ignorePasswordHistory") ||
3260           name.equalsIgnoreCase("skipPasswordHistory") ||
3261           name.equalsIgnoreCase("ignoreHistory") ||
3262           name.equalsIgnoreCase("skipHistory"))
3263      {
3264        properties.setIgnorePasswordHistory(parseBooleanValue(name, value));
3265      }
3266      else if (name.equalsIgnoreCase("ignore-minimum-password-age") ||
3267           name.equalsIgnoreCase("ignore-min-password-age") ||
3268           name.equalsIgnoreCase("ignore-password-age") ||
3269           name.equalsIgnoreCase("skip-minimum-password-age") ||
3270           name.equalsIgnoreCase("skip-min-password-age") ||
3271           name.equalsIgnoreCase("skip-password-age") ||
3272           name.equalsIgnoreCase("ignoreMinimumPasswordAge") ||
3273           name.equalsIgnoreCase("ignoreMinPasswordAge") ||
3274           name.equalsIgnoreCase("ignorePasswordAge") ||
3275           name.equalsIgnoreCase("skipMinimumPasswordAge") ||
3276           name.equalsIgnoreCase("skipMinPasswordAge") ||
3277           name.equalsIgnoreCase("skipPasswordAge"))
3278      {
3279        properties.setIgnoreMinimumPasswordAge(parseBooleanValue(name, value));
3280      }
3281      else if (name.equalsIgnoreCase("password-storage-scheme") ||
3282           name.equalsIgnoreCase("password-scheme") ||
3283           name.equalsIgnoreCase("storage-scheme") ||
3284           name.equalsIgnoreCase("scheme") ||
3285           name.equalsIgnoreCase("passwordStorageScheme") ||
3286           name.equalsIgnoreCase("passwordScheme") ||
3287           name.equalsIgnoreCase("storageScheme"))
3288      {
3289        properties.setPasswordStorageScheme(value);
3290      }
3291      else if (name.equalsIgnoreCase("must-change-password") ||
3292         name.equalsIgnoreCase("mustChangePassword"))
3293      {
3294        properties.setMustChangePassword(parseBooleanValue(name, value));
3295      }
3296    }
3297
3298    return new PasswordUpdateBehaviorRequestControl(properties, true);
3299  }
3300
3301
3302
3303  /**
3304   * Parses the provided value as the Boolean value for a password update
3305   * behavior property.
3306   *
3307   * @param  name   The name of the password update behavior property being
3308   *                parsed.
3309   * @param  value  The value to be parsed.
3310   *
3311   * @return  The Boolean value that was parsed.
3312   *
3313   * @throws  LDAPException  If the provided value cannot be parsed as a
3314   *                         Boolean value.
3315   */
3316  private static boolean parseBooleanValue(final String name,
3317                                           final String value)
3318          throws LDAPException
3319  {
3320    if (value.equalsIgnoreCase("true") ||
3321         value.equalsIgnoreCase("t") ||
3322         value.equalsIgnoreCase("yes") ||
3323         value.equalsIgnoreCase("y") ||
3324         value.equalsIgnoreCase("1"))
3325    {
3326      return true;
3327    }
3328    else if (value.equalsIgnoreCase("false") ||
3329         value.equalsIgnoreCase("f") ||
3330         value.equalsIgnoreCase("no") ||
3331         value.equalsIgnoreCase("n") ||
3332         value.equalsIgnoreCase("0"))
3333    {
3334      return false;
3335    }
3336    else
3337    {
3338      throw new LDAPException(ResultCode.PARAM_ERROR,
3339           ERR_LDAPMODIFY_INVALID_PW_UPDATE_BOOLEAN_VALUE.get(value, name));
3340    }
3341  }
3342
3343
3344
3345  /**
3346   * Performs the appropriate processing for an LDIF add change record.
3347   *
3348   * @param  changeRecord         The LDIF add change record to process.
3349   * @param  controls             The set of controls to include in the request.
3350   * @param  pool                 The connection pool to use to communicate with
3351   *                              the directory server.
3352   * @param  multiUpdateRequests  The list to which the request should be added
3353   *                              if it is to be processed as part of a
3354   *                              multi-update operation.  It may be
3355   *                              {@code null} if the operation should not be
3356   *                              processed via the multi-update operation.
3357   * @param  rejectWriter         The LDIF writer to use for recording
3358   *                              information about rejected changes.  It may be
3359   *                              {@code null} if no reject writer is
3360   *                              configured.
3361   *
3362   * @return  The result code obtained from processing.
3363   *
3364   * @throws  LDAPException  If the operation did not complete successfully
3365   *                         and processing should not continue.
3366   */
3367  private ResultCode doAdd(final LDIFAddChangeRecord changeRecord,
3368                           final List<Control> controls,
3369                           final LDAPConnectionPool pool,
3370                           final List<LDAPRequest> multiUpdateRequests,
3371                           final LDIFWriter rejectWriter)
3372          throws LDAPException
3373  {
3374    // Create the add request to process.
3375    final AddRequest addRequest = changeRecord.toAddRequest(true);
3376    for (final Control c : controls)
3377    {
3378      addRequest.addControl(c);
3379    }
3380
3381
3382    // If we should provide support for undelete operations and the entry
3383    // includes the ds-undelete-from-dn attribute, then add the undelete request
3384    // control.
3385    if (allowUndelete.isPresent() &&
3386        addRequest.hasAttribute(ATTR_UNDELETE_FROM_DN))
3387    {
3388      addRequest.addControl(new UndeleteRequestControl());
3389    }
3390
3391
3392    // If the entry to add includes a password, then add a password validation
3393    // details request control if appropriate.
3394    if (passwordValidationDetails.isPresent())
3395    {
3396      final Entry entryToAdd = addRequest.toEntry();
3397      if ((! entryToAdd.getAttributesWithOptions(ATTR_USER_PASSWORD,
3398                  null).isEmpty()) ||
3399          (! entryToAdd.getAttributesWithOptions(ATTR_AUTH_PASSWORD,
3400                  null).isEmpty()))
3401      {
3402        addRequest.addControl(new PasswordValidationDetailsRequestControl());
3403      }
3404    }
3405
3406
3407    // If the operation should be processed in a multi-update operation, then
3408    // just add the request to the list and return without doing anything else.
3409    if (multiUpdateErrorBehavior.isPresent())
3410    {
3411      multiUpdateRequests.add(addRequest);
3412      commentToOut(INFO_LDAPMODIFY_ADD_ADDED_TO_MULTI_UPDATE.get(
3413           addRequest.getDN()));
3414      return ResultCode.SUCCESS;
3415    }
3416
3417
3418    // If the --dryRun argument was provided, then we'll stop here.
3419    if (dryRun.isPresent())
3420    {
3421      commentToOut(INFO_LDAPMODIFY_DRY_RUN_ADD.get(addRequest.getDN(),
3422           dryRun.getIdentifierString()));
3423      return ResultCode.SUCCESS;
3424    }
3425
3426
3427    // Process the add operation and get the result.
3428    commentToOut(INFO_LDAPMODIFY_ADDING_ENTRY.get(addRequest.getDN()));
3429    if (verbose.isPresent())
3430    {
3431      for (final String ldifLine :
3432           addRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN))
3433      {
3434        out(ldifLine);
3435      }
3436      out();
3437    }
3438
3439    LDAPResult addResult;
3440    try
3441    {
3442      addResult = pool.add(addRequest);
3443    }
3444    catch (final LDAPException le)
3445    {
3446      Debug.debugException(le);
3447      addResult = le.toLDAPResult();
3448    }
3449
3450
3451    // Display information about the result.
3452    displayResult(addResult, useTransaction.isPresent());
3453
3454
3455    // See if the add operation succeeded or failed.  If it failed, and we
3456    // should end all processing, then throw an exception.
3457    switch (addResult.getResultCode().intValue())
3458    {
3459      case ResultCode.SUCCESS_INT_VALUE:
3460      case ResultCode.NO_OPERATION_INT_VALUE:
3461        break;
3462
3463      case ResultCode.ASSERTION_FAILED_INT_VALUE:
3464        writeRejectedChange(rejectWriter,
3465             INFO_LDAPMODIFY_ASSERTION_FAILED.get(addRequest.getDN(),
3466                  String.valueOf(assertionFilter.getValue())),
3467             addRequest.toLDIFChangeRecord(), addResult);
3468        throw new LDAPException(addResult);
3469
3470      default:
3471        writeRejectedChange(rejectWriter, null, addRequest.toLDIFChangeRecord(),
3472             addResult);
3473        if (useTransaction.isPresent() || (! continueOnError.isPresent()))
3474        {
3475          throw new LDAPException(addResult);
3476        }
3477        break;
3478    }
3479
3480    return addResult.getResultCode();
3481  }
3482
3483
3484
3485  /**
3486   * Performs the appropriate processing for an LDIF delete change record.
3487   *
3488   * @param  changeRecord         The LDIF delete change record to process.
3489   * @param  controls             The set of controls to include in the request.
3490   * @param  pool                 The connection pool to use to communicate with
3491   *                              the directory server.
3492   * @param  multiUpdateRequests  The list to which the request should be added
3493   *                              if it is to be processed as part of a
3494   *                              multi-update operation.  It may be
3495   *                              {@code null} if the operation should not be
3496   *                              processed via the multi-update operation.
3497   * @param  rejectWriter         The LDIF writer to use for recording
3498   *                              information about rejected changes.  It may be
3499   *                              {@code null} if no reject writer is
3500   *                              configured.
3501   *
3502   * @return  The result code obtained from processing.
3503   *
3504   * @throws  LDAPException  If the operation did not complete successfully
3505   *                         and processing should not continue.
3506   */
3507  private ResultCode doDelete(final LDIFDeleteChangeRecord changeRecord,
3508                              final List<Control> controls,
3509                              final LDAPConnectionPool pool,
3510                              final List<LDAPRequest> multiUpdateRequests,
3511                              final LDIFWriter rejectWriter)
3512          throws LDAPException
3513  {
3514    // If we should perform a client-side subtree delete, then do that
3515    // differently.
3516    if (clientSideSubtreeDelete.isPresent())
3517    {
3518      return doClientSideSubtreeDelete(changeRecord, controls, pool,
3519           rejectWriter);
3520    }
3521
3522
3523    // Create the delete request to process.
3524    final DeleteRequest deleteRequest = changeRecord.toDeleteRequest(true);
3525    for (final Control c : controls)
3526    {
3527      deleteRequest.addControl(c);
3528    }
3529
3530
3531    // If the operation should be processed in a multi-update operation, then
3532    // just add the request to the list and return without doing anything else.
3533    if (multiUpdateErrorBehavior.isPresent())
3534    {
3535      multiUpdateRequests.add(deleteRequest);
3536      commentToOut(INFO_LDAPMODIFY_DELETE_ADDED_TO_MULTI_UPDATE.get(
3537           deleteRequest.getDN()));
3538      return ResultCode.SUCCESS;
3539    }
3540
3541
3542    // If the --dryRun argument was provided, then we'll stop here.
3543    if (dryRun.isPresent())
3544    {
3545      commentToOut(INFO_LDAPMODIFY_DRY_RUN_DELETE.get(deleteRequest.getDN(),
3546           dryRun.getIdentifierString()));
3547      return ResultCode.SUCCESS;
3548    }
3549
3550
3551    // Process the delete operation and get the result.
3552    commentToOut(INFO_LDAPMODIFY_DELETING_ENTRY.get(deleteRequest.getDN()));
3553    if (verbose.isPresent())
3554    {
3555      for (final String ldifLine :
3556           deleteRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN))
3557      {
3558        out(ldifLine);
3559      }
3560      out();
3561    }
3562
3563
3564    LDAPResult deleteResult;
3565    try
3566    {
3567      deleteResult = pool.delete(deleteRequest);
3568    }
3569    catch (final LDAPException le)
3570    {
3571      Debug.debugException(le);
3572      deleteResult = le.toLDAPResult();
3573    }
3574
3575
3576    // Display information about the result.
3577    displayResult(deleteResult, useTransaction.isPresent());
3578
3579
3580    // See if the delete operation succeeded or failed.  If it failed, and we
3581    // should end all processing, then throw an exception.
3582    switch (deleteResult.getResultCode().intValue())
3583    {
3584      case ResultCode.SUCCESS_INT_VALUE:
3585      case ResultCode.NO_OPERATION_INT_VALUE:
3586        break;
3587
3588      case ResultCode.ASSERTION_FAILED_INT_VALUE:
3589        writeRejectedChange(rejectWriter,
3590             INFO_LDAPMODIFY_ASSERTION_FAILED.get(deleteRequest.getDN(),
3591                  String.valueOf(assertionFilter.getValue())),
3592             deleteRequest.toLDIFChangeRecord(), deleteResult);
3593        throw new LDAPException(deleteResult);
3594
3595      default:
3596        writeRejectedChange(rejectWriter, null,
3597             deleteRequest.toLDIFChangeRecord(), deleteResult);
3598        if (useTransaction.isPresent() || (! continueOnError.isPresent()))
3599        {
3600          throw new LDAPException(deleteResult);
3601        }
3602        break;
3603    }
3604
3605    return deleteResult.getResultCode();
3606  }
3607
3608
3609
3610  /**
3611   * Performs the appropriate processing for an LDIF delete change record.
3612   *
3613   * @param  changeRecord  The LDIF delete change record to process.
3614   * @param  controls      The set of controls to include in the request.
3615   * @param  pool          The connection pool to use to communicate with the
3616   *                       directory server.
3617   * @param  rejectWriter  The LDIF writer to use for recording information
3618   *                       about rejected changes.  It may be {@code null} if no
3619   *                       reject writer is configured.
3620   *
3621   * @return  The result code obtained from processing.
3622   *
3623   * @throws  LDAPException  If the operation did not complete successfully
3624   *                         and processing should not continue.
3625   */
3626  private ResultCode doClientSideSubtreeDelete(
3627                          final LDIFChangeRecord changeRecord,
3628                          final List<Control> controls,
3629                          final LDAPConnectionPool pool,
3630                          final LDIFWriter rejectWriter)
3631          throws LDAPException
3632  {
3633    // Create the subtree deleter with the provided set of controls.  Make sure
3634    // to include any controls in the delete change record itself.
3635    final List<Control> additionalControls;
3636    if (changeRecord.getControls().isEmpty())
3637    {
3638      additionalControls = controls;
3639    }
3640    else
3641    {
3642      additionalControls = new ArrayList<>(controls.size() +
3643           changeRecord.getControls().size());
3644      additionalControls.addAll(changeRecord.getControls());
3645      additionalControls.addAll(controls);
3646    }
3647
3648    final SubtreeDeleter subtreeDeleter = new SubtreeDeleter();
3649    subtreeDeleter.setAdditionalDeleteControls(additionalControls);
3650
3651
3652    // Perform the subtree delete.
3653    commentToOut(INFO_LDAPMODIFY_CLIENT_SIDE_DELETING_SUBTREE.get(
3654         changeRecord.getDN()));
3655    final SubtreeDeleterResult subtreeDeleterResult =
3656         subtreeDeleter.delete(pool, changeRecord.getDN());
3657
3658
3659    // Evaluate the result of the subtree delete.
3660    final LDAPResult finalResult;
3661    if (subtreeDeleterResult.completelySuccessful())
3662    {
3663      final long entriesDeleted = subtreeDeleterResult.getEntriesDeleted();
3664      if (entriesDeleted == 0L)
3665      {
3666        // This means that the base entry did not exist.  Even though the
3667        // subtree deleter returned a successful result, we'll use a final
3668        // result of "no such object".
3669        finalResult = new LDAPResult(-1, ResultCode.NO_SUCH_OBJECT,
3670             ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_0_ENTRIES.get(
3671                  changeRecord.getDN()),
3672             null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS);
3673      }
3674      else if (entriesDeleted == 1L)
3675      {
3676        // This means the base entry existed (and we deleted it successfully),
3677        // but did not have any subordinates.
3678        finalResult = new LDAPResult(-1, ResultCode.SUCCESS,
3679             INFO_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_1_ENTRY.get(
3680                  changeRecord.getDN()),
3681             null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS);
3682      }
3683      else
3684      {
3685        // This means that the base entry existed and had subordinates, and we
3686        // deleted all of them successfully.
3687        finalResult = new LDAPResult(-1, ResultCode.SUCCESS,
3688             INFO_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SUCCEEDED_WITH_ENTRIES.get(
3689                  subtreeDeleterResult.getEntriesDeleted(),
3690                  changeRecord.getDN()),
3691             null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS);
3692      }
3693    }
3694    else
3695    {
3696      // If there was a search error, then display information about it.
3697      final SearchResult searchError = subtreeDeleterResult.getSearchError();
3698      if (searchError != null)
3699      {
3700        commentToErr(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_SEARCH_ERROR.get());
3701        displayResult(searchError, false);
3702        err("#");
3703      }
3704
3705      final SortedMap<DN,LDAPResult> deleteErrors =
3706           subtreeDeleterResult.getDeleteErrorsDescendingMap();
3707      for (final Map.Entry<DN,LDAPResult> deleteError : deleteErrors.entrySet())
3708      {
3709        commentToErr(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_ERROR.get(
3710             String.valueOf(deleteError.getKey())));
3711        displayResult(deleteError.getValue(), false);
3712        err("#");
3713      }
3714
3715      ResultCode resultCode = ResultCode.OTHER;
3716      final StringBuilder buffer = new StringBuilder();
3717      buffer.append(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_ERR_BASE.get());
3718      if (searchError != null)
3719      {
3720        resultCode = searchError.getResultCode();
3721        buffer.append("  ");
3722        buffer.append(
3723             ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_SEARCH_ERR.get());
3724      }
3725
3726      if (! deleteErrors.isEmpty())
3727      {
3728        resultCode = deleteErrors.values().iterator().next().getResultCode();
3729        buffer.append("  ");
3730        final int numDeleteErrors = deleteErrors.size();
3731        if (numDeleteErrors == 1)
3732        {
3733          buffer.append(
3734               ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_ERR_COUNT_1.get());
3735        }
3736        else
3737        {
3738          buffer.append(
3739               ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_ERR_COUNT.get(
3740                    numDeleteErrors));
3741        }
3742      }
3743
3744      buffer.append("  ");
3745      final long deletedCount = subtreeDeleterResult.getEntriesDeleted();
3746      if (deletedCount == 1L)
3747      {
3748        buffer.append(
3749             ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_COUNT_1.get());
3750      }
3751      else
3752      {
3753        buffer.append(ERR_LDAPMODIFY_CLIENT_SIDE_SUB_DEL_FINAL_DEL_COUNT.get(
3754             deletedCount));
3755      }
3756
3757      finalResult = new LDAPResult(-1, resultCode, buffer.toString(), null,
3758           StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS);
3759    }
3760
3761
3762    // Display information about the final result.
3763    displayResult(finalResult, useTransaction.isPresent());
3764
3765
3766    // See if the delete operation succeeded or failed.  If it failed, and we
3767    // should end all processing, then throw an exception.
3768    switch (finalResult.getResultCode().intValue())
3769    {
3770      case ResultCode.SUCCESS_INT_VALUE:
3771      case ResultCode.NO_OPERATION_INT_VALUE:
3772        break;
3773
3774      default:
3775        writeRejectedChange(rejectWriter, null, changeRecord, finalResult);
3776        if (! continueOnError.isPresent())
3777        {
3778          throw new LDAPException(finalResult);
3779        }
3780        break;
3781    }
3782
3783    return finalResult.getResultCode();
3784  }
3785
3786
3787
3788  /**
3789   * Performs the appropriate processing for an LDIF modify change record.
3790   *
3791   * @param  changeRecord         The LDIF modify change record to process.
3792   * @param  controls             The set of controls to include in the request.
3793   * @param  pool                 The connection pool to use to communicate with
3794   *                              the directory server.
3795   * @param  multiUpdateRequests  The list to which the request should be added
3796   *                              if it is to be processed as part of a
3797   *                              multi-update operation.  It may be
3798   *                              {@code null} if the operation should not be
3799   *                              processed via the multi-update operation.
3800   * @param  rejectWriter         The LDIF writer to use for recording
3801   *                              information about rejected changes.  It may be
3802   *                              {@code null} if no reject writer is
3803   *                              configured.
3804   *
3805   * @return  The result code obtained from processing.
3806   *
3807   * @throws  LDAPException  If the operation did not complete successfully
3808   *                         and processing should not continue.
3809   */
3810  ResultCode doModify(final LDIFModifyChangeRecord changeRecord,
3811                      final List<Control> controls,
3812                      final LDAPConnectionPool pool,
3813                      final List<LDAPRequest> multiUpdateRequests,
3814                      final LDIFWriter rejectWriter)
3815             throws LDAPException
3816  {
3817    // Create the modify request to process.
3818    final ModifyRequest modifyRequest = changeRecord.toModifyRequest(true);
3819    for (final Control c : controls)
3820    {
3821      modifyRequest.addControl(c);
3822    }
3823
3824
3825    // If the modify request includes a password change, then add any controls
3826    // that are specific to that.
3827    if (retireCurrentPassword.isPresent() || purgeCurrentPassword.isPresent() ||
3828        passwordValidationDetails.isPresent())
3829    {
3830      for (final Modification m : modifyRequest.getModifications())
3831      {
3832        final String baseName = m.getAttribute().getBaseName();
3833        if (baseName.equalsIgnoreCase(ATTR_USER_PASSWORD) ||
3834            baseName.equalsIgnoreCase(ATTR_AUTH_PASSWORD))
3835        {
3836          if (retireCurrentPassword.isPresent())
3837          {
3838            modifyRequest.addControl(new RetirePasswordRequestControl(false));
3839          }
3840          else if (purgeCurrentPassword.isPresent())
3841          {
3842            modifyRequest.addControl(new PurgePasswordRequestControl(false));
3843          }
3844
3845          if (passwordValidationDetails.isPresent())
3846          {
3847            modifyRequest.addControl(
3848                 new PasswordValidationDetailsRequestControl());
3849          }
3850
3851          break;
3852        }
3853      }
3854    }
3855
3856
3857    // If the operation should be processed in a multi-update operation, then
3858    // just add the request to the list and return without doing anything else.
3859    if (multiUpdateErrorBehavior.isPresent())
3860    {
3861      multiUpdateRequests.add(modifyRequest);
3862      commentToOut(INFO_LDAPMODIFY_MODIFY_ADDED_TO_MULTI_UPDATE.get(
3863           modifyRequest.getDN()));
3864      return ResultCode.SUCCESS;
3865    }
3866
3867
3868    // If the --dryRun argument was provided, then we'll stop here.
3869    if (dryRun.isPresent())
3870    {
3871      commentToOut(INFO_LDAPMODIFY_DRY_RUN_MODIFY.get(modifyRequest.getDN(),
3872           dryRun.getIdentifierString()));
3873      return ResultCode.SUCCESS;
3874    }
3875
3876
3877    // Process the modify operation and get the result.
3878    commentToOut(INFO_LDAPMODIFY_MODIFYING_ENTRY.get(modifyRequest.getDN()));
3879    if (verbose.isPresent())
3880    {
3881      for (final String ldifLine :
3882           modifyRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN))
3883      {
3884        out(ldifLine);
3885      }
3886      out();
3887    }
3888
3889
3890    LDAPResult modifyResult;
3891    try
3892    {
3893      modifyResult = pool.modify(modifyRequest);
3894    }
3895    catch (final LDAPException le)
3896    {
3897      Debug.debugException(le);
3898      modifyResult = le.toLDAPResult();
3899    }
3900
3901
3902    // Display information about the result.
3903    displayResult(modifyResult, useTransaction.isPresent());
3904
3905
3906    // See if the modify operation succeeded or failed.  If it failed, and we
3907    // should end all processing, then throw an exception.
3908    switch (modifyResult.getResultCode().intValue())
3909    {
3910      case ResultCode.SUCCESS_INT_VALUE:
3911      case ResultCode.NO_OPERATION_INT_VALUE:
3912        break;
3913
3914      case ResultCode.ASSERTION_FAILED_INT_VALUE:
3915        writeRejectedChange(rejectWriter,
3916             INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyRequest.getDN(),
3917                  String.valueOf(assertionFilter.getValue())),
3918             modifyRequest.toLDIFChangeRecord(), modifyResult);
3919        throw new LDAPException(modifyResult);
3920
3921      default:
3922        writeRejectedChange(rejectWriter, null,
3923             modifyRequest.toLDIFChangeRecord(), modifyResult);
3924        if (useTransaction.isPresent() || (! continueOnError.isPresent()))
3925        {
3926          throw new LDAPException(modifyResult);
3927        }
3928        break;
3929    }
3930
3931    return modifyResult.getResultCode();
3932  }
3933
3934
3935
3936  /**
3937   * Performs the appropriate processing for an LDIF modify DN change record.
3938   *
3939   * @param  changeRecord         The LDIF modify DN change record to process.
3940   * @param  controls             The set of controls to include in the request.
3941   * @param  pool                 The connection pool to use to communicate with
3942   *                              the directory server.
3943   * @param  multiUpdateRequests  The list to which the request should be added
3944   *                              if it is to be processed as part of a
3945   *                              multi-update operation.  It may be
3946   *                              {@code null} if the operation should not be
3947   *                              processed via the multi-update operation.
3948   * @param  rejectWriter         The LDIF writer to use for recording
3949   *                              information about rejected changes.  It may be
3950   *                              {@code null} if no reject writer is
3951   *                              configured.
3952   *
3953   * @return  The result code obtained from processing.
3954   *
3955   * @throws  LDAPException  If the operation did not complete successfully
3956   *                         and processing should not continue.
3957   */
3958  private ResultCode doModifyDN(final LDIFModifyDNChangeRecord changeRecord,
3959                                final List<Control> controls,
3960                                final LDAPConnectionPool pool,
3961                                final List<LDAPRequest> multiUpdateRequests,
3962                                final LDIFWriter rejectWriter)
3963          throws LDAPException
3964  {
3965    // Create the modify DN request to process.
3966    final ModifyDNRequest modifyDNRequest =
3967         changeRecord.toModifyDNRequest(true);
3968    for (final Control c : controls)
3969    {
3970      modifyDNRequest.addControl(c);
3971    }
3972
3973
3974    // If the operation should be processed in a multi-update operation, then
3975    // just add the request to the list and return without doing anything else.
3976    if (multiUpdateErrorBehavior.isPresent())
3977    {
3978      multiUpdateRequests.add(modifyDNRequest);
3979      commentToOut(INFO_LDAPMODIFY_MODIFY_DN_ADDED_TO_MULTI_UPDATE.get(
3980           modifyDNRequest.getDN()));
3981      return ResultCode.SUCCESS;
3982    }
3983
3984
3985    // Try to determine the new DN that the entry will have after the operation.
3986    DN newDN = null;
3987    try
3988    {
3989      newDN = changeRecord.getNewDN();
3990    }
3991    catch (final Exception e)
3992    {
3993      Debug.debugException(e);
3994
3995      // This should only happen if the provided DN, new RDN, or new superior DN
3996      // was malformed.  Although we could reject the operation now, we'll go
3997      // ahead and send the request to the server in case it has some special
3998      // handling for the DN.
3999    }
4000
4001
4002    // If the --dryRun argument was provided, then we'll stop here.
4003    if (dryRun.isPresent())
4004    {
4005      if (modifyDNRequest.getNewSuperiorDN() == null)
4006      {
4007        if (newDN == null)
4008        {
4009          commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME.get(
4010               modifyDNRequest.getDN(), dryRun.getIdentifierString()));
4011        }
4012        else
4013        {
4014          commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME_TO.get(
4015               modifyDNRequest.getDN(), newDN.toString(),
4016               dryRun.getIdentifierString()));
4017        }
4018      }
4019      else
4020      {
4021        if (newDN == null)
4022        {
4023          commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE.get(
4024               modifyDNRequest.getDN(), dryRun.getIdentifierString()));
4025        }
4026        else
4027        {
4028          commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE_TO.get(
4029               modifyDNRequest.getDN(), newDN.toString(),
4030               dryRun.getIdentifierString()));
4031        }
4032      }
4033      return ResultCode.SUCCESS;
4034    }
4035
4036
4037    // Process the modify DN operation and get the result.
4038    final String currentDN = modifyDNRequest.getDN();
4039    if (modifyDNRequest.getNewSuperiorDN() == null)
4040    {
4041      if (newDN == null)
4042      {
4043        commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY.get(currentDN));
4044      }
4045      else
4046      {
4047        commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY_TO.get(currentDN,
4048             newDN.toString()));
4049      }
4050    }
4051    else
4052    {
4053      if (newDN == null)
4054      {
4055        commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY.get(currentDN));
4056      }
4057      else
4058      {
4059        commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY_TO.get(currentDN,
4060             newDN.toString()));
4061      }
4062    }
4063
4064    if (verbose.isPresent())
4065    {
4066      for (final String ldifLine :
4067           modifyDNRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN))
4068      {
4069        out(ldifLine);
4070      }
4071      out();
4072    }
4073
4074
4075    LDAPResult modifyDNResult;
4076    try
4077    {
4078      modifyDNResult = pool.modifyDN(modifyDNRequest);
4079    }
4080    catch (final LDAPException le)
4081    {
4082      Debug.debugException(le);
4083      modifyDNResult = le.toLDAPResult();
4084    }
4085
4086
4087    // Display information about the result.
4088    displayResult(modifyDNResult, useTransaction.isPresent());
4089
4090
4091    // See if the modify DN operation succeeded or failed.  If it failed, and we
4092    // should end all processing, then throw an exception.
4093    switch (modifyDNResult.getResultCode().intValue())
4094    {
4095      case ResultCode.SUCCESS_INT_VALUE:
4096      case ResultCode.NO_OPERATION_INT_VALUE:
4097        break;
4098
4099      case ResultCode.ASSERTION_FAILED_INT_VALUE:
4100        writeRejectedChange(rejectWriter,
4101             INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyDNRequest.getDN(),
4102                  String.valueOf(assertionFilter.getValue())),
4103             modifyDNRequest.toLDIFChangeRecord(), modifyDNResult);
4104        throw new LDAPException(modifyDNResult);
4105
4106      default:
4107        writeRejectedChange(rejectWriter, null,
4108             modifyDNRequest.toLDIFChangeRecord(), modifyDNResult);
4109        if (useTransaction.isPresent() || (! continueOnError.isPresent()))
4110        {
4111          throw new LDAPException(modifyDNResult);
4112        }
4113        break;
4114    }
4115
4116    return modifyDNResult.getResultCode();
4117  }
4118
4119
4120
4121  /**
4122   * Displays information about the provided result, including special
4123   * processing for a number of supported response controls.
4124   *
4125   * @param  result         The result to examine.
4126   * @param  inTransaction  Indicates whether the operation is part of a
4127   *                        transaction.
4128   */
4129  private void displayResult(final LDAPResult result,
4130                             final boolean inTransaction)
4131  {
4132    final ArrayList<String> resultLines = new ArrayList<>(10);
4133    ResultUtils.formatResult(resultLines, result, true, inTransaction, 0,
4134         WRAP_COLUMN);
4135
4136    if (result.getResultCode() == ResultCode.SUCCESS)
4137    {
4138      for (final String line : resultLines)
4139      {
4140        out(line);
4141      }
4142      out();
4143    }
4144    else
4145    {
4146      for (final String line : resultLines)
4147      {
4148        err(line);
4149      }
4150      err();
4151    }
4152  }
4153
4154
4155
4156  /**
4157   * Writes a line-wrapped, commented version of the provided message to
4158   * standard output.
4159   *
4160   * @param  message  The message to be written.
4161   */
4162  private void commentToOut(final String message)
4163  {
4164    for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2))
4165    {
4166      out("# ", line);
4167    }
4168  }
4169
4170
4171
4172  /**
4173   * Writes a line-wrapped, commented version of the provided message to
4174   * standard error.
4175   *
4176   * @param  message  The message to be written.
4177   */
4178  private void commentToErr(final String message)
4179  {
4180    for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2))
4181    {
4182      err("# ", line);
4183    }
4184  }
4185
4186
4187
4188  /**
4189   * Writes information about the rejected change to the reject writer.
4190   *
4191   * @param  writer        The LDIF writer to which the information should be
4192   *                       written.  It may be {@code null} if no reject file is
4193   *                       configured.
4194   * @param  comment       The comment to include before the change record, in
4195   *                       addition to the comment generated from the provided
4196   *                       LDAP result.  It may be {@code null} if no additional
4197   *                       comment should be included.
4198   * @param  changeRecord  The LDIF change record to be written.  It must not
4199   *                       be {@code null}.
4200   * @param  ldapResult    The LDAP result for the failed operation.  It must
4201   *                       not be {@code null}.
4202   */
4203  private void writeRejectedChange(final LDIFWriter writer,
4204                                   final String comment,
4205                                   final LDIFChangeRecord changeRecord,
4206                                   final LDAPResult ldapResult)
4207  {
4208    if (writer == null)
4209    {
4210      return;
4211    }
4212
4213
4214    final StringBuilder buffer = new StringBuilder();
4215    if (comment != null)
4216    {
4217      buffer.append(comment);
4218      buffer.append(StaticUtils.EOL);
4219      buffer.append(StaticUtils.EOL);
4220    }
4221
4222    final ArrayList<String> resultLines = new ArrayList<>(10);
4223    ResultUtils.formatResult(resultLines, ldapResult, false, false, 0, 0);
4224    for (final String resultLine : resultLines)
4225    {
4226      buffer.append(resultLine);
4227      buffer.append(StaticUtils.EOL);
4228    }
4229
4230    writeRejectedChange(writer, buffer.toString(), changeRecord);
4231  }
4232
4233
4234
4235  /**
4236   * Writes information about the rejected change to the reject writer.
4237   *
4238   * @param  writer        The LDIF writer to which the information should be
4239   *                       written.  It may be {@code null} if no reject file is
4240   *                       configured.
4241   * @param  comment       The comment to include before the change record.  It
4242   *                       may be {@code null} if no comment should be included.
4243   * @param  changeRecord  The LDIF change record to be written.  It may be
4244   *                       {@code null} if only a comment should be written.
4245   */
4246  void writeRejectedChange(final LDIFWriter writer, final String comment,
4247                           final LDIFChangeRecord changeRecord)
4248  {
4249    if (writer == null)
4250    {
4251      return;
4252    }
4253
4254    if (rejectWritten.compareAndSet(false, true))
4255    {
4256      try
4257      {
4258        writer.writeVersionHeader();
4259      }
4260      catch (final Exception e)
4261      {
4262        Debug.debugException(e);
4263      }
4264    }
4265
4266    try
4267    {
4268      if (comment != null)
4269      {
4270        writer.writeComment(comment, true, false);
4271      }
4272
4273      if (changeRecord != null)
4274      {
4275        writer.writeChangeRecord(changeRecord);
4276      }
4277    }
4278    catch (final Exception e)
4279    {
4280      Debug.debugException(e);
4281
4282      commentToErr(ERR_LDAPMODIFY_UNABLE_TO_WRITE_REJECTED_CHANGE.get(
4283           rejectFile.getValue().getAbsolutePath(),
4284           StaticUtils.getExceptionMessage(e)));
4285    }
4286  }
4287
4288
4289
4290  /**
4291   * {@inheritDoc}
4292   */
4293  @Override()
4294  public void handleUnsolicitedNotification(final LDAPConnection connection,
4295                                            final ExtendedResult notification)
4296  {
4297    final ArrayList<String> lines = new ArrayList<>(10);
4298    ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0,
4299         WRAP_COLUMN);
4300    for (final String line : lines)
4301    {
4302      err(line);
4303    }
4304    err();
4305  }
4306
4307
4308
4309  /**
4310   * {@inheritDoc}
4311   */
4312  @Override()
4313  public LinkedHashMap<String[],String> getExampleUsages()
4314  {
4315    final LinkedHashMap<String[],String> examples =
4316         new LinkedHashMap<>(StaticUtils.computeMapCapacity(2));
4317
4318    final String[] args1 =
4319    {
4320      "--hostname", "ldap.example.com",
4321      "--port", "389",
4322      "--bindDN", "uid=admin,dc=example,dc=com",
4323      "--bindPassword", "password",
4324      "--defaultAdd"
4325    };
4326    examples.put(args1, INFO_LDAPMODIFY_EXAMPLE_1.get());
4327
4328    final String[] args2 =
4329    {
4330      "--hostname", "ds1.example.com",
4331      "--port", "636",
4332      "--hostname", "ds2.example.com",
4333      "--port", "636",
4334      "--useSSL",
4335      "--bindDN", "uid=admin,dc=example,dc=com",
4336      "--bindPassword", "password",
4337      "--filename", "changes.ldif",
4338      "--modifyEntriesMatchingFilter", "(objectClass=person)",
4339      "--searchPageSize", "100"
4340    };
4341    examples.put(args2, INFO_LDAPMODIFY_EXAMPLE_2.get());
4342
4343    return examples;
4344  }
4345}