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