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