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