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 Alcatel-Lucent 8661
168 *   server products.  These classes provide support for proprietary
169 *   functionality or for external specifications that are not considered stable
170 *   or mature enough to be guaranteed to work in an interoperable way with
171 *   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<String>(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<String>(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<String>(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<String>(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, operationControl);
1189    parser.addExclusiveArgumentSet(useTransaction, addControl);
1190    parser.addExclusiveArgumentSet(useTransaction, deleteControl);
1191    parser.addExclusiveArgumentSet(useTransaction, modifyControl);
1192    parser.addExclusiveArgumentSet(useTransaction, modifyDNControl);
1193    parser.addExclusiveArgumentSet(useTransaction, modifyEntriesMatchingFilter);
1194    parser.addExclusiveArgumentSet(useTransaction,
1195         modifyEntriesMatchingFiltersFromFile);
1196    parser.addExclusiveArgumentSet(useTransaction, modifyEntryWithDN);
1197    parser.addExclusiveArgumentSet(useTransaction,
1198         modifyEntriesWithDNsFromFile);
1199
1200    // Multi-update is incompatible with a lot of settings.
1201    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, ratePerSecond);
1202    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, rejectFile);
1203    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1204         retryFailedOperations);
1205    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, continueOnError);
1206    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, dryRun);
1207    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, followReferrals);
1208    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, nameWithEntryUUID);
1209    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, noOperation);
1210    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, operationControl);
1211    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, addControl);
1212    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, deleteControl);
1213    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, modifyControl);
1214    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, modifyDNControl);
1215    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1216         modifyEntriesMatchingFilter);
1217    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1218         modifyEntriesMatchingFiltersFromFile);
1219    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior, modifyEntryWithDN);
1220    parser.addExclusiveArgumentSet(multiUpdateErrorBehavior,
1221         modifyEntriesWithDNsFromFile);
1222
1223    // Soft delete cannot be used with either hard delete or subtree delete.
1224    parser.addExclusiveArgumentSet(softDelete, hardDelete);
1225    parser.addExclusiveArgumentSet(softDelete, subtreeDelete);
1226
1227    // Password retiring and purging can't be used together.
1228    parser.addExclusiveArgumentSet(retireCurrentPassword, purgeCurrentPassword);
1229
1230    // Referral following cannot be used in conjunction with the manageDsaIT
1231    // control.
1232    parser.addExclusiveArgumentSet(followReferrals, manageDsaIT);
1233
1234    // The proxyAs and proxyV1As arguments cannot be used together.
1235    parser.addExclusiveArgumentSet(proxyAs, proxyV1As);
1236
1237    // The modifyEntriesMatchingFilter argument is incompatible with a lot of
1238    // settings, since it can only be used for modify operations.
1239    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, allowUndelete);
1240    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, defaultAdd);
1241    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, dryRun);
1242    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, hardDelete);
1243    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1244         ignoreNoUserModification);
1245    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1246         nameWithEntryUUID);
1247    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, softDelete);
1248    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, subtreeDelete);
1249    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1250         suppressReferentialIntegrityUpdates);
1251    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, addControl);
1252    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter, deleteControl);
1253    parser.addExclusiveArgumentSet(modifyEntriesMatchingFilter,
1254         modifyDNControl);
1255
1256    // The modifyEntriesMatchingFilterFromFile argument is incompatible with a
1257    // lot of settings, since it can only be used for modify operations.
1258    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1259         allowUndelete);
1260    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1261         defaultAdd);
1262    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1263         dryRun);
1264    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1265         hardDelete);
1266    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1267         ignoreNoUserModification);
1268    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1269         nameWithEntryUUID);
1270    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1271         softDelete);
1272    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1273         subtreeDelete);
1274    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1275         suppressReferentialIntegrityUpdates);
1276    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1277         addControl);
1278    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1279         deleteControl);
1280    parser.addExclusiveArgumentSet(modifyEntriesMatchingFiltersFromFile,
1281         modifyDNControl);
1282
1283    // The modifyEntryWithDN argument is incompatible with a lot of
1284    // settings, since it can only be used for modify operations.
1285    parser.addExclusiveArgumentSet(modifyEntryWithDN, allowUndelete);
1286    parser.addExclusiveArgumentSet(modifyEntryWithDN, defaultAdd);
1287    parser.addExclusiveArgumentSet(modifyEntryWithDN, dryRun);
1288    parser.addExclusiveArgumentSet(modifyEntryWithDN, hardDelete);
1289    parser.addExclusiveArgumentSet(modifyEntryWithDN, ignoreNoUserModification);
1290    parser.addExclusiveArgumentSet(modifyEntryWithDN, nameWithEntryUUID);
1291    parser.addExclusiveArgumentSet(modifyEntryWithDN, softDelete);
1292    parser.addExclusiveArgumentSet(modifyEntryWithDN, subtreeDelete);
1293    parser.addExclusiveArgumentSet(modifyEntryWithDN,
1294         suppressReferentialIntegrityUpdates);
1295    parser.addExclusiveArgumentSet(modifyEntryWithDN, addControl);
1296    parser.addExclusiveArgumentSet(modifyEntryWithDN, deleteControl);
1297    parser.addExclusiveArgumentSet(modifyEntryWithDN, modifyDNControl);
1298
1299    // The modifyEntriesWithDNsFromFile argument is incompatible with a lot of
1300    // settings, since it can only be used for modify operations.
1301    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, allowUndelete);
1302    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, defaultAdd);
1303    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, dryRun);
1304    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, hardDelete);
1305    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1306         ignoreNoUserModification);
1307    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1308         nameWithEntryUUID);
1309    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, softDelete);
1310    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, subtreeDelete);
1311    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1312         suppressReferentialIntegrityUpdates);
1313    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, addControl);
1314    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile, deleteControl);
1315    parser.addExclusiveArgumentSet(modifyEntriesWithDNsFromFile,
1316         modifyDNControl);
1317  }
1318
1319
1320
1321  /**
1322   * {@inheritDoc}
1323   */
1324  @Override()
1325  protected List<Control> getBindControls()
1326  {
1327    final ArrayList<Control> bindControls = new ArrayList<Control>(10);
1328
1329    if (bindControl.isPresent())
1330    {
1331      bindControls.addAll(bindControl.getValues());
1332    }
1333
1334    if (authorizationIdentity.isPresent())
1335    {
1336      bindControls.add(new AuthorizationIdentityRequestControl(false));
1337    }
1338
1339    if (getAuthorizationEntryAttribute.isPresent())
1340    {
1341      bindControls.add(new GetAuthorizationEntryRequestControl(true, true,
1342           getAuthorizationEntryAttribute.getValues()));
1343    }
1344
1345    if (getUserResourceLimits.isPresent())
1346    {
1347      bindControls.add(new GetUserResourceLimitsRequestControl());
1348    }
1349
1350    if (usePasswordPolicyControl.isPresent())
1351    {
1352      bindControls.add(new PasswordPolicyRequestControl());
1353    }
1354
1355    if (suppressOperationalAttributeUpdates.isPresent())
1356    {
1357      final EnumSet<SuppressType> suppressTypes =
1358           EnumSet.noneOf(SuppressType.class);
1359      for (final String s : suppressOperationalAttributeUpdates.getValues())
1360      {
1361        if (s.equalsIgnoreCase("last-access-time"))
1362        {
1363          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
1364        }
1365        else if (s.equalsIgnoreCase("last-login-time"))
1366        {
1367          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
1368        }
1369        else if (s.equalsIgnoreCase("last-login-ip"))
1370        {
1371          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
1372        }
1373      }
1374
1375      bindControls.add(new SuppressOperationalAttributeUpdateRequestControl(
1376           suppressTypes));
1377    }
1378
1379    return bindControls;
1380  }
1381
1382
1383
1384  /**
1385   * {@inheritDoc}
1386   */
1387  @Override()
1388  protected boolean supportsMultipleServers()
1389  {
1390    // We will support providing information about multiple servers.  This tool
1391    // will not communicate with multiple servers concurrently, but it can
1392    // accept information about multiple servers in the event that a large set
1393    // of changes is to be processed and a server goes down in the middle of
1394    // those changes.  In this case, we can resume processing on a newly-created
1395    // connection, possibly to a different server.
1396    return true;
1397  }
1398
1399
1400
1401  /**
1402   * {@inheritDoc}
1403   */
1404  @Override()
1405  public LDAPConnectionOptions getConnectionOptions()
1406  {
1407    final LDAPConnectionOptions options = new LDAPConnectionOptions();
1408
1409    options.setUseSynchronousMode(true);
1410    options.setFollowReferrals(followReferrals.isPresent());
1411    options.setUnsolicitedNotificationHandler(this);
1412
1413    return options;
1414  }
1415
1416
1417
1418  /**
1419   * {@inheritDoc}
1420   */
1421  @Override()
1422  public ResultCode doToolProcessing()
1423  {
1424    // Examine the arguments to determine the sets of controls to use for each
1425    // type of request.
1426    final ArrayList<Control> addControls = new ArrayList<Control>(10);
1427    final ArrayList<Control> deleteControls = new ArrayList<Control>(10);
1428    final ArrayList<Control> modifyControls = new ArrayList<Control>(10);
1429    final ArrayList<Control> modifyDNControls = new ArrayList<Control>(10);
1430    final ArrayList<Control> searchControls = new ArrayList<Control>(10);
1431    try
1432    {
1433      createRequestControls(addControls, deleteControls, modifyControls,
1434           modifyDNControls, searchControls);
1435    }
1436    catch (final LDAPException le)
1437    {
1438      Debug.debugException(le);
1439      for (final String line :
1440           ResultUtils.formatResult(le, true, 0, WRAP_COLUMN))
1441      {
1442        err(line);
1443      }
1444      return le.getResultCode();
1445    }
1446
1447
1448    // If an encryption passphrase file was specified, then read its value.
1449    String encryptionPassphrase = null;
1450    if (encryptionPassphraseFile.isPresent())
1451    {
1452      try
1453      {
1454        encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile(
1455             encryptionPassphraseFile.getValue());
1456      }
1457      catch (final LDAPException e)
1458      {
1459        Debug.debugException(e);
1460        wrapErr(0, WRAP_COLUMN, e.getMessage());
1461        return e.getResultCode();
1462      }
1463    }
1464
1465
1466    LDAPConnectionPool connectionPool = null;
1467    LDIFReader         ldifReader     = null;
1468    LDIFWriter         rejectWriter   = null;
1469    try
1470    {
1471      // Create a connection pool that will be used to communicate with the
1472      // directory server.  If we should use an administrative session, then
1473      // create a connect processor that will be used to start the session
1474      // before performing the bind.
1475      try
1476      {
1477        final StartAdministrativeSessionPostConnectProcessor p;
1478        if (useAdministrativeSession.isPresent())
1479        {
1480          p = new StartAdministrativeSessionPostConnectProcessor(
1481               new StartAdministrativeSessionExtendedRequest(getToolName(),
1482                    true));
1483        }
1484        else
1485        {
1486          p = null;
1487        }
1488
1489        if (! dryRun.isPresent())
1490        {
1491          connectionPool = getConnectionPool(1, 2, 0, p, null, true,
1492               new ReportBindResultLDAPConnectionPoolHealthCheck(this, true,
1493                    verbose.isPresent()));
1494        }
1495      }
1496      catch (final LDAPException le)
1497      {
1498        Debug.debugException(le);
1499
1500        // Unable to create the connection pool, which means that either the
1501        // connection could not be established or the attempt to authenticate
1502        // the connection failed.  If the bind failed, then the report bind
1503        // result health check should have already reported the bind failure.
1504        // If the failure was something else, then display that failure result.
1505        if (le.getResultCode() != ResultCode.INVALID_CREDENTIALS)
1506        {
1507          for (final String line :
1508               ResultUtils.formatResult(le, true, 0, WRAP_COLUMN))
1509          {
1510            err(line);
1511          }
1512        }
1513        return le.getResultCode();
1514      }
1515
1516      if ((connectionPool != null) && retryFailedOperations.isPresent())
1517      {
1518        connectionPool.setRetryFailedOperationsDueToInvalidConnections(true);
1519      }
1520
1521
1522      // Report that the connection was successfully established.
1523      if (connectionPool != null)
1524      {
1525        try
1526        {
1527          final LDAPConnection connection = connectionPool.getConnection();
1528          final String hostPort = connection.getHostPort();
1529          connectionPool.releaseConnection(connection);
1530          commentToOut(INFO_LDAPMODIFY_CONNECTION_ESTABLISHED.get(hostPort));
1531          out();
1532        }
1533        catch (final LDAPException le)
1534        {
1535          Debug.debugException(le);
1536          // This should never happen.
1537        }
1538      }
1539
1540
1541      // If we should process the operations in a transaction, then start that
1542      // now.
1543      final ASN1OctetString txnID;
1544      if (useTransaction.isPresent())
1545      {
1546        final Control[] startTxnControls;
1547        if (proxyAs.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 ProxiedAuthorizationV2RequestControl(proxyAs.getValue())
1555          };
1556        }
1557        else if (proxyV1As.isPresent())
1558        {
1559          // In a transaction, the proxied authorization control must only be
1560          // used in the start transaction request and not in any of the
1561          // subsequent operation requests.
1562          startTxnControls = new Control[]
1563          {
1564            new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue())
1565          };
1566        }
1567        else
1568        {
1569          startTxnControls = StaticUtils.NO_CONTROLS;
1570        }
1571
1572        try
1573        {
1574          final StartTransactionExtendedResult startTxnResult =
1575               (StartTransactionExtendedResult)
1576               connectionPool.processExtendedOperation(
1577                    new StartTransactionExtendedRequest(startTxnControls));
1578          if (startTxnResult.getResultCode() == ResultCode.SUCCESS)
1579          {
1580            txnID = startTxnResult.getTransactionID();
1581
1582            final TransactionSpecificationRequestControl c =
1583                 new TransactionSpecificationRequestControl(txnID);
1584            addControls.add(c);
1585            deleteControls.add(c);
1586            modifyControls.add(c);
1587            modifyDNControls.add(c);
1588
1589            final String txnIDString;
1590            if (StaticUtils.isPrintableString(txnID.getValue()))
1591            {
1592              txnIDString = txnID.stringValue();
1593            }
1594            else
1595            {
1596              final StringBuilder hexBuffer = new StringBuilder();
1597              StaticUtils.toHex(txnID.getValue(), ":", hexBuffer);
1598              txnIDString = hexBuffer.toString();
1599            }
1600
1601            commentToOut(INFO_LDAPMODIFY_STARTED_TXN.get(txnIDString));
1602          }
1603          else
1604          {
1605            commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get(
1606                 startTxnResult.getResultString()));
1607            return startTxnResult.getResultCode();
1608          }
1609        }
1610        catch (final LDAPException le)
1611        {
1612          Debug.debugException(le);
1613          commentToErr(ERR_LDAPMODIFY_CANNOT_START_TXN.get(
1614               StaticUtils.getExceptionMessage(le)));
1615          return le.getResultCode();
1616        }
1617      }
1618      else
1619      {
1620        txnID = null;
1621      }
1622
1623
1624      // Create an LDIF reader that will be used to read the changes to process.
1625      try
1626      {
1627        final InputStream ldifInputStream;
1628        if (ldifFile.isPresent())
1629        {
1630          ldifInputStream = ToolUtils.getInputStreamForLDIFFiles(
1631               ldifFile.getValues(), encryptionPassphrase, getOut(),
1632               getErr()).getFirst();
1633        }
1634        else
1635        {
1636          ldifInputStream = in;
1637        }
1638
1639        ldifReader = new LDIFReader(ldifInputStream, 0, null, null,
1640             characterSet.getValue());
1641      }
1642      catch (final Exception e)
1643      {
1644        commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_LDIF_READER.get(
1645             StaticUtils.getExceptionMessage(e)));
1646        return ResultCode.LOCAL_ERROR;
1647      }
1648
1649      if (stripTrailingSpaces.isPresent())
1650      {
1651        ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.STRIP);
1652      }
1653
1654
1655      // If appropriate, create a reject writer.
1656      if (rejectFile.isPresent())
1657      {
1658        try
1659        {
1660          rejectWriter = new LDIFWriter(rejectFile.getValue());
1661
1662          // Set the maximum allowed wrap column.  This is better than setting a
1663          // wrap column of zero because it will ensure that comments don't get
1664          // wrapped either.
1665          rejectWriter.setWrapColumn(Integer.MAX_VALUE);
1666        }
1667        catch (final Exception e)
1668        {
1669          Debug.debugException(e);
1670          commentToErr(ERR_LDAPMODIFY_CANNOT_CREATE_REJECT_WRITER.get(
1671               rejectFile.getValue().getAbsolutePath(),
1672               StaticUtils.getExceptionMessage(e)));
1673          return ResultCode.LOCAL_ERROR;
1674        }
1675      }
1676
1677
1678      // If appropriate, create a rate limiter.
1679      final FixedRateBarrier rateLimiter;
1680      if (ratePerSecond.isPresent())
1681      {
1682        rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue());
1683      }
1684      else
1685      {
1686        rateLimiter = null;
1687      }
1688
1689
1690      // Iterate through the set of changes to process.
1691      boolean commitTransaction = true;
1692      ResultCode resultCode = null;
1693      final ArrayList<LDAPRequest> multiUpdateRequests =
1694           new ArrayList<LDAPRequest>(10);
1695      final boolean isBulkModify = modifyEntriesMatchingFilter.isPresent() ||
1696           modifyEntriesMatchingFiltersFromFile.isPresent() ||
1697           modifyEntryWithDN.isPresent() ||
1698           modifyEntriesWithDNsFromFile.isPresent();
1699readChangeRecordLoop:
1700      while (true)
1701      {
1702        // If there is a rate limiter, then use it to sleep if necessary.
1703        if ((rateLimiter != null) && (! isBulkModify))
1704        {
1705          rateLimiter.await();
1706        }
1707
1708
1709        // Read the next LDIF change record.  If we get an error then handle it
1710        // and abort if appropriate.
1711        final LDIFChangeRecord changeRecord;
1712        try
1713        {
1714          changeRecord = ldifReader.readChangeRecord(defaultAdd.isPresent());
1715        }
1716        catch (final IOException ioe)
1717        {
1718          Debug.debugException(ioe);
1719
1720          final String message = ERR_LDAPMODIFY_IO_ERROR_READING_CHANGE.get(
1721               StaticUtils.getExceptionMessage(ioe));
1722          commentToErr(message);
1723          writeRejectedChange(rejectWriter, message, null);
1724          commitTransaction = false;
1725          resultCode = ResultCode.LOCAL_ERROR;
1726          break;
1727        }
1728        catch (final LDIFException le)
1729        {
1730          Debug.debugException(le);
1731
1732          final StringBuilder buffer = new StringBuilder();
1733          if (le.mayContinueReading() && (! useTransaction.isPresent()))
1734          {
1735            buffer.append(
1736                 ERR_LDAPMODIFY_RECOVERABLE_LDIF_ERROR_READING_CHANGE.get(
1737                      le.getLineNumber(), StaticUtils.getExceptionMessage(le)));
1738          }
1739          else
1740          {
1741            buffer.append(
1742                 ERR_LDAPMODIFY_UNRECOVERABLE_LDIF_ERROR_READING_CHANGE.get(
1743                      le.getLineNumber(), StaticUtils.getExceptionMessage(le)));
1744          }
1745
1746          if ((resultCode == null) || (resultCode == ResultCode.SUCCESS))
1747          {
1748            resultCode = ResultCode.LOCAL_ERROR;
1749          }
1750
1751          if ((le.getDataLines() != null) && (! le.getDataLines().isEmpty()))
1752          {
1753            buffer.append(StaticUtils.EOL);
1754            buffer.append(StaticUtils.EOL);
1755            buffer.append(ERR_LDAPMODIFY_INVALID_LINES.get());
1756            buffer.append(StaticUtils.EOL);
1757            for (final String s : le.getDataLines())
1758            {
1759              buffer.append(s);
1760              buffer.append(StaticUtils.EOL);
1761            }
1762          }
1763
1764          final String message = buffer.toString();
1765          commentToErr(message);
1766          writeRejectedChange(rejectWriter, message, null);
1767
1768          if (le.mayContinueReading() && (! useTransaction.isPresent()))
1769          {
1770            continue;
1771          }
1772          else
1773          {
1774            commitTransaction = false;
1775            resultCode = ResultCode.LOCAL_ERROR;
1776            break;
1777          }
1778        }
1779
1780
1781        // If we read a null change record, then there are no more changes to
1782        // process.  Otherwise, treat it appropriately based on the operation
1783        // type.
1784        if (changeRecord == null)
1785        {
1786          break;
1787        }
1788
1789
1790        // If we should modify entries matching a specified filter, then convert
1791        // the change record into a set of modifications.
1792        if (modifyEntriesMatchingFilter.isPresent())
1793        {
1794          for (final Filter filter : modifyEntriesMatchingFilter.getValues())
1795          {
1796            final ResultCode rc = handleModifyMatchingFilter(connectionPool,
1797                 changeRecord,
1798                 modifyEntriesMatchingFilter.getIdentifierString(),
1799                 filter, searchControls, modifyControls, rateLimiter,
1800                 rejectWriter);
1801            if (rc != ResultCode.SUCCESS)
1802            {
1803              if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) ||
1804                   (resultCode == ResultCode.NO_OPERATION))
1805              {
1806                resultCode = rc;
1807              }
1808            }
1809          }
1810        }
1811
1812        if (modifyEntriesMatchingFiltersFromFile.isPresent())
1813        {
1814          for (final File f : modifyEntriesMatchingFiltersFromFile.getValues())
1815          {
1816            final FilterFileReader filterReader;
1817            try
1818            {
1819              filterReader = new FilterFileReader(f);
1820            }
1821            catch (final Exception e)
1822            {
1823              Debug.debugException(e);
1824              commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_FILTER_FILE.get(
1825                   f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
1826              return ResultCode.LOCAL_ERROR;
1827            }
1828
1829            try
1830            {
1831              while (true)
1832              {
1833                final Filter filter;
1834                try
1835                {
1836                  filter = filterReader.readFilter();
1837                }
1838                catch (final IOException ioe)
1839                {
1840                  Debug.debugException(ioe);
1841                  commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_FILTER_FILE.get(
1842                       f.getAbsolutePath(),
1843                       StaticUtils.getExceptionMessage(ioe)));
1844                  return ResultCode.LOCAL_ERROR;
1845                }
1846                catch (final LDAPException le)
1847                {
1848                  Debug.debugException(le);
1849                  commentToErr(le.getMessage());
1850                  if (continueOnError.isPresent())
1851                  {
1852                    if ((resultCode == null) ||
1853                        (resultCode == ResultCode.SUCCESS) ||
1854                        (resultCode == ResultCode.NO_OPERATION))
1855                    {
1856                      resultCode = le.getResultCode();
1857                    }
1858                    continue;
1859                  }
1860                  else
1861                  {
1862                    return le.getResultCode();
1863                  }
1864                }
1865
1866                if (filter == null)
1867                {
1868                  break;
1869                }
1870
1871                final ResultCode rc = handleModifyMatchingFilter(connectionPool,
1872                     changeRecord,
1873                     modifyEntriesMatchingFiltersFromFile.getIdentifierString(),
1874                     filter, searchControls, modifyControls, rateLimiter,
1875                     rejectWriter);
1876                if (rc != ResultCode.SUCCESS)
1877                {
1878                  if ((resultCode == null) ||
1879                      (resultCode == ResultCode.SUCCESS) ||
1880                      (resultCode == ResultCode.NO_OPERATION))
1881                  {
1882                    resultCode = rc;
1883                  }
1884                }
1885              }
1886            }
1887            finally
1888            {
1889              try
1890              {
1891                filterReader.close();
1892              }
1893              catch (final Exception e)
1894              {
1895                Debug.debugException(e);
1896              }
1897            }
1898          }
1899        }
1900
1901        if (modifyEntryWithDN.isPresent())
1902        {
1903          for (final DN dn : modifyEntryWithDN.getValues())
1904          {
1905            final ResultCode rc = handleModifyWithDN(connectionPool,
1906                 changeRecord, modifyEntryWithDN.getIdentifierString(), dn,
1907                 modifyControls, rateLimiter, rejectWriter);
1908            if (rc != ResultCode.SUCCESS)
1909            {
1910              if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) ||
1911                   (resultCode == ResultCode.NO_OPERATION))
1912              {
1913                resultCode = rc;
1914              }
1915            }
1916          }
1917        }
1918
1919        if (modifyEntriesWithDNsFromFile.isPresent())
1920        {
1921          for (final File f : modifyEntriesWithDNsFromFile.getValues())
1922          {
1923            final DNFileReader dnReader;
1924            try
1925            {
1926              dnReader = new DNFileReader(f);
1927            }
1928            catch (final Exception e)
1929            {
1930              Debug.debugException(e);
1931              commentToErr(ERR_LDAPMODIFY_ERROR_OPENING_DN_FILE.get(
1932                   f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
1933              return ResultCode.LOCAL_ERROR;
1934            }
1935
1936            try
1937            {
1938              while (true)
1939              {
1940                final DN dn;
1941                try
1942                {
1943                  dn = dnReader.readDN();
1944                }
1945                catch (final IOException ioe)
1946                {
1947                  Debug.debugException(ioe);
1948                  commentToErr(ERR_LDAPMODIFY_IO_ERROR_READING_DN_FILE.get(
1949                       f.getAbsolutePath(),
1950                       StaticUtils.getExceptionMessage(ioe)));
1951                  return ResultCode.LOCAL_ERROR;
1952                }
1953                catch (final LDAPException le)
1954                {
1955                  Debug.debugException(le);
1956                  commentToErr(le.getMessage());
1957                  if (continueOnError.isPresent())
1958                  {
1959                    if ((resultCode == null) ||
1960                        (resultCode == ResultCode.SUCCESS) ||
1961                        (resultCode == ResultCode.NO_OPERATION))
1962                    {
1963                      resultCode = le.getResultCode();
1964                    }
1965                    continue;
1966                  }
1967                  else
1968                  {
1969                    return le.getResultCode();
1970                  }
1971                }
1972
1973                if (dn == null)
1974                {
1975                  break;
1976                }
1977
1978                final ResultCode rc = handleModifyWithDN(connectionPool,
1979                     changeRecord,
1980                     modifyEntriesWithDNsFromFile.getIdentifierString(), dn,
1981                     modifyControls, rateLimiter, rejectWriter);
1982                if (rc != ResultCode.SUCCESS)
1983                {
1984                  if ((resultCode == null) ||
1985                      (resultCode == ResultCode.SUCCESS) ||
1986                      (resultCode == ResultCode.NO_OPERATION))
1987                  {
1988                    resultCode = rc;
1989                  }
1990                }
1991              }
1992            }
1993            finally
1994            {
1995              try
1996              {
1997                dnReader.close();
1998              }
1999              catch (final Exception e)
2000              {
2001                Debug.debugException(e);
2002              }
2003            }
2004          }
2005        }
2006
2007        if (isBulkModify)
2008        {
2009          continue;
2010        }
2011
2012        try
2013        {
2014          final ResultCode rc;
2015          if (changeRecord instanceof LDIFAddChangeRecord)
2016          {
2017            rc = doAdd((LDIFAddChangeRecord) changeRecord, addControls,
2018                 connectionPool, multiUpdateRequests, rejectWriter);
2019          }
2020          else if (changeRecord instanceof LDIFDeleteChangeRecord)
2021          {
2022            rc = doDelete((LDIFDeleteChangeRecord) changeRecord, deleteControls,
2023                 connectionPool, multiUpdateRequests, rejectWriter);
2024          }
2025          else if (changeRecord instanceof LDIFModifyChangeRecord)
2026          {
2027            rc = doModify((LDIFModifyChangeRecord) changeRecord, modifyControls,
2028                 connectionPool, multiUpdateRequests, rejectWriter);
2029          }
2030          else if (changeRecord instanceof LDIFModifyDNChangeRecord)
2031          {
2032            rc = doModifyDN((LDIFModifyDNChangeRecord) changeRecord,
2033                 modifyDNControls, connectionPool, multiUpdateRequests,
2034                 rejectWriter);
2035          }
2036          else
2037          {
2038            // This should never happen.
2039            commentToErr(ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get());
2040            for (final String line : changeRecord.toLDIF())
2041            {
2042              err("#      " + line);
2043            }
2044            throw new LDAPException(ResultCode.PARAM_ERROR,
2045                 ERR_LDAPMODIFY_UNSUPPORTED_CHANGE_RECORD_HEADER.get() +
2046                      changeRecord.toString());
2047          }
2048
2049          if ((resultCode == null) && (rc != ResultCode.SUCCESS))
2050          {
2051            resultCode = rc;
2052          }
2053        }
2054        catch (final LDAPException le)
2055        {
2056          Debug.debugException(le);
2057
2058          commitTransaction = false;
2059          if (continueOnError.isPresent())
2060          {
2061            if ((resultCode == null) || (resultCode == ResultCode.SUCCESS) ||
2062                 (resultCode == ResultCode.NO_OPERATION))
2063            {
2064              resultCode = le.getResultCode();
2065            }
2066          }
2067          else
2068          {
2069            resultCode = le.getResultCode();
2070            break;
2071          }
2072        }
2073      }
2074
2075
2076      // If the operations are part of a transaction, then commit or abort that
2077      // transaction now.  Otherwise, if they should be part of a multi-update
2078      // operation, then process that now.
2079      if (useTransaction.isPresent())
2080      {
2081        LDAPResult endTxnResult;
2082        final EndTransactionExtendedRequest endTxnRequest =
2083             new EndTransactionExtendedRequest(txnID, commitTransaction);
2084        try
2085        {
2086          endTxnResult = connectionPool.processExtendedOperation(endTxnRequest);
2087        }
2088        catch (final LDAPException le)
2089        {
2090          endTxnResult = le.toLDAPResult();
2091        }
2092
2093        displayResult(endTxnResult, false);
2094        if (((resultCode == null) || (resultCode == ResultCode.SUCCESS)) &&
2095            (endTxnResult.getResultCode() != ResultCode.SUCCESS))
2096        {
2097          resultCode = endTxnResult.getResultCode();
2098        }
2099      }
2100      else if (multiUpdateErrorBehavior.isPresent())
2101      {
2102        final MultiUpdateErrorBehavior errorBehavior;
2103        if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase("atomic"))
2104        {
2105          errorBehavior = MultiUpdateErrorBehavior.ATOMIC;
2106        }
2107        else if (multiUpdateErrorBehavior.getValue().equalsIgnoreCase(
2108                      "abort-on-error"))
2109        {
2110          errorBehavior = MultiUpdateErrorBehavior.ABORT_ON_ERROR;
2111        }
2112        else
2113        {
2114          errorBehavior = MultiUpdateErrorBehavior.CONTINUE_ON_ERROR;
2115        }
2116
2117        final Control[] multiUpdateControls;
2118        if (proxyAs.isPresent())
2119        {
2120          multiUpdateControls = new Control[]
2121          {
2122            new ProxiedAuthorizationV2RequestControl(proxyAs.getValue())
2123          };
2124        }
2125        else if (proxyV1As.isPresent())
2126        {
2127          multiUpdateControls = new Control[]
2128          {
2129            new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue())
2130          };
2131        }
2132        else
2133        {
2134          multiUpdateControls = StaticUtils.NO_CONTROLS;
2135        }
2136
2137        ExtendedResult multiUpdateResult;
2138        try
2139        {
2140          commentToOut(INFO_LDAPMODIFY_SENDING_MULTI_UPDATE_REQUEST.get());
2141          final MultiUpdateExtendedRequest multiUpdateRequest =
2142               new MultiUpdateExtendedRequest(errorBehavior,
2143                    multiUpdateRequests, multiUpdateControls);
2144          multiUpdateResult =
2145               connectionPool.processExtendedOperation(multiUpdateRequest);
2146        }
2147        catch (final LDAPException le)
2148        {
2149          multiUpdateResult = new ExtendedResult(le);
2150        }
2151
2152        displayResult(multiUpdateResult, false);
2153        resultCode = multiUpdateResult.getResultCode();
2154      }
2155
2156
2157      if (resultCode == null)
2158      {
2159        return ResultCode.SUCCESS;
2160      }
2161      else
2162      {
2163        return resultCode;
2164      }
2165    }
2166    finally
2167    {
2168      if (rejectWriter != null)
2169      {
2170        try
2171        {
2172          rejectWriter.close();
2173        }
2174        catch (final Exception e)
2175        {
2176          Debug.debugException(e);
2177        }
2178      }
2179
2180      if (ldifReader != null)
2181      {
2182        try
2183        {
2184          ldifReader.close();
2185        }
2186        catch (final Exception e)
2187        {
2188          Debug.debugException(e);
2189        }
2190      }
2191
2192      if (connectionPool != null)
2193      {
2194        try
2195        {
2196          connectionPool.close();
2197        }
2198        catch (final Exception e)
2199        {
2200          Debug.debugException(e);
2201        }
2202      }
2203    }
2204  }
2205
2206
2207
2208  /**
2209   * Handles the processing for a change record when the tool should modify
2210   * entries matching a given filter.
2211   *
2212   * @param  connectionPool       The connection pool to use to communicate with
2213   *                              the directory server.
2214   * @param  changeRecord         The LDIF change record to be processed.
2215   * @param  argIdentifierString  The identifier string for the argument used to
2216   *                              specify the filter to use to identify the
2217   *                              entries to modify.
2218   * @param  filter               The filter to use to identify the entries to
2219   *                              modify.
2220   * @param  searchControls       The set of controls to include in the search
2221   *                              request.
2222   * @param  modifyControls       The set of controls to include in the modify
2223   *                              requests.
2224   * @param  rateLimiter          The fixed-rate barrier to use for rate
2225   *                              limiting.  It may be {@code null} if no rate
2226   *                              limiting is required.
2227   * @param  rejectWriter         The reject writer to use to record information
2228   *                              about any failed operations.
2229   *
2230   * @return  A result code obtained from processing.
2231   */
2232  private ResultCode handleModifyMatchingFilter(
2233                          final LDAPConnectionPool connectionPool,
2234                          final LDIFChangeRecord changeRecord,
2235                          final String argIdentifierString, final Filter filter,
2236                          final List<Control> searchControls,
2237                          final List<Control> modifyControls,
2238                          final FixedRateBarrier rateLimiter,
2239                          final LDIFWriter rejectWriter)
2240  {
2241    // If the provided change record isn't a modify change record, then that's
2242    // an error.  Reject it.
2243    if (! (changeRecord instanceof LDIFModifyChangeRecord))
2244    {
2245      writeRejectedChange(rejectWriter,
2246           ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString),
2247           changeRecord);
2248      return ResultCode.PARAM_ERROR;
2249    }
2250
2251    final LDIFModifyChangeRecord modifyChangeRecord =
2252         (LDIFModifyChangeRecord) changeRecord;
2253    final HashSet<DN> processedDNs = new HashSet<DN>(100);
2254
2255
2256    // If we need to use the simple paged results control, then we may have to
2257    // issue multiple searches.
2258    ASN1OctetString pagedResultsCookie = null;
2259    long entriesProcessed = 0L;
2260    ResultCode resultCode = ResultCode.SUCCESS;
2261    while (true)
2262    {
2263      // Construct the search request to send.
2264      final LDAPModifySearchListener listener =
2265           new LDAPModifySearchListener(this, modifyChangeRecord, filter,
2266                modifyControls, connectionPool, rateLimiter, rejectWriter,
2267                processedDNs);
2268
2269      final SearchRequest searchRequest =
2270           new SearchRequest(listener, modifyChangeRecord.getDN(),
2271                SearchScope.SUB, filter, SearchRequest.NO_ATTRIBUTES);
2272      searchRequest.setControls(searchControls);
2273      if (searchPageSize.isPresent())
2274      {
2275        searchRequest.addControl(new SimplePagedResultsControl(
2276             searchPageSize.getValue(), pagedResultsCookie));
2277      }
2278
2279
2280      // The connection pool's automatic retry feature can't work for searches
2281      // that return one or more entries before encountering a failure.  To get
2282      // around that, we'll check a connection out of the pool and use it to
2283      // process the search.  If an error occurs that indicates the connection
2284      // is no longer valid, we can replace it with a newly-established
2285      // connection and try again.  The search result listener will ensure that
2286      // no entry gets updated twice.
2287      LDAPConnection connection;
2288      try
2289      {
2290        connection = connectionPool.getConnection();
2291      }
2292      catch (final LDAPException le)
2293      {
2294        Debug.debugException(le);
2295
2296        writeRejectedChange(rejectWriter,
2297             ERR_LDAPMODIFY_CANNOT_GET_SEARCH_CONNECTION.get(
2298                  modifyChangeRecord.getDN(), String.valueOf(filter),
2299                  StaticUtils.getExceptionMessage(le)),
2300             modifyChangeRecord, le.toLDAPResult());
2301        return le.getResultCode();
2302      }
2303
2304      SearchResult searchResult;
2305      boolean connectionValid = false;
2306      try
2307      {
2308        try
2309        {
2310          searchResult = connection.search(searchRequest);
2311        }
2312        catch (final LDAPSearchException lse)
2313        {
2314          searchResult = lse.getSearchResult();
2315        }
2316
2317        if (searchResult.getResultCode() == ResultCode.SUCCESS)
2318        {
2319          connectionValid = true;
2320        }
2321        else if (searchResult.getResultCode().isConnectionUsable())
2322        {
2323          connectionValid = true;
2324          writeRejectedChange(rejectWriter,
2325               ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(),
2326                    String.valueOf(filter)),
2327               modifyChangeRecord, searchResult);
2328          return searchResult.getResultCode();
2329        }
2330        else if (retryFailedOperations.isPresent())
2331        {
2332          try
2333          {
2334            connection = connectionPool.replaceDefunctConnection(connection);
2335          }
2336          catch (final LDAPException le)
2337          {
2338            Debug.debugException(le);
2339            writeRejectedChange(rejectWriter,
2340                 ERR_LDAPMODIFY_SEARCH_FAILED_CANNOT_RECONNECT.get(
2341                      modifyChangeRecord.getDN(), String.valueOf(filter)),
2342                 modifyChangeRecord, searchResult);
2343            return searchResult.getResultCode();
2344          }
2345
2346          try
2347          {
2348            searchResult = connection.search(searchRequest);
2349          }
2350          catch (final LDAPSearchException lse)
2351          {
2352            Debug.debugException(lse);
2353            searchResult = lse.getSearchResult();
2354          }
2355
2356          if (searchResult.getResultCode() == ResultCode.SUCCESS)
2357          {
2358            connectionValid = true;
2359          }
2360          else
2361          {
2362            connectionValid = searchResult.getResultCode().isConnectionUsable();
2363            writeRejectedChange(rejectWriter,
2364                 ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(),
2365                      String.valueOf(filter)),
2366                 modifyChangeRecord, searchResult);
2367            return searchResult.getResultCode();
2368          }
2369        }
2370        else
2371        {
2372          writeRejectedChange(rejectWriter,
2373               ERR_LDAPMODIFY_SEARCH_FAILED.get(modifyChangeRecord.getDN(),
2374                    String.valueOf(filter)),
2375               modifyChangeRecord, searchResult);
2376          return searchResult.getResultCode();
2377        }
2378      }
2379      finally
2380      {
2381        if (connectionValid)
2382        {
2383          connectionPool.releaseConnection(connection);
2384        }
2385        else
2386        {
2387          connectionPool.releaseDefunctConnection(connection);
2388        }
2389      }
2390
2391
2392      // If we've gotten here, then the search was successful.  Check to see if
2393      // any of the modifications failed, and if so then update the result code
2394      // accordingly.
2395      if ((resultCode == ResultCode.SUCCESS) &&
2396          (listener.getResultCode() != ResultCode.SUCCESS))
2397      {
2398        resultCode = listener.getResultCode();
2399      }
2400
2401
2402      // If the search used the simple paged results control then we may need to
2403      // repeat the search to get the next page.
2404      entriesProcessed += searchResult.getEntryCount();
2405      if (searchPageSize.isPresent())
2406      {
2407        final SimplePagedResultsControl responseControl;
2408        try
2409        {
2410          responseControl = SimplePagedResultsControl.get(searchResult);
2411        }
2412        catch (final LDAPException le)
2413        {
2414          Debug.debugException(le);
2415          writeRejectedChange(rejectWriter,
2416               ERR_LDAPMODIFY_CANNOT_DECODE_PAGED_RESULTS_CONTROL.get(
2417                    modifyChangeRecord.getDN(), String.valueOf(filter)),
2418               modifyChangeRecord, le.toLDAPResult());
2419          return le.getResultCode();
2420        }
2421
2422        if (responseControl == null)
2423        {
2424          writeRejectedChange(rejectWriter,
2425               ERR_LDAPMODIFY_MISSING_PAGED_RESULTS_RESPONSE.get(
2426                    modifyChangeRecord.getDN(), String.valueOf(filter)),
2427               modifyChangeRecord);
2428          return ResultCode.CONTROL_NOT_FOUND;
2429        }
2430        else
2431        {
2432          pagedResultsCookie = responseControl.getCookie();
2433          if (responseControl.moreResultsToReturn())
2434          {
2435            if (verbose.isPresent())
2436            {
2437              commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED_MORE_PAGES.get(
2438                   modifyChangeRecord.getDN(), String.valueOf(filter),
2439                   entriesProcessed));
2440              for (final String resultLine :
2441                   ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN))
2442              {
2443                out(resultLine);
2444              }
2445              out();
2446            }
2447          }
2448          else
2449          {
2450            commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get(
2451                 entriesProcessed, modifyChangeRecord.getDN(),
2452                 String.valueOf(filter)));
2453            if (verbose.isPresent())
2454            {
2455              for (final String resultLine :
2456                   ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN))
2457              {
2458                out(resultLine);
2459              }
2460            }
2461
2462            out();
2463            return resultCode;
2464          }
2465        }
2466      }
2467      else
2468      {
2469        commentToOut(INFO_LDAPMODIFY_SEARCH_COMPLETED.get(
2470             entriesProcessed, modifyChangeRecord.getDN(),
2471             String.valueOf(filter)));
2472        if (verbose.isPresent())
2473        {
2474          for (final String resultLine :
2475               ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN))
2476          {
2477            out(resultLine);
2478          }
2479        }
2480
2481        out();
2482        return resultCode;
2483      }
2484    }
2485  }
2486
2487
2488
2489  /**
2490   * Handles the processing for a change record when the tool should modify an
2491   * entry with a given DN instead of the DN contained in the change record.
2492   *
2493   * @param  connectionPool       The connection pool to use to communicate with
2494   *                              the directory server.
2495   * @param  changeRecord         The LDIF change record to be processed.
2496   * @param  argIdentifierString  The identifier string for the argument used to
2497   *                              specify the DN of the entry to modify.
2498   * @param  dn                   The DN of the entry to modify.
2499   * @param  modifyControls       The set of controls to include in the modify
2500   *                              requests.
2501   * @param  rateLimiter          The fixed-rate barrier to use for rate
2502   *                              limiting.  It may be {@code null} if no rate
2503   *                              limiting is required.
2504   * @param  rejectWriter         The reject writer to use to record information
2505   *                              about any failed operations.
2506   *
2507   * @return  A result code obtained from processing.
2508   */
2509  private ResultCode handleModifyWithDN(
2510                          final LDAPConnectionPool connectionPool,
2511                          final LDIFChangeRecord changeRecord,
2512                          final String argIdentifierString, final DN dn,
2513                          final List<Control> modifyControls,
2514                          final FixedRateBarrier rateLimiter,
2515                          final LDIFWriter rejectWriter)
2516  {
2517    // If the provided change record isn't a modify change record, then that's
2518    // an error.  Reject it.
2519    if (! (changeRecord instanceof LDIFModifyChangeRecord))
2520    {
2521      writeRejectedChange(rejectWriter,
2522           ERR_LDAPMODIFY_NON_MODIFY_WITH_BULK.get(argIdentifierString),
2523           changeRecord);
2524      return ResultCode.PARAM_ERROR;
2525    }
2526
2527
2528    // Create a new modify change record with the provided DN instead of the
2529    // original DN.
2530    final LDIFModifyChangeRecord originalChangeRecord =
2531         (LDIFModifyChangeRecord) changeRecord;
2532    final LDIFModifyChangeRecord updatedChangeRecord =
2533         new LDIFModifyChangeRecord(dn.toString(),
2534              originalChangeRecord.getModifications(),
2535              originalChangeRecord.getControls());
2536
2537    if (rateLimiter != null)
2538    {
2539      rateLimiter.await();
2540    }
2541
2542    try
2543    {
2544      return doModify(updatedChangeRecord, modifyControls, connectionPool, null,
2545           rejectWriter);
2546    }
2547    catch (final LDAPException le)
2548    {
2549      Debug.debugException(le);
2550      return le.getResultCode();
2551    }
2552  }
2553
2554
2555
2556  /**
2557   * Populates lists of request controls that should be included in requests
2558   * of various types.
2559   *
2560   * @param  addControls       The list of controls to include in add requests.
2561   * @param  deleteControls    The list of controls to include in delete
2562   *                           requests.
2563   * @param  modifyControls    The list of controls to include in modify
2564   *                           requests.
2565   * @param  modifyDNControls  The list of controls to include in modify DN
2566   *                           requests.
2567   * @param  searchControls    The list of controls to include in search
2568   *                           requests.
2569   *
2570   * @throws  LDAPException  If a problem is encountered while creating any of
2571   *                         the requested controls.
2572   */
2573  private void createRequestControls(final List<Control> addControls,
2574                                     final List<Control> deleteControls,
2575                                     final List<Control> modifyControls,
2576                                     final List<Control> modifyDNControls,
2577                                     final List<Control> searchControls)
2578          throws LDAPException
2579  {
2580    if (addControl.isPresent())
2581    {
2582      addControls.addAll(addControl.getValues());
2583    }
2584
2585    if (deleteControl.isPresent())
2586    {
2587      deleteControls.addAll(deleteControl.getValues());
2588    }
2589
2590    if (modifyControl.isPresent())
2591    {
2592      modifyControls.addAll(modifyControl.getValues());
2593    }
2594
2595    if (modifyDNControl.isPresent())
2596    {
2597      modifyDNControls.addAll(modifyDNControl.getValues());
2598    }
2599
2600    if (operationControl.isPresent())
2601    {
2602      addControls.addAll(operationControl.getValues());
2603      deleteControls.addAll(operationControl.getValues());
2604      modifyControls.addAll(operationControl.getValues());
2605      modifyDNControls.addAll(operationControl.getValues());
2606    }
2607
2608    if (noOperation.isPresent())
2609    {
2610      final NoOpRequestControl c = new NoOpRequestControl();
2611      addControls.add(c);
2612      deleteControls.add(c);
2613      modifyControls.add(c);
2614      modifyDNControls.add(c);
2615    }
2616
2617    if (ignoreNoUserModification.isPresent())
2618    {
2619      addControls.add(new IgnoreNoUserModificationRequestControl());
2620    }
2621
2622    if (nameWithEntryUUID.isPresent())
2623    {
2624      addControls.add(new NameWithEntryUUIDRequestControl(true));
2625    }
2626
2627    if (permissiveModify.isPresent())
2628    {
2629      modifyControls.add(new PermissiveModifyRequestControl(false));
2630    }
2631
2632    if (suppressReferentialIntegrityUpdates.isPresent())
2633    {
2634      final SuppressReferentialIntegrityUpdatesRequestControl c =
2635           new SuppressReferentialIntegrityUpdatesRequestControl(true);
2636      deleteControls.add(c);
2637      modifyDNControls.add(c);
2638    }
2639
2640    if (suppressOperationalAttributeUpdates.isPresent())
2641    {
2642      final EnumSet<SuppressType> suppressTypes =
2643           EnumSet.noneOf(SuppressType.class);
2644      for (final String s : suppressOperationalAttributeUpdates.getValues())
2645      {
2646        if (s.equalsIgnoreCase("last-access-time"))
2647        {
2648          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
2649        }
2650        else if (s.equalsIgnoreCase("last-login-time"))
2651        {
2652          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
2653        }
2654        else if (s.equalsIgnoreCase("last-login-ip"))
2655        {
2656          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
2657        }
2658        else if (s.equalsIgnoreCase("lastmod"))
2659        {
2660          suppressTypes.add(SuppressType.LASTMOD);
2661        }
2662      }
2663
2664      final SuppressOperationalAttributeUpdateRequestControl c =
2665           new SuppressOperationalAttributeUpdateRequestControl(suppressTypes);
2666      addControls.add(c);
2667      deleteControls.add(c);
2668      modifyControls.add(c);
2669      modifyDNControls.add(c);
2670    }
2671
2672    if (usePasswordPolicyControl.isPresent())
2673    {
2674      final PasswordPolicyRequestControl c = new PasswordPolicyRequestControl();
2675      addControls.add(c);
2676      modifyControls.add(c);
2677    }
2678
2679    if (assuredReplication.isPresent())
2680    {
2681      AssuredReplicationLocalLevel localLevel = null;
2682      if (assuredReplicationLocalLevel.isPresent())
2683      {
2684        final String level = assuredReplicationLocalLevel.getValue();
2685        if (level.equalsIgnoreCase("none"))
2686        {
2687          localLevel = AssuredReplicationLocalLevel.NONE;
2688        }
2689        else if (level.equalsIgnoreCase("received-any-server"))
2690        {
2691          localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER;
2692        }
2693        else if (level.equalsIgnoreCase("processed-all-servers"))
2694        {
2695          localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS;
2696        }
2697      }
2698
2699      AssuredReplicationRemoteLevel remoteLevel = null;
2700      if (assuredReplicationRemoteLevel.isPresent())
2701      {
2702        final String level = assuredReplicationRemoteLevel.getValue();
2703        if (level.equalsIgnoreCase("none"))
2704        {
2705          remoteLevel = AssuredReplicationRemoteLevel.NONE;
2706        }
2707        else if (level.equalsIgnoreCase("received-any-remote-location"))
2708        {
2709          remoteLevel =
2710               AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION;
2711        }
2712        else if (level.equalsIgnoreCase("received-all-remote-locations"))
2713        {
2714          remoteLevel =
2715               AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS;
2716        }
2717        else if (level.equalsIgnoreCase("processed-all-remote-servers"))
2718        {
2719          remoteLevel =
2720               AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS;
2721        }
2722      }
2723
2724      Long timeoutMillis = null;
2725      if (assuredReplicationTimeout.isPresent())
2726      {
2727        timeoutMillis =
2728             assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS);
2729      }
2730
2731      final AssuredReplicationRequestControl c =
2732           new AssuredReplicationRequestControl(true, localLevel, localLevel,
2733                remoteLevel, remoteLevel, timeoutMillis, false);
2734      addControls.add(c);
2735      deleteControls.add(c);
2736      modifyControls.add(c);
2737      modifyDNControls.add(c);
2738    }
2739
2740    if (hardDelete.isPresent())
2741    {
2742      deleteControls.add(new HardDeleteRequestControl(true));
2743    }
2744
2745    if (replicationRepair.isPresent())
2746    {
2747      final ReplicationRepairRequestControl c =
2748           new ReplicationRepairRequestControl();
2749      addControls.add(c);
2750      deleteControls.add(c);
2751      modifyControls.add(c);
2752      modifyDNControls.add(c);
2753    }
2754
2755    if (softDelete.isPresent())
2756    {
2757      deleteControls.add(new SoftDeleteRequestControl(true, true));
2758    }
2759
2760    if (subtreeDelete.isPresent())
2761    {
2762      deleteControls.add(new SubtreeDeleteRequestControl());
2763    }
2764
2765    if (assertionFilter.isPresent())
2766    {
2767      final AssertionRequestControl c = new AssertionRequestControl(
2768           assertionFilter.getValue(), true);
2769      addControls.add(c);
2770      deleteControls.add(c);
2771      modifyControls.add(c);
2772      modifyDNControls.add(c);
2773    }
2774
2775    if (operationPurpose.isPresent())
2776    {
2777      final OperationPurposeRequestControl c =
2778           new OperationPurposeRequestControl(false, "ldapmodify",
2779                Version.NUMERIC_VERSION_STRING,
2780                LDAPModify.class.getName() + ".createRequestControls",
2781                operationPurpose.getValue());
2782      addControls.add(c);
2783      deleteControls.add(c);
2784      modifyControls.add(c);
2785      modifyDNControls.add(c);
2786    }
2787
2788    if (manageDsaIT.isPresent())
2789    {
2790      final ManageDsaITRequestControl c = new ManageDsaITRequestControl(true);
2791      addControls.add(c);
2792      deleteControls.add(c);
2793      modifyControls.add(c);
2794      modifyDNControls.add(c);
2795    }
2796
2797    if (passwordUpdateBehavior.isPresent())
2798    {
2799      final PasswordUpdateBehaviorRequestControl c =
2800           createPasswordUpdateBehaviorRequestControl(
2801                passwordUpdateBehavior.getIdentifierString(),
2802                passwordUpdateBehavior.getValues());
2803      addControls.add(c);
2804      modifyControls.add(c);
2805    }
2806
2807    if (preReadAttribute.isPresent())
2808    {
2809      final ArrayList<String> attrList = new ArrayList<String>(10);
2810      for (final String value : preReadAttribute.getValues())
2811      {
2812        final StringTokenizer tokenizer = new StringTokenizer(value, ", ");
2813        while (tokenizer.hasMoreTokens())
2814        {
2815          attrList.add(tokenizer.nextToken());
2816        }
2817      }
2818
2819      final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS);
2820      final PreReadRequestControl c = new PreReadRequestControl(attrArray);
2821      deleteControls.add(c);
2822      modifyControls.add(c);
2823      modifyDNControls.add(c);
2824    }
2825
2826    if (postReadAttribute.isPresent())
2827    {
2828      final ArrayList<String> attrList = new ArrayList<String>(10);
2829      for (final String value : postReadAttribute.getValues())
2830      {
2831        final StringTokenizer tokenizer = new StringTokenizer(value, ", ");
2832        while (tokenizer.hasMoreTokens())
2833        {
2834          attrList.add(tokenizer.nextToken());
2835        }
2836      }
2837
2838      final String[] attrArray = attrList.toArray(StaticUtils.NO_STRINGS);
2839      final PostReadRequestControl c = new PostReadRequestControl(attrArray);
2840      addControls.add(c);
2841      modifyControls.add(c);
2842      modifyDNControls.add(c);
2843    }
2844
2845    if (proxyAs.isPresent() && (! useTransaction.isPresent()) &&
2846        (! multiUpdateErrorBehavior.isPresent()))
2847    {
2848      final ProxiedAuthorizationV2RequestControl c =
2849           new ProxiedAuthorizationV2RequestControl(proxyAs.getValue());
2850      addControls.add(c);
2851      deleteControls.add(c);
2852      modifyControls.add(c);
2853      modifyDNControls.add(c);
2854      searchControls.add(c);
2855    }
2856
2857    if (proxyV1As.isPresent() && (! useTransaction.isPresent()) &&
2858        (! multiUpdateErrorBehavior.isPresent()))
2859    {
2860      final ProxiedAuthorizationV1RequestControl c =
2861           new ProxiedAuthorizationV1RequestControl(proxyV1As.getValue());
2862      addControls.add(c);
2863      deleteControls.add(c);
2864      modifyControls.add(c);
2865      modifyDNControls.add(c);
2866      searchControls.add(c);
2867    }
2868
2869    if (uniquenessAttribute.isPresent() || uniquenessFilter.isPresent())
2870    {
2871      final UniquenessRequestControlProperties uniquenessProperties;
2872      if (uniquenessAttribute.isPresent())
2873      {
2874        uniquenessProperties = new UniquenessRequestControlProperties(
2875             uniquenessAttribute.getValues());
2876        if (uniquenessFilter.isPresent())
2877        {
2878          uniquenessProperties.setFilter(uniquenessFilter.getValue());
2879        }
2880      }
2881      else
2882      {
2883        uniquenessProperties = new UniquenessRequestControlProperties(
2884             uniquenessFilter.getValue());
2885      }
2886
2887      if (uniquenessBaseDN.isPresent())
2888      {
2889        uniquenessProperties.setBaseDN(uniquenessBaseDN.getStringValue());
2890      }
2891
2892      if (uniquenessMultipleAttributeBehavior.isPresent())
2893      {
2894        final String value =
2895             uniquenessMultipleAttributeBehavior.getValue().toLowerCase();
2896        switch (value)
2897        {
2898          case "unique-within-each-attribute":
2899            uniquenessProperties.setMultipleAttributeBehavior(
2900                 UniquenessMultipleAttributeBehavior.
2901                      UNIQUE_WITHIN_EACH_ATTRIBUTE);
2902            break;
2903          case "unique-across-all-attributes-including-in-same-entry":
2904            uniquenessProperties.setMultipleAttributeBehavior(
2905                 UniquenessMultipleAttributeBehavior.
2906                      UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_IN_SAME_ENTRY);
2907            break;
2908          case "unique-across-all-attributes-except-in-same-entry":
2909            uniquenessProperties.setMultipleAttributeBehavior(
2910                 UniquenessMultipleAttributeBehavior.
2911                      UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_IN_SAME_ENTRY);
2912            break;
2913          case "unique-in-combination":
2914            uniquenessProperties.setMultipleAttributeBehavior(
2915                 UniquenessMultipleAttributeBehavior.UNIQUE_IN_COMBINATION);
2916            break;
2917        }
2918      }
2919
2920      if (uniquenessPreCommitValidationLevel.isPresent())
2921      {
2922        final String value =
2923             uniquenessPreCommitValidationLevel.getValue().toLowerCase();
2924        switch (value)
2925        {
2926          case "none":
2927            uniquenessProperties.setPreCommitValidationLevel(
2928                 UniquenessValidationLevel.NONE);
2929            break;
2930          case "all-subtree-views":
2931            uniquenessProperties.setPreCommitValidationLevel(
2932                 UniquenessValidationLevel.ALL_SUBTREE_VIEWS);
2933            break;
2934          case "all-backend-sets":
2935            uniquenessProperties.setPreCommitValidationLevel(
2936                 UniquenessValidationLevel.ALL_BACKEND_SETS);
2937            break;
2938          case "all-available-backend-servers":
2939            uniquenessProperties.setPreCommitValidationLevel(
2940                 UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS);
2941            break;
2942        }
2943      }
2944
2945      if (uniquenessPostCommitValidationLevel.isPresent())
2946      {
2947        final String value =
2948             uniquenessPostCommitValidationLevel.getValue().toLowerCase();
2949        switch (value)
2950        {
2951          case "none":
2952            uniquenessProperties.setPostCommitValidationLevel(
2953                 UniquenessValidationLevel.NONE);
2954            break;
2955          case "all-subtree-views":
2956            uniquenessProperties.setPostCommitValidationLevel(
2957                 UniquenessValidationLevel.ALL_SUBTREE_VIEWS);
2958            break;
2959          case "all-backend-sets":
2960            uniquenessProperties.setPostCommitValidationLevel(
2961                 UniquenessValidationLevel.ALL_BACKEND_SETS);
2962            break;
2963          case "all-available-backend-servers":
2964            uniquenessProperties.setPostCommitValidationLevel(
2965                 UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS);
2966            break;
2967        }
2968      }
2969
2970      final UniquenessRequestControl c =
2971           new UniquenessRequestControl(true, null, uniquenessProperties);
2972      addControls.add(c);
2973      modifyControls.add(c);
2974      modifyDNControls.add(c);
2975    }
2976  }
2977
2978
2979
2980  /**
2981   * Creates the password update behavior request control that should be
2982   * included in add and modify requests.
2983   *
2984   * @param  argIdentifier  The identifier string for the argument used to
2985   *                        configure the password update behavior request
2986   *                        control.
2987   * @param  argValues      The set of values for the password update behavior
2988   *                        request control.
2989   *
2990   * @return  The password update behavior request control that was created.
2991   *
2992   * @throws  LDAPException  If a problem is encountered while creating the
2993   *                         control.
2994   */
2995  static PasswordUpdateBehaviorRequestControl
2996              createPasswordUpdateBehaviorRequestControl(
2997                   final String argIdentifier, final List<String> argValues)
2998       throws LDAPException
2999  {
3000    final PasswordUpdateBehaviorRequestControlProperties properties =
3001         new PasswordUpdateBehaviorRequestControlProperties();
3002
3003    for (final String argValue : argValues)
3004    {
3005      int delimiterPos = argValue.indexOf('=');
3006      if (delimiterPos < 0)
3007      {
3008        delimiterPos = argValue.indexOf(':');
3009      }
3010
3011      if ((delimiterPos <= 0) || (delimiterPos >= (argValue.length() - 1)))
3012      {
3013        throw new LDAPException(ResultCode.PARAM_ERROR,
3014             ERR_LDAPMODIFY_MALFORMED_PW_UPDATE_BEHAVIOR.get(argValue,
3015                  argIdentifier));
3016      }
3017
3018      final String name = argValue.substring(0, delimiterPos).trim();
3019      final String value = argValue.substring(delimiterPos+1).trim();
3020      if (name.equalsIgnoreCase("is-self-change") ||
3021           name.equalsIgnoreCase("self-change") ||
3022           name.equalsIgnoreCase("isSelfChange") ||
3023           name.equalsIgnoreCase("selfChange"))
3024      {
3025        properties.setIsSelfChange(parseBooleanValue(name, value));
3026      }
3027      else if (name.equalsIgnoreCase("allow-pre-encoded-password") ||
3028           name.equalsIgnoreCase("allow-pre-encoded-passwords") ||
3029           name.equalsIgnoreCase("allow-pre-encoded") ||
3030           name.equalsIgnoreCase("allowPreEncodedPassword") ||
3031           name.equalsIgnoreCase("allowPreEncodedPasswords") ||
3032           name.equalsIgnoreCase("allowPreEncoded"))
3033      {
3034        properties.setAllowPreEncodedPassword(parseBooleanValue(name, value));
3035      }
3036      else if (name.equalsIgnoreCase("skip-password-validation") ||
3037           name.equalsIgnoreCase("skip-password-validators") ||
3038           name.equalsIgnoreCase("skip-validation") ||
3039           name.equalsIgnoreCase("skip-validators") ||
3040           name.equalsIgnoreCase("skipPasswordValidation") ||
3041           name.equalsIgnoreCase("skipPasswordValidators") ||
3042           name.equalsIgnoreCase("skipValidation") ||
3043           name.equalsIgnoreCase("skipValidators"))
3044      {
3045        properties.setSkipPasswordValidation(parseBooleanValue(name, value));
3046      }
3047      else if (name.equalsIgnoreCase("ignore-password-history") ||
3048           name.equalsIgnoreCase("skip-password-history") ||
3049           name.equalsIgnoreCase("ignore-history") ||
3050           name.equalsIgnoreCase("skip-history") ||
3051           name.equalsIgnoreCase("ignorePasswordHistory") ||
3052           name.equalsIgnoreCase("skipPasswordHistory") ||
3053           name.equalsIgnoreCase("ignoreHistory") ||
3054           name.equalsIgnoreCase("skipHistory"))
3055      {
3056        properties.setIgnorePasswordHistory(parseBooleanValue(name, value));
3057      }
3058      else if (name.equalsIgnoreCase("ignore-minimum-password-age") ||
3059           name.equalsIgnoreCase("ignore-min-password-age") ||
3060           name.equalsIgnoreCase("ignore-password-age") ||
3061           name.equalsIgnoreCase("skip-minimum-password-age") ||
3062           name.equalsIgnoreCase("skip-min-password-age") ||
3063           name.equalsIgnoreCase("skip-password-age") ||
3064           name.equalsIgnoreCase("ignoreMinimumPasswordAge") ||
3065           name.equalsIgnoreCase("ignoreMinPasswordAge") ||
3066           name.equalsIgnoreCase("ignorePasswordAge") ||
3067           name.equalsIgnoreCase("skipMinimumPasswordAge") ||
3068           name.equalsIgnoreCase("skipMinPasswordAge") ||
3069           name.equalsIgnoreCase("skipPasswordAge"))
3070      {
3071        properties.setIgnoreMinimumPasswordAge(parseBooleanValue(name, value));
3072      }
3073      else if (name.equalsIgnoreCase("password-storage-scheme") ||
3074           name.equalsIgnoreCase("password-scheme") ||
3075           name.equalsIgnoreCase("storage-scheme") ||
3076           name.equalsIgnoreCase("scheme") ||
3077           name.equalsIgnoreCase("passwordStorageScheme") ||
3078           name.equalsIgnoreCase("passwordScheme") ||
3079           name.equalsIgnoreCase("storageScheme"))
3080      {
3081        properties.setPasswordStorageScheme(value);
3082      }
3083      else if (name.equalsIgnoreCase("must-change-password") ||
3084         name.equalsIgnoreCase("mustChangePassword"))
3085      {
3086        properties.setMustChangePassword(parseBooleanValue(name, value));
3087      }
3088    }
3089
3090    return new PasswordUpdateBehaviorRequestControl(properties, true);
3091  }
3092
3093
3094
3095  /**
3096   * Parses the provided value as the Boolean value for a password update
3097   * behavior property.
3098   *
3099   * @param  name   The name of the password update behavior property being
3100   *                parsed.
3101   * @param  value  The value to be parsed.
3102   *
3103   * @return  The Boolean value that was parsed.
3104   *
3105   * @throws  LDAPException  If the provided value cannot be parsed as a
3106   *                         Boolean value.
3107   */
3108  private static boolean parseBooleanValue(final String name,
3109                                           final String value)
3110          throws LDAPException
3111  {
3112    if (value.equalsIgnoreCase("true") ||
3113         value.equalsIgnoreCase("t") ||
3114         value.equalsIgnoreCase("yes") ||
3115         value.equalsIgnoreCase("y") ||
3116         value.equalsIgnoreCase("1"))
3117    {
3118      return true;
3119    }
3120    else if (value.equalsIgnoreCase("false") ||
3121         value.equalsIgnoreCase("f") ||
3122         value.equalsIgnoreCase("no") ||
3123         value.equalsIgnoreCase("n") ||
3124         value.equalsIgnoreCase("0"))
3125    {
3126      return false;
3127    }
3128    else
3129    {
3130      throw new LDAPException(ResultCode.PARAM_ERROR,
3131           ERR_LDAPMODIFY_INVALID_PW_UPDATE_BOOLEAN_VALUE.get(value, name));
3132    }
3133  }
3134
3135
3136
3137  /**
3138   * Performs the appropriate processing for an LDIF add change record.
3139   *
3140   * @param  changeRecord         The LDIF add change record to process.
3141   * @param  controls             The set of controls to include in the request.
3142   * @param  pool                 The connection pool to use to communicate with
3143   *                              the directory server.
3144   * @param  multiUpdateRequests  The list to which the request should be added
3145   *                              if it is to be processed as part of a
3146   *                              multi-update operation.  It may be
3147   *                              {@code null} if the operation should not be
3148   *                              processed via the multi-update operation.
3149   * @param  rejectWriter         The LDIF writer to use for recording
3150   *                              information about rejected changes.  It may be
3151   *                              {@code null} if no reject writer is
3152   *                              configured.
3153   *
3154   * @return  The result code obtained from processing.
3155   *
3156   * @throws  LDAPException  If the operation did not complete successfully
3157   *                         and processing should not continue.
3158   */
3159  private ResultCode doAdd(final LDIFAddChangeRecord changeRecord,
3160                           final List<Control> controls,
3161                           final LDAPConnectionPool pool,
3162                           final List<LDAPRequest> multiUpdateRequests,
3163                           final LDIFWriter rejectWriter)
3164          throws LDAPException
3165  {
3166    // Create the add request to process.
3167    final AddRequest addRequest = changeRecord.toAddRequest(true);
3168    for (final Control c : controls)
3169    {
3170      addRequest.addControl(c);
3171    }
3172
3173
3174    // If we should provide support for undelete operations and the entry
3175    // includes the ds-undelete-from-dn attribute, then add the undelete request
3176    // control.
3177    if (allowUndelete.isPresent() &&
3178        addRequest.hasAttribute(ATTR_UNDELETE_FROM_DN))
3179    {
3180      addRequest.addControl(new UndeleteRequestControl());
3181    }
3182
3183
3184    // If the entry to add includes a password, then add a password validation
3185    // details request control if appropriate.
3186    if (passwordValidationDetails.isPresent())
3187    {
3188      final Entry entryToAdd = addRequest.toEntry();
3189      if ((! entryToAdd.getAttributesWithOptions(ATTR_USER_PASSWORD,
3190                  null).isEmpty()) ||
3191          (! entryToAdd.getAttributesWithOptions(ATTR_AUTH_PASSWORD,
3192                  null).isEmpty()))
3193      {
3194        addRequest.addControl(new PasswordValidationDetailsRequestControl());
3195      }
3196    }
3197
3198
3199    // If the operation should be processed in a multi-update operation, then
3200    // just add the request to the list and return without doing anything else.
3201    if (multiUpdateErrorBehavior.isPresent())
3202    {
3203      multiUpdateRequests.add(addRequest);
3204      commentToOut(INFO_LDAPMODIFY_ADD_ADDED_TO_MULTI_UPDATE.get(
3205           addRequest.getDN()));
3206      return ResultCode.SUCCESS;
3207    }
3208
3209
3210    // If the --dryRun argument was provided, then we'll stop here.
3211    if (dryRun.isPresent())
3212    {
3213      commentToOut(INFO_LDAPMODIFY_DRY_RUN_ADD.get(addRequest.getDN(),
3214           dryRun.getIdentifierString()));
3215      return ResultCode.SUCCESS;
3216    }
3217
3218
3219    // Process the add operation and get the result.
3220    commentToOut(INFO_LDAPMODIFY_ADDING_ENTRY.get(addRequest.getDN()));
3221    if (verbose.isPresent())
3222    {
3223      for (final String ldifLine :
3224           addRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN))
3225      {
3226        out(ldifLine);
3227      }
3228      out();
3229    }
3230
3231    LDAPResult addResult;
3232    try
3233    {
3234      addResult = pool.add(addRequest);
3235    }
3236    catch (final LDAPException le)
3237    {
3238      Debug.debugException(le);
3239      addResult = le.toLDAPResult();
3240    }
3241
3242
3243    // Display information about the result.
3244    displayResult(addResult, useTransaction.isPresent());
3245
3246
3247    // See if the add operation succeeded or failed.  If it failed, and we
3248    // should end all processing, then throw an exception.
3249    switch (addResult.getResultCode().intValue())
3250    {
3251      case ResultCode.SUCCESS_INT_VALUE:
3252      case ResultCode.NO_OPERATION_INT_VALUE:
3253        break;
3254
3255      case ResultCode.ASSERTION_FAILED_INT_VALUE:
3256        writeRejectedChange(rejectWriter,
3257             INFO_LDAPMODIFY_ASSERTION_FAILED.get(addRequest.getDN(),
3258                  String.valueOf(assertionFilter.getValue())),
3259             addRequest.toLDIFChangeRecord(), addResult);
3260        throw new LDAPException(addResult);
3261
3262      default:
3263        writeRejectedChange(rejectWriter, null, addRequest.toLDIFChangeRecord(),
3264             addResult);
3265        if (useTransaction.isPresent() || (! continueOnError.isPresent()))
3266        {
3267          throw new LDAPException(addResult);
3268        }
3269        break;
3270    }
3271
3272    return addResult.getResultCode();
3273  }
3274
3275
3276
3277  /**
3278   * Performs the appropriate processing for an LDIF delete change record.
3279   *
3280   * @param  changeRecord         The LDIF delete change record to process.
3281   * @param  controls             The set of controls to include in the request.
3282   * @param  pool                 The connection pool to use to communicate with
3283   *                              the directory server.
3284   * @param  multiUpdateRequests  The list to which the request should be added
3285   *                              if it is to be processed as part of a
3286   *                              multi-update operation.  It may be
3287   *                              {@code null} if the operation should not be
3288   *                              processed via the multi-update operation.
3289   * @param  rejectWriter         The LDIF writer to use for recording
3290   *                              information about rejected changes.  It may be
3291   *                              {@code null} if no reject writer is
3292   *                              configured.
3293   *
3294   * @return  The result code obtained from processing.
3295   *
3296   * @throws  LDAPException  If the operation did not complete successfully
3297   *                         and processing should not continue.
3298   */
3299  private ResultCode doDelete(final LDIFDeleteChangeRecord changeRecord,
3300                              final List<Control> controls,
3301                              final LDAPConnectionPool pool,
3302                              final List<LDAPRequest> multiUpdateRequests,
3303                              final LDIFWriter rejectWriter)
3304          throws LDAPException
3305  {
3306    // Create the delete request to process.
3307    final DeleteRequest deleteRequest = changeRecord.toDeleteRequest(true);
3308    for (final Control c : controls)
3309    {
3310      deleteRequest.addControl(c);
3311    }
3312
3313
3314    // If the operation should be processed in a multi-update operation, then
3315    // just add the request to the list and return without doing anything else.
3316    if (multiUpdateErrorBehavior.isPresent())
3317    {
3318      multiUpdateRequests.add(deleteRequest);
3319      commentToOut(INFO_LDAPMODIFY_DELETE_ADDED_TO_MULTI_UPDATE.get(
3320           deleteRequest.getDN()));
3321      return ResultCode.SUCCESS;
3322    }
3323
3324
3325    // If the --dryRun argument was provided, then we'll stop here.
3326    if (dryRun.isPresent())
3327    {
3328      commentToOut(INFO_LDAPMODIFY_DRY_RUN_DELETE.get(deleteRequest.getDN(),
3329           dryRun.getIdentifierString()));
3330      return ResultCode.SUCCESS;
3331    }
3332
3333
3334    // Process the delete operation and get the result.
3335    commentToOut(INFO_LDAPMODIFY_DELETING_ENTRY.get(deleteRequest.getDN()));
3336    if (verbose.isPresent())
3337    {
3338      for (final String ldifLine :
3339           deleteRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN))
3340      {
3341        out(ldifLine);
3342      }
3343      out();
3344    }
3345
3346
3347    LDAPResult deleteResult;
3348    try
3349    {
3350      deleteResult = pool.delete(deleteRequest);
3351    }
3352    catch (final LDAPException le)
3353    {
3354      Debug.debugException(le);
3355      deleteResult = le.toLDAPResult();
3356    }
3357
3358
3359    // Display information about the result.
3360    displayResult(deleteResult, useTransaction.isPresent());
3361
3362
3363    // See if the delete operation succeeded or failed.  If it failed, and we
3364    // should end all processing, then throw an exception.
3365    switch (deleteResult.getResultCode().intValue())
3366    {
3367      case ResultCode.SUCCESS_INT_VALUE:
3368      case ResultCode.NO_OPERATION_INT_VALUE:
3369        break;
3370
3371      case ResultCode.ASSERTION_FAILED_INT_VALUE:
3372        writeRejectedChange(rejectWriter,
3373             INFO_LDAPMODIFY_ASSERTION_FAILED.get(deleteRequest.getDN(),
3374                  String.valueOf(assertionFilter.getValue())),
3375             deleteRequest.toLDIFChangeRecord(), deleteResult);
3376        throw new LDAPException(deleteResult);
3377
3378      default:
3379        writeRejectedChange(rejectWriter, null,
3380             deleteRequest.toLDIFChangeRecord(), deleteResult);
3381        if (useTransaction.isPresent() || (! continueOnError.isPresent()))
3382        {
3383          throw new LDAPException(deleteResult);
3384        }
3385        break;
3386    }
3387
3388    return deleteResult.getResultCode();
3389  }
3390
3391
3392
3393  /**
3394   * Performs the appropriate processing for an LDIF modify change record.
3395   *
3396   * @param  changeRecord         The LDIF modify change record to process.
3397   * @param  controls             The set of controls to include in the request.
3398   * @param  pool                 The connection pool to use to communicate with
3399   *                              the directory server.
3400   * @param  multiUpdateRequests  The list to which the request should be added
3401   *                              if it is to be processed as part of a
3402   *                              multi-update operation.  It may be
3403   *                              {@code null} if the operation should not be
3404   *                              processed via the multi-update operation.
3405   * @param  rejectWriter         The LDIF writer to use for recording
3406   *                              information about rejected changes.  It may be
3407   *                              {@code null} if no reject writer is
3408   *                              configured.
3409   *
3410   * @return  The result code obtained from processing.
3411   *
3412   * @throws  LDAPException  If the operation did not complete successfully
3413   *                         and processing should not continue.
3414   */
3415  ResultCode doModify(final LDIFModifyChangeRecord changeRecord,
3416                      final List<Control> controls,
3417                      final LDAPConnectionPool pool,
3418                      final List<LDAPRequest> multiUpdateRequests,
3419                      final LDIFWriter rejectWriter)
3420             throws LDAPException
3421  {
3422    // Create the modify request to process.
3423    final ModifyRequest modifyRequest = changeRecord.toModifyRequest(true);
3424    for (final Control c : controls)
3425    {
3426      modifyRequest.addControl(c);
3427    }
3428
3429
3430    // If the modify request includes a password change, then add any controls
3431    // that are specific to that.
3432    if (retireCurrentPassword.isPresent() || purgeCurrentPassword.isPresent() ||
3433        passwordValidationDetails.isPresent())
3434    {
3435      for (final Modification m : modifyRequest.getModifications())
3436      {
3437        final String baseName = m.getAttribute().getBaseName();
3438        if (baseName.equalsIgnoreCase(ATTR_USER_PASSWORD) ||
3439            baseName.equalsIgnoreCase(ATTR_AUTH_PASSWORD))
3440        {
3441          if (retireCurrentPassword.isPresent())
3442          {
3443            modifyRequest.addControl(new RetirePasswordRequestControl(false));
3444          }
3445          else if (purgeCurrentPassword.isPresent())
3446          {
3447            modifyRequest.addControl(new PurgePasswordRequestControl(false));
3448          }
3449
3450          if (passwordValidationDetails.isPresent())
3451          {
3452            modifyRequest.addControl(
3453                 new PasswordValidationDetailsRequestControl());
3454          }
3455
3456          break;
3457        }
3458      }
3459    }
3460
3461
3462    // If the operation should be processed in a multi-update operation, then
3463    // just add the request to the list and return without doing anything else.
3464    if (multiUpdateErrorBehavior.isPresent())
3465    {
3466      multiUpdateRequests.add(modifyRequest);
3467      commentToOut(INFO_LDAPMODIFY_MODIFY_ADDED_TO_MULTI_UPDATE.get(
3468           modifyRequest.getDN()));
3469      return ResultCode.SUCCESS;
3470    }
3471
3472
3473    // If the --dryRun argument was provided, then we'll stop here.
3474    if (dryRun.isPresent())
3475    {
3476      commentToOut(INFO_LDAPMODIFY_DRY_RUN_MODIFY.get(modifyRequest.getDN(),
3477           dryRun.getIdentifierString()));
3478      return ResultCode.SUCCESS;
3479    }
3480
3481
3482    // Process the modify operation and get the result.
3483    commentToOut(INFO_LDAPMODIFY_MODIFYING_ENTRY.get(modifyRequest.getDN()));
3484    if (verbose.isPresent())
3485    {
3486      for (final String ldifLine :
3487           modifyRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN))
3488      {
3489        out(ldifLine);
3490      }
3491      out();
3492    }
3493
3494
3495    LDAPResult modifyResult;
3496    try
3497    {
3498      modifyResult = pool.modify(modifyRequest);
3499    }
3500    catch (final LDAPException le)
3501    {
3502      Debug.debugException(le);
3503      modifyResult = le.toLDAPResult();
3504    }
3505
3506
3507    // Display information about the result.
3508    displayResult(modifyResult, useTransaction.isPresent());
3509
3510
3511    // See if the modify operation succeeded or failed.  If it failed, and we
3512    // should end all processing, then throw an exception.
3513    switch (modifyResult.getResultCode().intValue())
3514    {
3515      case ResultCode.SUCCESS_INT_VALUE:
3516      case ResultCode.NO_OPERATION_INT_VALUE:
3517        break;
3518
3519      case ResultCode.ASSERTION_FAILED_INT_VALUE:
3520        writeRejectedChange(rejectWriter,
3521             INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyRequest.getDN(),
3522                  String.valueOf(assertionFilter.getValue())),
3523             modifyRequest.toLDIFChangeRecord(), modifyResult);
3524        throw new LDAPException(modifyResult);
3525
3526      default:
3527        writeRejectedChange(rejectWriter, null,
3528             modifyRequest.toLDIFChangeRecord(), modifyResult);
3529        if (useTransaction.isPresent() || (! continueOnError.isPresent()))
3530        {
3531          throw new LDAPException(modifyResult);
3532        }
3533        break;
3534    }
3535
3536    return modifyResult.getResultCode();
3537  }
3538
3539
3540
3541  /**
3542   * Performs the appropriate processing for an LDIF modify DN change record.
3543   *
3544   * @param  changeRecord         The LDIF modify DN change record to process.
3545   * @param  controls             The set of controls to include in the request.
3546   * @param  pool                 The connection pool to use to communicate with
3547   *                              the directory server.
3548   * @param  multiUpdateRequests  The list to which the request should be added
3549   *                              if it is to be processed as part of a
3550   *                              multi-update operation.  It may be
3551   *                              {@code null} if the operation should not be
3552   *                              processed via the multi-update operation.
3553   * @param  rejectWriter         The LDIF writer to use for recording
3554   *                              information about rejected changes.  It may be
3555   *                              {@code null} if no reject writer is
3556   *                              configured.
3557   *
3558   * @return  The result code obtained from processing.
3559   *
3560   * @throws  LDAPException  If the operation did not complete successfully
3561   *                         and processing should not continue.
3562   */
3563  private ResultCode doModifyDN(final LDIFModifyDNChangeRecord changeRecord,
3564                                final List<Control> controls,
3565                                final LDAPConnectionPool pool,
3566                                final List<LDAPRequest> multiUpdateRequests,
3567                                final LDIFWriter rejectWriter)
3568          throws LDAPException
3569  {
3570    // Create the modify DN request to process.
3571    final ModifyDNRequest modifyDNRequest =
3572         changeRecord.toModifyDNRequest(true);
3573    for (final Control c : controls)
3574    {
3575      modifyDNRequest.addControl(c);
3576    }
3577
3578
3579    // If the operation should be processed in a multi-update operation, then
3580    // just add the request to the list and return without doing anything else.
3581    if (multiUpdateErrorBehavior.isPresent())
3582    {
3583      multiUpdateRequests.add(modifyDNRequest);
3584      commentToOut(INFO_LDAPMODIFY_MODIFY_DN_ADDED_TO_MULTI_UPDATE.get(
3585           modifyDNRequest.getDN()));
3586      return ResultCode.SUCCESS;
3587    }
3588
3589
3590    // Try to determine the new DN that the entry will have after the operation.
3591    DN newDN = null;
3592    try
3593    {
3594      newDN = changeRecord.getNewDN();
3595    }
3596    catch (final Exception e)
3597    {
3598      Debug.debugException(e);
3599
3600      // This should only happen if the provided DN, new RDN, or new superior DN
3601      // was malformed.  Although we could reject the operation now, we'll go
3602      // ahead and send the request to the server in case it has some special
3603      // handling for the DN.
3604    }
3605
3606
3607    // If the --dryRun argument was provided, then we'll stop here.
3608    if (dryRun.isPresent())
3609    {
3610      if (modifyDNRequest.getNewSuperiorDN() == null)
3611      {
3612        if (newDN == null)
3613        {
3614          commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME.get(
3615               modifyDNRequest.getDN(), dryRun.getIdentifierString()));
3616        }
3617        else
3618        {
3619          commentToOut(INFO_LDAPMODIFY_DRY_RUN_RENAME_TO.get(
3620               modifyDNRequest.getDN(), newDN.toString(),
3621               dryRun.getIdentifierString()));
3622        }
3623      }
3624      else
3625      {
3626        if (newDN == null)
3627        {
3628          commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE.get(
3629               modifyDNRequest.getDN(), dryRun.getIdentifierString()));
3630        }
3631        else
3632        {
3633          commentToOut(INFO_LDAPMODIFY_DRY_RUN_MOVE_TO.get(
3634               modifyDNRequest.getDN(), newDN.toString(),
3635               dryRun.getIdentifierString()));
3636        }
3637      }
3638      return ResultCode.SUCCESS;
3639    }
3640
3641
3642    // Process the modify DN operation and get the result.
3643    final String currentDN = modifyDNRequest.getDN();
3644    if (modifyDNRequest.getNewSuperiorDN() == null)
3645    {
3646      if (newDN == null)
3647      {
3648        commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY.get(currentDN));
3649      }
3650      else
3651      {
3652        commentToOut(INFO_LDAPMODIFY_MOVING_ENTRY_TO.get(currentDN,
3653             newDN.toString()));
3654      }
3655    }
3656    else
3657    {
3658      if (newDN == null)
3659      {
3660        commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY.get(currentDN));
3661      }
3662      else
3663      {
3664        commentToOut(INFO_LDAPMODIFY_RENAMING_ENTRY_TO.get(currentDN,
3665             newDN.toString()));
3666      }
3667    }
3668
3669    if (verbose.isPresent())
3670    {
3671      for (final String ldifLine :
3672           modifyDNRequest.toLDIFChangeRecord().toLDIF(WRAP_COLUMN))
3673      {
3674        out(ldifLine);
3675      }
3676      out();
3677    }
3678
3679
3680    LDAPResult modifyDNResult;
3681    try
3682    {
3683      modifyDNResult = pool.modifyDN(modifyDNRequest);
3684    }
3685    catch (final LDAPException le)
3686    {
3687      Debug.debugException(le);
3688      modifyDNResult = le.toLDAPResult();
3689    }
3690
3691
3692    // Display information about the result.
3693    displayResult(modifyDNResult, useTransaction.isPresent());
3694
3695
3696    // See if the modify DN operation succeeded or failed.  If it failed, and we
3697    // should end all processing, then throw an exception.
3698    switch (modifyDNResult.getResultCode().intValue())
3699    {
3700      case ResultCode.SUCCESS_INT_VALUE:
3701      case ResultCode.NO_OPERATION_INT_VALUE:
3702        break;
3703
3704      case ResultCode.ASSERTION_FAILED_INT_VALUE:
3705        writeRejectedChange(rejectWriter,
3706             INFO_LDAPMODIFY_ASSERTION_FAILED.get(modifyDNRequest.getDN(),
3707                  String.valueOf(assertionFilter.getValue())),
3708             modifyDNRequest.toLDIFChangeRecord(), modifyDNResult);
3709        throw new LDAPException(modifyDNResult);
3710
3711      default:
3712        writeRejectedChange(rejectWriter, null,
3713             modifyDNRequest.toLDIFChangeRecord(), modifyDNResult);
3714        if (useTransaction.isPresent() || (! continueOnError.isPresent()))
3715        {
3716          throw new LDAPException(modifyDNResult);
3717        }
3718        break;
3719    }
3720
3721    return modifyDNResult.getResultCode();
3722  }
3723
3724
3725
3726  /**
3727   * Displays information about the provided result, including special
3728   * processing for a number of supported response controls.
3729   *
3730   * @param  result         The result to examine.
3731   * @param  inTransaction  Indicates whether the operation is part of a
3732   *                        transaction.
3733   */
3734  void displayResult(final LDAPResult result, final boolean inTransaction)
3735  {
3736    final ArrayList<String> resultLines = new ArrayList<String>(10);
3737    ResultUtils.formatResult(resultLines, result, true, inTransaction, 0,
3738         WRAP_COLUMN);
3739
3740    if (result.getResultCode() == ResultCode.SUCCESS)
3741    {
3742      for (final String line : resultLines)
3743      {
3744        out(line);
3745      }
3746      out();
3747    }
3748    else
3749    {
3750      for (final String line : resultLines)
3751      {
3752        err(line);
3753      }
3754      err();
3755    }
3756  }
3757
3758
3759
3760  /**
3761   * Writes a line-wrapped, commented version of the provided message to
3762   * standard output.
3763   *
3764   * @param  message  The message to be written.
3765   */
3766  private void commentToOut(final String message)
3767  {
3768    for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2))
3769    {
3770      out("# ", line);
3771    }
3772  }
3773
3774
3775
3776  /**
3777   * Writes a line-wrapped, commented version of the provided message to
3778   * standard error.
3779   *
3780   * @param  message  The message to be written.
3781   */
3782  private void commentToErr(final String message)
3783  {
3784    for (final String line : StaticUtils.wrapLine(message, WRAP_COLUMN - 2))
3785    {
3786      err("# ", line);
3787    }
3788  }
3789
3790
3791
3792  /**
3793   * Writes information about the rejected change to the reject writer.
3794   *
3795   * @param  writer        The LDIF writer to which the information should be
3796   *                       written.  It may be {@code null} if no reject file is
3797   *                       configured.
3798   * @param  comment       The comment to include before the change record, in
3799   *                       addition to the comment generated from the provided
3800   *                       LDAP result.  It may be {@code null} if no additional
3801   *                       comment should be included.
3802   * @param  changeRecord  The LDIF change record to be written.  It must not
3803   *                       be {@code null}.
3804   * @param  ldapResult    The LDAP result for the failed operation.  It must
3805   *                       not be {@code null}.
3806   */
3807  private void writeRejectedChange(final LDIFWriter writer,
3808                                   final String comment,
3809                                   final LDIFChangeRecord changeRecord,
3810                                   final LDAPResult ldapResult)
3811  {
3812    if (writer == null)
3813    {
3814      return;
3815    }
3816
3817
3818    final StringBuilder buffer = new StringBuilder();
3819    if (comment != null)
3820    {
3821      buffer.append(comment);
3822      buffer.append(StaticUtils.EOL);
3823      buffer.append(StaticUtils.EOL);
3824    }
3825
3826    final ArrayList<String> resultLines = new ArrayList<String>(10);
3827    ResultUtils.formatResult(resultLines, ldapResult, false, false, 0, 0);
3828    for (final String resultLine : resultLines)
3829    {
3830      buffer.append(resultLine);
3831      buffer.append(StaticUtils.EOL);
3832    }
3833
3834    writeRejectedChange(writer, buffer.toString(), changeRecord);
3835  }
3836
3837
3838
3839  /**
3840   * Writes information about the rejected change to the reject writer.
3841   *
3842   * @param  writer        The LDIF writer to which the information should be
3843   *                       written.  It may be {@code null} if no reject file is
3844   *                       configured.
3845   * @param  comment       The comment to include before the change record.  It
3846   *                       may be {@code null} if no comment should be included.
3847   * @param  changeRecord  The LDIF change record to be written.  It may be
3848   *                       {@code null} if only a comment should be written.
3849   */
3850  void writeRejectedChange(final LDIFWriter writer, final String comment,
3851                           final LDIFChangeRecord changeRecord)
3852  {
3853    if (writer == null)
3854    {
3855      return;
3856    }
3857
3858    if (rejectWritten.compareAndSet(false, true))
3859    {
3860      try
3861      {
3862        writer.writeVersionHeader();
3863      }
3864      catch (final Exception e)
3865      {
3866        Debug.debugException(e);
3867      }
3868    }
3869
3870    try
3871    {
3872      if (comment != null)
3873      {
3874        writer.writeComment(comment, true, false);
3875      }
3876
3877      if (changeRecord != null)
3878      {
3879        writer.writeChangeRecord(changeRecord);
3880      }
3881    }
3882    catch (final Exception e)
3883    {
3884      Debug.debugException(e);
3885
3886      commentToErr(ERR_LDAPMODIFY_UNABLE_TO_WRITE_REJECTED_CHANGE.get(
3887           rejectFile.getValue().getAbsolutePath(),
3888           StaticUtils.getExceptionMessage(e)));
3889    }
3890  }
3891
3892
3893
3894  /**
3895   * {@inheritDoc}
3896   */
3897  @Override()
3898  public void handleUnsolicitedNotification(final LDAPConnection connection,
3899                                            final ExtendedResult notification)
3900  {
3901    final ArrayList<String> lines = new ArrayList<String>(10);
3902    ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0,
3903         WRAP_COLUMN);
3904    for (final String line : lines)
3905    {
3906      err(line);
3907    }
3908    err();
3909  }
3910
3911
3912
3913  /**
3914   * {@inheritDoc}
3915   */
3916  @Override()
3917  public LinkedHashMap<String[],String> getExampleUsages()
3918  {
3919    final LinkedHashMap<String[],String> examples =
3920         new LinkedHashMap<String[],String>(2);
3921
3922    final String[] args1 =
3923    {
3924      "--hostname", "ldap.example.com",
3925      "--port", "389",
3926      "--bindDN", "uid=admin,dc=example,dc=com",
3927      "--bindPassword", "password",
3928      "--defaultAdd"
3929    };
3930    examples.put(args1, INFO_LDAPMODIFY_EXAMPLE_1.get());
3931
3932    final String[] args2 =
3933    {
3934      "--hostname", "ds1.example.com",
3935      "--port", "636",
3936      "--hostname", "ds2.example.com",
3937      "--port", "636",
3938      "--useSSL",
3939      "--bindDN", "uid=admin,dc=example,dc=com",
3940      "--bindPassword", "password",
3941      "--filename", "changes.ldif",
3942      "--modifyEntriesMatchingFilter", "(objectClass=person)",
3943      "--searchPageSize", "100"
3944    };
3945    examples.put(args2, INFO_LDAPMODIFY_EXAMPLE_2.get());
3946
3947    return examples;
3948  }
3949}