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