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