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