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