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