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