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