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