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