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