001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2007-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 * Portions Copyright 2012 profiq s.r.o.
017 */
018package org.opends.server.tools.dsreplication;
019
020import java.io.BufferedWriter;
021import java.io.File;
022import java.io.FileWriter;
023import java.io.IOException;
024import java.io.OutputStream;
025import java.io.PrintStream;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.Comparator;
030import java.util.Date;
031import java.util.HashMap;
032import java.util.HashSet;
033import java.util.LinkedHashMap;
034import java.util.LinkedHashSet;
035import java.util.LinkedList;
036import java.util.List;
037import java.util.Map;
038import java.util.Objects;
039import java.util.Set;
040import java.util.SortedSet;
041import java.util.TreeMap;
042import java.util.TreeSet;
043import java.util.concurrent.atomic.AtomicReference;
044
045import javax.naming.NameAlreadyBoundException;
046import javax.naming.NameNotFoundException;
047import javax.naming.NamingEnumeration;
048import javax.naming.NamingException;
049import javax.naming.NoPermissionException;
050import javax.naming.directory.BasicAttributes;
051import javax.naming.directory.DirContext;
052import javax.naming.directory.SearchControls;
053import javax.naming.directory.SearchResult;
054import javax.naming.ldap.InitialLdapContext;
055import javax.naming.ldap.LdapName;
056import javax.net.ssl.KeyManager;
057import javax.net.ssl.SSLException;
058import javax.net.ssl.SSLHandshakeException;
059import javax.net.ssl.TrustManager;
060
061import org.forgerock.i18n.LocalizableMessage;
062import org.forgerock.i18n.LocalizableMessageBuilder;
063import org.forgerock.i18n.LocalizedIllegalArgumentException;
064import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0;
065import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
066import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2;
067import org.forgerock.i18n.slf4j.LocalizedLogger;
068import org.opends.admin.ads.*;
069import org.opends.admin.ads.ADSContext.ADSPropertySyntax;
070import org.opends.admin.ads.ADSContext.AdministratorProperty;
071import org.opends.admin.ads.ADSContext.ServerProperty;
072import org.opends.admin.ads.util.ApplicationTrustManager;
073import org.opends.admin.ads.util.OpendsCertificateException;
074import org.opends.admin.ads.util.PreferredConnection;
075import org.opends.admin.ads.util.ServerLoader;
076import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
077import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
078import org.opends.guitools.controlpanel.util.*;
079import org.opends.quicksetup.ApplicationException;
080import org.opends.quicksetup.Constants;
081import org.opends.quicksetup.Installation;
082import org.opends.quicksetup.event.ProgressUpdateEvent;
083import org.opends.quicksetup.event.ProgressUpdateListener;
084import org.opends.quicksetup.installer.Installer;
085import org.opends.quicksetup.installer.InstallerHelper;
086import org.opends.quicksetup.installer.PeerNotFoundException;
087import org.opends.quicksetup.installer.offline.OfflineInstaller;
088import org.opends.quicksetup.util.PlainTextProgressMessageFormatter;
089import org.opends.server.admin.*;
090import org.opends.server.admin.client.ManagementContext;
091import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
092import org.opends.server.admin.client.ldap.LDAPManagementContext;
093import org.opends.server.admin.std.client.*;
094import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn;
095import org.opends.server.admin.std.meta.ReplicationServerCfgDefn;
096import org.opends.server.admin.std.meta.ReplicationSynchronizationProviderCfgDefn;
097import org.opends.server.core.DirectoryServer;
098import org.opends.server.tasks.PurgeConflictsHistoricalTask;
099import org.opends.server.tools.dsreplication.EnableReplicationUserData.EnableReplicationServerData;
100import org.opends.server.tools.tasks.TaskEntry;
101import org.opends.server.tools.tasks.TaskScheduleInteraction;
102import org.opends.server.tools.tasks.TaskScheduleUserData;
103import org.forgerock.opendj.ldap.DN;
104import org.opends.server.types.HostPort;
105import org.opends.server.types.InitializationException;
106import org.opends.server.types.NullOutputStream;
107import org.opends.server.types.OpenDsException;
108import org.opends.server.util.BuildVersion;
109import org.opends.server.util.ServerConstants;
110import org.opends.server.util.SetupUtils;
111import org.opends.server.util.StaticUtils;
112import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
113import org.opends.server.util.cli.PointAdder;
114
115import com.forgerock.opendj.cli.Argument;
116import com.forgerock.opendj.cli.ArgumentException;
117import com.forgerock.opendj.cli.BooleanArgument;
118import com.forgerock.opendj.cli.CliConstants;
119import com.forgerock.opendj.cli.ClientException;
120import com.forgerock.opendj.cli.CommandBuilder;
121import com.forgerock.opendj.cli.ConsoleApplication;
122import com.forgerock.opendj.cli.FileBasedArgument;
123import com.forgerock.opendj.cli.IntegerArgument;
124import com.forgerock.opendj.cli.MenuBuilder;
125import com.forgerock.opendj.cli.MenuResult;
126import com.forgerock.opendj.cli.ReturnCode;
127import com.forgerock.opendj.cli.StringArgument;
128import com.forgerock.opendj.cli.SubCommand;
129import com.forgerock.opendj.cli.TabSeparatedTablePrinter;
130import com.forgerock.opendj.cli.TableBuilder;
131import com.forgerock.opendj.cli.TablePrinter;
132import com.forgerock.opendj.cli.TextTablePrinter;
133import com.forgerock.opendj.cli.ValidationCallback;
134
135import static com.forgerock.opendj.cli.ArgumentConstants.*;
136import static com.forgerock.opendj.cli.Utils.*;
137import static com.forgerock.opendj.util.OperatingSystem.*;
138import static com.forgerock.opendj.cli.CommonArguments.*;
139
140import static java.util.Collections.*;
141import static org.forgerock.util.Utils.*;
142import static org.opends.admin.ads.util.ConnectionUtils.*;
143import static org.opends.admin.ads.util.PreferredConnection.*;
144import static org.opends.admin.ads.ServerDescriptor.getReplicationServer;
145import static org.opends.admin.ads.ServerDescriptor.getServerRepresentation;
146import static org.opends.admin.ads.ServerDescriptor.getSuffixDisplay;
147import static org.opends.messages.AdminToolMessages.*;
148import static org.opends.messages.QuickSetupMessages.*;
149import static org.opends.messages.ToolMessages.*;
150import static org.opends.quicksetup.util.Utils.*;
151import static org.opends.server.tools.dsreplication.ReplicationCliArgumentParser.*;
152import static org.opends.server.tools.dsreplication.ReplicationCliReturnCode.*;
153import static org.opends.server.util.StaticUtils.*;
154
155/**
156 * This class provides a tool that can be used to enable and disable replication
157 * and also to initialize the contents of a replicated suffix with the contents
158 * of another suffix.  It also allows to display the replicated status of the
159 * different base DNs of the servers that are registered in the ADS.
160 */
161public class ReplicationCliMain extends ConsoleApplication
162{
163  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
164  /** The fully-qualified name of this class. */
165  private static final String CLASS_NAME = ReplicationCliMain.class.getName();
166  /** Prefix for log files. */
167  public static final String LOG_FILE_PREFIX = "opendj-replication-";
168  /** Suffix for log files. */
169  public static final String LOG_FILE_SUFFIX = ".log";
170
171  /**
172   * Property used to call the dsreplication script and ReplicationCliMain to
173   * know which are the java properties to be used (those of dsreplication or
174   * those of dsreplication.offline).
175   */
176  private static final String SCRIPT_CALL_STATUS = "org.opends.server.dsreplicationcallstatus";
177
178  /** The value set by the dsreplication script if it is called the first time. */
179  private static final String FIRST_SCRIPT_CALL = "firstcall";
180  private static final LocalizableMessage EMPTY_MSG = LocalizableMessage.raw("");
181
182  private boolean forceNonInteractive;
183
184  /** Always use SSL with the administration connector. */
185  private final boolean useSSL = true;
186  private final boolean useStartTLS = false;
187
188  /**
189   * The enumeration containing the different options we display when we ask
190   * the user to provide the subcommand interactively.
191   */
192  private enum SubcommandChoice
193  {
194    /** Enable replication. */
195    ENABLE(ENABLE_REPLICATION_SUBCMD_NAME, INFO_REPLICATION_ENABLE_MENU_PROMPT.get()),
196    /** Disable replication. */
197    DISABLE(DISABLE_REPLICATION_SUBCMD_NAME, INFO_REPLICATION_DISABLE_MENU_PROMPT.get()),
198    /** Initialize replication. */
199    INITIALIZE(INITIALIZE_REPLICATION_SUBCMD_NAME, INFO_REPLICATION_INITIALIZE_MENU_PROMPT.get()),
200    /** Initialize All. */
201    INITIALIZE_ALL(INITIALIZE_ALL_REPLICATION_SUBCMD_NAME, INFO_REPLICATION_INITIALIZE_ALL_MENU_PROMPT.get()),
202    /** Pre external initialization. */
203    PRE_EXTERNAL_INITIALIZATION(PRE_EXTERNAL_INITIALIZATION_SUBCMD_NAME,
204        INFO_REPLICATION_PRE_EXTERNAL_INITIALIZATION_MENU_PROMPT.get()),
205    /** Post external initialization. */
206    POST_EXTERNAL_INITIALIZATION(POST_EXTERNAL_INITIALIZATION_SUBCMD_NAME,
207        INFO_REPLICATION_POST_EXTERNAL_INITIALIZATION_MENU_PROMPT.get()),
208    /** Replication status. */
209    STATUS(STATUS_REPLICATION_SUBCMD_NAME, INFO_REPLICATION_STATUS_MENU_PROMPT.get()),
210    /** Replication purge historical. */
211    PURGE_HISTORICAL(PURGE_HISTORICAL_SUBCMD_NAME, INFO_REPLICATION_PURGE_HISTORICAL_MENU_PROMPT.get()),
212    /** Set changelog change number from another server. */
213    RESET_CHANGE_NUMBER(ReplicationCliArgumentParser.RESET_CHANGE_NUMBER_SUBCMD_NAME,
214        INFO_DESCRIPTION_RESET_CHANGE_NUMBER.get()),
215    /** Cancel operation. */
216    CANCEL(null, null);
217
218    private final String name;
219    private LocalizableMessage prompt;
220
221    private SubcommandChoice(String name, LocalizableMessage prompt)
222    {
223      this.name = name;
224      this.prompt = prompt;
225    }
226
227    private LocalizableMessage getPrompt()
228    {
229      return prompt;
230    }
231
232    private String getName()
233    {
234      return name;
235    }
236
237    private static SubcommandChoice fromName(String subCommandName)
238    {
239      SubcommandChoice[] f = values();
240      for (SubcommandChoice subCommand : f)
241      {
242        if (subCommand.name.equals(subCommandName))
243        {
244          return subCommand;
245        }
246      }
247      return null;
248    }
249  }
250
251  /** Abstract some of the operations when two servers must be queried for information. */
252  private interface OperationBetweenSourceAndDestinationServers
253  {
254    /**
255     * Returns whether we should stop processing after asking the user for additional information.
256     * Might connect to servers to run configuration checks.
257     * @param baseDNs user specified baseDNs
258     * @param source the source server
259     * @param dest the destination server
260     * @param interactive if user has to input information
261     * @return whether we should stop
262     */
263    boolean continueAfterUserInput(Collection<String> baseDNs, InitialLdapContext source, InitialLdapContext dest,
264        boolean interactive);
265
266    /**
267     * Confirm with the user whether the current task should continue.
268     *
269     * @param uData servers address and authentication parameters
270     * @param ctxSource LDAP Context for the destination server
271     * @param ctxDestination LDAP Context for the source server
272     * @param defaultValue default yes or no
273     * @return whether the current task should be interrupted
274     */
275    boolean confirmOperation(SourceDestinationServerUserData uData, InitialLdapContext ctxSource,
276        InitialLdapContext ctxDestination, final boolean defaultValue);
277  }
278
279  /** The argument parser to be used. */
280  private ReplicationCliArgumentParser argParser;
281  private FileBasedArgument userProvidedAdminPwdFile;
282  private LDAPConnectionConsoleInteraction sourceServerCI;
283  private CommandBuilder firstServerCommandBuilder;
284  /** The message formatter. */
285  private PlainTextProgressMessageFormatter formatter = new PlainTextProgressMessageFormatter();
286
287  /**
288   * Constructor for the ReplicationCliMain object.
289   *
290   * @param out the print stream to use for standard output.
291   * @param err the print stream to use for standard error.
292   */
293  public ReplicationCliMain(PrintStream out, PrintStream err)
294  {
295    super(out, err);
296  }
297
298  /**
299   * The main method for the replication tool.
300   *
301   * @param args the command-line arguments provided to this program.
302   */
303
304  public static void main(String[] args)
305  {
306    int retCode = mainCLI(args, true, System.out, System.err);
307    System.exit(retCode);
308  }
309
310  /**
311   * Parses the provided command-line arguments and uses that information to
312   * run the replication tool.
313   *
314   * @param args the command-line arguments provided to this program.
315   *
316   * @return The error code.
317   */
318
319  public static int mainCLI(String[] args)
320  {
321    return mainCLI(args, true, System.out, System.err);
322  }
323
324  /**
325   * Parses the provided command-line arguments and uses that information to
326   * run the replication tool.
327   *
328   * @param  args              The command-line arguments provided to this
329   *                           program.
330   * @param initializeServer   Indicates whether to initialize the server.
331   * @param  outStream         The output stream to use for standard output, or
332   *                           <CODE>null</CODE> if standard output is not
333   *                           needed.
334   * @param  errStream         The output stream to use for standard error, or
335   *                           <CODE>null</CODE> if standard error is not
336   *                           needed.
337   * @return The error code.
338   */
339  public static int mainCLI(String[] args, boolean initializeServer,
340      OutputStream outStream, OutputStream errStream)
341  {
342    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
343    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
344
345    try
346    {
347      ControlPanelLog.initLogFileHandler(
348          File.createTempFile(LOG_FILE_PREFIX, LOG_FILE_SUFFIX));
349    } catch (Throwable t) {
350      System.err.println("Unable to initialize log");
351      t.printStackTrace();
352    }
353    ReplicationCliMain replicationCli = new ReplicationCliMain(out, err);
354    ReplicationCliReturnCode result = replicationCli.execute(args, initializeServer);
355    if (result.getReturnCode() == 0)
356    {
357      // Delete the temp log file, in case of success.
358      ControlPanelLog.closeAndDeleteLogFile();
359    }
360    return result.getReturnCode();
361  }
362
363  /**
364   * Parses the provided command-line arguments and uses that information to
365   * run the replication tool.
366   *
367   * @param args the command-line arguments provided to this program.
368   * @param  initializeServer  Indicates whether to initialize the server.
369   *
370   * @return The error code.
371   */
372  public ReplicationCliReturnCode execute(String[] args, boolean initializeServer)
373  {
374    // Create the command-line argument parser for use with this program.
375    try
376    {
377      createArgumenParser();
378    }
379    catch (ArgumentException ae)
380    {
381      errPrintln(ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
382      logger.error(LocalizableMessage.raw("Complete error stack:"), ae);
383      return CANNOT_INITIALIZE_ARGS;
384    }
385
386    argParser.getSecureArgsList().initArgumentsWithConfiguration(argParser);
387
388    // Parse the command-line arguments provided to this program.
389    try
390    {
391      argParser.parseArguments(args);
392    }
393    catch (ArgumentException ae)
394    {
395      argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
396      logger.error(LocalizableMessage.raw("Complete error stack:"), ae);
397      return ERROR_USER_DATA;
398    }
399
400    // If we should just display usage or version information, then print it and exit.
401    if (argParser.usageOrVersionDisplayed())
402    {
403      return SUCCESSFUL_NOP;
404    }
405
406    // Checks the version - if upgrade required, the tool is unusable
407    try
408    {
409      BuildVersion.checkVersionMismatch();
410    }
411    catch (InitializationException e)
412    {
413      errPrintln(e.getMessageObject());
414      return CANNOT_INITIALIZE_ARGS;
415    }
416
417    // Check that the provided parameters are compatible.
418    LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
419    argParser.validateOptions(buf);
420    if (buf.length() > 0)
421    {
422      errPrintln(buf.toMessage());
423      errPrintln(LocalizableMessage.raw(argParser.getUsage()));
424      return ERROR_USER_DATA;
425    }
426
427    if (initializeServer)
428    {
429      DirectoryServer.bootstrapClient();
430
431      // Bootstrap definition classes.
432      try
433      {
434        if (!ClassLoaderProvider.getInstance().isEnabled())
435        {
436          ClassLoaderProvider.getInstance().enable();
437        }
438        // Switch off class name validation in client.
439        ClassPropertyDefinition.setAllowClassValidation(false);
440
441        // Switch off attribute type name validation in client.
442        AttributeTypePropertyDefinition.setCheckSchema(false);
443      }
444      catch (InitializationException ie)
445      {
446        errPrintln(ie.getMessageObject());
447        return ERROR_INITIALIZING_ADMINISTRATION_FRAMEWORK;
448      }
449    }
450
451    if (argParser.getSecureArgsList().getBindPasswordFileArg().isPresent())
452    {
453      try
454      {
455        userProvidedAdminPwdFile = FileBasedArgument.builder("adminPasswordFile")
456                .shortIdentifier(OPTION_SHORT_BINDPWD_FILE)
457                .description(INFO_DESCRIPTION_REPLICATION_ADMIN_BINDPASSWORDFILE.get())
458                .valuePlaceholder(INFO_BINDPWD_FILE_PLACEHOLDER.get())
459                .buildArgument();
460        userProvidedAdminPwdFile.getNameToValueMap().putAll(
461            argParser.getSecureArgsList().getBindPasswordFileArg().getNameToValueMap());
462      }
463      catch (Throwable t)
464      {
465        throw new IllegalStateException("Unexpected error: " + t, t);
466      }
467    }
468    sourceServerCI = new LDAPConnectionConsoleInteraction(this, argParser.getSecureArgsList());
469    sourceServerCI.setDisplayLdapIfSecureParameters(false);
470
471    ReplicationCliReturnCode returnValue = SUCCESSFUL_NOP;
472    String subCommand = null;
473    final SubcommandChoice subcommandChoice = getSubcommandChoice(argParser.getSubCommand());
474    if (subcommandChoice != null)
475    {
476      subCommand = subcommandChoice.getName();
477      returnValue = execute(subcommandChoice);
478    }
479    else if (argParser.isInteractive())
480    {
481      final SubcommandChoice subCommandChoice = promptForSubcommand();
482      if (subCommandChoice == null || SubcommandChoice.CANCEL.equals(subCommandChoice))
483      {
484        return USER_CANCELLED;
485      }
486
487      subCommand = subCommandChoice.getName();
488      if (subCommand != null)
489      {
490        String[] newArgs = new String[args.length + 1];
491        newArgs[0] = subCommand;
492        System.arraycopy(args, 0, newArgs, 1, args.length);
493        // The server (if requested) has already been initialized.
494        return execute(newArgs, false);
495      }
496    }
497    else
498    {
499      errPrintln(ERR_REPLICATION_VALID_SUBCOMMAND_NOT_FOUND.get("--" + OPTION_LONG_NO_PROMPT));
500      errPrintln(LocalizableMessage.raw(argParser.getUsage()));
501      return ERROR_USER_DATA;
502    }
503
504    // Display the log file only if the operation is successful (when there
505    // is a critical error this is already displayed).
506    if (returnValue == SUCCESSFUL && displayLogFileAtEnd(subCommand))
507    {
508      File logFile = ControlPanelLog.getLogFile();
509      if (logFile != null)
510      {
511        println();
512        println(INFO_GENERAL_SEE_FOR_DETAILS.get(logFile.getPath()));
513        println();
514      }
515    }
516
517    return returnValue;
518  }
519
520  private SubcommandChoice getSubcommandChoice(SubCommand subCommand)
521  {
522    if (subCommand != null)
523    {
524      return SubcommandChoice.fromName(subCommand.getName());
525    }
526    return null;
527  }
528
529  private ReplicationCliReturnCode execute(SubcommandChoice subcommandChoice)
530  {
531    switch (subcommandChoice)
532    {
533    case ENABLE:
534      return enableReplication();
535    case DISABLE:
536      return disableReplication();
537    case INITIALIZE:
538      return initializeReplication();
539    case INITIALIZE_ALL:
540      return initializeAllReplication();
541    case PRE_EXTERNAL_INITIALIZATION:
542      return preExternalInitialization();
543    case POST_EXTERNAL_INITIALIZATION:
544      return postExternalInitialization();
545    case STATUS:
546      return statusReplication();
547    case PURGE_HISTORICAL:
548      return purgeHistorical();
549    case RESET_CHANGE_NUMBER:
550      return resetChangeNumber();
551    default:
552      return SUCCESSFUL_NOP;
553    }
554  }
555
556  /**
557   * Prompts the user to give the Global Administrator UID.
558   *
559   * @param defaultValue
560   *          the default value that will be proposed in the prompt message.
561   * @param logger
562   *          the Logger to be used to log the error message.
563   * @return the Global Administrator UID as provided by the user.
564   */
565  private String askForAdministratorUID(String defaultValue, LocalizedLogger logger)
566  {
567    return ask(logger, INFO_ADMINISTRATOR_UID_PROMPT.get(), defaultValue);
568  }
569
570  /**
571   * Prompts the user to give the Global Administrator password.
572   *
573   * @param logger
574   *          the Logger to be used to log the error message.
575   * @return the Global Administrator password as provided by the user.
576   */
577  private String askForAdministratorPwd(LocalizedLogger logger)
578  {
579    try
580    {
581      return new String(readPassword(INFO_ADMINISTRATOR_PWD_PROMPT.get()));
582    }
583    catch (ClientException ex)
584    {
585      logger.warn(LocalizableMessage.raw("Error reading input: " + ex, ex));
586      return null;
587    }
588  }
589
590  private String ask(LocalizedLogger logger, LocalizableMessage msgPrompt, String defaultValue)
591  {
592    try
593    {
594      return readInput(msgPrompt, defaultValue);
595    }
596    catch (ClientException ce)
597    {
598      logger.warn(LocalizableMessage.raw("Error reading input: " + ce, ce));
599      return defaultValue;
600    }
601  }
602
603  /**
604   * Commodity method used to repeatidly ask the user to provide an integer
605   * value.
606   *
607   * @param prompt
608   *          the prompt message.
609   * @param defaultValue
610   *          the default value to be proposed to the user.
611   * @param logger
612   *          the logger where the errors will be written.
613   * @return the value provided by the user.
614   */
615  private int askInteger(LocalizableMessage prompt, int defaultValue, LocalizedLogger logger)
616  {
617    int newInt = -1;
618    while (newInt == -1)
619    {
620      try
621      {
622        newInt = readInteger(prompt, defaultValue);
623      }
624      catch (ClientException ce)
625      {
626        newInt = -1;
627        logger.warn(LocalizableMessage.raw("Error reading input: " + ce, ce));
628      }
629    }
630    return newInt;
631  }
632
633  /**
634   * Interactively retrieves an integer value from the console.
635   *
636   * @param prompt
637   *          The message prompt.
638   * @param defaultValue
639   *          The default value.
640   * @return Returns the value.
641   * @throws ClientException
642   *           If the value could not be retrieved for some reason.
643   */
644  public final int readInteger(
645      LocalizableMessage prompt, final int defaultValue) throws ClientException
646  {
647    ValidationCallback<Integer> callback = new ValidationCallback<Integer>()
648    {
649      @Override
650      public Integer validate(ConsoleApplication app, String input)
651          throws ClientException
652      {
653        String ninput = input.trim();
654        if (ninput.length() == 0)
655        {
656          return defaultValue;
657        }
658
659        try
660        {
661          int i = Integer.parseInt(ninput);
662          if (i < 1)
663          {
664            throw new NumberFormatException();
665          }
666          return i;
667        }
668        catch (NumberFormatException e)
669        {
670          // Try again...
671          app.errPrintln();
672          app.errPrintln(ERR_BAD_INTEGER.get(ninput));
673          app.errPrintln();
674          return null;
675        }
676      }
677
678    };
679
680    if (defaultValue != -1)
681    {
682      prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt, defaultValue);
683    }
684
685    return readValidatedInput(prompt, callback, CONFIRMATION_MAX_TRIES);
686  }
687
688  private boolean isFirstCallFromScript()
689  {
690    return FIRST_SCRIPT_CALL.equals(System.getProperty(SCRIPT_CALL_STATUS));
691  }
692
693  private void createArgumenParser() throws ArgumentException
694  {
695    argParser = new ReplicationCliArgumentParser(CLASS_NAME);
696    argParser.initializeParser(getOutputStream());
697  }
698
699  /**
700   * Based on the data provided in the command-line it enables replication
701   * between two servers.
702   * @return the error code if the operation failed and 0 if it was successful.
703   */
704  private ReplicationCliReturnCode enableReplication()
705  {
706    EnableReplicationUserData uData = new EnableReplicationUserData();
707    if (argParser.isInteractive())
708    {
709      try
710      {
711        if (promptIfRequired(uData))
712        {
713          return enableReplication(uData);
714        }
715        else
716        {
717          return USER_CANCELLED;
718        }
719      }
720      catch (ReplicationCliException rce)
721      {
722        errPrintln();
723        errPrintln(getCriticalExceptionMessage(rce));
724        return rce.getErrorCode();
725      }
726    }
727    else
728    {
729      initializeWithArgParser(uData);
730      return enableReplication(uData);
731    }
732  }
733
734  /**
735   * Based on the data provided in the command-line it disables replication
736   * in the server.
737   * @return the error code if the operation failed and SUCCESSFUL if it was
738   * successful.
739   */
740  private ReplicationCliReturnCode disableReplication()
741  {
742    DisableReplicationUserData uData = new DisableReplicationUserData();
743    if (argParser.isInteractive())
744    {
745      try
746      {
747        if (promptIfRequired(uData))
748        {
749          return disableReplication(uData);
750        }
751        else
752        {
753          return USER_CANCELLED;
754        }
755      }
756      catch (ReplicationCliException rce)
757      {
758        errPrintln();
759        errPrintln(getCriticalExceptionMessage(rce));
760        return rce.getErrorCode();
761      }
762    }
763    else
764    {
765      initializeWithArgParser(uData);
766      return disableReplication(uData);
767    }
768  }
769
770  /**
771   * Based on the data provided in the command-line initialize the contents
772   * of the whole replication topology.
773   * @return the error code if the operation failed and SUCCESSFUL if it was
774   * successful.
775   */
776  private ReplicationCliReturnCode initializeAllReplication()
777  {
778    InitializeAllReplicationUserData uData =
779      new InitializeAllReplicationUserData();
780    if (argParser.isInteractive())
781    {
782      if (promptIfRequired(uData))
783      {
784        return initializeAllReplication(uData);
785      }
786      else
787      {
788        return USER_CANCELLED;
789      }
790    }
791    else
792    {
793      initializeWithArgParser(uData);
794      return initializeAllReplication(uData);
795    }
796  }
797
798  /**
799   * Based on the data provided in the command-line execute the pre external
800   * initialization operation.
801   * @return the error code if the operation failed and SUCCESSFUL if it was
802   * successful.
803   */
804  private ReplicationCliReturnCode preExternalInitialization()
805  {
806    PreExternalInitializationUserData uData =
807      new PreExternalInitializationUserData();
808    if (argParser.isInteractive())
809    {
810      if (promptIfRequiredForPreOrPost(uData))
811      {
812        return preExternalInitialization(uData);
813      }
814      else
815      {
816        return USER_CANCELLED;
817      }
818    }
819    else
820    {
821      initializeWithArgParser(uData);
822      return preExternalInitialization(uData);
823    }
824  }
825
826  /**
827   * Based on the data provided in the command-line execute the post external
828   * initialization operation.
829   * @return the error code if the operation failed and SUCCESSFUL if it was
830   * successful.
831   */
832  private ReplicationCliReturnCode postExternalInitialization()
833  {
834    PostExternalInitializationUserData uData =
835      new PostExternalInitializationUserData();
836    if (argParser.isInteractive())
837    {
838      if (promptIfRequiredForPreOrPost(uData))
839      {
840        return postExternalInitialization(uData);
841      }
842      else
843      {
844        return USER_CANCELLED;
845      }
846    }
847    else
848    {
849      initializeWithArgParser(uData);
850      return postExternalInitialization(uData);
851    }
852  }
853
854  /**
855   * Based on the data provided in the command-line it displays replication
856   * status.
857   * @return the error code if the operation failed and SUCCESSFUL if it was
858   * successful.
859   */
860  private ReplicationCliReturnCode statusReplication()
861  {
862    StatusReplicationUserData uData = new StatusReplicationUserData();
863    if (argParser.isInteractive())
864    {
865      try
866      {
867        if (promptIfRequired(uData))
868        {
869          return statusReplication(uData);
870        }
871        else
872        {
873          return USER_CANCELLED;
874        }
875      }
876      catch (ReplicationCliException rce)
877      {
878        errPrintln();
879        errPrintln(getCriticalExceptionMessage(rce));
880        return rce.getErrorCode();
881      }
882    }
883    else
884    {
885      initializeWithArgParser(uData);
886      return statusReplication(uData);
887    }
888  }
889
890  /**
891   * Based on the data provided in the command-line it displays replication
892   * status.
893   * @return the error code if the operation failed and SUCCESSFUL if it was
894   * successful.
895   */
896  private ReplicationCliReturnCode purgeHistorical()
897  {
898    final PurgeHistoricalUserData uData = new PurgeHistoricalUserData();
899    if (argParser.isInteractive())
900    {
901      if (promptIfRequired(uData))
902      {
903        return purgeHistorical(uData);
904      }
905      else
906      {
907        return USER_CANCELLED;
908      }
909    }
910    else
911    {
912      initializeWithArgParser(uData);
913      return purgeHistorical(uData);
914    }
915  }
916
917  /**
918   * Initializes the contents of the provided purge historical replication user
919   * data object with what was provided in the command-line without prompting to
920   * the user.
921   * @param uData the purge historical replication user data object to be
922   * initialized.
923   */
924  private void initializeWithArgParser(PurgeHistoricalUserData uData)
925  {
926    PurgeHistoricalUserData.initializeWithArgParser(uData, argParser);
927  }
928
929  private ReplicationCliReturnCode purgeHistorical(PurgeHistoricalUserData uData)
930  {
931      return uData.isOnline()
932          ? purgeHistoricalRemotely(uData)
933          : purgeHistoricalLocally(uData);
934  }
935
936  private ReplicationCliReturnCode purgeHistoricalLocally(
937      PurgeHistoricalUserData uData)
938  {
939    List<String> baseDNs = uData.getBaseDNs();
940    checkSuffixesForLocalPurgeHistorical(baseDNs, false);
941    if (!baseDNs.isEmpty())
942    {
943      uData.setBaseDNs(baseDNs);
944      if (mustPrintCommandBuilder())
945      {
946        printNewCommandBuilder(PURGE_HISTORICAL_SUBCMD_NAME, uData);
947      }
948
949      try
950      {
951        return purgeHistoricalLocallyTask(uData);
952      }
953      catch (ReplicationCliException rce)
954      {
955        errPrintln();
956        errPrintln(getCriticalExceptionMessage(rce));
957        logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
958        return rce.getErrorCode();
959      }
960    }
961    else
962    {
963      return HISTORICAL_CANNOT_BE_PURGED_ON_BASEDN;
964    }
965  }
966
967  private void printPurgeProgressMessage(PurgeHistoricalUserData uData)
968  {
969    String separator = formatter.getLineBreak().toString() + formatter.getTab();
970    println();
971    LocalizableMessage msg = formatter.getFormattedProgress(
972        INFO_PROGRESS_PURGE_HISTORICAL.get(separator, joinAsString(separator, uData.getBaseDNs())));
973    print(msg);
974    println();
975  }
976
977  private ReplicationCliReturnCode purgeHistoricalLocallyTask(PurgeHistoricalUserData uData)
978      throws ReplicationCliException
979  {
980    ReplicationCliReturnCode returnCode = SUCCESSFUL;
981    if (isFirstCallFromScript())
982    {
983      // Launch the process: launch dsreplication in non-interactive mode with
984      // the recursive property set.
985      ArrayList<String> args = new ArrayList<>();
986      args.add(getCommandLinePath(getCommandName()));
987      args.add(PURGE_HISTORICAL_SUBCMD_NAME);
988      args.add("--"+argParser.noPromptArg.getLongIdentifier());
989      args.add("--"+argParser.maximumDurationArg.getLongIdentifier());
990      args.add(String.valueOf(uData.getMaximumDuration()));
991      for (String baseDN : uData.getBaseDNs())
992      {
993        args.add("--"+argParser.baseDNsArg.getLongIdentifier());
994        args.add(baseDN);
995      }
996      ProcessBuilder pb = new ProcessBuilder(args);
997      // Use the java args in the script.
998      Map<String, String> env = pb.environment();
999      env.put("RECURSIVE_LOCAL_CALL", "true");
1000      try
1001      {
1002        Process process = pb.start();
1003        ProcessReader outReader =
1004            new ProcessReader(process, getOutputStream(), false);
1005        ProcessReader errReader =
1006            new ProcessReader(process, getErrorStream(), true);
1007
1008        outReader.startReading();
1009        errReader.startReading();
1010
1011        int code = process.waitFor();
1012        for (ReplicationCliReturnCode c : ReplicationCliReturnCode.values())
1013        {
1014          if (c.getReturnCode() == code)
1015          {
1016            returnCode = c;
1017            break;
1018          }
1019        }
1020      }
1021      catch (Exception e)
1022      {
1023        LocalizableMessage msg = ERR_LAUNCHING_PURGE_HISTORICAL.get();
1024        ReplicationCliReturnCode code = ERROR_LAUNCHING_PURGE_HISTORICAL;
1025        throw new ReplicationCliException(
1026            getThrowableMsg(msg, e), code, e);
1027      }
1028    }
1029    else
1030    {
1031      printPurgeProgressMessage(uData);
1032      LocalPurgeHistorical localPurgeHistorical =
1033        new LocalPurgeHistorical(uData, this, formatter,
1034            argParser.getConfigFile(),
1035            argParser.getConfigClass());
1036      returnCode = localPurgeHistorical.execute();
1037
1038      if (returnCode == SUCCESSFUL)
1039      {
1040        printSuccessMessage(uData, null);
1041      }
1042    }
1043    return returnCode;
1044  }
1045
1046  /**
1047   * Returns an InitialLdapContext using the provided parameters. We try to
1048   * guarantee that the connection is able to read the configuration.
1049   *
1050   * @param host
1051   *          the host name.
1052   * @param port
1053   *          the port to connect.
1054   * @param useSSL
1055   *          whether to use SSL or not.
1056   * @param useStartTLS
1057   *          whether to use StartTLS or not.
1058   * @param bindDn
1059   *          the bind dn to be used.
1060   * @param pwd
1061   *          the password.
1062   * @param connectTimeout
1063   *          the timeout in milliseconds to connect to the server.
1064   * @param trustManager
1065   *          the trust manager.
1066   * @return an InitialLdapContext connected.
1067   * @throws NamingException
1068   *           if there was an error establishing the connection.
1069   */
1070  private InitialLdapContext createAdministrativeContext(String host,
1071      int port, boolean useSSL, boolean useStartTLS, String bindDn, String pwd,
1072      int connectTimeout, ApplicationTrustManager trustManager)
1073      throws NamingException
1074  {
1075    InitialLdapContext ctx;
1076    String ldapUrl = getLDAPUrl(host, port, useSSL);
1077    if (useSSL)
1078    {
1079      ctx = createLdapsContext(ldapUrl, bindDn, pwd, connectTimeout, null, trustManager, null);
1080    }
1081    else if (useStartTLS)
1082    {
1083      ctx = createStartTLSContext(ldapUrl, bindDn, pwd, connectTimeout, null, trustManager, null);
1084    }
1085    else
1086    {
1087      ctx = createLdapContext(ldapUrl, bindDn, pwd, connectTimeout, null);
1088    }
1089    if (!connectedAsAdministrativeUser(ctx))
1090    {
1091      throw new NoPermissionException(ERR_NOT_ADMINISTRATIVE_USER.get().toString());
1092    }
1093    return ctx;
1094  }
1095
1096  /**
1097   * Creates an Initial LDAP Context interacting with the user if the
1098   * application is interactive.
1099   *
1100   * @param ci
1101   *          the LDAPConnectionConsoleInteraction object that is assumed to
1102   *          have been already run.
1103   * @return the initial LDAP context or <CODE>null</CODE> if the user did not
1104   *         accept to trust the certificates.
1105   * @throws ClientException
1106   *           if there was an error establishing the connection.
1107   */
1108  private InitialLdapContext createInitialLdapContextInteracting(LDAPConnectionConsoleInteraction ci)
1109      throws ClientException
1110  {
1111    return createInitialLdapContextInteracting(ci, isInteractive() && ci.isTrustStoreInMemory());
1112  }
1113
1114  private OpendsCertificateException getCertificateRootException(Throwable t)
1115  {
1116    while (t != null)
1117    {
1118      t = t.getCause();
1119      if (t instanceof OpendsCertificateException)
1120      {
1121        return (OpendsCertificateException) t;
1122      }
1123    }
1124    return null;
1125  }
1126
1127  /**
1128   * Creates an Initial LDAP Context interacting with the user if the
1129   * application is interactive.
1130   *
1131   * @param ci
1132   *          the LDAPConnectionConsoleInteraction object that is assumed to
1133   *          have been already run.
1134   * @param promptForCertificate
1135   *          whether we should prompt for the certificate or not.
1136   * @return the initial LDAP context or <CODE>null</CODE> if the user did not
1137   *         accept to trust the certificates.
1138   * @throws ClientException
1139   *           if there was an error establishing the connection.
1140   */
1141  private InitialLdapContext createInitialLdapContextInteracting(LDAPConnectionConsoleInteraction ci,
1142      boolean promptForCertificate) throws ClientException
1143  {
1144    // Interact with the user though the console to get
1145    // LDAP connection information
1146    String hostName = getHostNameForLdapUrl(ci.getHostName());
1147    Integer portNumber = ci.getPortNumber();
1148    String bindDN = ci.getBindDN();
1149    String bindPassword = ci.getBindPassword();
1150    TrustManager trustManager = ci.getTrustManager();
1151    KeyManager keyManager = ci.getKeyManager();
1152
1153    InitialLdapContext ctx;
1154
1155    if (ci.useSSL())
1156    {
1157      String ldapsUrl = "ldaps://" + hostName + ":" + portNumber;
1158      while (true)
1159      {
1160        try
1161        {
1162          ctx = createLdapsContext(ldapsUrl, bindDN, bindPassword, ci.getConnectTimeout(),
1163              null, trustManager, keyManager);
1164          ctx.reconnect(null);
1165          break;
1166        }
1167        catch (NamingException e)
1168        {
1169          if (promptForCertificate)
1170          {
1171            OpendsCertificateException oce = getCertificateRootException(e);
1172            if (oce != null)
1173            {
1174              String authType = null;
1175              if (trustManager instanceof ApplicationTrustManager)
1176              {
1177                ApplicationTrustManager appTrustManager =
1178                    (ApplicationTrustManager) trustManager;
1179                authType = appTrustManager.getLastRefusedAuthType();
1180              }
1181              if (ci.checkServerCertificate(oce.getChain(), authType, hostName))
1182              {
1183                // If the certificate is trusted, update the trust manager.
1184                trustManager = ci.getTrustManager();
1185
1186                // Try to connect again.
1187                continue;
1188              }
1189              else
1190              {
1191                // Assume user canceled.
1192                return null;
1193              }
1194            }
1195          }
1196          if (e.getCause() != null)
1197          {
1198            if (!isInteractive()
1199                && !ci.isTrustAll()
1200                && (getCertificateRootException(e) != null
1201                  || e.getCause() instanceof SSLHandshakeException))
1202            {
1203              LocalizableMessage message =
1204                  ERR_FAILED_TO_CONNECT_NOT_TRUSTED.get(hostName, portNumber);
1205              throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR, message);
1206            }
1207            if (e.getCause() instanceof SSLException)
1208            {
1209              LocalizableMessage message =
1210                  ERR_FAILED_TO_CONNECT_WRONG_PORT.get(hostName, portNumber);
1211              throw new ClientException(
1212                  ReturnCode.CLIENT_SIDE_CONNECT_ERROR, message);
1213            }
1214          }
1215          String hostPort =
1216              ServerDescriptor.getServerRepresentation(hostName, portNumber);
1217          LocalizableMessage message = getMessageForException(e, hostPort);
1218          throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR, message);
1219        }
1220      }
1221    }
1222    else if (ci.useStartTLS())
1223    {
1224      String ldapUrl = "ldap://" + hostName + ":" + portNumber;
1225      while (true)
1226      {
1227        try
1228        {
1229          ctx = createStartTLSContext(ldapUrl, bindDN,
1230                  bindPassword, CliConstants.DEFAULT_LDAP_CONNECT_TIMEOUT, null,
1231                  trustManager, keyManager, null);
1232          ctx.reconnect(null);
1233          break;
1234        }
1235        catch (NamingException e)
1236        {
1237          if (promptForCertificate)
1238          {
1239            OpendsCertificateException oce = getCertificateRootException(e);
1240            if (oce != null)
1241            {
1242              String authType = null;
1243              if (trustManager instanceof ApplicationTrustManager)
1244              {
1245                ApplicationTrustManager appTrustManager =
1246                    (ApplicationTrustManager) trustManager;
1247                authType = appTrustManager.getLastRefusedAuthType();
1248              }
1249
1250              if (ci.checkServerCertificate(oce.getChain(), authType, hostName))
1251              {
1252                // If the certificate is trusted, update the trust manager.
1253                trustManager = ci.getTrustManager();
1254
1255                // Try to connect again.
1256                continue;
1257              }
1258              else
1259              {
1260                // Assume user cancelled.
1261                return null;
1262              }
1263            }
1264            else
1265            {
1266              LocalizableMessage message =
1267                  ERR_FAILED_TO_CONNECT.get(hostName, portNumber);
1268              throw new ClientException(
1269                  ReturnCode.CLIENT_SIDE_CONNECT_ERROR, message);
1270            }
1271          }
1272          LocalizableMessage message =
1273              ERR_FAILED_TO_CONNECT.get(hostName, portNumber);
1274          throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
1275              message);
1276        }
1277      }
1278    }
1279    else
1280    {
1281      String ldapUrl = "ldap://" + hostName + ":" + portNumber;
1282      while (true)
1283      {
1284        try
1285        {
1286          ctx = createLdapContext(ldapUrl, bindDN, bindPassword,
1287                  CliConstants.DEFAULT_LDAP_CONNECT_TIMEOUT, null);
1288          ctx.reconnect(null);
1289          break;
1290        }
1291        catch (NamingException e)
1292        {
1293          LocalizableMessage message =
1294              ERR_FAILED_TO_CONNECT.get(hostName, portNumber);
1295          throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
1296              message);
1297        }
1298      }
1299    }
1300    return ctx;
1301  }
1302
1303  private ReplicationCliReturnCode purgeHistoricalRemotely(
1304      PurgeHistoricalUserData uData)
1305  {
1306    // Connect to the provided server
1307    InitialLdapContext ctx = createAdministrativeContext(uData);
1308    if (ctx == null)
1309    {
1310      return ERROR_CONNECTING;
1311    }
1312
1313    try
1314    {
1315      List<String> baseDNs = uData.getBaseDNs();
1316      checkSuffixesForPurgeHistorical(baseDNs, ctx, false);
1317      if (baseDNs.isEmpty())
1318      {
1319        return HISTORICAL_CANNOT_BE_PURGED_ON_BASEDN;
1320      }
1321      uData.setBaseDNs(baseDNs);
1322      if (mustPrintCommandBuilder())
1323      {
1324        printNewCommandBuilder(PURGE_HISTORICAL_SUBCMD_NAME, uData);
1325      }
1326
1327      try
1328      {
1329        return purgeHistoricalRemoteTask(ctx, uData);
1330      }
1331      catch (ReplicationCliException rce)
1332      {
1333        errPrintln();
1334        errPrintln(getCriticalExceptionMessage(rce));
1335        logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
1336        return rce.getErrorCode();
1337      }
1338    }
1339    finally
1340    {
1341      close(ctx);
1342    }
1343  }
1344
1345  private ReplicationCliReturnCode resetChangeNumber()
1346  {
1347    final String changeNumber;
1348    final SourceDestinationServerUserData uData = new SourceDestinationServerUserData();
1349
1350    if (!argParser.isInteractive())
1351    {
1352      initializeWithArgParser(uData);
1353      return resetChangeNumber(uData);
1354    }
1355    OperationBetweenSourceAndDestinationServers
1356        resetChangeNumberOperations = new OperationBetweenSourceAndDestinationServers()
1357    {
1358      @Override
1359      public boolean continueAfterUserInput(Collection<String> baseDNs, InitialLdapContext source,
1360          InitialLdapContext dest, boolean interactive)
1361      {
1362        TopologyCacheFilter filter = new TopologyCacheFilter();
1363        filter.setSearchMonitoringInformation(false);
1364
1365        if (!argParser.resetChangeNumber.isPresent())
1366        {
1367          String cn = getNewestChangeNumber(source);
1368          if (cn.isEmpty())
1369          {
1370            return true;
1371          }
1372          argParser.setResetChangeNumber(
1373              ask(logger, INFO_RESET_CHANGE_NUMBER_TO.get(uData.getSource(), uData.getDestination()), cn));
1374        }
1375        return false;
1376      }
1377
1378      @Override
1379      public boolean confirmOperation(SourceDestinationServerUserData uData, InitialLdapContext ctxSource,
1380          InitialLdapContext ctxDestination, boolean defaultValue)
1381      {
1382        return !askConfirmation(INFO_RESET_CHANGE_NUMBER_CONFIRM_RESET.get(uData.getDestinationHostPort()),
1383            defaultValue);
1384      }
1385    };
1386
1387    return promptIfRequired(uData, resetChangeNumberOperations) ? resetChangeNumber(uData) : USER_CANCELLED;
1388  }
1389
1390  private ReplicationCliReturnCode resetChangeNumber(SourceDestinationServerUserData uData)
1391  {
1392
1393    InitialLdapContext ctxSource = createAdministrativeContext(uData, uData.getSource());
1394    InitialLdapContext ctxDest = createAdministrativeContext(uData, uData.getDestination());
1395    if (!getCommonSuffixes(ctxSource, ctxDest, SuffixRelationType.NOT_FULLY_REPLICATED).isEmpty())
1396    {
1397      errPrintln(ERROR_RESET_CHANGE_NUMBER_SERVERS_BASEDNS_DIFFER.get(uData.getSourceHostPort(),
1398          uData.getDestinationHostPort()));
1399      return ERROR_RESET_CHANGE_NUMBER_BASEDNS_SHOULD_EQUAL;
1400    }
1401    if (mustPrintCommandBuilder())
1402    {
1403      printNewCommandBuilder(RESET_CHANGE_NUMBER_SUBCMD_NAME, uData);
1404    }
1405    try
1406    {
1407      String newStartCN;
1408      if (argParser.resetChangeNumber.isPresent())
1409      {
1410        newStartCN = String.valueOf(argParser.getResetChangeNumber());
1411      }
1412      else
1413      {
1414        newStartCN = getNewestChangeNumber(ctxSource);
1415        if (newStartCN.isEmpty())
1416        {
1417          return ERROR_UNKNOWN_CHANGE_NUMBER;
1418        }
1419        argParser.setResetChangeNumber(newStartCN);
1420      }
1421      SearchControls ctls = new SearchControls();
1422      ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1423      ctls.setReturningAttributes(
1424          new String[] {
1425              "changeNumber",
1426              "replicationCSN",
1427              "targetDN"
1428          });
1429      NamingEnumeration<SearchResult> listeners = ctxSource.search(new LdapName("cn=changelog"),
1430          "(changeNumber=" + newStartCN + ")", ctls);
1431      if (!listeners.hasMore())
1432      {
1433        errPrintln(ERROR_RESET_CHANGE_NUMBER_UNKNOWN_NUMBER.get(newStartCN, uData.getSourceHostPort()));
1434        return ERROR_UNKNOWN_CHANGE_NUMBER;
1435      }
1436      SearchResult sr = listeners.next();
1437      String newStartCSN = getFirstValue(sr, "replicationCSN");
1438      if (newStartCSN == null)
1439      {
1440        errPrintln(ERROR_RESET_CHANGE_NUMBER_NO_CSN_FOUND.get(newStartCN, uData.getSourceHostPort()));
1441        return ERROR_RESET_CHANGE_NUMBER_NO_CSN;
1442      }
1443      String targetDN = getFirstValue(sr, "targetDN");
1444      DN targetBaseDN = DN.rootDN();
1445      try
1446      {
1447        for (String adn : getCommonSuffixes(ctxSource, ctxDest, SuffixRelationType.REPLICATED))
1448        {
1449          DN dn = DN.valueOf(adn);
1450          if (DN.valueOf(targetDN).isSubordinateOrEqualTo(dn) && dn.isSubordinateOrEqualTo(targetBaseDN))
1451          {
1452            targetBaseDN = dn;
1453          }
1454        }
1455      }
1456      catch (LocalizedIllegalArgumentException e)
1457      {
1458        errPrintln(ERROR_RESET_CHANGE_NUMBER_EXCEPTION.get(e.getLocalizedMessage()));
1459        return ERROR_RESET_CHANGE_NUMBER_PROBLEM;
1460      }
1461      if (targetBaseDN.isRootDN())
1462      {
1463        errPrintln(ERROR_RESET_CHANGE_NUMBER_NO_BASEDN.get(newStartCN, targetDN, newStartCSN));
1464        return ERROR_RESET_CHANGE_NUMBER_UNKNOWN_BASEDN;
1465      }
1466      logger.info(INFO_RESET_CHANGE_NUMBER_INFO.get(uData.getDestinationHostPort(),
1467          newStartCN, newStartCSN, targetBaseDN.toString()));
1468      Map<String, String> taskAttrs = new TreeMap<>();
1469      taskAttrs.put("ds-task-reset-change-number-to", newStartCN);
1470      taskAttrs.put("ds-task-reset-change-number-csn", newStartCSN);
1471      taskAttrs.put("ds-task-reset-change-number-base-dn", targetBaseDN.toString());
1472      String taskDN = createServerTask(ctxDest,
1473          "ds-task-reset-change-number", "org.opends.server.tasks.ResetChangeNumberTask", "dsreplication-reset-cn",
1474          taskAttrs);
1475      waitUntilResetChangeNumberTaskEnds(ctxDest, taskDN);
1476      return SUCCESSFUL;
1477    }
1478    catch (ReplicationCliException | NamingException | NullPointerException e)
1479    {
1480      errPrintln(ERROR_RESET_CHANGE_NUMBER_EXCEPTION.get(e.getLocalizedMessage()));
1481      return ERROR_RESET_CHANGE_NUMBER_PROBLEM;
1482    }
1483  }
1484
1485  private String getNewestChangeNumber(InitialLdapContext source)
1486  {
1487    try
1488    {
1489      SearchControls ctls = new SearchControls();
1490      ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
1491      ctls.setReturningAttributes(new String[] {"lastChangeNumber"});
1492      NamingEnumeration<SearchResult> results = source.search(new LdapName(""), "objectclass=*", ctls);
1493      if (results.hasMore()) {
1494        return getFirstValue(results.next(), "lastChangeNumber");
1495      }
1496    }
1497    catch (NamingException e)
1498    {
1499      errPrintln(ERROR_RESET_CHANGE_NUMBER_EXCEPTION.get(e.getLocalizedMessage()));
1500    }
1501
1502    return "";
1503  }
1504
1505  private void waitUntilResetChangeNumberTaskEnds(InitialLdapContext server, String taskDN)
1506      throws ReplicationCliException
1507  {
1508    String lastLogMsg = null;
1509    while (true)
1510    {
1511      sleepCatchInterrupt(500);
1512      try
1513      {
1514        SearchResult sr = getLastSearchResult(server, taskDN, "ds-task-log-message", "ds-task-state" );
1515        String logMsg = getFirstValue(sr, "ds-task-log-message");
1516        if (logMsg != null && !logMsg.equals(lastLogMsg))
1517        {
1518          logger.info(LocalizableMessage.raw(logMsg));
1519          lastLogMsg = logMsg;
1520        }
1521        InstallerHelper helper = new InstallerHelper();
1522        String state = getFirstValue(sr, "ds-task-state");
1523
1524        if (helper.isDone(state) || helper.isStoppedByError(state))
1525        {
1526          LocalizableMessage errorMsg = ERR_UNEXPECTED_DURING_TASK_WITH_LOG.get(lastLogMsg, state, server);
1527
1528          if (helper.isCompletedWithErrors(state))
1529          {
1530            logger.warn(LocalizableMessage.raw("Completed with error: " + errorMsg));
1531            errPrintln(errorMsg);
1532          }
1533          else if (!helper.isSuccessful(state) || helper.isStoppedByError(state))
1534          {
1535            logger.warn(LocalizableMessage.raw("Error: " + errorMsg));
1536            throw new ReplicationCliException(errorMsg, ERROR_LAUNCHING_RESET_CHANGE_NUMBER, null);
1537          }
1538          else
1539          {
1540            print(INFO_RESET_CHANGE_NUMBER_TASK_FINISHED.get());
1541            println();
1542          }
1543          return;
1544        }
1545      }
1546      catch (NameNotFoundException x)
1547      {
1548        return;
1549      }
1550      catch (NamingException ne)
1551      {
1552        throw new ReplicationCliException(getThrowableMsg(ERR_READING_SERVER_TASK_PROGRESS.get(), ne),
1553            ERROR_CONNECTING, ne);
1554      }
1555    }
1556  }
1557
1558  private InitialLdapContext createAdministrativeContext(MonoServerReplicationUserData uData)
1559  {
1560    final String bindDn = getAdministratorDN(uData.getAdminUid());
1561    return createAdministrativeContext(uData, bindDn);
1562  }
1563
1564  private InitialLdapContext createAdministrativeContext(MonoServerReplicationUserData uData, final String bindDn)
1565  {
1566    try
1567    {
1568      return createAdministrativeContext(uData.getHostName(), uData.getPort(),
1569          useSSL, useStartTLS, bindDn,
1570          uData.getAdminPwd(), getConnectTimeout(), getTrustManager(sourceServerCI));
1571    }
1572    catch (NamingException ne)
1573    {
1574      String hostPort = getServerRepresentation(uData.getHostName(), uData.getPort());
1575      errPrintln();
1576      errPrintln(getMessageForException(ne, hostPort));
1577      logger.error(LocalizableMessage.raw("Complete error stack:"), ne);
1578      return null;
1579    }
1580  }
1581
1582  private void printSuccessMessage(PurgeHistoricalUserData uData, String taskID)
1583  {
1584    println();
1585    if (!uData.isOnline())
1586    {
1587      print(
1588          INFO_PROGRESS_PURGE_HISTORICAL_FINISHED_PROCEDURE.get());
1589    }
1590    else if (uData.getTaskSchedule().isStartNow())
1591    {
1592      print(INFO_TASK_TOOL_TASK_SUCESSFULL.get(
1593          INFO_PURGE_HISTORICAL_TASK_NAME.get(),
1594          taskID));
1595    }
1596    else if (uData.getTaskSchedule().getStartDate() != null)
1597    {
1598      print(INFO_TASK_TOOL_TASK_SCHEDULED_FUTURE.get(
1599          INFO_PURGE_HISTORICAL_TASK_NAME.get(),
1600          taskID,
1601          StaticUtils.formatDateTimeString(
1602              uData.getTaskSchedule().getStartDate())));
1603    }
1604    else
1605    {
1606      print(INFO_TASK_TOOL_RECURRING_TASK_SCHEDULED.get(
1607          INFO_PURGE_HISTORICAL_TASK_NAME.get(),
1608          taskID));
1609    }
1610
1611    println();
1612  }
1613
1614  /**
1615   * Launches the purge historical operation using the
1616   * provided connection.
1617   * @param ctx the connection to the server.
1618   * @throws ReplicationCliException if there is an error performing the
1619   * operation.
1620   */
1621  private ReplicationCliReturnCode purgeHistoricalRemoteTask(
1622      InitialLdapContext ctx,
1623      PurgeHistoricalUserData uData)
1624  throws ReplicationCliException
1625  {
1626    printPurgeProgressMessage(uData);
1627    ReplicationCliReturnCode returnCode = SUCCESSFUL;
1628    boolean taskCreated = false;
1629    boolean isOver = false;
1630    String dn = null;
1631    String taskID = null;
1632    while (!taskCreated)
1633    {
1634      BasicAttributes attrs = PurgeHistoricalUserData.getTaskAttributes(uData);
1635      dn = PurgeHistoricalUserData.getTaskDN(attrs);
1636      taskID = PurgeHistoricalUserData.getTaskID(attrs);
1637      try
1638      {
1639        DirContext dirCtx = ctx.createSubcontext(dn, attrs);
1640        taskCreated = true;
1641        logger.info(LocalizableMessage.raw("created task entry: "+attrs));
1642        dirCtx.close();
1643      }
1644      catch (NamingException ne)
1645      {
1646        logger.error(LocalizableMessage.raw("Error creating task "+attrs, ne));
1647        LocalizableMessage msg = ERR_LAUNCHING_PURGE_HISTORICAL.get();
1648        ReplicationCliReturnCode code = ERROR_LAUNCHING_PURGE_HISTORICAL;
1649        throw new ReplicationCliException(
1650            getThrowableMsg(msg, ne), code, ne);
1651      }
1652    }
1653
1654    // Polling only makes sense when we are recurrently scheduling a task
1655    // or the task is being executed now.
1656    String lastLogMsg = null;
1657    while (!isOver && uData.getTaskSchedule().getStartDate() == null)
1658    {
1659      sleepCatchInterrupt(500);
1660      try
1661      {
1662        SearchResult sr = getFirstSearchResult(ctx, dn,
1663            "ds-task-log-message",
1664            "ds-task-state",
1665            "ds-task-purge-conflicts-historical-purged-values-count",
1666            "ds-task-purge-conflicts-historical-purge-completed-in-time",
1667            "ds-task-purge-conflicts-historical-purge-completed-in-time",
1668            "ds-task-purge-conflicts-historical-last-purged-changenumber");
1669        String logMsg = getFirstValue(sr, "ds-task-log-message");
1670        if (logMsg != null && !logMsg.equals(lastLogMsg))
1671        {
1672          logger.info(LocalizableMessage.raw(logMsg));
1673          lastLogMsg = logMsg;
1674        }
1675        InstallerHelper helper = new InstallerHelper();
1676        String state = getFirstValue(sr, "ds-task-state");
1677
1678        if (helper.isDone(state) || helper.isStoppedByError(state))
1679        {
1680          isOver = true;
1681          LocalizableMessage errorMsg = getPurgeErrorMsg(lastLogMsg, state, ctx);
1682
1683          if (helper.isCompletedWithErrors(state))
1684          {
1685            logger.warn(LocalizableMessage.raw("Completed with error: "+errorMsg));
1686            errPrintln(errorMsg);
1687          }
1688          else if (!helper.isSuccessful(state) ||
1689              helper.isStoppedByError(state))
1690          {
1691            logger.warn(LocalizableMessage.raw("Error: "+errorMsg));
1692            ReplicationCliReturnCode code = ERROR_LAUNCHING_PURGE_HISTORICAL;
1693            throw new ReplicationCliException(errorMsg, code, null);
1694          }
1695        }
1696      }
1697      catch (NameNotFoundException x)
1698      {
1699        isOver = true;
1700      }
1701      catch (NamingException ne)
1702      {
1703        LocalizableMessage msg = ERR_READING_SERVER_TASK_PROGRESS.get();
1704        throw new ReplicationCliException(
1705          getThrowableMsg(msg, ne), ERROR_CONNECTING, ne);
1706      }
1707    }
1708
1709    if (returnCode == SUCCESSFUL)
1710    {
1711      printSuccessMessage(uData, taskID);
1712    }
1713    return returnCode;
1714  }
1715
1716  private SearchResult getFirstSearchResult(InitialLdapContext ctx, String dn, String... returnedAttributes)
1717      throws NamingException
1718  {
1719    SearchControls searchControls = new SearchControls();
1720    searchControls.setCountLimit(1);
1721    searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
1722    searchControls.setReturningAttributes(returnedAttributes);
1723    NamingEnumeration<SearchResult> res = ctx.search(dn, "objectclass=*", searchControls);
1724    try
1725    {
1726      SearchResult sr = null;
1727      sr = res.next();
1728      return sr;
1729    }
1730    finally
1731    {
1732      res.close();
1733    }
1734  }
1735
1736  private LocalizableMessage getPurgeErrorMsg(String lastLogMsg, String state, InitialLdapContext ctx)
1737  {
1738    String server = getHostPort(ctx);
1739    if (lastLogMsg != null)
1740    {
1741      return ERR_UNEXPECTED_DURING_TASK_WITH_LOG.get(lastLogMsg, state, server);
1742    }
1743    return ERR_UNEXPECTED_DURING_TASK_NO_LOG.get(state, server);
1744  }
1745
1746  /**
1747   * Checks that historical can actually be purged in the provided baseDNs
1748   * for the server.
1749   * @param suffixes the suffixes provided by the user.  This Collection is
1750   * updated with the base DNs that the user provided interactively.
1751   * @param ctx connection to the server.
1752   * @param interactive whether to ask the user to provide interactively
1753   * base DNs if none of the provided base DNs can be purged.
1754   */
1755  private void checkSuffixesForPurgeHistorical(Collection<String> suffixes,
1756      InitialLdapContext ctx, boolean interactive)
1757  {
1758    checkSuffixesForPurgeHistorical(suffixes, getReplicas(ctx), interactive);
1759  }
1760
1761  /**
1762   * Checks that historical can actually be purged in the provided baseDNs
1763   * for the local server.
1764   * @param suffixes the suffixes provided by the user.  This Collection is
1765   * updated with the base DNs that the user provided interactively.
1766   * @param interactive whether to ask the user to provide interactively
1767   * base DNs if none of the provided base DNs can be purged.
1768   */
1769  private void checkSuffixesForLocalPurgeHistorical(Collection<String> suffixes,
1770      boolean interactive)
1771  {
1772    checkSuffixesForPurgeHistorical(suffixes, getLocalReplicas(), interactive);
1773  }
1774
1775  private Collection<ReplicaDescriptor> getLocalReplicas()
1776  {
1777    Collection<ReplicaDescriptor> replicas = new ArrayList<>();
1778    ConfigFromFile configFromFile = new ConfigFromFile();
1779    configFromFile.readConfiguration();
1780    Collection<BackendDescriptor> backends = configFromFile.getBackends();
1781    for (BackendDescriptor backend : backends)
1782    {
1783      for (BaseDNDescriptor baseDN : backend.getBaseDns())
1784      {
1785        SuffixDescriptor suffix = new SuffixDescriptor();
1786        suffix.setDN(baseDN.getDn().toString());
1787
1788        ReplicaDescriptor replica = new ReplicaDescriptor();
1789
1790        if (baseDN.getType() == BaseDNDescriptor.Type.REPLICATED)
1791        {
1792          replica.setReplicationId(baseDN.getReplicaID());
1793        }
1794        else
1795        {
1796          replica.setReplicationId(-1);
1797        }
1798        replica.setBackendName(backend.getBackendID());
1799        replica.setSuffix(suffix);
1800        suffix.setReplicas(singleton(replica));
1801
1802        replicas.add(replica);
1803      }
1804    }
1805    return replicas;
1806  }
1807
1808  private void checkSuffixesForPurgeHistorical(Collection<String> suffixes, Collection<ReplicaDescriptor> replicas,
1809      boolean interactive)
1810  {
1811    TreeSet<String> availableSuffixes = new TreeSet<>();
1812    TreeSet<String> notReplicatedSuffixes = new TreeSet<>();
1813
1814    for (ReplicaDescriptor rep : replicas)
1815    {
1816      String dn = rep.getSuffix().getDN();
1817      if (rep.isReplicated())
1818      {
1819        availableSuffixes.add(dn);
1820      }
1821      else
1822      {
1823        notReplicatedSuffixes.add(dn);
1824      }
1825    }
1826
1827    checkSuffixesForPurgeHistorical(suffixes, availableSuffixes, notReplicatedSuffixes, interactive);
1828  }
1829
1830  private void checkSuffixesForPurgeHistorical(Collection<String> suffixes,
1831      Collection<String> availableSuffixes,
1832      Collection<String> notReplicatedSuffixes,
1833      boolean interactive)
1834  {
1835    if (availableSuffixes.isEmpty())
1836    {
1837      errPrintln();
1838      errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_PURGE_HISTORICAL.get());
1839      suffixes.clear();
1840    }
1841    else
1842    {
1843      // Verify that the provided suffixes are configured in the servers.
1844      TreeSet<String> notFound = new TreeSet<>();
1845      TreeSet<String> alreadyNotReplicated = new TreeSet<>();
1846      for (String dn : suffixes)
1847      {
1848        if (!containsDN(availableSuffixes, dn))
1849        {
1850          if (containsDN(notReplicatedSuffixes, dn))
1851          {
1852            alreadyNotReplicated.add(dn);
1853          }
1854          else
1855          {
1856            notFound.add(dn);
1857          }
1858        }
1859      }
1860      suffixes.removeAll(notFound);
1861      suffixes.removeAll(alreadyNotReplicated);
1862      if (!notFound.isEmpty())
1863      {
1864        errPrintln();
1865        errPrintln(ERR_REPLICATION_PURGE_SUFFIXES_NOT_FOUND.get(toSingleLine(notFound)));
1866      }
1867      if (interactive)
1868      {
1869        askConfirmations(suffixes, availableSuffixes,
1870            ERR_NO_SUFFIXES_AVAILABLE_TO_PURGE_HISTORICAL,
1871            ERR_NO_SUFFIXES_SELECTED_TO_PURGE_HISTORICAL,
1872            INFO_REPLICATION_PURGE_HISTORICAL_PROMPT);
1873      }
1874    }
1875  }
1876
1877  private void askConfirmations(Collection<String> suffixes,
1878      Collection<String> availableSuffixes, Arg0 noSuffixAvailableMsg,
1879      Arg0 noSuffixSelectedMsg, Arg1<Object> confirmationMsgPromt)
1880  {
1881    if (containsOnlySchemaOrAdminSuffix(availableSuffixes))
1882    {
1883      // In interactive mode we do not propose to manage the administration suffix.
1884      errPrintln();
1885      errPrintln(noSuffixAvailableMsg.get());
1886      return;
1887    }
1888
1889    while (suffixes.isEmpty())
1890    {
1891      errPrintln();
1892      errPrintln(noSuffixSelectedMsg.get());
1893      boolean confirmationLimitReached = askConfirmations(confirmationMsgPromt, availableSuffixes, suffixes);
1894      if (confirmationLimitReached)
1895      {
1896        suffixes.clear();
1897        break;
1898      }
1899    }
1900  }
1901
1902  private boolean containsOnlySchemaOrAdminSuffix(Collection<String> suffixes)
1903  {
1904    for (String suffix : suffixes)
1905    {
1906      if (!isSchemaOrInternalAdminSuffix(suffix))
1907      {
1908        return false;
1909      }
1910    }
1911    return true;
1912  }
1913
1914  private boolean isSchemaOrInternalAdminSuffix(String suffix)
1915  {
1916    return areDnsEqual(suffix, ADSContext.getAdministrationSuffixDN())
1917        || areDnsEqual(suffix, Constants.SCHEMA_DN)
1918        || areDnsEqual(suffix,  Constants.REPLICATION_CHANGES_DN);
1919  }
1920
1921  /**
1922   * Based on the data provided in the command-line it initializes replication
1923   * between two servers.
1924   * @return the error code if the operation failed and SUCCESSFUL if it was
1925   * successful.
1926   */
1927  private ReplicationCliReturnCode initializeReplication()
1928  {
1929    SourceDestinationServerUserData uData = new SourceDestinationServerUserData();
1930    if (!argParser.isInteractive())
1931    {
1932      initializeWithArgParser(uData);
1933      return initializeReplication(uData);
1934    }
1935
1936    OperationBetweenSourceAndDestinationServers
1937        initializeReplicationOperations = new OperationBetweenSourceAndDestinationServers()
1938    {
1939      @Override
1940      public boolean continueAfterUserInput(Collection<String> baseDNs, InitialLdapContext source,
1941          InitialLdapContext dest, boolean interactive)
1942      {
1943        checkSuffixesForInitializeReplication(baseDNs, source, dest, interactive);
1944        return baseDNs.isEmpty();
1945      }
1946
1947      @Override
1948      public boolean confirmOperation(SourceDestinationServerUserData uData, InitialLdapContext ctxSource,
1949          InitialLdapContext ctxDestination, boolean defaultValue)
1950      {
1951        return !askConfirmation(getInitializeReplicationPrompt(uData, ctxSource, ctxDestination), defaultValue);
1952      }
1953    };
1954    return promptIfRequired(uData, initializeReplicationOperations) ? initializeReplication(uData) : USER_CANCELLED;
1955  }
1956
1957  /**
1958   * Updates the contents of the provided PurgeHistoricalUserData
1959   * object with the information provided in the command-line.  If some
1960   * information is missing, ask the user to provide valid data.
1961   * We assume that if this method is called we are in interactive mode.
1962   * @param uData the object to be updated.
1963   * @return <CODE>true</CODE> if the object was successfully updated and
1964   * <CODE>false</CODE> if the user canceled the operation.
1965   */
1966  private boolean promptIfRequired(PurgeHistoricalUserData uData)
1967  {
1968    InitialLdapContext ctx = null;
1969    try
1970    {
1971      ctx = getInitialLdapContext(uData);
1972      if (ctx == null)
1973      {
1974        return false;
1975      }
1976
1977      /* Prompt for maximum duration */
1978      int maximumDuration = argParser.getMaximumDuration();
1979      if (!argParser.maximumDurationArg.isPresent())
1980      {
1981        println();
1982        maximumDuration = askInteger(INFO_REPLICATION_PURGE_HISTORICAL_MAXIMUM_DURATION_PROMPT.get(),
1983            getDefaultValue(argParser.maximumDurationArg), logger);
1984      }
1985      uData.setMaximumDuration(maximumDuration);
1986
1987      List<String> suffixes = argParser.getBaseDNs();
1988      if (uData.isOnline())
1989      {
1990        checkSuffixesForPurgeHistorical(suffixes, ctx, true);
1991      }
1992      else
1993      {
1994        checkSuffixesForLocalPurgeHistorical(suffixes, true);
1995      }
1996      if (suffixes.isEmpty())
1997      {
1998        return false;
1999      }
2000      uData.setBaseDNs(suffixes);
2001
2002      if (uData.isOnline())
2003      {
2004        List<? extends TaskEntry> taskEntries = getAvailableTaskEntries(ctx);
2005
2006        TaskScheduleInteraction interaction =
2007            new TaskScheduleInteraction(uData.getTaskSchedule(), argParser.taskArgs, this,
2008                INFO_PURGE_HISTORICAL_TASK_NAME.get());
2009        interaction.setFormatter(formatter);
2010        interaction.setTaskEntries(taskEntries);
2011        try
2012        {
2013          interaction.run();
2014        }
2015        catch (ClientException ce)
2016        {
2017          errPrintln(ce.getMessageObject());
2018          return false;
2019        }
2020      }
2021      return true;
2022    }
2023    finally
2024    {
2025      close(ctx);
2026    }
2027  }
2028
2029  private InitialLdapContext getInitialLdapContext(PurgeHistoricalUserData uData)
2030  {
2031    boolean firstTry = true;
2032    Boolean serverRunning = null;
2033
2034    while (true)
2035    {
2036      boolean promptForConnection = firstTry && argParser.connectionArgumentsPresent();
2037      if (!promptForConnection)
2038      {
2039        if (serverRunning == null)
2040        {
2041          serverRunning = Utilities.isServerRunning(Installation.getLocal().getInstanceDirectory());
2042        }
2043
2044        if (!serverRunning)
2045        {
2046          try
2047          {
2048            println();
2049            promptForConnection = !askConfirmation(
2050                INFO_REPLICATION_PURGE_HISTORICAL_LOCAL_PROMPT.get(), true, logger);
2051          }
2052          catch (ClientException ce)
2053          {
2054            errPrintln(ce.getMessageObject());
2055          }
2056
2057          if (!promptForConnection)
2058          {
2059            uData.setOnline(false);
2060            return null;
2061          }
2062        }
2063      }
2064
2065      try
2066      {
2067        sourceServerCI.run();
2068
2069        InitialLdapContext ctx = createInitialLdapContextInteracting(sourceServerCI);
2070        if (ctx != null)
2071        {
2072          uData.setOnline(true);
2073          uData.setHostName(sourceServerCI.getHostName());
2074          uData.setPort(sourceServerCI.getPortNumber());
2075          uData.setAdminUid(sourceServerCI.getAdministratorUID());
2076          uData.setAdminPwd(sourceServerCI.getBindPassword());
2077        }
2078        return ctx;
2079      }
2080      catch (ClientException ce)
2081      {
2082        logger.warn(LocalizableMessage.raw("Client exception " + ce));
2083        errPrintln();
2084        errPrintln(ce.getMessageObject());
2085        errPrintln();
2086        sourceServerCI.resetConnectionArguments();
2087      }
2088      catch (ArgumentException ae)
2089      {
2090        logger.warn(LocalizableMessage.raw("Argument exception " + ae));
2091        argParser.displayMessageAndUsageReference(getErrStream(), ae.getMessageObject());
2092        return null;
2093      }
2094      firstTry = false;
2095    }
2096  }
2097
2098  private List<? extends TaskEntry> getAvailableTaskEntries(
2099      InitialLdapContext ctx)
2100  {
2101    List<TaskEntry> taskEntries = new ArrayList<>();
2102    List<OpenDsException> exceptions = new ArrayList<>();
2103    ConfigFromDirContext cfg = new ConfigFromDirContext();
2104    cfg.updateTaskInformation(ctx, exceptions, taskEntries);
2105    for (OpenDsException ode : exceptions)
2106    {
2107      logger.warn(LocalizableMessage.raw("Error retrieving task entries: "+ode, ode));
2108    }
2109    return taskEntries;
2110  }
2111
2112  /**
2113   * Updates the contents of the provided EnableReplicationUserData object
2114   * with the information provided in the command-line.  If some information
2115   * is missing, ask the user to provide valid data.
2116   * We assume that if this method is called we are in interactive mode.
2117   * @param uData the object to be updated.
2118   * @return <CODE>true</CODE> if the object was successfully updated and
2119   * <CODE>false</CODE> if the user cancelled the operation.
2120   * @throws ReplicationCliException if a critical error occurs reading the
2121   * ADS.
2122   */
2123  private boolean promptIfRequired(EnableReplicationUserData uData)
2124  throws ReplicationCliException
2125  {
2126    boolean cancelled = false;
2127
2128    boolean administratorDefined = false;
2129
2130    sourceServerCI.setUseAdminOrBindDn(true);
2131
2132    String adminPwd = argParser.getBindPasswordAdmin();
2133    String adminUid = argParser.getAdministratorUID();
2134
2135    /* Try to connect to the first server. */
2136    String host1 = getValue(argParser.server1.hostNameArg);
2137    int port1 = getValue(argParser.server1.portArg);
2138    String bindDn1 = getValue(argParser.server1.bindDnArg);
2139    String pwd1 = argParser.server1.getBindPassword();
2140    String pwd = null;
2141    Map<String, String> pwdFile = null;
2142    if (argParser.server1.bindPasswordArg.isPresent())
2143    {
2144      pwd = argParser.server1.bindPasswordArg.getValue();
2145    }
2146    else if (argParser.server1.bindPasswordFileArg.isPresent())
2147    {
2148      pwdFile = argParser.server1.bindPasswordFileArg.getNameToValueMap();
2149    }
2150    else if (bindDn1 == null)
2151    {
2152      pwd = adminPwd;
2153      if (argParser.getSecureArgsList().getBindPasswordFileArg().isPresent())
2154      {
2155        pwdFile = argParser.getSecureArgsList().getBindPasswordFileArg().
2156          getNameToValueMap();
2157      }
2158    }
2159
2160    /*
2161     * Use a copy of the argument properties since the map might be cleared
2162     * in initializeGlobalArguments.
2163     */
2164    sourceServerCI.initializeGlobalArguments(host1, port1, adminUid, bindDn1, pwd,
2165        pwdFile == null ? null : new LinkedHashMap<String, String>(pwdFile));
2166    InitialLdapContext ctx1 = null;
2167
2168    while (ctx1 == null && !cancelled)
2169    {
2170      try
2171      {
2172        sourceServerCI.setHeadingMessage(INFO_REPLICATION_ENABLE_HOST1_CONNECTION_PARAMETERS.get());
2173        sourceServerCI.run();
2174        host1 = sourceServerCI.getHostName();
2175        port1 = sourceServerCI.getPortNumber();
2176        if (sourceServerCI.getProvidedAdminUID() != null)
2177        {
2178          adminUid = sourceServerCI.getProvidedAdminUID();
2179          if (sourceServerCI.getProvidedBindDN() == null)
2180          {
2181            // If the explicit bind DN is not null, the password corresponds
2182            // to that bind DN.  We are in the case where the user provides
2183            // bind DN on first server and admin UID globally.
2184            adminPwd = sourceServerCI.getBindPassword();
2185          }
2186        }
2187        bindDn1 = sourceServerCI.getBindDN();
2188        pwd1 = sourceServerCI.getBindPassword();
2189
2190        ctx1 = createInitialLdapContextInteracting(sourceServerCI);
2191        if (ctx1 == null)
2192        {
2193          cancelled = true;
2194        }
2195      }
2196      catch (ClientException ce)
2197      {
2198        logger.warn(LocalizableMessage.raw("Client exception "+ce));
2199        errPrintln();
2200        errPrintln(ce.getMessageObject());
2201        errPrintln();
2202        sourceServerCI.resetConnectionArguments();
2203      }
2204      catch (ArgumentException ae)
2205      {
2206        logger.warn(LocalizableMessage.raw("Argument exception "+ae));
2207        argParser.displayMessageAndUsageReference(getErrStream(), ae.getMessageObject());
2208        cancelled = true;
2209      }
2210    }
2211
2212    if (!cancelled)
2213    {
2214      uData.getServer1().setHostName(host1);
2215      uData.getServer1().setPort(port1);
2216      uData.getServer1().setBindDn(bindDn1);
2217      uData.getServer1().setPwd(pwd1);
2218    }
2219    int replicationPort1 = -1;
2220    boolean secureReplication1 = argParser.server1.secureReplicationArg.isPresent();
2221    boolean configureReplicationServer1 = argParser.server1.configureReplicationServer();
2222    boolean configureReplicationDomain1 = argParser.server1.configureReplicationDomain();
2223    if (ctx1 != null)
2224    {
2225      int repPort1 = getReplicationPort(ctx1);
2226      boolean replicationServer1Configured = repPort1 > 0;
2227      if (replicationServer1Configured && !configureReplicationServer1)
2228      {
2229        final LocalizableMessage msg =
2230            INFO_REPLICATION_SERVER_CONFIGURED_WARNING_PROMPT.get(getHostPort(ctx1), repPort1);
2231        if (!askConfirmation(msg, false))
2232        {
2233          cancelled = true;
2234        }
2235      }
2236
2237      // Try to get the replication port for server 1 only if it is required.
2238      if (!cancelled
2239          && configureReplicationServer1
2240          && !replicationServer1Configured
2241          && argParser.advancedArg.isPresent()
2242          && configureReplicationDomain1)
2243      {
2244        // Only ask if the replication domain will be configured (if not
2245        // the replication server MUST be configured).
2246        try
2247        {
2248          configureReplicationServer1 = askConfirmation(
2249              INFO_REPLICATION_ENABLE_REPLICATION_SERVER1_PROMPT.get(),
2250              true, logger);
2251        }
2252        catch (ClientException ce)
2253        {
2254          errPrintln(ce.getMessageObject());
2255          cancelled = true;
2256        }
2257      }
2258      if (!cancelled
2259          && configureReplicationServer1
2260          && !replicationServer1Configured)
2261      {
2262        boolean tryWithDefault = argParser.getReplicationPort1() != -1;
2263        while (replicationPort1 == -1)
2264        {
2265          if (tryWithDefault)
2266          {
2267            replicationPort1 = argParser.getReplicationPort1();
2268            tryWithDefault = false;
2269          }
2270          else
2271          {
2272            replicationPort1 = askPort(
2273                INFO_REPLICATION_ENABLE_REPLICATIONPORT1_PROMPT.get(),
2274                getDefaultValue(argParser.server1.replicationPortArg), logger);
2275            println();
2276          }
2277          if (!argParser.skipReplicationPortCheck() && isLocalHost(host1))
2278          {
2279            if (!SetupUtils.canUseAsPort(replicationPort1))
2280            {
2281              errPrintln();
2282              errPrintln(getCannotBindToPortError(replicationPort1));
2283              errPrintln();
2284              replicationPort1 = -1;
2285            }
2286          }
2287          else if (replicationPort1 == port1)
2288          {
2289            // This is something that we must do in any case... this test is
2290            // already included when we call SetupUtils.canUseAsPort
2291            errPrintln();
2292            errPrintln(ERR_REPLICATION_PORT_AND_REPLICATION_PORT_EQUAL.get(host1, replicationPort1));
2293            errPrintln();
2294            replicationPort1 = -1;
2295          }
2296        }
2297        if (!secureReplication1)
2298        {
2299          try
2300          {
2301            secureReplication1 =
2302              askConfirmation(INFO_REPLICATION_ENABLE_SECURE1_PROMPT.get(replicationPort1),
2303                  false, logger);
2304          }
2305          catch (ClientException ce)
2306          {
2307            errPrintln(ce.getMessageObject());
2308            cancelled = true;
2309          }
2310          println();
2311        }
2312      }
2313      if (!cancelled &&
2314          configureReplicationDomain1 &&
2315          configureReplicationServer1 &&
2316          argParser.advancedArg.isPresent())
2317      {
2318        // Only necessary to ask if the replication server will be configured
2319        try
2320        {
2321          configureReplicationDomain1 = askConfirmation(
2322              INFO_REPLICATION_ENABLE_REPLICATION_DOMAIN1_PROMPT.get(),
2323              true, logger);
2324        }
2325        catch (ClientException ce)
2326        {
2327          errPrintln(ce.getMessageObject());
2328          cancelled = true;
2329        }
2330      }
2331      // If the server contains an ADS. Try to load it and only load it: if
2332      // there are issues with the ADS they will be encountered in the
2333      // enableReplication(EnableReplicationUserData) method.  Here we have
2334      // to load the ADS to ask the user to accept the certificates and
2335      // eventually admin authentication data.
2336      if (!cancelled)
2337      {
2338        AtomicReference<InitialLdapContext> aux = new AtomicReference<>(ctx1);
2339        cancelled = !loadADSAndAcceptCertificates(sourceServerCI, aux, uData, true);
2340        ctx1 = aux.get();
2341      }
2342      if (!cancelled)
2343      {
2344        administratorDefined |= hasAdministrator(ctx1);
2345        if (uData.getAdminPwd() != null)
2346        {
2347          adminPwd = uData.getAdminPwd();
2348        }
2349      }
2350    }
2351    uData.getServer1().setReplicationPort(replicationPort1);
2352    uData.getServer1().setSecureReplication(secureReplication1);
2353    uData.getServer1().setConfigureReplicationServer(configureReplicationServer1);
2354    uData.getServer1().setConfigureReplicationDomain(configureReplicationDomain1);
2355    firstServerCommandBuilder = new CommandBuilder();
2356    if (mustPrintCommandBuilder())
2357    {
2358      firstServerCommandBuilder.append(sourceServerCI.getCommandBuilder());
2359    }
2360
2361    /* Prompt for information on the second server. */
2362    String host2 = null;
2363    int port2 = -1;
2364    String bindDn2 = null;
2365    String pwd2 = null;
2366    LDAPConnectionConsoleInteraction destinationServerCI = new LDAPConnectionConsoleInteraction(this,
2367        argParser.getSecureArgsList());
2368    destinationServerCI.resetHeadingDisplayed();
2369
2370    boolean doNotDisplayFirstError = false;
2371
2372    if (!cancelled)
2373    {
2374      host2 = getValue(argParser.server2.hostNameArg);
2375      port2 = getValue(argParser.server2.portArg);
2376      bindDn2 = getValue(argParser.server2.bindDnArg);
2377      pwd2 = argParser.server2.getBindPassword();
2378
2379      pwdFile = null;
2380      pwd = null;
2381      if (argParser.server2.bindPasswordArg.isPresent())
2382      {
2383        pwd = argParser.server2.bindPasswordArg.getValue();
2384      }
2385      else if (argParser.server2.bindPasswordFileArg.isPresent())
2386      {
2387        pwdFile = argParser.server2.bindPasswordFileArg.getNameToValueMap();
2388      }
2389      else if (bindDn2 == null)
2390      {
2391        doNotDisplayFirstError = true;
2392        pwd = adminPwd;
2393        if (argParser.getSecureArgsList().getBindPasswordFileArg().isPresent())
2394        {
2395          pwdFile = argParser.getSecureArgsList().getBindPasswordFileArg().
2396            getNameToValueMap();
2397        }
2398      }
2399
2400      /*
2401       * Use a copy of the argument properties since the map might be cleared
2402       * in initializeGlobalArguments.
2403       */
2404      destinationServerCI.initializeGlobalArguments(host2, port2, adminUid, bindDn2, pwd,
2405          pwdFile == null ? null : new LinkedHashMap<String, String>(pwdFile));
2406      destinationServerCI.setUseAdminOrBindDn(true);
2407    }
2408    InitialLdapContext ctx2 = null;
2409
2410    while (ctx2 == null && !cancelled)
2411    {
2412      try
2413      {
2414        destinationServerCI.setHeadingMessage(INFO_REPLICATION_ENABLE_HOST2_CONNECTION_PARAMETERS.get());
2415        destinationServerCI.run();
2416        host2 = destinationServerCI.getHostName();
2417        port2 = destinationServerCI.getPortNumber();
2418        if (destinationServerCI.getProvidedAdminUID() != null)
2419        {
2420          adminUid = destinationServerCI.getProvidedAdminUID();
2421          if (destinationServerCI.getProvidedBindDN() == null)
2422          {
2423            // If the explicit bind DN is not null, the password corresponds
2424            // to that bind DN.  We are in the case where the user provides
2425            // bind DN on first server and admin UID globally.
2426            adminPwd = destinationServerCI.getBindPassword();
2427          }
2428        }
2429        bindDn2 = destinationServerCI.getBindDN();
2430        pwd2 = destinationServerCI.getBindPassword();
2431
2432        boolean error = false;
2433        if (host1.equalsIgnoreCase(host2) && port1 == port2)
2434        {
2435          port2 = -1;
2436          errPrintln();
2437          errPrintln(ERR_REPLICATION_ENABLE_SAME_SERVER_PORT.get(host1, port1));
2438          errPrintln();
2439          error = true;
2440        }
2441
2442        if (!error)
2443        {
2444          ctx2 = createInitialLdapContextInteracting(destinationServerCI, true);
2445          if (ctx2 == null)
2446          {
2447            cancelled = true;
2448          }
2449        }
2450      }
2451      catch (ClientException ce)
2452      {
2453        logger.warn(LocalizableMessage.raw("Client exception "+ce));
2454        if (!doNotDisplayFirstError)
2455        {
2456          errPrintln();
2457          errPrintln(ce.getMessageObject());
2458          errPrintln();
2459          destinationServerCI.resetConnectionArguments();
2460        }
2461        else
2462        {
2463          // Reset only the credential parameters.
2464          destinationServerCI.resetConnectionArguments();
2465          destinationServerCI.initializeGlobalArguments(host2, port2, null, null, null, null);
2466        }
2467      }
2468      catch (ArgumentException ae)
2469      {
2470        logger.warn(LocalizableMessage.raw("Argument exception "+ae));
2471        argParser.displayMessageAndUsageReference(getErrStream(), ae.getMessageObject());
2472        cancelled = true;
2473      }
2474      finally
2475      {
2476        doNotDisplayFirstError = false;
2477      }
2478    }
2479
2480    if (!cancelled)
2481    {
2482      uData.getServer2().setHostName(host2);
2483      uData.getServer2().setPort(port2);
2484      uData.getServer2().setBindDn(bindDn2);
2485      uData.getServer2().setPwd(pwd2);
2486    }
2487
2488    int replicationPort2 = -1;
2489    boolean secureReplication2 = argParser.server2.secureReplicationArg.isPresent();
2490    boolean configureReplicationServer2 = argParser.server2.configureReplicationServer();
2491    boolean configureReplicationDomain2 = argParser.server2.configureReplicationDomain();
2492    if (ctx2 != null)
2493    {
2494      int repPort2 = getReplicationPort(ctx2);
2495      boolean replicationServer2Configured = repPort2 > 0;
2496      if (replicationServer2Configured && !configureReplicationServer2)
2497      {
2498        final LocalizableMessage prompt =
2499            INFO_REPLICATION_SERVER_CONFIGURED_WARNING_PROMPT.get(getHostPort(ctx2), repPort2);
2500        if (!askConfirmation(prompt, false))
2501        {
2502          cancelled = true;
2503        }
2504      }
2505
2506      // Try to get the replication port for server 2 only if it is required.
2507      if (!cancelled
2508          && configureReplicationServer2
2509          && !replicationServer2Configured)
2510      {
2511        // Only ask if the replication domain will be configured (if not the
2512        // replication server MUST be configured).
2513        if (argParser.advancedArg.isPresent() &&
2514            configureReplicationDomain2)
2515        {
2516          try
2517          {
2518            configureReplicationServer2 = askConfirmation(
2519                INFO_REPLICATION_ENABLE_REPLICATION_SERVER2_PROMPT.get(),
2520                true, logger);
2521          }
2522          catch (ClientException ce)
2523          {
2524            errPrintln(ce.getMessageObject());
2525            cancelled = true;
2526          }
2527        }
2528        if (!cancelled
2529            && configureReplicationServer2
2530            && !replicationServer2Configured)
2531        {
2532          boolean tryWithDefault = argParser.getReplicationPort2() != -1;
2533          while (replicationPort2 == -1)
2534          {
2535            if (tryWithDefault)
2536            {
2537              replicationPort2 = argParser.getReplicationPort2();
2538              tryWithDefault = false;
2539            }
2540            else
2541            {
2542              replicationPort2 = askPort(
2543                  INFO_REPLICATION_ENABLE_REPLICATIONPORT2_PROMPT.get(),
2544                  getDefaultValue(argParser.server2.replicationPortArg), logger);
2545              println();
2546            }
2547            if (!argParser.skipReplicationPortCheck() &&
2548                isLocalHost(host2))
2549            {
2550              if (!SetupUtils.canUseAsPort(replicationPort2))
2551              {
2552                errPrintln();
2553                errPrintln(getCannotBindToPortError(replicationPort2));
2554                errPrintln();
2555                replicationPort2 = -1;
2556              }
2557            }
2558            else if (replicationPort2 == port2)
2559            {
2560              // This is something that we must do in any case... this test is
2561              // already included when we call SetupUtils.canUseAsPort
2562              errPrintln();
2563              errPrintln(ERR_REPLICATION_PORT_AND_REPLICATION_PORT_EQUAL.get(host2, replicationPort2));
2564              replicationPort2 = -1;
2565            }
2566            if (host1.equalsIgnoreCase(host2)
2567                && replicationPort1 > 0
2568                && replicationPort1 == replicationPort2)
2569            {
2570              errPrintln();
2571              errPrintln(ERR_REPLICATION_SAME_REPLICATION_PORT.get(replicationPort2, host1));
2572              errPrintln();
2573              replicationPort2 = -1;
2574            }
2575          }
2576          if (!secureReplication2)
2577          {
2578            try
2579            {
2580              secureReplication2 =
2581                askConfirmation(INFO_REPLICATION_ENABLE_SECURE2_PROMPT.get(replicationPort2), false, logger);
2582            }
2583            catch (ClientException ce)
2584            {
2585              errPrintln(ce.getMessageObject());
2586              cancelled = true;
2587            }
2588            println();
2589          }
2590        }
2591      }
2592      if (!cancelled &&
2593          configureReplicationDomain2 &&
2594          configureReplicationServer2 &&
2595          argParser.advancedArg.isPresent())
2596      {
2597        // Only necessary to ask if the replication server will be configured
2598        try
2599        {
2600          configureReplicationDomain2 = askConfirmation(
2601              INFO_REPLICATION_ENABLE_REPLICATION_DOMAIN2_PROMPT.get(),
2602              true, logger);
2603        }
2604        catch (ClientException ce)
2605        {
2606          errPrintln(ce.getMessageObject());
2607          cancelled = true;
2608        }
2609      }
2610      // If the server contains an ADS. Try to load it and only load it: if
2611      // there are issues with the ADS they will be encountered in the
2612      // enableReplication(EnableReplicationUserData) method.  Here we have
2613      // to load the ADS to ask the user to accept the certificates.
2614      if (!cancelled)
2615      {
2616        AtomicReference<InitialLdapContext> aux = new AtomicReference<>(ctx2);
2617        cancelled = !loadADSAndAcceptCertificates(destinationServerCI, aux, uData, false);
2618        ctx2 = aux.get();
2619      }
2620      if (!cancelled)
2621      {
2622        administratorDefined |= hasAdministrator(ctx2);
2623      }
2624    }
2625    uData.getServer2().setReplicationPort(replicationPort2);
2626    uData.getServer2().setSecureReplication(secureReplication2);
2627    uData.getServer2().setConfigureReplicationServer(configureReplicationServer2);
2628    uData.getServer2().setConfigureReplicationDomain(configureReplicationDomain2);
2629
2630    // If the adminUid and adminPwd are not set in the EnableReplicationUserData
2631    // object, that means that there are no administrators and that they
2632    // must be created. The adminUId and adminPwd are updated inside
2633    // loadADSAndAcceptCertificates.
2634    boolean promptedForAdmin = false;
2635
2636    // There is a case where we haven't had need for the administrator
2637    // credentials even if the administrators are defined: where all the servers
2638    // can be accessed with another user (for instance if all the server have
2639    // defined cn=directory manager and all the entries have the same password).
2640    if (!cancelled && uData.getAdminUid() == null && !administratorDefined)
2641    {
2642      if (adminUid == null)
2643      {
2644        println(INFO_REPLICATION_ENABLE_ADMINISTRATOR_MUST_BE_CREATED.get());
2645        promptedForAdmin = true;
2646        adminUid= askForAdministratorUID(
2647            getDefaultValue(argParser.getAdminUidArg()), logger);
2648        println();
2649      }
2650      uData.setAdminUid(adminUid);
2651    }
2652
2653    if (uData.getAdminPwd() == null)
2654    {
2655      uData.setAdminPwd(adminPwd);
2656    }
2657    if (!cancelled && uData.getAdminPwd() == null && !administratorDefined)
2658    {
2659      adminPwd = null;
2660      int nPasswordPrompts = 0;
2661      while (adminPwd == null)
2662      {
2663        if (nPasswordPrompts > CONFIRMATION_MAX_TRIES)
2664        {
2665          errPrintln(ERR_CONFIRMATION_TRIES_LIMIT_REACHED.get(
2666              CONFIRMATION_MAX_TRIES));
2667          cancelled = true;
2668          break;
2669        }
2670        nPasswordPrompts ++;
2671        if (!promptedForAdmin)
2672        {
2673          println();
2674          println(INFO_REPLICATION_ENABLE_ADMINISTRATOR_MUST_BE_CREATED.get());
2675          println();
2676        }
2677        while (adminPwd == null)
2678        {
2679          adminPwd = askForAdministratorPwd(logger);
2680          println();
2681        }
2682        String adminPwdConfirm = null;
2683        while (adminPwdConfirm == null)
2684        {
2685          try
2686          {
2687            adminPwdConfirm = String.valueOf(readPassword(INFO_ADMINISTRATOR_PWD_CONFIRM_PROMPT.get()));
2688          }
2689          catch (ClientException ex)
2690          {
2691            logger.warn(LocalizableMessage.raw("Error reading input: " + ex, ex));
2692          }
2693          println();
2694        }
2695        if (!adminPwd.equals(adminPwdConfirm))
2696        {
2697          println();
2698          errPrintln(ERR_ADMINISTRATOR_PWD_DO_NOT_MATCH.get());
2699          println();
2700          adminPwd = null;
2701        }
2702      }
2703      uData.setAdminPwd(adminPwd);
2704    }
2705
2706    if (!cancelled)
2707    {
2708      List<String> suffixes = argParser.getBaseDNs();
2709      checkSuffixesForEnableReplication(suffixes, ctx1, ctx2, true, uData);
2710      cancelled = suffixes.isEmpty();
2711
2712      uData.setBaseDNs(suffixes);
2713    }
2714
2715    close(ctx1, ctx2);
2716    uData.setReplicateSchema(!argParser.noSchemaReplication());
2717
2718    return !cancelled;
2719  }
2720
2721  /**
2722   * Updates the contents of the provided DisableReplicationUserData object
2723   * with the information provided in the command-line.  If some information
2724   * is missing, ask the user to provide valid data.
2725   * We assume that if this method is called we are in interactive mode.
2726   * @param uData the object to be updated.
2727   * @return <CODE>true</CODE> if the object was successfully updated and
2728   * <CODE>false</CODE> if the user cancelled the operation.
2729   * @throws ReplicationCliException if there is a critical error reading the
2730   * ADS.
2731   */
2732  private boolean promptIfRequired(DisableReplicationUserData uData)
2733  throws ReplicationCliException
2734  {
2735    boolean cancelled = false;
2736
2737    String adminPwd = argParser.getBindPasswordAdmin();
2738    String adminUid = argParser.getAdministratorUID();
2739    String bindDn = argParser.getBindDNToDisable();
2740
2741    // This is done because we want to ask explicitly for this
2742
2743    String host = argParser.getHostNameToDisable();
2744    int port = argParser.getPortToDisable();
2745
2746    /* Try to connect to the server. */
2747    InitialLdapContext ctx = null;
2748
2749    while (ctx == null && !cancelled)
2750    {
2751      try
2752      {
2753        sourceServerCI.setUseAdminOrBindDn(true);
2754        sourceServerCI.run();
2755        host = sourceServerCI.getHostName();
2756        port = sourceServerCI.getPortNumber();
2757        bindDn = sourceServerCI.getProvidedBindDN();
2758        adminUid = sourceServerCI.getProvidedAdminUID();
2759        adminPwd = sourceServerCI.getBindPassword();
2760
2761        ctx = createInitialLdapContextInteracting(sourceServerCI);
2762        if (ctx == null)
2763        {
2764          cancelled = true;
2765        }
2766      }
2767      catch (ClientException ce)
2768      {
2769        logger.warn(LocalizableMessage.raw("Client exception "+ce));
2770        errPrintln();
2771        errPrintln(ce.getMessageObject());
2772        errPrintln();
2773        sourceServerCI.resetConnectionArguments();
2774      }
2775      catch (ArgumentException ae)
2776      {
2777        logger.warn(LocalizableMessage.raw("Argument exception "+ae));
2778        argParser.displayMessageAndUsageReference(getErrStream(), ae.getMessageObject());
2779        cancelled = true;
2780      }
2781    }
2782
2783    if (!cancelled)
2784    {
2785      uData.setHostName(host);
2786      uData.setPort(port);
2787      uData.setAdminUid(adminUid);
2788      uData.setBindDn(bindDn);
2789      uData.setAdminPwd(adminPwd);
2790    }
2791    if (ctx != null && adminUid != null)
2792    {
2793      // If the server contains an ADS, try to load it and only load it: if
2794      // there are issues with the ADS they will be encountered in the
2795      // disableReplication(DisableReplicationUserData) method.  Here we have
2796      // to load the ADS to ask the user to accept the certificates and
2797      // eventually admin authentication data.
2798      AtomicReference<InitialLdapContext> aux = new AtomicReference<>(ctx);
2799      cancelled = !loadADSAndAcceptCertificates(sourceServerCI, aux, uData, false);
2800      ctx = aux.get();
2801    }
2802
2803    boolean disableAll = argParser.disableAllArg.isPresent();
2804    boolean disableReplicationServer =
2805      argParser.disableReplicationServerArg.isPresent();
2806    if (disableAll ||
2807        (argParser.advancedArg.isPresent() &&
2808        argParser.getBaseDNs().isEmpty() &&
2809        !disableReplicationServer))
2810    {
2811      try
2812      {
2813        disableAll = askConfirmation(INFO_REPLICATION_PROMPT_DISABLE_ALL.get(),
2814          disableAll, logger);
2815      }
2816      catch (ClientException ce)
2817      {
2818        errPrintln(ce.getMessageObject());
2819        cancelled = true;
2820      }
2821    }
2822    int repPort = getReplicationPort(ctx);
2823    if (!disableAll
2824        && (argParser.advancedArg.isPresent() || disableReplicationServer)
2825        && repPort > 0)
2826    {
2827      try
2828      {
2829        disableReplicationServer = askConfirmation(
2830            INFO_REPLICATION_PROMPT_DISABLE_REPLICATION_SERVER.get(repPort),
2831            disableReplicationServer,
2832            logger);
2833      }
2834      catch (ClientException ce)
2835      {
2836        errPrintln(ce.getMessageObject());
2837        cancelled = true;
2838      }
2839    }
2840    if (disableReplicationServer && repPort < 0)
2841    {
2842      disableReplicationServer = false;
2843      final LocalizableMessage msg = INFO_REPLICATION_PROMPT_NO_REPLICATION_SERVER_TO_DISABLE.get(getHostPort(ctx));
2844      try
2845      {
2846        cancelled = askConfirmation(msg, false, logger);
2847      }
2848      catch (ClientException ce)
2849      {
2850        errPrintln(ce.getMessageObject());
2851        cancelled = true;
2852      }
2853    }
2854    if (repPort > 0 && disableAll)
2855    {
2856      disableReplicationServer = true;
2857    }
2858    uData.setDisableAll(disableAll);
2859    uData.setDisableReplicationServer(disableReplicationServer);
2860    if (!cancelled && !disableAll)
2861    {
2862      List<String> suffixes = argParser.getBaseDNs();
2863      checkSuffixesForDisableReplication(suffixes, ctx, true, !disableReplicationServer);
2864      cancelled = suffixes.isEmpty() && !disableReplicationServer;
2865
2866      uData.setBaseDNs(suffixes);
2867
2868      if (!uData.disableReplicationServer() && repPort > 0 &&
2869          disableAllBaseDns(ctx, uData) && !argParser.advancedArg.isPresent())
2870      {
2871        try
2872        {
2873          uData.setDisableReplicationServer(askConfirmation(
2874              INFO_REPLICATION_DISABLE_ALL_SUFFIXES_DISABLE_REPLICATION_SERVER.get(getHostPort(ctx), repPort), true,
2875              logger));
2876        }
2877        catch (ClientException ce)
2878        {
2879          errPrintln(ce.getMessageObject());
2880          cancelled = true;
2881        }
2882      }
2883    }
2884
2885    if (!cancelled)
2886    {
2887      // Ask for confirmation to disable if not already done.
2888      boolean disableADS = false;
2889      boolean disableSchema = false;
2890      for (String dn : uData.getBaseDNs())
2891      {
2892        if (areDnsEqual(ADSContext.getAdministrationSuffixDN(), dn))
2893        {
2894          disableADS = true;
2895        }
2896        else if (areDnsEqual(Constants.SCHEMA_DN, dn))
2897        {
2898          disableSchema = true;
2899        }
2900      }
2901      if (disableADS)
2902      {
2903        println();
2904        LocalizableMessage msg = INFO_REPLICATION_CONFIRM_DISABLE_ADS.get(ADSContext.getAdministrationSuffixDN());
2905        cancelled = !askConfirmation(msg, true);
2906        println();
2907      }
2908      if (disableSchema)
2909      {
2910        println();
2911        LocalizableMessage msg = INFO_REPLICATION_CONFIRM_DISABLE_SCHEMA.get();
2912        cancelled = !askConfirmation(msg, true);
2913        println();
2914      }
2915      if (!disableSchema && !disableADS)
2916      {
2917        println();
2918        if (!uData.disableAll() && !uData.getBaseDNs().isEmpty())
2919        {
2920          cancelled = !askConfirmation(INFO_REPLICATION_CONFIRM_DISABLE_GENERIC.get(), true);
2921        }
2922        println();
2923      }
2924    }
2925
2926    close(ctx);
2927
2928    return !cancelled;
2929  }
2930
2931  /**
2932   * Updates the contents of the provided InitializeAllReplicationUserData
2933   * object with the information provided in the command-line.  If some
2934   * information is missing, ask the user to provide valid data.
2935   * We assume that if this method is called we are in interactive mode.
2936   * @param uData the object to be updated.
2937   * @return <CODE>true</CODE> if the object was successfully updated and
2938   * <CODE>false</CODE> if the user cancelled the operation.
2939   */
2940  private boolean promptIfRequired(InitializeAllReplicationUserData uData)
2941  {
2942    InitialLdapContext ctx = null;
2943    try
2944    {
2945      ctx = getInitialLdapContext(uData);
2946      if (ctx == null)
2947      {
2948        return false;
2949      }
2950
2951      List<String> suffixes = argParser.getBaseDNs();
2952      checkSuffixesForInitializeReplication(suffixes, ctx, true);
2953      if (suffixes.isEmpty())
2954      {
2955        return false;
2956      }
2957      uData.setBaseDNs(suffixes);
2958
2959      // Ask for confirmation to initialize.
2960      println();
2961      if (!askConfirmation(getPrompt(uData, ctx), true))
2962      {
2963        return false;
2964      }
2965      println();
2966      return true;
2967    }
2968    finally
2969    {
2970      close(ctx);
2971    }
2972  }
2973
2974  private LocalizableMessage getPrompt(InitializeAllReplicationUserData uData, InitialLdapContext ctx)
2975  {
2976    String hostPortSource = getHostPort(ctx);
2977    if (initializeADS(uData.getBaseDNs()))
2978    {
2979      return INFO_REPLICATION_CONFIRM_INITIALIZE_ALL_ADS.get(ADSContext.getAdministrationSuffixDN(), hostPortSource);
2980    }
2981    return INFO_REPLICATION_CONFIRM_INITIALIZE_ALL_GENERIC.get(hostPortSource);
2982  }
2983
2984  private boolean askConfirmation(final LocalizableMessage msg, final boolean defaultValue)
2985  {
2986    try
2987    {
2988      return askConfirmation(msg, defaultValue, logger);
2989    }
2990    catch (ClientException ce)
2991    {
2992      errPrintln(ce.getMessageObject());
2993      return false;
2994    }
2995  }
2996
2997  /**
2998   * Updates the contents of the provided user data
2999   * object with the information provided in the command-line.
3000   * If some information is missing, ask the user to provide valid data.
3001   * We assume that if this method is called we are in interactive mode.
3002   * @param uData the object to be updated.
3003   * @return <CODE>true</CODE> if the object was successfully updated and
3004   * <CODE>false</CODE> if the user cancelled the operation.
3005   */
3006  private boolean promptIfRequiredForPreOrPost(MonoServerReplicationUserData uData)
3007  {
3008    InitialLdapContext ctx = null;
3009    try
3010    {
3011      ctx = getInitialLdapContext(uData);
3012      if (ctx == null)
3013      {
3014        return false;
3015      }
3016      List<String> suffixes = argParser.getBaseDNs();
3017      checkSuffixesForInitializeReplication(suffixes, ctx, true);
3018      uData.setBaseDNs(suffixes);
3019      return !suffixes.isEmpty();
3020    }
3021    finally
3022    {
3023      close(ctx);
3024    }
3025  }
3026
3027  private InitialLdapContext getInitialLdapContext(MonoServerReplicationUserData uData)
3028  {
3029    // Try to connect to the server.
3030    while (true)
3031    {
3032      try
3033      {
3034        if (uData instanceof InitializeAllReplicationUserData)
3035        {
3036          sourceServerCI.setHeadingMessage(INFO_INITIALIZE_SOURCE_CONNECTION_PARAMETERS.get());
3037        }
3038        sourceServerCI.run();
3039
3040        InitialLdapContext ctx = createInitialLdapContextInteracting(sourceServerCI);
3041        if (ctx != null)
3042        {
3043          uData.setHostName(sourceServerCI.getHostName());
3044          uData.setPort(sourceServerCI.getPortNumber());
3045          uData.setAdminUid(sourceServerCI.getAdministratorUID());
3046          uData.setAdminPwd(sourceServerCI.getBindPassword());
3047          if (uData instanceof StatusReplicationUserData)
3048          {
3049            ((StatusReplicationUserData) uData).setScriptFriendly(argParser.isScriptFriendly());
3050          }
3051        }
3052        return ctx;
3053      }
3054      catch (ClientException ce)
3055      {
3056        logger.warn(LocalizableMessage.raw("Client exception " + ce));
3057        errPrintln();
3058        errPrintln(ce.getMessageObject());
3059        errPrintln();
3060        sourceServerCI.resetConnectionArguments();
3061      }
3062      catch (ArgumentException ae)
3063      {
3064        logger.warn(LocalizableMessage.raw("Argument exception " + ae));
3065        argParser.displayMessageAndUsageReference(getErrStream(), ae.getMessageObject());
3066        return null;
3067      }
3068    }
3069  }
3070
3071  /**
3072   * Updates the contents of the provided StatusReplicationUserData object
3073   * with the information provided in the command-line.  If some information
3074   * is missing, ask the user to provide valid data.
3075   * We assume that if this method is called we are in interactive mode.
3076   * @param uData the object to be updated.
3077   * @return <CODE>true</CODE> if the object was successfully updated and
3078   * <CODE>false</CODE> if the user cancelled the operation.
3079   * @throws ReplicationCliException if a critical error occurs reading the
3080   * ADS.
3081   */
3082  private boolean promptIfRequired(StatusReplicationUserData uData)
3083  throws ReplicationCliException
3084  {
3085    InitialLdapContext ctx = null;
3086    try
3087    {
3088      ctx = getInitialLdapContext(uData);
3089      if (ctx == null)
3090      {
3091        return false;
3092      }
3093
3094      // If the server contains an ADS, try to load it and only load it: if
3095      // there are issues with the ADS they will be encountered in the
3096      // statusReplication(StatusReplicationUserData) method. Here we have
3097      // to load the ADS to ask the user to accept the certificates and
3098      // eventually admin authentication data.
3099      AtomicReference<InitialLdapContext> aux = new AtomicReference<>(ctx);
3100      boolean cancelled = !loadADSAndAcceptCertificates(sourceServerCI, aux, uData, false);
3101      ctx = aux.get();
3102      if (cancelled)
3103      {
3104        return false;
3105      }
3106
3107      if (!cancelled)
3108      {
3109        uData.setBaseDNs(argParser.getBaseDNs());
3110      }
3111      return !cancelled;
3112    }
3113    finally
3114    {
3115      close(ctx);
3116    }
3117  }
3118
3119  /**
3120   * Updates the contents of the provided InitializeReplicationUserData object
3121   * with the information provided in the command-line.  If some information
3122   * is missing, ask the user to provide valid data.
3123   * We assume that if this method is called we are in interactive mode.
3124   * @param uData the object to be updated.
3125   * @param serversOperations Additional processing for the command
3126   * @return <CODE>true</CODE> if the object was successfully updated and
3127   * <CODE>false</CODE> if the user cancelled the operation.
3128   */
3129  private boolean promptIfRequired(SourceDestinationServerUserData uData,
3130      OperationBetweenSourceAndDestinationServers serversOperations)
3131  {
3132    boolean cancelled = false;
3133
3134    String adminPwd = argParser.getBindPasswordAdmin();
3135    String adminUid = argParser.getAdministratorUID();
3136
3137    String hostSource = argParser.getHostNameSource();
3138    int portSource = argParser.getPortSource();
3139
3140    Map<String, String> pwdFile = null;
3141    if (argParser.getSecureArgsList().getBindPasswordFileArg().isPresent())
3142    {
3143      pwdFile = argParser.getSecureArgsList().getBindPasswordFileArg().getNameToValueMap();
3144    }
3145
3146    /*
3147     * Use a copy of the argument properties since the map might be cleared
3148     * in initializeGlobalArguments.
3149     */
3150    sourceServerCI.initializeGlobalArguments(hostSource, portSource, adminUid, null, adminPwd,
3151        pwdFile == null ? null : new LinkedHashMap<String, String>(pwdFile));
3152    /* Try to connect to the source server. */
3153    InitialLdapContext ctxSource = null;
3154
3155    while (ctxSource == null && !cancelled)
3156    {
3157      try
3158      {
3159        sourceServerCI.setHeadingMessage(INFO_INITIALIZE_SOURCE_CONNECTION_PARAMETERS.get());
3160        sourceServerCI.run();
3161        hostSource = sourceServerCI.getHostName();
3162        portSource = sourceServerCI.getPortNumber();
3163        adminUid = sourceServerCI.getAdministratorUID();
3164        adminPwd = sourceServerCI.getBindPassword();
3165
3166        ctxSource = createInitialLdapContextInteracting(sourceServerCI);
3167
3168        if (ctxSource == null)
3169        {
3170          cancelled = true;
3171        }
3172      }
3173      catch (ClientException ce)
3174      {
3175        logger.warn(LocalizableMessage.raw("Client exception "+ce));
3176        errPrintln();
3177        errPrintln(ce.getMessageObject());
3178        errPrintln();
3179        sourceServerCI.resetConnectionArguments();
3180      }
3181      catch (ArgumentException ae)
3182      {
3183        logger.warn(LocalizableMessage.raw("Argument exception "+ae));
3184        argParser.displayMessageAndUsageReference(getErrStream(), ae.getMessageObject());
3185        cancelled = true;
3186      }
3187    }
3188    if (!cancelled)
3189    {
3190      uData.setHostNameSource(hostSource);
3191      uData.setPortSource(portSource);
3192      uData.setAdminUid(adminUid);
3193      uData.setAdminPwd(adminPwd);
3194    }
3195
3196    firstServerCommandBuilder = new CommandBuilder();
3197    if (mustPrintCommandBuilder())
3198    {
3199      firstServerCommandBuilder.append(sourceServerCI.getCommandBuilder());
3200    }
3201
3202    /* Prompt for destination server credentials */
3203    String hostDestination = argParser.getHostNameDestination();
3204    int portDestination = argParser.getPortDestination();
3205
3206    /*
3207     * Use a copy of the argument properties since the map might be cleared
3208     * in initializeGlobalArguments.
3209     */
3210    LDAPConnectionConsoleInteraction destinationServerCI = new LDAPConnectionConsoleInteraction(this,
3211        argParser.getSecureArgsList());
3212    destinationServerCI.initializeGlobalArguments(hostDestination, portDestination, adminUid, null, adminPwd,
3213        pwdFile == null ? null : new LinkedHashMap<String, String>(pwdFile));
3214    /* Try to connect to the destination server. */
3215    InitialLdapContext ctxDestination = null;
3216
3217    destinationServerCI.resetHeadingDisplayed();
3218    while (ctxDestination == null && !cancelled)
3219    {
3220      try
3221      {
3222        destinationServerCI.setHeadingMessage(INFO_INITIALIZE_DESTINATION_CONNECTION_PARAMETERS.get());
3223        destinationServerCI.run();
3224        hostDestination = destinationServerCI.getHostName();
3225        portDestination = destinationServerCI.getPortNumber();
3226
3227        boolean error = false;
3228        if (hostSource.equalsIgnoreCase(hostDestination)
3229            && portSource == portDestination)
3230        {
3231          portDestination = -1;
3232          errPrintln();
3233          errPrintln(ERR_SOURCE_DESTINATION_INITIALIZE_SAME_SERVER_PORT.get(hostSource, portSource));
3234          errPrintln();
3235          error = true;
3236        }
3237
3238        if (!error)
3239        {
3240          ctxDestination = createInitialLdapContextInteracting(destinationServerCI, true);
3241
3242          if (ctxDestination == null)
3243          {
3244            cancelled = true;
3245          }
3246        }
3247      }
3248      catch (ClientException ce)
3249      {
3250        logger.warn(LocalizableMessage.raw("Client exception "+ce));
3251        errPrintln();
3252        errPrintln(ce.getMessageObject());
3253        errPrintln();
3254        destinationServerCI.resetConnectionArguments();
3255      }
3256      catch (ArgumentException ae)
3257      {
3258        logger.warn(LocalizableMessage.raw("Argument exception "+ae));
3259        argParser.displayMessageAndUsageReference(getErrStream(), ae.getMessageObject());
3260        cancelled = true;
3261      }
3262    }
3263    if (!cancelled)
3264    {
3265      uData.setHostNameDestination(hostDestination);
3266      uData.setPortDestination(portDestination);
3267    }
3268
3269    if (!cancelled)
3270    {
3271      List<String> suffixes = argParser.getBaseDNs();
3272      cancelled = serversOperations.continueAfterUserInput(suffixes, ctxSource, ctxDestination, true);
3273      uData.setBaseDNs(suffixes);
3274    }
3275
3276    if (!cancelled)
3277    {
3278      println();
3279      cancelled = serversOperations.confirmOperation(uData, ctxSource, ctxDestination, true);
3280      println();
3281    }
3282
3283    close(ctxSource, ctxDestination);
3284    return !cancelled;
3285  }
3286
3287  private LocalizableMessage getInitializeReplicationPrompt(SourceDestinationServerUserData uData,
3288      InitialLdapContext ctxSource, InitialLdapContext ctxDestination)
3289  {
3290    String hostPortSource = getHostPort(ctxSource);
3291    String hostPortDestination = getHostPort(ctxDestination);
3292    if (initializeADS(uData.getBaseDNs()))
3293    {
3294      final String adminSuffixDN = ADSContext.getAdministrationSuffixDN();
3295      return INFO_REPLICATION_CONFIRM_INITIALIZE_ADS.get(adminSuffixDN, hostPortDestination, hostPortSource);
3296    }
3297    return INFO_REPLICATION_CONFIRM_INITIALIZE_GENERIC.get(hostPortDestination, hostPortSource);
3298  }
3299
3300  private boolean initializeADS(List<String> baseDNs)
3301  {
3302    for (String dn : baseDNs)
3303    {
3304      if (areDnsEqual(ADSContext.getAdministrationSuffixDN(), dn))
3305      {
3306        return true;
3307      }
3308    }
3309    return false;
3310  }
3311
3312  /**
3313   * Returns the trust manager to be used by this application.
3314   * @param ci the LDAP connection to the server
3315   * @return the trust manager to be used by this application.
3316   */
3317  private ApplicationTrustManager getTrustManager(LDAPConnectionConsoleInteraction ci)
3318  {
3319    return isInteractive() ? ci.getTrustManager() : argParser.getTrustManager();
3320  }
3321
3322  /**
3323   * Initializes the contents of the provided enable replication user data
3324   * object with what was provided in the command-line without prompting to the
3325   * user.
3326   * @param uData the enable replication user data object to be initialized.
3327   */
3328  private void initializeWithArgParser(EnableReplicationUserData uData)
3329  {
3330    initialize(uData);
3331
3332    final String adminDN = getAdministratorDN(uData.getAdminUid());
3333    final String adminPwd = uData.getAdminPwd();
3334    setConnectionDetails(uData.getServer1(), argParser.server1, adminDN, adminPwd);
3335    setConnectionDetails(uData.getServer2(), argParser.server2, adminDN, adminPwd);
3336
3337    uData.setReplicateSchema(!argParser.noSchemaReplication());
3338
3339    setReplicationDetails(uData.getServer1(), argParser.server1);
3340    setReplicationDetails(uData.getServer2(), argParser.server2);
3341  }
3342
3343  private void setConnectionDetails(
3344      EnableReplicationServerData server, ServerArgs args, String adminDN, String adminPwd)
3345  {
3346    server.setHostName(getValueOrDefault(args.hostNameArg));
3347    server.setPort(getValueOrDefault(args.portArg));
3348
3349    String pwd = args.getBindPassword();
3350    if (pwd == null)
3351    {
3352      server.setBindDn(adminDN);
3353      server.setPwd(adminPwd);
3354    }
3355    else
3356    {
3357      // Best-effort: try to use admin, if it does not work, use bind DN.
3358      try
3359      {
3360        InitialLdapContext ctx = createAdministrativeContext(server.getHostName(), server.getPort(),
3361            useSSL, useStartTLS, adminDN, adminPwd, getConnectTimeout(), getTrustManager(sourceServerCI));
3362        server.setBindDn(adminDN);
3363        server.setPwd(adminPwd);
3364        ctx.close();
3365      }
3366      catch (Throwable t)
3367      {
3368        server.setBindDn(getValueOrDefault(args.bindDnArg));
3369        server.setPwd(pwd);
3370      }
3371    }
3372  }
3373
3374  private void setReplicationDetails(EnableReplicationServerData server, ServerArgs args)
3375  {
3376    server.setSecureReplication(args.secureReplicationArg.isPresent());
3377    server.setConfigureReplicationDomain(args.configureReplicationDomain());
3378    server.setConfigureReplicationServer(args.configureReplicationServer());
3379    if (server.configureReplicationServer())
3380    {
3381      server.setReplicationPort(getValueOrDefault(args.replicationPortArg));
3382    }
3383  }
3384
3385  /**
3386   * Initializes the contents of the provided initialize replication user data
3387   * object with what was provided in the command-line without prompting to the
3388   * user.
3389   * @param uData the initialize replication user data object to be initialized.
3390   */
3391  private void initializeWithArgParser(SourceDestinationServerUserData uData)
3392  {
3393    initialize(uData);
3394
3395    uData.setHostNameSource(argParser.getHostNameSourceOrDefault());
3396    uData.setPortSource(argParser.getPortSourceOrDefault());
3397    uData.setHostNameDestination(argParser.getHostNameDestinationOrDefault());
3398    uData.setPortDestination(argParser.getPortDestinationOrDefault());
3399  }
3400
3401  /**
3402   * Initializes the contents of the provided disable replication user data
3403   * object with what was provided in the command-line without prompting to the
3404   * user.
3405   * @param uData the disable replication user data object to be initialized.
3406   */
3407  private void initializeWithArgParser(DisableReplicationUserData uData)
3408  {
3409    uData.setBaseDNs(new LinkedList<String>(argParser.getBaseDNs()));
3410    String adminUid = argParser.getAdministratorUID();
3411    String bindDn = argParser.getBindDNToDisable();
3412    if (bindDn == null && adminUid == null)
3413    {
3414      adminUid = argParser.getAdministratorUIDOrDefault();
3415      bindDn = getAdministratorDN(adminUid);
3416    }
3417    uData.setAdminUid(adminUid);
3418    uData.setBindDn(bindDn);
3419    uData.setAdminPwd(argParser.getBindPasswordAdmin());
3420
3421    uData.setHostName(argParser.getHostNameToDisableOrDefault());
3422    uData.setPort(argParser.getPortToDisableOrDefault());
3423
3424    uData.setDisableAll(argParser.disableAllArg.isPresent());
3425    uData.setDisableReplicationServer(argParser.disableReplicationServerArg.isPresent());
3426  }
3427
3428  /**
3429   * Initializes the contents of the provided user data object with what was
3430   * provided in the command-line without prompting to the user.
3431   * @param uData the user data object to be initialized.
3432   */
3433  private void initializeWithArgParser(MonoServerReplicationUserData uData)
3434  {
3435    initialize(uData);
3436
3437    uData.setHostName(argParser.getHostNameToInitializeAllOrDefault());
3438    uData.setPort(argParser.getPortToInitializeAllOrDefault());
3439  }
3440
3441  /**
3442   * Initializes the contents of the provided status replication user data
3443   * object with what was provided in the command-line without prompting to the
3444   * user.
3445   * @param uData the status replication user data object to be initialized.
3446   */
3447  private void initializeWithArgParser(StatusReplicationUserData uData)
3448  {
3449    initialize(uData);
3450
3451    uData.setHostName(argParser.getHostNameToStatusOrDefault());
3452    uData.setPort(argParser.getPortToStatusOrDefault());
3453    uData.setScriptFriendly(argParser.isScriptFriendly());
3454  }
3455
3456  private void initialize(ReplicationUserData uData)
3457  {
3458    uData.setBaseDNs(new LinkedList<String>(argParser.getBaseDNs()));
3459    uData.setAdminUid(argParser.getAdministratorUIDOrDefault());
3460    uData.setAdminPwd(argParser.getBindPasswordAdmin());
3461  }
3462
3463  /**
3464   * Tells whether the server to which the LdapContext is connected has a
3465   * replication port or not.
3466   * @param ctx the InitialLdapContext to be used.
3467   * @return <CODE>true</CODE> if the replication port for the server could
3468   * be found and <CODE>false</CODE> otherwise.
3469   */
3470  private boolean hasReplicationPort(InitialLdapContext ctx)
3471  {
3472    return getReplicationPort(ctx) != -1;
3473  }
3474
3475  /**
3476   * Returns the replication port of server to which the LdapContext is
3477   * connected and -1 if the replication port could not be found.
3478   * @param ctx the InitialLdapContext to be used.
3479   * @return the replication port of server to which the LdapContext is
3480   * connected and -1 if the replication port could not be found.
3481   */
3482  private int getReplicationPort(InitialLdapContext ctx)
3483  {
3484    int replicationPort = -1;
3485    try
3486    {
3487      ManagementContext mCtx = LDAPManagementContext.createFromContext(
3488          JNDIDirContextAdaptor.adapt(ctx));
3489      RootCfgClient root = mCtx.getRootConfiguration();
3490
3491      ReplicationSynchronizationProviderCfgClient sync =
3492          (ReplicationSynchronizationProviderCfgClient)
3493          root.getSynchronizationProvider("Multimaster Synchronization");
3494      if (sync.hasReplicationServer())
3495      {
3496        ReplicationServerCfgClient replicationServer =
3497          sync.getReplicationServer();
3498        replicationPort = replicationServer.getReplicationPort();
3499      }
3500    }
3501    catch (Throwable t)
3502    {
3503      logger.warn(LocalizableMessage.raw("Unexpected error retrieving the replication port: " + t, t));
3504    }
3505    return replicationPort;
3506  }
3507
3508  /**
3509   * Loads the ADS with the provided context.  If there are certificates to
3510   * be accepted we prompt them to the user.  If there are errors loading the
3511   * servers we display them to the user and we ask for confirmation.  If the
3512   * provided ctx is not using Global Administrator credentials, we prompt the
3513   * user to provide them and update the provide ReplicationUserData
3514   * accordingly.
3515   *
3516   * @param ci the LDAP connection to the server
3517   * @param ctx the Ldap context to be used in an array: note the context
3518   * may be modified with the new credentials provided by the user.
3519   * @param uData the ReplicationUserData to be updated.
3520   * @param isFirstOrSourceServer whether this is the first server in the
3521   * enable replication subcommand or the source server in the initialize server
3522   * subcommand.
3523   * @throws ReplicationCliException if a critical error occurred.
3524   * @return <CODE>true</CODE> if everything went fine and the user accepted
3525   * all the certificates and confirmed everything.  Returns <CODE>false</CODE>
3526   * if the user did not accept a certificate or any of the confirmation
3527   * messages.
3528   */
3529  private boolean loadADSAndAcceptCertificates(LDAPConnectionConsoleInteraction ci,
3530      AtomicReference<InitialLdapContext> ctx, ReplicationUserData uData, boolean isFirstOrSourceServer)
3531  throws ReplicationCliException
3532  {
3533    boolean cancelled = false;
3534    boolean triedWithUserProvidedAdmin = false;
3535    final InitialLdapContext ctx1 = ctx.get();
3536    String host = getHostName(ctx1);
3537    int port = getPort(ctx1);
3538    boolean isSSL = isSSL(ctx1);
3539    boolean isStartTLS = isStartTLS(ctx1);
3540    if (getTrustManager(ci) == null)
3541    {
3542      // This is required when the user did  connect to the server using SSL or
3543      // Start TLS.  In this case LDAPConnectionConsoleInteraction.run does not
3544      // initialize the keystore and the trust manager is null.
3545      forceTrustManagerInitialization(ci);
3546    }
3547    try
3548    {
3549      ADSContext adsContext = new ADSContext(ctx1);
3550      if (adsContext.hasAdminData())
3551      {
3552        boolean reloadTopology = true;
3553        LinkedList<LocalizableMessage> exceptionMsgs = new LinkedList<>();
3554        while (reloadTopology && !cancelled)
3555        {
3556          // We must recreate the cache because the trust manager in the
3557          // LDAPConnectionConsoleInteraction object might have changed.
3558
3559          TopologyCache cache = new TopologyCache(adsContext,
3560              getTrustManager(ci), getConnectTimeout());
3561          cache.getFilter().setSearchMonitoringInformation(false);
3562          cache.getFilter().setSearchBaseDNInformation(false);
3563          cache.setPreferredConnections(getPreferredConnections(ctx1));
3564          cache.reloadTopology();
3565
3566          reloadTopology = false;
3567          exceptionMsgs.clear();
3568
3569          /* Analyze if we had any exception while loading servers.  For the
3570           * moment only throw the exception found if the user did not provide
3571           * the Administrator DN and this caused a problem authenticating in
3572           * one server or if there is a certificate problem.
3573           */
3574          Set<TopologyCacheException> exceptions = new HashSet<>();
3575          Set<ServerDescriptor> servers = cache.getServers();
3576          for (ServerDescriptor server : servers)
3577          {
3578            TopologyCacheException e = server.getLastException();
3579            if (e != null)
3580            {
3581              exceptions.add(e);
3582            }
3583          }
3584          /* Check the exceptions and see if we throw them or not. */
3585          boolean notGlobalAdministratorError = false;
3586          for (TopologyCacheException e : exceptions)
3587          {
3588            if (notGlobalAdministratorError)
3589            {
3590              break;
3591            }
3592
3593            switch (e.getType())
3594            {
3595              case NOT_GLOBAL_ADMINISTRATOR:
3596                notGlobalAdministratorError = true;
3597                boolean connected = false;
3598
3599                String adminUid = uData.getAdminUid();
3600                String adminPwd = uData.getAdminPwd();
3601
3602                boolean errorDisplayed = false;
3603                while (!connected)
3604                {
3605                  if (!triedWithUserProvidedAdmin && adminPwd == null)
3606                  {
3607                    adminUid = argParser.getAdministratorUIDOrDefault();
3608                    adminPwd = argParser.getBindPasswordAdmin();
3609                    triedWithUserProvidedAdmin = true;
3610                  }
3611                  if (adminPwd == null)
3612                  {
3613                    if (!errorDisplayed)
3614                    {
3615                      println();
3616                      println(
3617                          INFO_NOT_GLOBAL_ADMINISTRATOR_PROVIDED.get());
3618                      errorDisplayed = true;
3619                    }
3620                    adminUid = askForAdministratorUID(
3621                        getDefaultValue(argParser.getAdminUidArg()), logger);
3622                    println();
3623                    adminPwd = askForAdministratorPwd(logger);
3624                    println();
3625                  }
3626                  close(ctx1);
3627                  try
3628                  {
3629                    final InitialLdapContext ctx2 = createAdministrativeContext(host, port, isSSL,
3630                        isStartTLS, getAdministratorDN(adminUid),
3631                        adminPwd, getConnectTimeout(), getTrustManager(ci));
3632                    ctx.set(ctx2);
3633                    adsContext = new ADSContext(ctx2);
3634                    cache = new TopologyCache(adsContext, getTrustManager(ci),
3635                        getConnectTimeout());
3636                    cache.getFilter().setSearchMonitoringInformation(false);
3637                    cache.getFilter().setSearchBaseDNInformation(false);
3638                    cache.setPreferredConnections(getPreferredConnections(ctx2));
3639                    connected = true;
3640                  }
3641                  catch (Throwable t)
3642                  {
3643                    errPrintln();
3644                    errPrintln(
3645                        ERR_ERROR_CONNECTING_TO_SERVER_PROMPT_AGAIN.get(
3646                          getServerRepresentation(host, port), t.getMessage()));
3647                    logger.warn(LocalizableMessage.raw("Complete error stack:", t));
3648                    errPrintln();
3649                  }
3650                }
3651                uData.setAdminUid(adminUid);
3652                uData.setAdminPwd(adminPwd);
3653                if (uData instanceof EnableReplicationUserData)
3654                {
3655                  EnableReplicationUserData enableData = (EnableReplicationUserData) uData;
3656                  EnableReplicationServerData server =
3657                      isFirstOrSourceServer ? enableData.getServer1() : enableData.getServer2();
3658                  server.setBindDn(getAdministratorDN(adminUid));
3659                  server.setPwd(adminPwd);
3660                }
3661                reloadTopology = true;
3662              break;
3663            case GENERIC_CREATING_CONNECTION:
3664              if (isCertificateException(e.getCause()))
3665              {
3666                reloadTopology = true;
3667                cancelled = !ci.promptForCertificateConfirmation(e.getCause(),
3668                    e.getTrustManager(), e.getLdapUrl(), logger);
3669              }
3670              else
3671              {
3672                exceptionMsgs.add(getMessage(e));
3673              }
3674              break;
3675            default:
3676              exceptionMsgs.add(getMessage(e));
3677            }
3678          }
3679        }
3680        if (!exceptionMsgs.isEmpty() && !cancelled)
3681        {
3682          if (uData instanceof StatusReplicationUserData)
3683          {
3684            errPrintln(
3685                ERR_REPLICATION_STATUS_READING_REGISTERED_SERVERS.get(
3686                    getMessageFromCollection(exceptionMsgs,
3687                        Constants.LINE_SEPARATOR)));
3688            errPrintln();
3689          }
3690          else
3691          {
3692            LocalizableMessage msg = ERR_REPLICATION_READING_REGISTERED_SERVERS_CONFIRM_UPDATE_REMOTE.get(
3693                getMessageFromCollection(exceptionMsgs, Constants.LINE_SEPARATOR));
3694            cancelled = !askConfirmation(msg, true);
3695          }
3696        }
3697      }
3698    }
3699    catch (ADSContextException ace)
3700    {
3701      logger.error(LocalizableMessage.raw("Complete error stack:"), ace);
3702      throw new ReplicationCliException(
3703          ERR_REPLICATION_READING_ADS.get(ace.getMessage()),
3704          ERROR_READING_ADS, ace);
3705    }
3706    catch (TopologyCacheException tce)
3707    {
3708      logger.error(LocalizableMessage.raw("Complete error stack:"), tce);
3709      throw new ReplicationCliException(
3710          ERR_REPLICATION_READING_ADS.get(tce.getMessage()),
3711          ERROR_READING_TOPOLOGY_CACHE, tce);
3712    }
3713    return !cancelled;
3714  }
3715
3716  /**
3717   * Tells whether there is a Global Administrator defined in the server
3718   * to which the InitialLdapContext is connected.
3719   * @param ctx the InitialLdapContext.
3720   * @return <CODE>true</CODE> if we could find an administrator and
3721   * <CODE>false</CODE> otherwise.
3722   */
3723  private boolean hasAdministrator(InitialLdapContext ctx)
3724  {
3725    try
3726    {
3727      ADSContext adsContext = new ADSContext(ctx);
3728      if (adsContext.hasAdminData())
3729      {
3730        Set<?> administrators = adsContext.readAdministratorRegistry();
3731        return !administrators.isEmpty();
3732      }
3733    }
3734    catch (Throwable t)
3735    {
3736      logger.warn(LocalizableMessage.raw(
3737          "Unexpected error retrieving the ADS data: "+t, t));
3738    }
3739    return false;
3740  }
3741
3742  /**
3743   * Tells whether there is a Global Administrator corresponding to the provided
3744   * ReplicationUserData defined in the server to which the InitialLdapContext
3745   * is connected.
3746   * @param ctx the InitialLdapContext.
3747   * @param uData the user data
3748   * @return <CODE>true</CODE> if we could find an administrator and
3749   * <CODE>false</CODE> otherwise.
3750   */
3751  private boolean hasAdministrator(InitialLdapContext ctx,
3752      ReplicationUserData uData)
3753  {
3754    String adminUid = uData.getAdminUid();
3755    try
3756    {
3757      ADSContext adsContext = new ADSContext(ctx);
3758      Set<Map<AdministratorProperty, Object>> administrators =
3759        adsContext.readAdministratorRegistry();
3760      for (Map<AdministratorProperty, Object> admin : administrators)
3761      {
3762        String uid = (String)admin.get(AdministratorProperty.UID);
3763        // If the administrator UID is null it means that we are just
3764        // checking for the existence of an administrator
3765        if (uid != null && (uid.equalsIgnoreCase(adminUid) || adminUid == null))
3766        {
3767          return true;
3768        }
3769      }
3770    }
3771    catch (Throwable t)
3772    {
3773      logger.warn(LocalizableMessage.raw(
3774          "Unexpected error retrieving the ADS data: "+t, t));
3775    }
3776    return false;
3777  }
3778
3779  /** Helper type for the {@link #getCommonSuffixes(InitialLdapContext, InitialLdapContext, SuffixRelationType)}. */
3780  private enum SuffixRelationType
3781  {
3782    NOT_REPLICATED, FULLY_REPLICATED, REPLICATED, NOT_FULLY_REPLICATED, ALL
3783  }
3784
3785  /**
3786   * Returns a Collection containing a list of suffixes that are defined in
3787   * two servers at the same time (depending on the value of the argument
3788   * replicated this list contains only the suffixes that are replicated
3789   * between the servers or the list of suffixes that are not replicated
3790   * between the servers).
3791   * @param ctx1 the connection to the first server.
3792   * @param ctx2 the connection to the second server.
3793   * @param type whether to return a list with the suffixes that are
3794   * replicated, fully replicated (replicas have exactly the same list of
3795   * replication servers), not replicated or all the common suffixes.
3796   * @return a Collection containing a list of suffixes that are replicated
3797   * (or those that can be replicated) in two servers.
3798   */
3799  private List<String> getCommonSuffixes(InitialLdapContext ctx1, InitialLdapContext ctx2, SuffixRelationType type)
3800  {
3801    LinkedList<String> suffixes = new LinkedList<>();
3802    try
3803    {
3804      TopologyCacheFilter filter = new TopologyCacheFilter();
3805      filter.setSearchMonitoringInformation(false);
3806      ServerDescriptor server1 = ServerDescriptor.createStandalone(ctx1, filter);
3807      ServerDescriptor server2 = ServerDescriptor.createStandalone(ctx2, filter);
3808
3809      for (ReplicaDescriptor rep1 : server1.getReplicas())
3810      {
3811        for (ReplicaDescriptor rep2 : server2.getReplicas())
3812        {
3813          String rep1SuffixDN = rep1.getSuffix().getDN();
3814          String rep2SuffixDN = rep2.getSuffix().getDN();
3815          boolean areDnsEqual = areDnsEqual(rep1SuffixDN, rep2SuffixDN);
3816          switch (type)
3817          {
3818          case NOT_REPLICATED:
3819            if (!areReplicated(rep1, rep2) && areDnsEqual)
3820            {
3821              suffixes.add(rep1SuffixDN);
3822            }
3823            break;
3824          case FULLY_REPLICATED:
3825            if (areFullyReplicated(rep1, rep2))
3826            {
3827              suffixes.add(rep1SuffixDN);
3828            }
3829            break;
3830          case REPLICATED:
3831            if (areReplicated(rep1, rep2))
3832            {
3833              suffixes.add(rep1SuffixDN);
3834            }
3835            break;
3836          case NOT_FULLY_REPLICATED:
3837            if (!areFullyReplicated(rep1, rep2) && areDnsEqual)
3838            {
3839              suffixes.add(rep1SuffixDN);
3840            }
3841            break;
3842          case ALL:
3843            if (areDnsEqual)
3844            {
3845              suffixes.add(rep1SuffixDN);
3846            }
3847            break;
3848            default:
3849              throw new IllegalStateException("Unknown type: "+type);
3850          }
3851        }
3852      }
3853    }
3854    catch (Throwable t)
3855    {
3856      logger.warn(LocalizableMessage.raw(
3857          "Unexpected error retrieving the server configuration: "+t, t));
3858    }
3859    return suffixes;
3860  }
3861
3862  /**
3863   * Tells whether the two provided replicas are fully replicated or not.  The
3864   * code in fact checks that both replicas have the same DN that they are
3865   * replicated if both servers are replication servers and that both replicas
3866   * make reference to the other replication server.
3867   * @param rep1 the first replica.
3868   * @param rep2 the second replica.
3869   * @return <CODE>true</CODE> if we can assure that the two replicas are
3870   * replicated using the replication server and replication port information
3871   * and <CODE>false</CODE> otherwise.
3872   */
3873  private boolean areFullyReplicated(ReplicaDescriptor rep1,
3874      ReplicaDescriptor rep2)
3875  {
3876    if (areDnsEqual(rep1.getSuffix().getDN(), rep2.getSuffix().getDN()) &&
3877        rep1.isReplicated() && rep2.isReplicated() &&
3878        rep1.getServer().isReplicationServer() &&
3879        rep2.getServer().isReplicationServer())
3880    {
3881     Set<String> servers1 = rep1.getReplicationServers();
3882     Set<String> servers2 = rep2.getReplicationServers();
3883     String server1 = rep1.getServer().getReplicationServerHostPort();
3884     String server2 = rep2.getServer().getReplicationServerHostPort();
3885      return servers1.contains(server2) && servers2.contains(server1);
3886    }
3887    return false;
3888  }
3889
3890  /**
3891   * Tells whether the two provided replicas are replicated or not.  The
3892   * code in fact checks that both replicas have the same DN and that they
3893   * have at least one common replication server referenced.
3894   * @param rep1 the first replica.
3895   * @param rep2 the second replica.
3896   * @return <CODE>true</CODE> if we can assure that the two replicas are
3897   * replicated and <CODE>false</CODE> otherwise.
3898   */
3899  private boolean areReplicated(ReplicaDescriptor rep1, ReplicaDescriptor rep2)
3900  {
3901    if (areDnsEqual(rep1.getSuffix().getDN(), rep2.getSuffix().getDN()) &&
3902        rep1.isReplicated() && rep2.isReplicated())
3903    {
3904      Set<String> servers1 = rep1.getReplicationServers();
3905      Set<String> servers2 = rep2.getReplicationServers();
3906      servers1.retainAll(servers2);
3907      return !servers1.isEmpty();
3908    }
3909    return false;
3910  }
3911
3912  /**
3913   * Returns a Collection containing a list of replicas in a server.
3914   * @param ctx the connection to the server.
3915   * @return a Collection containing a list of replicas in a server.
3916   */
3917  private Collection<ReplicaDescriptor> getReplicas(InitialLdapContext ctx)
3918  {
3919    LinkedList<ReplicaDescriptor> suffixes = new LinkedList<>();
3920    TopologyCacheFilter filter = new TopologyCacheFilter();
3921    filter.setSearchMonitoringInformation(false);
3922    try
3923    {
3924      ServerDescriptor server = ServerDescriptor.createStandalone(ctx, filter);
3925      suffixes.addAll(server.getReplicas());
3926    }
3927    catch (Throwable t)
3928    {
3929      logger.warn(LocalizableMessage.raw(
3930          "Unexpected error retrieving the server configuration: "+t, t));
3931    }
3932    return suffixes;
3933  }
3934
3935  /**
3936   * Enables the replication between two servers using the parameters in the
3937   * provided EnableReplicationUserData.  This method does not prompt to the
3938   * user for information if something is missing.
3939   * @param uData the EnableReplicationUserData object.
3940   * @return ReplicationCliReturnCode.SUCCESSFUL if the operation was
3941   * successful and the replication could be enabled and an error code
3942   * otherwise.
3943   */
3944  private ReplicationCliReturnCode enableReplication(EnableReplicationUserData uData)
3945  {
3946    InitialLdapContext ctx1 = null;
3947    InitialLdapContext ctx2 = null;
3948    try
3949    {
3950      println();
3951      print(formatter.getFormattedWithPoints(INFO_REPLICATION_CONNECTING.get()));
3952
3953      LinkedList<LocalizableMessage> errorMessages = new LinkedList<>();
3954      ctx1 = createAdministrativeContext(uData, true, errorMessages);
3955      ctx2 = createAdministrativeContext(uData, false, errorMessages);
3956
3957      if (!errorMessages.isEmpty())
3958      {
3959        errPrintLn(errorMessages);
3960        return ERROR_CONNECTING;
3961      }
3962
3963      // This done is for the message informing that we are connecting.
3964      print(formatter.getFormattedDone());
3965      println();
3966
3967      if (!argParser.isInteractive())
3968      {
3969        checksForNonInteractiveMode(uData, ctx1, ctx2, errorMessages);
3970        if (!errorMessages.isEmpty())
3971        {
3972          errPrintLn(errorMessages);
3973          return ERROR_USER_DATA;
3974        }
3975      }
3976
3977      List<String> suffixes = uData.getBaseDNs();
3978      checkSuffixesForEnableReplication(suffixes, ctx1, ctx2, false, uData);
3979      if (suffixes.isEmpty())
3980      {
3981        // The error messages are already displayed in the method
3982        // checkSuffixesForEnableReplication.
3983        return REPLICATION_CANNOT_BE_ENABLED_ON_BASEDN;
3984      }
3985
3986      uData.setBaseDNs(suffixes);
3987      if (mustPrintCommandBuilder())
3988      {
3989        printNewCommandBuilder(ENABLE_REPLICATION_SUBCMD_NAME, uData);
3990      }
3991
3992      if (!isInteractive())
3993      {
3994        checkReplicationServerAlreadyConfigured(ctx1, uData.getServer1());
3995        checkReplicationServerAlreadyConfigured(ctx2, uData.getServer2());
3996      }
3997
3998      try
3999      {
4000        updateConfiguration(ctx1, ctx2, uData);
4001        printSuccessfullyEnabled(ctx1, ctx2);
4002        return SUCCESSFUL;
4003      }
4004      catch (ReplicationCliException rce)
4005      {
4006        errPrintln();
4007        errPrintln(getCriticalExceptionMessage(rce));
4008        logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
4009        return rce.getErrorCode();
4010      }
4011    }
4012    finally
4013    {
4014      close(ctx1, ctx2);
4015    }
4016  }
4017
4018  private void checkReplicationServerAlreadyConfigured(InitialLdapContext ctx, EnableReplicationServerData server)
4019  {
4020    int repPort = getReplicationPort(ctx);
4021    if (!server.configureReplicationServer() && repPort > 0)
4022    {
4023      println(INFO_REPLICATION_SERVER_CONFIGURED_WARNING.get(getHostPort(ctx), repPort));
4024      println();
4025    }
4026  }
4027
4028  private void checksForNonInteractiveMode(EnableReplicationUserData uData,
4029      InitialLdapContext ctx1, InitialLdapContext ctx2, LinkedList<LocalizableMessage> errorMessages)
4030  {
4031    EnableReplicationServerData server1 = uData.getServer1();
4032    EnableReplicationServerData server2 = uData.getServer2();
4033    String host1 = server1.getHostName();
4034    String host2 = server2.getHostName();
4035
4036    int replPort1 = checkReplicationPort(ctx1, server1, errorMessages);
4037    int replPort2 = checkReplicationPort(ctx2, server2, errorMessages);
4038    if (replPort1 > 0 && replPort1 == replPort2 && host1.equalsIgnoreCase(host2))
4039    {
4040      errorMessages.add(ERR_REPLICATION_SAME_REPLICATION_PORT.get(replPort1, host1));
4041    }
4042
4043    if (argParser.skipReplicationPortCheck())
4044    {
4045      // This is something that we must do in any case... this test is
4046      // already included when we call SetupUtils.canUseAsPort
4047      checkAdminAndReplicationPortsAreDifferent(replPort1, server1, errorMessages);
4048      checkAdminAndReplicationPortsAreDifferent(replPort2, server2, errorMessages);
4049    }
4050  }
4051
4052  private int checkReplicationPort(
4053      InitialLdapContext ctx, EnableReplicationServerData server, LinkedList<LocalizableMessage> errorMessages)
4054  {
4055    int replPort = getReplicationPort(ctx);
4056    boolean hasReplicationPort = replPort > 0;
4057    if (replPort < 0 && server.configureReplicationServer())
4058    {
4059      replPort = server.getReplicationPort();
4060    }
4061    boolean checkReplicationPort = replPort > 0;
4062    if (!hasReplicationPort
4063        && checkReplicationPort
4064        && !argParser.skipReplicationPortCheck()
4065        && server.configureReplicationServer()
4066        && isLocalHost(server.getHostName())
4067        && !SetupUtils.canUseAsPort(replPort))
4068    {
4069      errorMessages.add(getCannotBindToPortError(replPort));
4070    }
4071    return replPort;
4072  }
4073
4074  private void checkAdminAndReplicationPortsAreDifferent(
4075      int replPort, EnableReplicationServerData server, LinkedList<LocalizableMessage> errorMessages)
4076  {
4077    if (replPort > 0 && replPort == server.getPort())
4078    {
4079      errorMessages.add(ERR_REPLICATION_PORT_AND_REPLICATION_PORT_EQUAL.get(server.getHostName(), replPort));
4080    }
4081  }
4082
4083  private void printSuccessfullyEnabled(InitialLdapContext ctx1, InitialLdapContext ctx2)
4084  {
4085    long time1 = getServerClock(ctx1);
4086    long time2 = getServerClock(ctx2);
4087    if (time1 != -1
4088        && time2 != -1
4089        && Math.abs(time1 - time2) > Installer.THRESHOLD_CLOCK_DIFFERENCE_WARNING * 60 * 1000)
4090    {
4091        println(INFO_WARNING_SERVERS_CLOCK_DIFFERENCE.get(getHostPort(ctx1), getHostPort(ctx2),
4092            Installer.THRESHOLD_CLOCK_DIFFERENCE_WARNING));
4093    }
4094    println();
4095    println(INFO_REPLICATION_POST_ENABLE_INFO.get("dsreplication", INITIALIZE_REPLICATION_SUBCMD_NAME));
4096    println();
4097  }
4098
4099  private void errPrintLn(LinkedList<LocalizableMessage> errorMessages)
4100  {
4101    for (LocalizableMessage msg : errorMessages)
4102    {
4103      errPrintln();
4104      errPrintln(msg);
4105    }
4106  }
4107
4108  private InitialLdapContext createAdministrativeContext(EnableReplicationUserData uData, boolean isFirstSetOfValues,
4109      LinkedList<LocalizableMessage> errorMessages)
4110  {
4111    EnableReplicationServerData server = isFirstSetOfValues ? uData.getServer1() : uData.getServer2();
4112    try
4113    {
4114      return createAdministrativeContext(
4115          server.getHostName(), server.getPort(), useSSL, useStartTLS, server.getBindDn(), server.getPwd(),
4116          getConnectTimeout(), getTrustManager(sourceServerCI));
4117    }
4118    catch (NamingException ne)
4119    {
4120      String hostPort = getServerRepresentation(server.getHostName(), server.getPort());
4121      errorMessages.add(getMessageForException(ne, hostPort));
4122      logger.error(LocalizableMessage.raw("Complete error stack:"), ne);
4123      return null;
4124    }
4125  }
4126
4127  /**
4128   * Disables the replication in the server for the provided suffixes using the
4129   * data in the DisableReplicationUserData object.  This method does not prompt
4130   * to the user for information if something is missing.
4131   * @param uData the DisableReplicationUserData object.
4132   * @return ReplicationCliReturnCode.SUCCESSFUL if the operation was
4133   * successful and an error code otherwise.
4134   */
4135  private ReplicationCliReturnCode disableReplication(DisableReplicationUserData uData)
4136  {
4137    print(formatter.getFormattedWithPoints(INFO_REPLICATION_CONNECTING.get()));
4138    String bindDn = uData.getAdminUid() != null
4139        ? getAdministratorDN(uData.getAdminUid())
4140        : uData.getBindDn();
4141
4142    InitialLdapContext ctx = createAdministrativeContext(uData, bindDn);
4143    if (ctx == null)
4144    {
4145      return ERROR_CONNECTING;
4146    }
4147
4148    try
4149    {
4150      // This done is for the message informing that we are connecting.
4151      print(formatter.getFormattedDone());
4152      println();
4153
4154      List<String> suffixes = uData.getBaseDNs();
4155      checkSuffixesForDisableReplication(suffixes, ctx, false, !uData.disableReplicationServer());
4156      if (suffixes.isEmpty() && !uData.disableReplicationServer() && !uData.disableAll())
4157      {
4158        return REPLICATION_CANNOT_BE_DISABLED_ON_BASEDN;
4159      }
4160      uData.setBaseDNs(suffixes);
4161
4162      if (!isInteractive())
4163      {
4164        boolean hasReplicationPort = hasReplicationPort(ctx);
4165        if (uData.disableAll() && hasReplicationPort)
4166        {
4167          uData.setDisableReplicationServer(true);
4168        }
4169        else if (uData.disableReplicationServer() && !hasReplicationPort && !uData.disableAll())
4170        {
4171          uData.setDisableReplicationServer(false);
4172          println(INFO_REPLICATION_WARNING_NO_REPLICATION_SERVER_TO_DISABLE.get(getHostPort(ctx)));
4173          println();
4174        }
4175      }
4176
4177      if (mustPrintCommandBuilder())
4178      {
4179        printNewCommandBuilder(DISABLE_REPLICATION_SUBCMD_NAME, uData);
4180      }
4181
4182      if (!isInteractive() && !uData.disableReplicationServer() && !uData.disableAll() && disableAllBaseDns(ctx, uData)
4183          && hasReplicationPort(ctx))
4184      {
4185        // Inform the user that the replication server will not be disabled.
4186        // Inform also of the user of the disableReplicationServerArg
4187        println(INFO_REPLICATION_DISABLE_ALL_SUFFIXES_KEEP_REPLICATION_SERVER.get(getHostPort(ctx),
4188            argParser.disableReplicationServerArg.getLongIdentifier(), argParser.disableAllArg.getLongIdentifier()));
4189      }
4190      try
4191      {
4192        updateConfiguration(ctx, uData);
4193        return SUCCESSFUL;
4194      }
4195      catch (ReplicationCliException rce)
4196      {
4197        errPrintln();
4198        errPrintln(getCriticalExceptionMessage(rce));
4199        logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
4200        return rce.getErrorCode();
4201      }
4202    }
4203    finally
4204    {
4205      close(ctx);
4206    }
4207  }
4208
4209  /**
4210   * Displays the replication status of the baseDNs specified in the
4211   * StatusReplicationUserData object.  This method does not prompt
4212   * to the user for information if something is missing.
4213   * @param uData the StatusReplicationUserData object.
4214   * @return ReplicationCliReturnCode.SUCCESSFUL if the operation was
4215   * successful and an error code otherwise.
4216   */
4217  private ReplicationCliReturnCode statusReplication(
4218      StatusReplicationUserData uData)
4219  {
4220    final InitialLdapContext ctx = createAdministrativeContext(uData);
4221    if (ctx == null)
4222    {
4223      return ERROR_CONNECTING;
4224    }
4225
4226    try
4227    {
4228      try
4229      {
4230        displayStatus(ctx, uData);
4231        return SUCCESSFUL;
4232      }
4233      catch (ReplicationCliException rce)
4234      {
4235        errPrintln();
4236        errPrintln(getCriticalExceptionMessage(rce));
4237        logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
4238        return rce.getErrorCode();
4239      }
4240    }
4241    finally
4242    {
4243      close(ctx);
4244    }
4245  }
4246
4247  /**
4248   * Initializes the contents of one server with the contents of the other
4249   * using the parameters in the provided InitializeReplicationUserData.
4250   * This method does not prompt to the user for information if something is
4251   * missing.
4252   * @param uData the InitializeReplicationUserData object.
4253   * @return ReplicationCliReturnCode.SUCCESSFUL if the operation was
4254   * successful and an error code otherwise.
4255   */
4256  private ReplicationCliReturnCode initializeReplication(SourceDestinationServerUserData uData)
4257  {
4258    InitialLdapContext ctxSource = createAdministrativeContext(uData, uData.getSource());
4259    InitialLdapContext ctxDestination = createAdministrativeContext(uData, uData.getDestination());
4260    try
4261    {
4262      if (ctxSource == null || ctxDestination == null)
4263      {
4264        return ERROR_CONNECTING;
4265      }
4266
4267      List<String> baseDNs = uData.getBaseDNs();
4268      checkSuffixesForInitializeReplication(baseDNs, ctxSource, ctxDestination, false);
4269      if (baseDNs.isEmpty())
4270      {
4271        return REPLICATION_CANNOT_BE_INITIALIZED_ON_BASEDN;
4272      }
4273      if (mustPrintCommandBuilder())
4274      {
4275        uData.setBaseDNs(baseDNs);
4276        printNewCommandBuilder(INITIALIZE_REPLICATION_SUBCMD_NAME, uData);
4277      }
4278
4279      ReplicationCliReturnCode returnValue = SUCCESSFUL_NOP;
4280      for (String baseDN : baseDNs)
4281      {
4282        try
4283        {
4284          println();
4285          print(formatter.getFormattedProgress(INFO_PROGRESS_INITIALIZING_SUFFIX.get(baseDN, getHostPort(ctxSource))));
4286          println();
4287          initializeSuffix(baseDN, ctxSource, ctxDestination, true);
4288          returnValue = SUCCESSFUL;
4289        }
4290        catch (ReplicationCliException rce)
4291        {
4292          errPrintln();
4293          errPrintln(getCriticalExceptionMessage(rce));
4294          returnValue = rce.getErrorCode();
4295          logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
4296        }
4297      }
4298      return returnValue;
4299    }
4300    finally
4301    {
4302      close(ctxDestination, ctxSource);
4303    }
4304  }
4305
4306  private InitialLdapContext createAdministrativeContext(SourceDestinationServerUserData uData, HostPort server)
4307  {
4308    try
4309    {
4310      return createAdministrativeContext(
4311          server.getHost(), server.getPort(), useSSL, useStartTLS,
4312          getAdministratorDN(uData.getAdminUid()), uData.getAdminPwd(),
4313          getConnectTimeout(), getTrustManager(sourceServerCI));
4314    }
4315    catch (NamingException ne)
4316    {
4317      errPrintln();
4318      errPrintln(getMessageForException(ne, server.toString()));
4319      logger.error(LocalizableMessage.raw("Complete error stack:"), ne);
4320      return null;
4321    }
4322  }
4323
4324  /**
4325   * Initializes the contents of a whole topology with the contents of the other
4326   * using the parameters in the provided InitializeAllReplicationUserData.
4327   * This method does not prompt to the user for information if something is
4328   * missing.
4329   * @param uData the InitializeAllReplicationUserData object.
4330   * @return ReplicationCliReturnCode.SUCCESSFUL if the operation was
4331   * successful and an error code otherwise.
4332   */
4333  private ReplicationCliReturnCode initializeAllReplication(
4334      InitializeAllReplicationUserData uData)
4335  {
4336    final InitialLdapContext ctx = createAdministrativeContext(uData);
4337    if (ctx == null)
4338    {
4339      return ERROR_CONNECTING;
4340    }
4341
4342    try
4343    {
4344      List<String> baseDNs = uData.getBaseDNs();
4345      checkSuffixesForInitializeReplication(baseDNs, ctx, false);
4346      if (baseDNs.isEmpty())
4347      {
4348        return REPLICATION_CANNOT_BE_INITIALIZED_ON_BASEDN;
4349      }
4350      if (mustPrintCommandBuilder())
4351      {
4352        uData.setBaseDNs(baseDNs);
4353        printNewCommandBuilder(INITIALIZE_ALL_REPLICATION_SUBCMD_NAME, uData);
4354      }
4355
4356      ReplicationCliReturnCode returnValue = SUCCESSFUL_NOP;
4357      for (String baseDN : baseDNs)
4358      {
4359        try
4360        {
4361          println();
4362          print(formatter.getFormattedProgress(INFO_PROGRESS_INITIALIZING_SUFFIX.get(baseDN, getHostPort(ctx))));
4363          println();
4364          initializeAllSuffix(baseDN, ctx, true);
4365          returnValue = SUCCESSFUL;
4366        }
4367        catch (ReplicationCliException rce)
4368        {
4369          errPrintln();
4370          errPrintln(getCriticalExceptionMessage(rce));
4371          returnValue = rce.getErrorCode();
4372          logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
4373        }
4374      }
4375      return returnValue;
4376    }
4377    finally
4378    {
4379      close(ctx);
4380    }
4381  }
4382
4383  /**
4384   * Performs the operation that must be made before initializing the topology
4385   * using the import-ldif command or the binary copy.  The operation uses
4386   * the parameters in the provided InitializeAllReplicationUserData.
4387   * This method does not prompt to the user for information if something is
4388   * missing.
4389   * @param uData the PreExternalInitializationUserData object.
4390   * @return ReplicationCliReturnCode.SUCCESSFUL if the operation was
4391   * successful and an error code otherwise.
4392   */
4393  private ReplicationCliReturnCode preExternalInitialization(
4394      PreExternalInitializationUserData uData)
4395  {
4396    InitialLdapContext ctx = createAdministrativeContext(uData);
4397    if (ctx == null)
4398    {
4399      return ERROR_CONNECTING;
4400    }
4401
4402    try
4403    {
4404      List<String> baseDNs = uData.getBaseDNs();
4405      checkSuffixesForInitializeReplication(baseDNs, ctx, false);
4406      if (baseDNs.isEmpty())
4407      {
4408        return REPLICATION_CANNOT_BE_INITIALIZED_ON_BASEDN;
4409      }
4410      if (mustPrintCommandBuilder())
4411      {
4412        uData.setBaseDNs(baseDNs);
4413        printNewCommandBuilder(PRE_EXTERNAL_INITIALIZATION_SUBCMD_NAME, uData);
4414      }
4415
4416      ReplicationCliReturnCode returnValue = SUCCESSFUL;
4417      for (String baseDN : baseDNs)
4418      {
4419        try
4420        {
4421          println();
4422          print(formatter.getFormattedWithPoints(INFO_PROGRESS_PRE_EXTERNAL_INITIALIZATION.get(baseDN)));
4423          preExternalInitialization(baseDN, ctx);
4424          print(formatter.getFormattedDone());
4425          println();
4426        }
4427        catch (ReplicationCliException rce)
4428        {
4429          errPrintln();
4430          errPrintln(getCriticalExceptionMessage(rce));
4431          returnValue = rce.getErrorCode();
4432          logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
4433        }
4434      }
4435      println();
4436      print(INFO_PROGRESS_PRE_INITIALIZATION_FINISHED_PROCEDURE.get(POST_EXTERNAL_INITIALIZATION_SUBCMD_NAME));
4437      println();
4438      return returnValue;
4439    }
4440    finally
4441    {
4442      close(ctx);
4443    }
4444  }
4445
4446  /**
4447   * Performs the operation that must be made after initializing the topology
4448   * using the import-ldif command or the binary copy.  The operation uses
4449   * the parameters in the provided InitializeAllReplicationUserData.
4450   * This method does not prompt to the user for information if something is
4451   * missing.
4452   * @param uData the PostExternalInitializationUserData object.
4453   * @return ReplicationCliReturnCode.SUCCESSFUL if the operation was
4454   * successful and an error code otherwise.
4455   */
4456  private ReplicationCliReturnCode postExternalInitialization(
4457      PostExternalInitializationUserData uData)
4458  {
4459    InitialLdapContext ctx = createAdministrativeContext(uData);
4460    if (ctx == null)
4461    {
4462      return ERROR_CONNECTING;
4463    }
4464
4465    try
4466    {
4467      List<String> baseDNs = uData.getBaseDNs();
4468      checkSuffixesForInitializeReplication(baseDNs, ctx, false);
4469      if (baseDNs.isEmpty())
4470      {
4471        return REPLICATION_CANNOT_BE_INITIALIZED_ON_BASEDN;
4472      }
4473      if (mustPrintCommandBuilder())
4474      {
4475        uData.setBaseDNs(baseDNs);
4476        printNewCommandBuilder(POST_EXTERNAL_INITIALIZATION_SUBCMD_NAME, uData);
4477      }
4478
4479      ReplicationCliReturnCode returnValue = SUCCESSFUL;
4480      for (String baseDN : baseDNs)
4481      {
4482        try
4483        {
4484          println();
4485          print(formatter.getFormattedWithPoints(INFO_PROGRESS_POST_EXTERNAL_INITIALIZATION.get(baseDN)));
4486          postExternalInitialization(baseDN, ctx);
4487          println(formatter.getFormattedDone());
4488          println();
4489        }
4490        catch (ReplicationCliException rce)
4491        {
4492          errPrintln();
4493          errPrintln(getCriticalExceptionMessage(rce));
4494          returnValue = rce.getErrorCode();
4495          logger.error(LocalizableMessage.raw("Complete error stack:"), rce);
4496        }
4497      }
4498      println();
4499      print(INFO_PROGRESS_POST_INITIALIZATION_FINISHED_PROCEDURE.get());
4500      println();
4501      return returnValue;
4502    }
4503    finally
4504    {
4505      close(ctx);
4506    }
4507  }
4508
4509  /**
4510   * Checks that replication can actually be enabled in the provided baseDNs
4511   * for the two servers.
4512   * @param suffixes the suffixes provided by the user.  This Collection is
4513   * updated by removing the base DNs that cannot be enabled and with the
4514   * base DNs that the user provided interactively.
4515   * @param ctx1 connection to the first server.
4516   * @param ctx2 connection to the second server.
4517   * @param interactive whether to ask the user to provide interactively
4518   * base DNs if none of the provided base DNs can be enabled.
4519   * @param uData the user data.  This object will not be updated by this method
4520   * but it is assumed that it contains information about whether the
4521   * replication domains must be configured or not.
4522   */
4523  private void checkSuffixesForEnableReplication(Collection<String> suffixes,
4524      InitialLdapContext ctx1, InitialLdapContext ctx2,
4525      boolean interactive, EnableReplicationUserData uData)
4526  {
4527    EnableReplicationServerData server1 = uData.getServer1();
4528    EnableReplicationServerData server2 = uData.getServer2();
4529    final TreeSet<String> availableSuffixes = new TreeSet<>();
4530    final TreeSet<String> alreadyReplicatedSuffixes = new TreeSet<>();
4531    if (server1.configureReplicationDomain() &&
4532        server2.configureReplicationDomain())
4533    {
4534      availableSuffixes.addAll(getCommonSuffixes(ctx1, ctx2,
4535            SuffixRelationType.NOT_FULLY_REPLICATED));
4536      alreadyReplicatedSuffixes.addAll(getCommonSuffixes(ctx1, ctx2,
4537            SuffixRelationType.FULLY_REPLICATED));
4538    }
4539    else if (server1.configureReplicationDomain())
4540    {
4541      updateAvailableAndReplicatedSuffixesForOneDomain(ctx1, ctx2,
4542          availableSuffixes, alreadyReplicatedSuffixes);
4543    }
4544    else if (server2.configureReplicationDomain())
4545    {
4546      updateAvailableAndReplicatedSuffixesForOneDomain(ctx2, ctx1,
4547          availableSuffixes, alreadyReplicatedSuffixes);
4548    }
4549    else
4550    {
4551      updateAvailableAndReplicatedSuffixesForNoDomain(ctx1, ctx2,
4552          availableSuffixes, alreadyReplicatedSuffixes);
4553    }
4554
4555    if (availableSuffixes.isEmpty())
4556    {
4557      println();
4558      if (!server1.configureReplicationDomain() &&
4559          !server1.configureReplicationDomain() &&
4560          alreadyReplicatedSuffixes.isEmpty())
4561      {
4562        // Use a clarifying message: there is no replicated base DN.
4563        errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_ENABLE_REPLICATION_NO_DOMAIN.get());
4564      }
4565      else
4566      {
4567        errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_ENABLE_REPLICATION.get());
4568      }
4569
4570      List<String> userProvidedSuffixes = argParser.getBaseDNs();
4571      TreeSet<String> userProvidedReplicatedSuffixes = new TreeSet<>();
4572
4573      for (String s1 : userProvidedSuffixes)
4574      {
4575        for (String s2 : alreadyReplicatedSuffixes)
4576        {
4577          if (areDnsEqual(s1, s2))
4578          {
4579            userProvidedReplicatedSuffixes.add(s1);
4580          }
4581        }
4582      }
4583      if (!userProvidedReplicatedSuffixes.isEmpty())
4584      {
4585        println();
4586        println(INFO_ALREADY_REPLICATED_SUFFIXES.get(toSingleLine(userProvidedReplicatedSuffixes)));
4587      }
4588      suffixes.clear();
4589    }
4590    else
4591    {
4592      //  Verify that the provided suffixes are configured in the servers.
4593      TreeSet<String> notFound = new TreeSet<>();
4594      TreeSet<String> alreadyReplicated = new TreeSet<>();
4595      for (String dn : suffixes)
4596      {
4597        if (!containsDN(availableSuffixes, dn))
4598        {
4599          if (containsDN(alreadyReplicatedSuffixes, dn))
4600          {
4601            alreadyReplicated.add(dn);
4602          }
4603          else
4604          {
4605            notFound.add(dn);
4606          }
4607        }
4608      }
4609      suffixes.removeAll(notFound);
4610      suffixes.removeAll(alreadyReplicated);
4611      if (!notFound.isEmpty())
4612      {
4613        errPrintln();
4614        errPrintln(ERR_REPLICATION_ENABLE_SUFFIXES_NOT_FOUND.get(toSingleLine(notFound)));
4615      }
4616      if (!alreadyReplicated.isEmpty())
4617      {
4618        println();
4619        println(INFO_ALREADY_REPLICATED_SUFFIXES.get(toSingleLine(alreadyReplicated)));
4620      }
4621      if (interactive)
4622      {
4623        askConfirmations(suffixes, availableSuffixes,
4624            ERR_NO_SUFFIXES_AVAILABLE_TO_ENABLE_REPLICATION,
4625            ERR_NO_SUFFIXES_SELECTED_TO_REPLICATE,
4626            INFO_REPLICATION_ENABLE_SUFFIX_PROMPT);
4627      }
4628    }
4629  }
4630
4631  /**
4632   * Checks that replication can actually be disabled in the provided baseDNs
4633   * for the server.
4634   * @param suffixes the suffixes provided by the user.  This Collection is
4635   * updated by removing the base DNs that cannot be disabled and with the
4636   * base DNs that the user provided interactively.
4637   * @param ctx connection to the server.
4638   * @param interactive whether to ask the user to provide interactively
4639   * base DNs if none of the provided base DNs can be disabled.
4640   * @param displayErrors whether to display errors or not.
4641   */
4642  private void checkSuffixesForDisableReplication(Collection<String> suffixes,
4643      InitialLdapContext ctx, boolean interactive, boolean displayErrors)
4644  {
4645    // whether the user must provide base DNs or not
4646    // (if it is <CODE>false</CODE> the user will be proposed the suffixes only once)
4647    final boolean areSuffixRequired = displayErrors;
4648
4649    TreeSet<String> availableSuffixes = new TreeSet<>();
4650    TreeSet<String> notReplicatedSuffixes = new TreeSet<>();
4651
4652    Collection<ReplicaDescriptor> replicas = getReplicas(ctx);
4653    for (ReplicaDescriptor rep : replicas)
4654    {
4655      String dn = rep.getSuffix().getDN();
4656      if (rep.isReplicated())
4657      {
4658        availableSuffixes.add(dn);
4659      }
4660      else
4661      {
4662        notReplicatedSuffixes.add(dn);
4663      }
4664    }
4665    if (availableSuffixes.isEmpty())
4666    {
4667      if (displayErrors)
4668      {
4669        errPrintln();
4670        errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_DISABLE_REPLICATION.get());
4671      }
4672      List<String> userProvidedSuffixes = argParser.getBaseDNs();
4673      TreeSet<String> userProvidedNotReplicatedSuffixes = new TreeSet<>();
4674      for (String s1 : userProvidedSuffixes)
4675      {
4676        for (String s2 : notReplicatedSuffixes)
4677        {
4678          if (areDnsEqual(s1, s2))
4679          {
4680            userProvidedNotReplicatedSuffixes.add(s1);
4681          }
4682        }
4683      }
4684      if (!userProvidedNotReplicatedSuffixes.isEmpty() && displayErrors)
4685      {
4686        println();
4687        println(INFO_ALREADY_NOT_REPLICATED_SUFFIXES.get(
4688            toSingleLine(userProvidedNotReplicatedSuffixes)));
4689      }
4690      suffixes.clear();
4691    }
4692    else
4693    {
4694      // Verify that the provided suffixes are configured in the servers.
4695      TreeSet<String> notFound = new TreeSet<>();
4696      TreeSet<String> alreadyNotReplicated = new TreeSet<>();
4697      for (String dn : suffixes)
4698      {
4699        if (!containsDN(availableSuffixes, dn))
4700        {
4701          if (containsDN(notReplicatedSuffixes, dn))
4702          {
4703            alreadyNotReplicated.add(dn);
4704          }
4705          else
4706          {
4707            notFound.add(dn);
4708          }
4709        }
4710      }
4711      suffixes.removeAll(notFound);
4712      suffixes.removeAll(alreadyNotReplicated);
4713      if (!notFound.isEmpty() && displayErrors)
4714      {
4715        errPrintln();
4716        errPrintln(ERR_REPLICATION_DISABLE_SUFFIXES_NOT_FOUND.get(toSingleLine(notFound)));
4717      }
4718      if (!alreadyNotReplicated.isEmpty() && displayErrors)
4719      {
4720        println();
4721        println(INFO_ALREADY_NOT_REPLICATED_SUFFIXES.get(toSingleLine(alreadyNotReplicated)));
4722      }
4723      if (interactive)
4724      {
4725        while (suffixes.isEmpty())
4726        {
4727          if (containsOnlySchemaOrAdminSuffix(availableSuffixes))
4728          {
4729            // In interactive mode we do not propose to manage the administration suffix.
4730            if (displayErrors)
4731            {
4732              errPrintln();
4733              errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_DISABLE_REPLICATION.get());
4734            }
4735            break;
4736          }
4737
4738          if (areSuffixRequired)
4739          {
4740            errPrintln();
4741            errPrintln(ERR_NO_SUFFIXES_SELECTED_TO_DISABLE.get());
4742          }
4743          boolean confirmationLimitReached =
4744              askConfirmations(INFO_REPLICATION_DISABLE_SUFFIX_PROMPT, availableSuffixes, suffixes);
4745          if (confirmationLimitReached)
4746          {
4747            suffixes.clear();
4748            break;
4749          }
4750          if (!areSuffixRequired)
4751          {
4752            break;
4753          }
4754        }
4755      }
4756    }
4757  }
4758
4759  private boolean askConfirmations(Arg1<Object> confirmationMsg,
4760      Collection<String> availableSuffixes, Collection<String> suffixes)
4761  {
4762    for (String dn : availableSuffixes)
4763    {
4764      if (!isSchemaOrInternalAdminSuffix(dn))
4765      {
4766        try
4767        {
4768          if (askConfirmation(confirmationMsg.get(dn), true, logger))
4769          {
4770            suffixes.add(dn);
4771          }
4772        }
4773        catch (ClientException ce)
4774        {
4775          errPrintln(ce.getMessageObject());
4776          return true;
4777        }
4778      }
4779    }
4780    return false;
4781  }
4782
4783  /**
4784   * Checks that replication can actually be initialized in the provided baseDNs
4785   * for the server.
4786   * @param suffixes the suffixes provided by the user.  This Collection is
4787   * updated by removing the base DNs that cannot be initialized and with the
4788   * base DNs that the user provided interactively.
4789   * @param ctx connection to the server.
4790   * @param interactive whether to ask the user to provide interactively
4791   * base DNs if none of the provided base DNs can be initialized.
4792   */
4793  private void checkSuffixesForInitializeReplication(
4794      Collection<String> suffixes, InitialLdapContext ctx, boolean interactive)
4795  {
4796    TreeSet<String> availableSuffixes = new TreeSet<>();
4797    TreeSet<String> notReplicatedSuffixes = new TreeSet<>();
4798
4799    Collection<ReplicaDescriptor> replicas = getReplicas(ctx);
4800    for (ReplicaDescriptor rep : replicas)
4801    {
4802      String dn = rep.getSuffix().getDN();
4803      if (rep.isReplicated())
4804      {
4805        availableSuffixes.add(dn);
4806      }
4807      else
4808      {
4809        notReplicatedSuffixes.add(dn);
4810      }
4811    }
4812    if (availableSuffixes.isEmpty())
4813    {
4814      println();
4815      if (argParser.isInitializeAllReplicationSubcommand())
4816      {
4817        errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_INITIALIZE_ALL_REPLICATION.get());
4818      }
4819      else
4820      {
4821        errPrintln(
4822            ERR_NO_SUFFIXES_AVAILABLE_TO_INITIALIZE_LOCAL_REPLICATION.get());
4823      }
4824      List<String> userProvidedSuffixes = argParser.getBaseDNs();
4825      TreeSet<String> userProvidedNotReplicatedSuffixes = new TreeSet<>();
4826      for (String s1 : userProvidedSuffixes)
4827      {
4828        for (String s2 : notReplicatedSuffixes)
4829        {
4830          if (areDnsEqual(s1, s2))
4831          {
4832            userProvidedNotReplicatedSuffixes.add(s1);
4833          }
4834        }
4835      }
4836      if (!userProvidedNotReplicatedSuffixes.isEmpty())
4837      {
4838        println();
4839        println(INFO_ALREADY_NOT_REPLICATED_SUFFIXES.get(
4840            toSingleLine(userProvidedNotReplicatedSuffixes)));
4841      }
4842      suffixes.clear();
4843    }
4844    else
4845    {
4846      // Verify that the provided suffixes are configured in the servers.
4847      TreeSet<String> notFound = new TreeSet<>();
4848      TreeSet<String> alreadyNotReplicated = new TreeSet<>();
4849      for (String dn : suffixes)
4850      {
4851        if (!containsDN(availableSuffixes, dn))
4852        {
4853          if (containsDN(notReplicatedSuffixes, dn))
4854          {
4855            alreadyNotReplicated.add(dn);
4856          }
4857          else
4858          {
4859            notFound.add(dn);
4860          }
4861        }
4862      }
4863      suffixes.removeAll(notFound);
4864      suffixes.removeAll(alreadyNotReplicated);
4865      if (!notFound.isEmpty())
4866      {
4867        errPrintln();
4868        errPrintln(ERR_REPLICATION_INITIALIZE_LOCAL_SUFFIXES_NOT_FOUND.get(toSingleLine(notFound)));
4869      }
4870      if (!alreadyNotReplicated.isEmpty())
4871      {
4872        println();
4873        println(INFO_ALREADY_NOT_REPLICATED_SUFFIXES.get(toSingleLine(alreadyNotReplicated)));
4874      }
4875      if (interactive)
4876      {
4877        boolean confirmationLimitReached = false;
4878        while (suffixes.isEmpty())
4879        {
4880          println();
4881          if (containsOnlySchemaOrAdminSuffix(availableSuffixes))
4882          {
4883            // In interactive mode we do not propose to manage the administration suffix.
4884            if (argParser.isInitializeAllReplicationSubcommand())
4885            {
4886              errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_INITIALIZE_ALL_REPLICATION.get());
4887            }
4888            else
4889            {
4890              errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_INITIALIZE_LOCAL_REPLICATION.get());
4891            }
4892            break;
4893          }
4894          else
4895          {
4896            if (argParser.isInitializeAllReplicationSubcommand())
4897            {
4898              errPrintln(ERR_NO_SUFFIXES_SELECTED_TO_INITIALIZE_ALL.get());
4899            }
4900            else if (argParser.isPreExternalInitializationSubcommand())
4901            {
4902              errPrintln(ERR_NO_SUFFIXES_SELECTED_TO_PRE_EXTERNAL_INITIALIZATION.get());
4903            }
4904            else if (argParser.isPostExternalInitializationSubcommand())
4905            {
4906              errPrintln(ERR_NO_SUFFIXES_SELECTED_TO_POST_EXTERNAL_INITIALIZATION.get());
4907            }
4908
4909            for (String dn : availableSuffixes)
4910            {
4911              if (!isSchemaOrInternalAdminSuffix(dn))
4912              {
4913                boolean addSuffix;
4914                try
4915                {
4916                  if (argParser.isPreExternalInitializationSubcommand())
4917                  {
4918                    addSuffix = askConfirmation(
4919                    INFO_REPLICATION_PRE_EXTERNAL_INITIALIZATION_SUFFIX_PROMPT.
4920                        get(dn), true, logger);
4921                  }
4922                  else if (argParser.isPostExternalInitializationSubcommand())
4923                  {
4924                    addSuffix = askConfirmation(
4925                    INFO_REPLICATION_POST_EXTERNAL_INITIALIZATION_SUFFIX_PROMPT.
4926                        get(dn), true, logger);
4927                  }
4928                  else
4929                  {
4930                    addSuffix = askConfirmation(
4931                        INFO_REPLICATION_INITIALIZE_ALL_SUFFIX_PROMPT.get(dn),
4932                        true, logger);
4933                  }
4934                }
4935                catch (ClientException ce)
4936                {
4937                  errPrintln(ce.getMessageObject());
4938                  confirmationLimitReached = true;
4939                  break;
4940                }
4941                if (addSuffix)
4942                {
4943                  suffixes.add(dn);
4944                }
4945              }
4946            }
4947          }
4948          if (confirmationLimitReached)
4949          {
4950            suffixes.clear();
4951            break;
4952          }
4953        }
4954      }
4955    }
4956  }
4957
4958  private String toSingleLine(Collection<String> notFound)
4959  {
4960    return joinAsString(Constants.LINE_SEPARATOR, notFound);
4961  }
4962
4963  /**
4964   * Checks that we can initialize the provided baseDNs between the two servers.
4965   * @param suffixes the suffixes provided by the user.  This Collection is
4966   * updated by removing the base DNs that cannot be enabled and with the
4967   * base DNs that the user provided interactively.
4968   * @param ctxSource connection to the source server.
4969   * @param ctxDestination connection to the destination server.
4970   * @param interactive whether to ask the user to provide interactively
4971   * base DNs if none of the provided base DNs can be initialized.
4972   */
4973  private void checkSuffixesForInitializeReplication(
4974      Collection<String> suffixes, InitialLdapContext ctxSource,
4975      InitialLdapContext ctxDestination, boolean interactive)
4976  {
4977    TreeSet<String> availableSuffixes = new TreeSet<>(
4978        getCommonSuffixes(ctxSource, ctxDestination, SuffixRelationType.REPLICATED));
4979    if (availableSuffixes.isEmpty())
4980    {
4981      errPrintln();
4982      errPrintln(ERR_NO_SUFFIXES_AVAILABLE_TO_INITIALIZE_REPLICATION.get());
4983      suffixes.clear();
4984    }
4985    else
4986    {
4987      // Verify that the provided suffixes are configured in the servers.
4988      LinkedList<String> notFound = new LinkedList<>();
4989      for (String dn : suffixes)
4990      {
4991        if (!containsDN(availableSuffixes, dn))
4992        {
4993          notFound.add(dn);
4994        }
4995      }
4996      suffixes.removeAll(notFound);
4997      if (!notFound.isEmpty())
4998      {
4999        errPrintln();
5000        errPrintln(ERR_SUFFIXES_CANNOT_BE_INITIALIZED.get(toSingleLine(notFound)));
5001      }
5002      if (interactive)
5003      {
5004        askConfirmations(suffixes, availableSuffixes,
5005            ERR_NO_SUFFIXES_AVAILABLE_TO_INITIALIZE_REPLICATION,
5006            ERR_NO_SUFFIXES_SELECTED_TO_INITIALIZE,
5007            INFO_REPLICATION_INITIALIZE_SUFFIX_PROMPT);
5008      }
5009    }
5010  }
5011
5012  /**
5013   * Updates the configuration in the two servers (and in other servers if
5014   * they are referenced) to enable replication.
5015   * @param ctx1 the connection to the first server.
5016   * @param ctx2 the connection to the second server.
5017   * @param uData the EnableReplicationUserData object containing the required
5018   * parameters to update the configuration.
5019   * @throws ReplicationCliException if there is an error.
5020   */
5021  private void updateConfiguration(InitialLdapContext ctx1,
5022      InitialLdapContext ctx2, EnableReplicationUserData uData)
5023  throws ReplicationCliException
5024  {
5025    final Set<String> twoReplServers = new LinkedHashSet<>();
5026    final Set<String> allRepServers = new LinkedHashSet<>();
5027    final Map<String, Set<String>> hmRepServers = new HashMap<>();
5028    final Set<Integer> usedReplicationServerIds = new HashSet<>();
5029    final Map<String, Set<Integer>> hmUsedReplicationDomainIds = new HashMap<>();
5030
5031    TopologyCacheFilter filter = new TopologyCacheFilter();
5032    filter.setSearchMonitoringInformation(false);
5033    filter.addBaseDNToSearch(ADSContext.getAdministrationSuffixDN());
5034    filter.addBaseDNToSearch(Constants.SCHEMA_DN);
5035    addBaseDNs(filter, uData.getBaseDNs());
5036    ServerDescriptor serverDesc1 = createStandalone(ctx1, filter);
5037    ServerDescriptor serverDesc2 = createStandalone(ctx2, filter);
5038
5039    ADSContext adsCtx1 = new ADSContext(ctx1);
5040    ADSContext adsCtx2 = new ADSContext(ctx2);
5041
5042    if (!argParser.isInteractive())
5043    {
5044      // Inform the user of the potential errors that we found in the already
5045      // registered servers.
5046      final Set<LocalizableMessage> messages = new LinkedHashSet<>();
5047      try
5048      {
5049        final Set<PreferredConnection> cnx = new LinkedHashSet<>();
5050        cnx.addAll(getPreferredConnections(ctx1));
5051        cnx.addAll(getPreferredConnections(ctx2));
5052        TopologyCache cache1 = createTopologyCache(adsCtx1, cnx, uData);
5053        if (cache1 != null)
5054        {
5055          messages.addAll(cache1.getErrorMessages());
5056        }
5057        TopologyCache cache2 = createTopologyCache(adsCtx2, cnx, uData);
5058        if (cache2 != null)
5059        {
5060          messages.addAll(cache2.getErrorMessages());
5061        }
5062      }
5063      catch (TopologyCacheException tce)
5064      {
5065        throw new ReplicationCliException(
5066            ERR_REPLICATION_READING_ADS.get(tce.getMessage()),
5067            ERROR_READING_TOPOLOGY_CACHE, tce);
5068      }
5069      catch (ADSContextException adce)
5070      {
5071        throw new ReplicationCliException(
5072            ERR_REPLICATION_READING_ADS.get(adce.getMessage()),
5073            ERROR_READING_ADS, adce);
5074      }
5075      if (!messages.isEmpty())
5076      {
5077        errPrintln(ERR_REPLICATION_READING_REGISTERED_SERVERS_WARNING.get(
5078                getMessageFromCollection(messages,
5079                    Constants.LINE_SEPARATOR)));
5080      }
5081    }
5082    // Check whether there is more than one replication server in the topology.
5083    Set<String> baseDNsWithOneReplicationServer = new TreeSet<>();
5084    Set<String> baseDNsWithNoReplicationServer = new TreeSet<>();
5085    updateBaseDnsWithNotEnoughReplicationServer(adsCtx1, adsCtx2, uData,
5086       baseDNsWithNoReplicationServer, baseDNsWithOneReplicationServer);
5087
5088    if (!baseDNsWithNoReplicationServer.isEmpty())
5089    {
5090      LocalizableMessage errorMsg =
5091        ERR_REPLICATION_NO_REPLICATION_SERVER.get(toSingleLine(baseDNsWithNoReplicationServer));
5092      throw new ReplicationCliException(errorMsg, ERROR_USER_DATA, null);
5093    }
5094    else if (!baseDNsWithOneReplicationServer.isEmpty())
5095    {
5096      if (isInteractive())
5097      {
5098        LocalizableMessage confirmMsg = INFO_REPLICATION_ONLY_ONE_REPLICATION_SERVER_CONFIRM.get(
5099            toSingleLine(baseDNsWithOneReplicationServer));
5100        try
5101        {
5102          if (!confirmAction(confirmMsg, false))
5103          {
5104            throw new ReplicationCliException(
5105                ERR_REPLICATION_USER_CANCELLED.get(), USER_CANCELLED, null);
5106          }
5107        }
5108        catch (Throwable t)
5109        {
5110          throw new ReplicationCliException(
5111              ERR_REPLICATION_USER_CANCELLED.get(), USER_CANCELLED, t);
5112        }
5113      }
5114      else
5115      {
5116        errPrintln(INFO_REPLICATION_ONLY_ONE_REPLICATION_SERVER_WARNING.get(
5117            toSingleLine(baseDNsWithOneReplicationServer)));
5118        errPrintln();
5119      }
5120    }
5121
5122    // These are used to identify which server we use to initialize
5123    // the contents of the other server (if any).
5124    InitialLdapContext ctxSource = null;
5125    InitialLdapContext ctxDestination = null;
5126    ADSContext adsCtxSource = null;
5127
5128    boolean adsAlreadyReplicated = false;
5129    boolean adsMergeDone = false;
5130
5131    print(formatter.getFormattedWithPoints(
5132        INFO_REPLICATION_ENABLE_UPDATING_ADS_CONTENTS.get()));
5133    try
5134    {
5135      if (adsCtx1.hasAdminData() && adsCtx2.hasAdminData())
5136      {
5137        Set<Map<ServerProperty, Object>> registry1 = adsCtx1.readServerRegistry();
5138        Set<Map<ServerProperty, Object>> registry2 = adsCtx2.readServerRegistry();
5139        if (registry2.size() <= 1)
5140        {
5141          if (!hasAdministrator(adsCtx1.getDirContext(), uData))
5142          {
5143            adsCtx1.createAdministrator(getAdministratorProperties(uData));
5144          }
5145          serverDesc2.updateAdsPropertiesWithServerProperties();
5146          registerServer(adsCtx1, serverDesc2.getAdsProperties());
5147          if (!ADSContext.isRegistered(serverDesc1, registry1))
5148          {
5149            serverDesc1.updateAdsPropertiesWithServerProperties();
5150            registerServer(adsCtx1, serverDesc1.getAdsProperties());
5151          }
5152
5153          ctxSource = ctx1;
5154          ctxDestination = ctx2;
5155          adsCtxSource = adsCtx1;
5156        }
5157        else if (registry1.size() <= 1)
5158        {
5159          if (!hasAdministrator(adsCtx2.getDirContext(), uData))
5160          {
5161            adsCtx2.createAdministrator(getAdministratorProperties(uData));
5162          }
5163          serverDesc1.updateAdsPropertiesWithServerProperties();
5164          registerServer(adsCtx2, serverDesc1.getAdsProperties());
5165
5166          if (!ADSContext.isRegistered(serverDesc2, registry2))
5167          {
5168            serverDesc2.updateAdsPropertiesWithServerProperties();
5169            registerServer(adsCtx2, serverDesc2.getAdsProperties());
5170          }
5171
5172          ctxSource = ctx2;
5173          ctxDestination = ctx1;
5174          adsCtxSource = adsCtx2;
5175        }
5176        else if (!areEqual(registry1, registry2))
5177        {
5178          print(formatter.getFormattedDone());
5179          println();
5180
5181          boolean isFirstSource = mergeRegistries(adsCtx1, adsCtx2);
5182          ctxSource = isFirstSource ? ctx1 : ctx2;
5183          adsMergeDone = true;
5184        }
5185        else
5186        {
5187          // They are already replicated: nothing to do in terms of ADS
5188          // initialization or ADS update data
5189          adsAlreadyReplicated = isBaseDNReplicated(serverDesc1, serverDesc2, ADSContext.getAdministrationSuffixDN());
5190
5191          if (!adsAlreadyReplicated)
5192          {
5193            // Try to merge if both are replicated
5194            boolean isADS1Replicated = isBaseDNReplicated(serverDesc1, ADSContext.getAdministrationSuffixDN());
5195            boolean isADS2Replicated = isBaseDNReplicated(serverDesc2, ADSContext.getAdministrationSuffixDN());
5196            if (isADS1Replicated && isADS2Replicated)
5197            {
5198              // Merge
5199              print(formatter.getFormattedDone());
5200              println();
5201
5202              boolean isFirstSource = mergeRegistries(adsCtx1, adsCtx2);
5203              ctxSource = isFirstSource ? ctx1 : ctx2;
5204              adsMergeDone = true;
5205            }
5206            else if (isADS1Replicated || !isADS2Replicated)
5207            {
5208              // The case where only the first ADS is replicated or none
5209              // is replicated.
5210              if (!hasAdministrator(adsCtx1.getDirContext(), uData))
5211              {
5212                adsCtx1.createAdministrator(getAdministratorProperties(uData));
5213              }
5214              serverDesc2.updateAdsPropertiesWithServerProperties();
5215              registerServer(adsCtx1, serverDesc2.getAdsProperties());
5216              if (!ADSContext.isRegistered(serverDesc1, registry1))
5217              {
5218                serverDesc1.updateAdsPropertiesWithServerProperties();
5219                registerServer(adsCtx1, serverDesc1.getAdsProperties());
5220              }
5221
5222              ctxSource = ctx1;
5223              ctxDestination = ctx2;
5224              adsCtxSource = adsCtx1;
5225            }
5226            else if (isADS2Replicated)
5227            {
5228              if (!hasAdministrator(adsCtx2.getDirContext(), uData))
5229              {
5230                adsCtx2.createAdministrator(getAdministratorProperties(uData));
5231              }
5232              serverDesc1.updateAdsPropertiesWithServerProperties();
5233              registerServer(adsCtx2, serverDesc1.getAdsProperties());
5234              if (!ADSContext.isRegistered(serverDesc2, registry2))
5235              {
5236                serverDesc2.updateAdsPropertiesWithServerProperties();
5237                registerServer(adsCtx2, serverDesc2.getAdsProperties());
5238              }
5239
5240              ctxSource = ctx2;
5241              ctxDestination = ctx1;
5242              adsCtxSource = adsCtx2;
5243            }
5244          }
5245        }
5246      }
5247      else if (!adsCtx1.hasAdminData() && adsCtx2.hasAdminData())
5248      {
5249        if (!hasAdministrator(adsCtx2.getDirContext(), uData))
5250        {
5251          adsCtx2.createAdministrator(getAdministratorProperties(uData));
5252        }
5253        serverDesc1.updateAdsPropertiesWithServerProperties();
5254        registerServer(adsCtx2, serverDesc1.getAdsProperties());
5255        Set<Map<ServerProperty, Object>> registry2 = adsCtx2.readServerRegistry();
5256        if (!ADSContext.isRegistered(serverDesc2, registry2))
5257        {
5258          serverDesc2.updateAdsPropertiesWithServerProperties();
5259          registerServer(adsCtx2, serverDesc2.getAdsProperties());
5260        }
5261
5262        ctxSource = ctx2;
5263        ctxDestination = ctx1;
5264        adsCtxSource = adsCtx2;
5265      }
5266      else if (adsCtx1.hasAdminData() && !adsCtx2.hasAdminData())
5267      {
5268        if (!hasAdministrator(adsCtx1.getDirContext(), uData))
5269        {
5270          adsCtx1.createAdministrator(getAdministratorProperties(uData));
5271        }
5272        serverDesc2.updateAdsPropertiesWithServerProperties();
5273        registerServer(adsCtx1, serverDesc2.getAdsProperties());
5274        Set<Map<ServerProperty, Object>> registry1 = adsCtx1.readServerRegistry();
5275        if (!ADSContext.isRegistered(serverDesc1, registry1))
5276        {
5277          serverDesc1.updateAdsPropertiesWithServerProperties();
5278          registerServer(adsCtx1, serverDesc1.getAdsProperties());
5279        }
5280
5281        ctxSource = ctx1;
5282        ctxDestination = ctx2;
5283        adsCtxSource = adsCtx1;
5284      }
5285      else
5286      {
5287        adsCtx1.createAdminData(null);
5288        if (!hasAdministrator(ctx1, uData))
5289        {
5290          // This could occur if the user created an administrator without
5291          // registering any server.
5292          adsCtx1.createAdministrator(getAdministratorProperties(uData));
5293        }
5294        serverDesc1.updateAdsPropertiesWithServerProperties();
5295        adsCtx1.registerServer(serverDesc1.getAdsProperties());
5296        serverDesc2.updateAdsPropertiesWithServerProperties();
5297        adsCtx1.registerServer(serverDesc2.getAdsProperties());
5298
5299        ctxSource = ctx1;
5300        ctxDestination = ctx2;
5301        adsCtxSource = adsCtx1;
5302      }
5303    }
5304    catch (ADSContextException adce)
5305    {
5306      throw new ReplicationCliException(
5307          ERR_REPLICATION_UPDATING_ADS.get(adce.getMessageObject()),
5308          ERROR_UPDATING_ADS, adce);
5309    }
5310    if (!adsAlreadyReplicated && !adsMergeDone)
5311    {
5312      try
5313      {
5314        ServerDescriptor.seedAdsTrustStore(ctxDestination,
5315            adsCtxSource.getTrustedCertificates());
5316      }
5317      catch (Throwable t)
5318      {
5319        logger.error(LocalizableMessage.raw("Error seeding truststores: "+t, t));
5320        throw new ReplicationCliException(
5321            ERR_REPLICATION_ENABLE_SEEDING_TRUSTSTORE.get(getHostPort(ctxDestination),
5322            getHostPort(adsCtxSource.getDirContext()), toString(t)),
5323            ERROR_SEEDING_TRUSTORE, t);
5324      }
5325    }
5326    if (!adsMergeDone)
5327    {
5328      print(formatter.getFormattedDone());
5329      println();
5330    }
5331    List<String> baseDNs = uData.getBaseDNs();
5332    if (!adsAlreadyReplicated
5333        && !containsDN(baseDNs, ADSContext.getAdministrationSuffixDN()))
5334    {
5335      baseDNs.add(ADSContext.getAdministrationSuffixDN());
5336      uData.setBaseDNs(baseDNs);
5337    }
5338
5339    if (uData.replicateSchema())
5340    {
5341      baseDNs = uData.getBaseDNs();
5342      baseDNs.add(Constants.SCHEMA_DN);
5343      uData.setBaseDNs(baseDNs);
5344    }
5345
5346    TopologyCache cache1 = null;
5347    TopologyCache cache2 = null;
5348    try
5349    {
5350      Set<PreferredConnection> cnx = new LinkedHashSet<>();
5351      cnx.addAll(getPreferredConnections(ctx1));
5352      cnx.addAll(getPreferredConnections(ctx2));
5353      cache1 = createTopologyCache(adsCtx1, cnx, uData);
5354      if (cache1 != null)
5355      {
5356        usedReplicationServerIds.addAll(getReplicationServerIds(cache1));
5357      }
5358      cache2 = createTopologyCache(adsCtx2, cnx, uData);
5359      if (cache1 != null)
5360      {
5361        usedReplicationServerIds.addAll(getReplicationServerIds(cache1));
5362      }
5363    }
5364    catch (ADSContextException adce)
5365    {
5366      throw new ReplicationCliException(
5367          ERR_REPLICATION_READING_ADS.get(adce.getMessage()),
5368          ERROR_READING_ADS, adce);
5369    }
5370    catch (TopologyCacheException tce)
5371    {
5372      throw new ReplicationCliException(
5373          ERR_REPLICATION_READING_ADS.get(tce.getMessage()),
5374          ERROR_READING_TOPOLOGY_CACHE, tce);
5375    }
5376
5377    addToSets(serverDesc1, uData.getServer1(), ctx1, twoReplServers, usedReplicationServerIds);
5378    addToSets(serverDesc2, uData.getServer2(), ctx2, twoReplServers, usedReplicationServerIds);
5379
5380    for (String baseDN : uData.getBaseDNs())
5381    {
5382      Set<String> repServersForBaseDN = new LinkedHashSet<>();
5383      repServersForBaseDN.addAll(getReplicationServers(baseDN, cache1, serverDesc1));
5384      repServersForBaseDN.addAll(getReplicationServers(baseDN, cache2, serverDesc2));
5385      repServersForBaseDN.addAll(twoReplServers);
5386      hmRepServers.put(baseDN, repServersForBaseDN);
5387
5388      Set<Integer> ids = new HashSet<>();
5389      ids.addAll(getReplicationDomainIds(baseDN, serverDesc1));
5390      ids.addAll(getReplicationDomainIds(baseDN, serverDesc2));
5391      if (cache1 != null)
5392      {
5393        for (ServerDescriptor server : cache1.getServers())
5394        {
5395          ids.addAll(getReplicationDomainIds(baseDN, server));
5396        }
5397      }
5398      if (cache2 != null)
5399      {
5400        for (ServerDescriptor server : cache2.getServers())
5401        {
5402          ids.addAll(getReplicationDomainIds(baseDN, server));
5403        }
5404      }
5405      hmUsedReplicationDomainIds.put(baseDN, ids);
5406    }
5407    for (Set<String> v : hmRepServers.values())
5408    {
5409      allRepServers.addAll(v);
5410    }
5411
5412    Set<String> alreadyConfiguredReplicationServers = new HashSet<>();
5413    configureServer(ctx1, serverDesc1, uData.getServer1(), argParser.server1.replicationPortArg,
5414        usedReplicationServerIds, allRepServers, alreadyConfiguredReplicationServers,
5415        WARN_FIRST_REPLICATION_SERVER_ALREADY_CONFIGURED);
5416    configureServer(ctx2, serverDesc2, uData.getServer2(), argParser.server2.replicationPortArg,
5417        usedReplicationServerIds, allRepServers, alreadyConfiguredReplicationServers,
5418        WARN_SECOND_REPLICATION_SERVER_ALREADY_CONFIGURED);
5419
5420    for (String baseDN : uData.getBaseDNs())
5421    {
5422      Set<String> repServers = hmRepServers.get(baseDN);
5423      Set<Integer> usedIds = hmUsedReplicationDomainIds.get(baseDN);
5424      Set<String> alreadyConfiguredServers = new HashSet<>();
5425
5426      configureToReplicateBaseDN(uData.getServer1(), ctx1, serverDesc1, cache1, baseDN,
5427          usedIds, alreadyConfiguredServers, repServers, allRepServers, alreadyConfiguredReplicationServers);
5428
5429      configureToReplicateBaseDN(uData.getServer2(), ctx2, serverDesc2, cache2, baseDN,
5430          usedIds, alreadyConfiguredServers, repServers, allRepServers, alreadyConfiguredReplicationServers);
5431    }
5432
5433    // Now that replication is configured in all servers, simply try to
5434    // initialize the contents of one ADS with the other (in the case where
5435    // already both servers were replicating the same ADS there is nothing to be done).
5436    if (adsMergeDone)
5437    {
5438      PointAdder pointAdder = new PointAdder(this);
5439      print(INFO_ENABLE_REPLICATION_INITIALIZING_ADS_ALL.get(getHostPort(ctxSource)));
5440      pointAdder.start();
5441      try
5442      {
5443        initializeAllSuffix(ADSContext.getAdministrationSuffixDN(), ctxSource, false);
5444      }
5445      finally
5446      {
5447        pointAdder.stop();
5448      }
5449      print(formatter.getSpace());
5450      print(formatter.getFormattedDone());
5451      println();
5452    }
5453    else if (ctxSource != null && ctxDestination != null)
5454    {
5455      print(formatter.getFormattedWithPoints(
5456          INFO_ENABLE_REPLICATION_INITIALIZING_ADS.get(
5457              getHostPort(ctxDestination), getHostPort(ctxSource))));
5458
5459      initializeSuffix(ADSContext.getAdministrationSuffixDN(), ctxSource, ctxDestination, false);
5460      print(formatter.getFormattedDone());
5461      println();
5462    }
5463
5464    // If we must initialize the schema do so.
5465    if (mustInitializeSchema(serverDesc1, serverDesc2, uData))
5466    {
5467      if (argParser.useSecondServerAsSchemaSource())
5468      {
5469        ctxSource = ctx2;
5470        ctxDestination = ctx1;
5471      }
5472      else
5473      {
5474        ctxSource = ctx1;
5475        ctxDestination = ctx2;
5476      }
5477      if (adsMergeDone)
5478      {
5479        PointAdder pointAdder = new PointAdder(this);
5480        println(INFO_ENABLE_REPLICATION_INITIALIZING_SCHEMA.get(
5481            getHostPort(ctxDestination), getHostPort(ctxSource)));
5482        pointAdder.start();
5483        try
5484        {
5485          initializeAllSuffix(Constants.SCHEMA_DN, ctxSource, false);
5486        }
5487        finally
5488        {
5489          pointAdder.stop();
5490        }
5491        print(formatter.getSpace());
5492      }
5493      else
5494      {
5495        print(formatter.getFormattedWithPoints(INFO_ENABLE_REPLICATION_INITIALIZING_SCHEMA.get(
5496            getHostPort(ctxDestination), getHostPort(ctxSource))));
5497        initializeSuffix(Constants.SCHEMA_DN, ctxSource, ctxDestination, false);
5498      }
5499      print(formatter.getFormattedDone());
5500      println();
5501    }
5502  }
5503
5504  private void addToSets(ServerDescriptor serverDesc, EnableReplicationServerData serverData, InitialLdapContext ctx,
5505      final Set<String> twoReplServers, final Set<Integer> usedReplicationServerIds)
5506  {
5507    if (serverDesc.isReplicationServer())
5508    {
5509      twoReplServers.add(serverDesc.getReplicationServerHostPort());
5510      usedReplicationServerIds.add(serverDesc.getReplicationServerId());
5511    }
5512    else if (serverData.configureReplicationServer())
5513    {
5514      twoReplServers.add(getReplicationServer(getHostName(ctx), serverData.getReplicationPort()));
5515    }
5516  }
5517
5518  private void configureToReplicateBaseDN(EnableReplicationServerData server, InitialLdapContext ctx,
5519      ServerDescriptor serverDesc, TopologyCache cache, String baseDN, Set<Integer> usedIds,
5520      Set<String> alreadyConfiguredServers, Set<String> repServers, final Set<String> allRepServers,
5521      Set<String> alreadyConfiguredReplicationServers) throws ReplicationCliException
5522  {
5523    if (server.configureReplicationDomain()
5524        || areDnsEqual(baseDN, ADSContext.getAdministrationSuffixDN()))
5525    {
5526      try
5527      {
5528        configureToReplicateBaseDN(ctx, baseDN, repServers, usedIds);
5529      }
5530      catch (OpenDsException ode)
5531      {
5532        LocalizableMessage msg = getMessageForEnableException(getHostPort(ctx), baseDN);
5533        throw new ReplicationCliException(msg, ERROR_ENABLING_REPLICATION_ON_BASEDN, ode);
5534      }
5535    }
5536    alreadyConfiguredServers.add(serverDesc.getId());
5537
5538    if (cache != null)
5539    {
5540      configureToReplicateBaseDN(baseDN, repServers, usedIds, cache, serverDesc, alreadyConfiguredServers,
5541          allRepServers, alreadyConfiguredReplicationServers);
5542    }
5543  }
5544
5545  private void configureServer(InitialLdapContext ctx, ServerDescriptor serverDesc,
5546      EnableReplicationServerData enableServer, IntegerArgument replicationPortArg,
5547      Set<Integer> usedReplicationServerIds, Set<String> allRepServers,
5548      Set<String> alreadyConfiguredReplicationServers, Arg2<Number, Number> replicationServerAlreadyConfiguredMsg)
5549      throws ReplicationCliException
5550  {
5551    if (!serverDesc.isReplicationServer() && enableServer.configureReplicationServer())
5552    {
5553      try
5554      {
5555        configureAsReplicationServer(ctx, enableServer.getReplicationPort(), enableServer.isSecureReplication(),
5556            allRepServers, usedReplicationServerIds);
5557      }
5558      catch (OpenDsException ode)
5559      {
5560        throw errorConfiguringReplicationServer(ctx, ode);
5561      }
5562    }
5563    else if (serverDesc.isReplicationServer())
5564    {
5565      try
5566      {
5567        updateReplicationServer(ctx, allRepServers);
5568      }
5569      catch (OpenDsException ode)
5570      {
5571        throw errorConfiguringReplicationServer(ctx, ode);
5572      }
5573      if (replicationPortArg.isPresent() && enableServer.getReplicationPort() != serverDesc.getReplicationServerPort())
5574      {
5575        LocalizableMessage msg = replicationServerAlreadyConfiguredMsg.get(
5576            serverDesc.getReplicationServerPort(), enableServer.getReplicationPort());
5577        logger.warn(msg);
5578        errPrintln(msg);
5579      }
5580    }
5581    alreadyConfiguredReplicationServers.add(serverDesc.getId());
5582  }
5583
5584  private ReplicationCliException errorConfiguringReplicationServer(InitialLdapContext ctx, OpenDsException ode)
5585  {
5586    return new ReplicationCliException(
5587        ERR_REPLICATION_CONFIGURING_REPLICATIONSERVER.get(getHostPort(ctx)),
5588        ERROR_CONFIGURING_REPLICATIONSERVER, ode);
5589  }
5590
5591  private TopologyCache createTopologyCache(ADSContext adsCtx, Set<PreferredConnection> cnx, ReplicationUserData uData)
5592      throws ADSContextException, TopologyCacheException
5593  {
5594    if (adsCtx.hasAdminData())
5595    {
5596      TopologyCache cache = new TopologyCache(adsCtx, getTrustManager(sourceServerCI), getConnectTimeout());
5597      cache.setPreferredConnections(cnx);
5598      cache.getFilter().setSearchMonitoringInformation(false);
5599      addBaseDNs(cache.getFilter(), uData.getBaseDNs());
5600      cache.reloadTopology();
5601      return cache;
5602    }
5603    return null;
5604  }
5605
5606  private ServerDescriptor createStandalone(InitialLdapContext ctx, TopologyCacheFilter filter)
5607      throws ReplicationCliException
5608  {
5609    try
5610    {
5611      return ServerDescriptor.createStandalone(ctx, filter);
5612    }
5613    catch (NamingException ne)
5614    {
5615      throw new ReplicationCliException(
5616          getMessageForException(ne, getHostPort(ctx)),
5617          ERROR_READING_CONFIGURATION, ne);
5618    }
5619  }
5620
5621  /**
5622   * Updates the configuration in the server (and in other servers if
5623   * they are referenced) to disable replication.
5624   * @param ctx the connection to the server.
5625   * @param uData the DisableReplicationUserData object containing the required
5626   * parameters to update the configuration.
5627   * @throws ReplicationCliException if there is an error.
5628   */
5629  private void updateConfiguration(InitialLdapContext ctx,
5630      DisableReplicationUserData uData) throws ReplicationCliException
5631  {
5632    TopologyCacheFilter filter = new TopologyCacheFilter();
5633    filter.setSearchMonitoringInformation(false);
5634    if (!uData.disableAll())
5635    {
5636      filter.addBaseDNToSearch(ADSContext.getAdministrationSuffixDN());
5637      addBaseDNs(filter, uData.getBaseDNs());
5638    }
5639    ServerDescriptor server = createStandalone(ctx, filter);
5640
5641    ADSContext adsCtx = new ADSContext(ctx);
5642
5643    TopologyCache cache = null;
5644    // Only try to update remote server if the user provided a Global
5645    // Administrator to authenticate.
5646    boolean tryToUpdateRemote = uData.getAdminUid() != null;
5647    try
5648    {
5649      if (adsCtx.hasAdminData() && tryToUpdateRemote)
5650      {
5651        cache = new TopologyCache(adsCtx, getTrustManager(sourceServerCI), getConnectTimeout());
5652        cache.setPreferredConnections(getPreferredConnections(ctx));
5653        cache.getFilter().setSearchMonitoringInformation(false);
5654        if (!uData.disableAll())
5655        {
5656          addBaseDNs(cache.getFilter(), uData.getBaseDNs());
5657        }
5658        cache.reloadTopology();
5659      }
5660    }
5661    catch (ADSContextException adce)
5662    {
5663      throw new ReplicationCliException(
5664          ERR_REPLICATION_READING_ADS.get(adce.getMessage()),
5665          ERROR_READING_ADS, adce);
5666    }
5667    catch (TopologyCacheException tce)
5668    {
5669      throw new ReplicationCliException(
5670          ERR_REPLICATION_READING_ADS.get(tce.getMessage()),
5671          ERROR_READING_TOPOLOGY_CACHE, tce);
5672    }
5673    if (!argParser.isInteractive())
5674    {
5675      // Inform the user of the potential errors that we found.
5676      Set<LocalizableMessage> messages = new LinkedHashSet<>();
5677      if (cache != null)
5678      {
5679        messages.addAll(cache.getErrorMessages());
5680      }
5681      if (!messages.isEmpty())
5682      {
5683        errPrintln(
5684            ERR_REPLICATION_READING_REGISTERED_SERVERS_WARNING.get(
5685                getMessageFromCollection(messages,
5686                    Constants.LINE_SEPARATOR)));
5687      }
5688    }
5689
5690    final boolean disableReplicationServer = server.isReplicationServer()
5691        && (uData.disableReplicationServer() || uData.disableAll());
5692    if (cache != null && disableReplicationServer)
5693    {
5694      String replicationServer = server.getReplicationServerHostPort();
5695      // Figure out if this is the last replication server for a given
5696      // topology (containing a different replica) or there will be only
5697      // another replication server left (single point of failure).
5698      Set<SuffixDescriptor> lastRepServer = new TreeSet<>(new SuffixComparator());
5699      Set<SuffixDescriptor> beforeLastRepServer = new TreeSet<>(new SuffixComparator());
5700
5701      for (SuffixDescriptor suffix : cache.getSuffixes())
5702      {
5703        if (isSchemaOrInternalAdminSuffix(suffix.getDN()))
5704        {
5705          // Do not display these suffixes.
5706          continue;
5707        }
5708
5709        Set<String> repServers = suffix.getReplicationServers();
5710        if (repServers.size() <= 2
5711            && containsIgnoreCase(repServers, replicationServer))
5712        {
5713          if (repServers.size() == 2)
5714          {
5715            beforeLastRepServer.add(suffix);
5716          }
5717          else
5718          {
5719            lastRepServer.add(suffix);
5720          }
5721        }
5722      }
5723
5724      // Inform the user
5725      if (!beforeLastRepServer.isEmpty())
5726      {
5727        Set<String> baseDNs = new LinkedHashSet<>();
5728        for (SuffixDescriptor suffix : beforeLastRepServer)
5729        {
5730          if (!isSchemaOrInternalAdminSuffix(suffix.getDN()))
5731          {
5732            // Do not display these suffixes.
5733            baseDNs.add(suffix.getDN());
5734          }
5735        }
5736        if (!baseDNs.isEmpty())
5737        {
5738          String arg = toSingleLine(baseDNs);
5739          if (!isInteractive())
5740          {
5741            println(INFO_DISABLE_REPLICATION_ONE_POINT_OF_FAILURE.get(arg));
5742          }
5743          else
5744          {
5745            LocalizableMessage msg = INFO_DISABLE_REPLICATION_ONE_POINT_OF_FAILURE_PROMPT.get(arg);
5746            if (!askConfirmation(msg, false))
5747            {
5748              throw new ReplicationCliException(ERR_REPLICATION_USER_CANCELLED.get(), USER_CANCELLED, null);
5749            }
5750          }
5751        }
5752      }
5753      if (!lastRepServer.isEmpty())
5754      {
5755        // Check that there are other replicas and that this message, really
5756        // makes sense to be displayed.
5757        Set<String> suffixArg = new LinkedHashSet<>();
5758        for (SuffixDescriptor suffix : lastRepServer)
5759        {
5760          boolean baseDNSpecified = false;
5761          for (String baseDN : uData.getBaseDNs())
5762          {
5763            if (!isSchemaOrInternalAdminSuffix(baseDN) && areDnsEqual(baseDN, suffix.getDN()))
5764            {
5765              baseDNSpecified = true;
5766              break;
5767            }
5768          }
5769          if (!baseDNSpecified)
5770          {
5771            Set<ServerDescriptor> servers = new TreeSet<>(new ServerComparator());
5772            for (ReplicaDescriptor replica : suffix.getReplicas())
5773            {
5774              servers.add(replica.getServer());
5775            }
5776            suffixArg.add(getSuffixDisplay(suffix.getDN(), servers));
5777          }
5778          else if (suffix.getReplicas().size() > 1)
5779          {
5780            // If there is just one replica, it is the one in this server.
5781            Set<ServerDescriptor> servers = new TreeSet<>(new ServerComparator());
5782            for (ReplicaDescriptor replica : suffix.getReplicas())
5783            {
5784              if (!replica.getServer().isSameServer(server))
5785              {
5786                servers.add(replica.getServer());
5787              }
5788            }
5789            if (!servers.isEmpty())
5790            {
5791              suffixArg.add(getSuffixDisplay(suffix.getDN(), servers));
5792            }
5793          }
5794        }
5795
5796        if (!suffixArg.isEmpty())
5797        {
5798          String arg = toSingleLine(suffixArg);
5799          if (!isInteractive())
5800          {
5801            println(INFO_DISABLE_REPLICATION_DISABLE_IN_REMOTE.get(arg));
5802          }
5803          else
5804          {
5805            LocalizableMessage msg = INFO_DISABLE_REPLICATION_DISABLE_IN_REMOTE_PROMPT.get(arg);
5806            if (!askConfirmation(msg, false))
5807            {
5808              throw new ReplicationCliException(ERR_REPLICATION_USER_CANCELLED.get(), USER_CANCELLED, null);
5809            }
5810          }
5811        }
5812      }
5813    }
5814
5815    /**
5816     * Try to figure out if we must explicitly disable replication on
5817     * cn=admin data and cn=schema.
5818     */
5819    boolean forceDisableSchema = false;
5820    boolean forceDisableADS = false;
5821    boolean schemaReplicated = false;
5822    boolean adsReplicated = false;
5823    boolean disableAllBaseDns = disableAllBaseDns(ctx, uData);
5824
5825    Collection<ReplicaDescriptor> replicas = getReplicas(ctx);
5826    for (ReplicaDescriptor rep : replicas)
5827    {
5828      String dn = rep.getSuffix().getDN();
5829      if (rep.isReplicated())
5830      {
5831        if (areDnsEqual(ADSContext.getAdministrationSuffixDN(), dn))
5832        {
5833          adsReplicated = true;
5834        }
5835        else if (areDnsEqual(Constants.SCHEMA_DN, dn))
5836        {
5837          schemaReplicated = true;
5838        }
5839      }
5840    }
5841
5842    if (disableAllBaseDns &&
5843        (disableReplicationServer || !server.isReplicationServer()))
5844    {
5845      // Unregister the server from the ADS if no other server has dependencies
5846      // with it (no replicated base DNs and no replication server).
5847      server.updateAdsPropertiesWithServerProperties();
5848      try
5849      {
5850        adsCtx.unregisterServer(server.getAdsProperties());
5851        // To be sure that the change gets propagated
5852        sleepCatchInterrupt(2000);
5853      }
5854      catch (ADSContextException adce)
5855      {
5856        logger.error(LocalizableMessage.raw("Error unregistering server: "+
5857            server.getAdsProperties(), adce));
5858        if (adce.getError() != ADSContextException.ErrorType.NOT_YET_REGISTERED)
5859        {
5860          throw new ReplicationCliException(
5861              ERR_REPLICATION_UPDATING_ADS.get(adce.getMessageObject()),
5862              ERROR_READING_ADS, adce);
5863        }
5864      }
5865    }
5866
5867    Set<String> suffixesToDisable = new HashSet<>();
5868    if (uData.disableAll())
5869    {
5870      for (ReplicaDescriptor replica : server.getReplicas())
5871      {
5872        if (replica.isReplicated())
5873        {
5874          suffixesToDisable.add(replica.getSuffix().getDN());
5875        }
5876      }
5877    }
5878    else
5879    {
5880      suffixesToDisable.addAll(uData.getBaseDNs());
5881
5882      if (disableAllBaseDns &&
5883          (disableReplicationServer || !server.isReplicationServer()))
5884      {
5885        forceDisableSchema = schemaReplicated;
5886        forceDisableADS = adsReplicated;
5887      }
5888      for (String dn : uData.getBaseDNs())
5889      {
5890        if (areDnsEqual(ADSContext.getAdministrationSuffixDN(), dn))
5891        {
5892          // The user already asked this to be explicitly disabled
5893          forceDisableADS = false;
5894        }
5895        else if (areDnsEqual(Constants.SCHEMA_DN, dn))
5896        {
5897          // The user already asked this to be explicitly disabled
5898          forceDisableSchema = false;
5899        }
5900      }
5901
5902      if (forceDisableSchema)
5903      {
5904        suffixesToDisable.add(Constants.SCHEMA_DN);
5905      }
5906      if (forceDisableADS)
5907      {
5908        suffixesToDisable.add(ADSContext.getAdministrationSuffixDN());
5909      }
5910    }
5911
5912    String replicationServerHostPort =
5913        server.isReplicationServer() ? server.getReplicationServerHostPort() : null;
5914
5915    for (String baseDN : suffixesToDisable)
5916    {
5917      try
5918      {
5919        deleteReplicationDomain(ctx, baseDN);
5920      }
5921      catch (OpenDsException ode)
5922      {
5923        LocalizableMessage msg = getMessageForDisableException(getHostPort(ctx), baseDN);
5924        throw new ReplicationCliException(msg,
5925            ERROR_DISABLING_REPLICATION_ON_BASEDN, ode);
5926      }
5927    }
5928
5929    boolean replicationServerDisabled = false;
5930    if (replicationServerHostPort != null && cache != null)
5931    {
5932      Set<ServerDescriptor> serversToUpdate = new LinkedHashSet<>();
5933      Set<String> baseDNsToUpdate = new HashSet<>(suffixesToDisable);
5934      for (String baseDN : baseDNsToUpdate)
5935      {
5936        SuffixDescriptor suffix = getSuffix(baseDN, cache, server);
5937        if (suffix != null)
5938        {
5939          for (ReplicaDescriptor replica : suffix.getReplicas())
5940          {
5941            serversToUpdate.add(replica.getServer());
5942          }
5943        }
5944      }
5945      if (disableReplicationServer)
5946      {
5947        // Find references in all servers.
5948        for (SuffixDescriptor suffix : cache.getSuffixes())
5949        {
5950          if (containsIgnoreCase(suffix.getReplicationServers(), replicationServerHostPort))
5951          {
5952            baseDNsToUpdate.add(suffix.getDN());
5953            for (ReplicaDescriptor replica : suffix.getReplicas())
5954            {
5955              serversToUpdate.add(replica.getServer());
5956            }
5957          }
5958        }
5959      }
5960      String bindDn = getBindDN(ctx);
5961      String pwd = getBindPassword(ctx);
5962      for (ServerDescriptor s : serversToUpdate)
5963      {
5964        removeReferencesInServer(s, replicationServerHostPort, bindDn, pwd,
5965            baseDNsToUpdate, disableReplicationServer,
5966            getPreferredConnections(ctx));
5967      }
5968
5969      if (disableReplicationServer)
5970      {
5971        // Disable replication server
5972        disableReplicationServer(ctx);
5973        replicationServerDisabled = true;
5974        // Wait to be sure that changes are taken into account and reset the
5975        // contents of the ADS.
5976        sleepCatchInterrupt(5000);
5977      }
5978    }
5979    if (disableReplicationServer && !replicationServerDisabled)
5980    {
5981      // This can happen if we could not retrieve the TopologyCache
5982      disableReplicationServer(ctx);
5983      replicationServerDisabled = true;
5984    }
5985
5986    if (uData.disableAll())
5987    {
5988      try
5989      {
5990        // Delete all contents from ADSContext.
5991        print(formatter.getFormattedWithPoints(
5992            INFO_REPLICATION_REMOVE_ADS_CONTENTS.get()));
5993        adsCtx.removeAdminData(false /* avoid self-disconnect */);
5994        print(formatter.getFormattedDone());
5995        println();
5996      }
5997      catch (ADSContextException adce)
5998      {
5999        logger.error(LocalizableMessage.raw("Error removing contents of cn=admin data: "+
6000            adce, adce));
6001        throw new ReplicationCliException(
6002            ERR_REPLICATION_UPDATING_ADS.get(adce.getMessageObject()),
6003            ERROR_UPDATING_ADS, adce);
6004      }
6005    }
6006    else if (disableAllBaseDns &&
6007        (disableReplicationServer || !server.isReplicationServer()))
6008    {
6009      // Unregister the servers from the ADS of the local server.
6010      try
6011      {
6012        for (Map<ADSContext.ServerProperty, Object> s : adsCtx.readServerRegistry())
6013        {
6014          adsCtx.unregisterServer(s);
6015        }
6016        // To be sure that the change gets propagated
6017        sleepCatchInterrupt(2000);
6018      }
6019      catch (ADSContextException adce)
6020      {
6021        // This is not critical, do not send an error
6022        logger.warn(LocalizableMessage.raw("Error unregistering server: "+
6023            server.getAdsProperties(), adce));
6024      }
6025    }
6026  }
6027
6028  private void addBaseDNs(TopologyCacheFilter filter, List<String> baseDNs)
6029  {
6030    for (String dn : baseDNs)
6031    {
6032      filter.addBaseDNToSearch(dn);
6033    }
6034  }
6035
6036  /**
6037   * Displays the replication status of the different base DNs in the servers
6038   * registered in the ADS.
6039   * @param ctx the connection to the server.
6040   * @param uData the StatusReplicationUserData object containing the required
6041   * parameters to update the configuration.
6042   * @throws ReplicationCliException if there is an error.
6043   */
6044  private void displayStatus(InitialLdapContext ctx,
6045      StatusReplicationUserData uData) throws ReplicationCliException
6046  {
6047    ADSContext adsCtx = new ADSContext(ctx);
6048
6049    boolean somethingDisplayed = false;
6050    TopologyCache cache;
6051    try
6052    {
6053      cache = new TopologyCache(adsCtx, getTrustManager(sourceServerCI), getConnectTimeout());
6054      cache.setPreferredConnections(getPreferredConnections(ctx));
6055      addBaseDNs(cache.getFilter(), uData.getBaseDNs());
6056      cache.reloadTopology();
6057    }
6058    catch (TopologyCacheException tce)
6059    {
6060      throw new ReplicationCliException(
6061          ERR_REPLICATION_READING_ADS.get(tce.getMessage()),
6062          ERROR_READING_TOPOLOGY_CACHE, tce);
6063    }
6064    if (mustPrintCommandBuilder())
6065    {
6066      printNewCommandBuilder(STATUS_REPLICATION_SUBCMD_NAME, uData);
6067    }
6068    if (!argParser.isInteractive())
6069    {
6070      // Inform the user of the potential errors that we found.
6071      Set<LocalizableMessage> messages = new LinkedHashSet<>(cache.getErrorMessages());
6072      if (!messages.isEmpty())
6073      {
6074        errPrintln(ERR_REPLICATION_STATUS_READING_REGISTERED_SERVERS.get(
6075            getMessageFromCollection(messages, Constants.LINE_SEPARATOR)));
6076      }
6077    }
6078
6079    List<String> userBaseDNs = uData.getBaseDNs();
6080    List<Set<ReplicaDescriptor>> replicaLists = new LinkedList<>();
6081
6082    boolean oneReplicated = false;
6083
6084    boolean displayAll = userBaseDNs.isEmpty();
6085    for (SuffixDescriptor suffix : cache.getSuffixes())
6086    {
6087      String dn = suffix.getDN();
6088
6089      // If no base DNs where specified display all the base DNs but the schema
6090      // and cn=admin data.
6091      boolean found = containsDN(userBaseDNs, dn) || (displayAll && !isSchemaOrInternalAdminSuffix(dn));
6092      if (found)
6093      {
6094        if (isAnyReplicated(suffix))
6095        {
6096          oneReplicated = true;
6097          replicaLists.add(suffix.getReplicas());
6098        }
6099        else
6100        {
6101          // Check if there are already some non replicated base DNs.
6102          found = false;
6103          for (Set<ReplicaDescriptor> replicas : replicaLists)
6104          {
6105            ReplicaDescriptor replica = replicas.iterator().next();
6106            if (!replica.isReplicated() &&
6107                areDnsEqual(dn, replica.getSuffix().getDN()))
6108            {
6109              replicas.addAll(suffix.getReplicas());
6110              found = true;
6111              break;
6112            }
6113          }
6114          if (!found)
6115          {
6116            replicaLists.add(suffix.getReplicas());
6117          }
6118        }
6119      }
6120    }
6121
6122    if (!oneReplicated && displayAll)
6123    {
6124      // Maybe there are some replication server configured...
6125      SortedSet<ServerDescriptor> rServers = new TreeSet<>(new ReplicationServerComparator());
6126      for (ServerDescriptor server : cache.getServers())
6127      {
6128        if (server.isReplicationServer())
6129        {
6130          rServers.add(server);
6131        }
6132      }
6133      if (!rServers.isEmpty())
6134      {
6135        displayStatus(rServers, uData.isScriptFriendly(), getPreferredConnections(ctx));
6136        somethingDisplayed = true;
6137      }
6138    }
6139
6140    if (!replicaLists.isEmpty())
6141    {
6142      List<Set<ReplicaDescriptor>> orderedReplicaLists = new LinkedList<>();
6143      for (Set<ReplicaDescriptor> replicas : replicaLists)
6144      {
6145        String dn1 = replicas.iterator().next().getSuffix().getDN();
6146        boolean inserted = false;
6147        for (int i=0; i<orderedReplicaLists.size() && !inserted; i++)
6148        {
6149          String dn2 =
6150            orderedReplicaLists.get(i).iterator().next().getSuffix().getDN();
6151          if (dn1.compareTo(dn2) < 0)
6152          {
6153            orderedReplicaLists.add(i, replicas);
6154            inserted = true;
6155          }
6156        }
6157        if (!inserted)
6158        {
6159          orderedReplicaLists.add(replicas);
6160        }
6161      }
6162      Set<ReplicaDescriptor> replicasWithNoReplicationServer = new HashSet<>();
6163      Set<ServerDescriptor> serversWithNoReplica = new HashSet<>();
6164      displayStatus(orderedReplicaLists, uData.isScriptFriendly(),
6165            getPreferredConnections(ctx),
6166            cache.getServers(),
6167            replicasWithNoReplicationServer, serversWithNoReplica);
6168      somethingDisplayed = true;
6169
6170      if (oneReplicated && !uData.isScriptFriendly())
6171      {
6172        println();
6173        print(INFO_REPLICATION_STATUS_REPLICATED_LEGEND.get());
6174
6175        if (!replicasWithNoReplicationServer.isEmpty() ||
6176            !serversWithNoReplica.isEmpty())
6177        {
6178          println();
6179          print(
6180              INFO_REPLICATION_STATUS_NOT_A_REPLICATION_SERVER_LEGEND.get());
6181
6182          println();
6183          print(
6184              INFO_REPLICATION_STATUS_NOT_A_REPLICATION_DOMAIN_LEGEND.get());
6185        }
6186        println();
6187        somethingDisplayed = true;
6188      }
6189    }
6190    if (!somethingDisplayed)
6191    {
6192      if (displayAll)
6193      {
6194        print(INFO_REPLICATION_STATUS_NO_REPLICATION_INFORMATION.get());
6195        println();
6196      }
6197      else
6198      {
6199        print(INFO_REPLICATION_STATUS_NO_BASEDNS.get());
6200        println();
6201      }
6202    }
6203  }
6204
6205  private boolean isAnyReplicated(SuffixDescriptor suffix)
6206  {
6207    for (ReplicaDescriptor replica : suffix.getReplicas())
6208    {
6209      if (replica.isReplicated())
6210      {
6211        return true;
6212      }
6213    }
6214    return false;
6215  }
6216
6217  /**
6218   * Displays the replication status of the replicas provided.  The code assumes
6219   * that all the replicas have the same baseDN and that if they are replicated
6220   * all the replicas are replicated with each other.
6221   * Note: the code assumes that all the objects come from the same read of the
6222   * topology cache.  So comparisons in terms of pointers can be made.
6223   * @param orderedReplicaLists the list of replicas that we are trying to
6224   * display.
6225   * @param scriptFriendly whether to display it on script-friendly mode or not.
6226   * @param cnx the preferred connections used to connect to the server.
6227   * @param servers all the servers configured in the topology.
6228   * @param replicasWithNoReplicationServer the set of replicas that will be
6229   * updated with all the replicas that have no replication server.
6230   * @param serversWithNoReplica the set of servers that will be updated with
6231   * all the servers that act as replication server in the topology but have
6232   * no replica.
6233   */
6234  private void displayStatus(
6235      List<Set<ReplicaDescriptor>> orderedReplicaLists,
6236      boolean scriptFriendly, Set<PreferredConnection> cnx,
6237      Set<ServerDescriptor> servers,
6238      Set<ReplicaDescriptor> replicasWithNoReplicationServer,
6239      Set<ServerDescriptor> serversWithNoReplica)
6240  {
6241    Set<ReplicaDescriptor> orderedReplicas = new LinkedHashSet<>();
6242    Set<String> hostPorts = new TreeSet<>();
6243    Set<ServerDescriptor> notAddedReplicationServers = new TreeSet<>(new ReplicationServerComparator());
6244    for (Set<ReplicaDescriptor> replicas : orderedReplicaLists)
6245    {
6246      for (ReplicaDescriptor replica : replicas)
6247      {
6248        hostPorts.add(getHostPort2(replica.getServer(), cnx));
6249      }
6250      for (String hostPort : hostPorts)
6251      {
6252        for (ReplicaDescriptor replica : replicas)
6253        {
6254          if (getHostPort2(replica.getServer(), cnx).equals(hostPort))
6255          {
6256            orderedReplicas.add(replica);
6257          }
6258        }
6259      }
6260      for (ServerDescriptor server : servers)
6261      {
6262        if (server.isReplicationServer() && isRepServerNotInDomain(replicas, server))
6263        {
6264          notAddedReplicationServers.add(server);
6265        }
6266      }
6267    }
6268
6269    /*
6270     * The table has the following columns:
6271     * - suffix DN;
6272     * - server;
6273     * - number of entries;
6274     * - replication enabled indicator;
6275     * - directory server instance ID;
6276     * - replication server;
6277     * - replication server ID;
6278     * - missing changes;
6279     * - age of the oldest change, and
6280     * - security enabled indicator.
6281     */
6282    TableBuilder tableBuilder = new TableBuilder();
6283
6284    /* Table headings. */
6285    tableBuilder.appendHeading(
6286      INFO_REPLICATION_STATUS_HEADER_SUFFIX_DN.get());
6287    tableBuilder.appendHeading(
6288      INFO_REPLICATION_STATUS_HEADER_SERVERPORT.get());
6289    tableBuilder.appendHeading(
6290      INFO_REPLICATION_STATUS_HEADER_NUMBER_ENTRIES.get());
6291    tableBuilder.appendHeading(
6292      INFO_REPLICATION_STATUS_HEADER_REPLICATION_ENABLED.get());
6293    tableBuilder.appendHeading(INFO_REPLICATION_STATUS_HEADER_DS_ID.get());
6294    tableBuilder.appendHeading(INFO_REPLICATION_STATUS_HEADER_RS_ID.get());
6295    tableBuilder.appendHeading(
6296        INFO_REPLICATION_STATUS_HEADER_REPLICATION_PORT.get());
6297    tableBuilder.appendHeading(
6298      INFO_REPLICATION_STATUS_HEADER_MISSING_CHANGES.get());
6299    tableBuilder.appendHeading(
6300      INFO_REPLICATION_STATUS_HEADER_AGE_OF_OLDEST_MISSING_CHANGE.get());
6301    tableBuilder.appendHeading(
6302      INFO_REPLICATION_STATUS_HEADER_SECURE.get());
6303
6304    /* Table data. */
6305    for (ReplicaDescriptor replica : orderedReplicas)
6306    {
6307      tableBuilder.startRow();
6308      // Suffix DN
6309      tableBuilder.appendCell(LocalizableMessage.raw(replica.getSuffix().getDN()));
6310      // Server port
6311      tableBuilder.appendCell(
6312          LocalizableMessage.raw(getHostPort2(replica.getServer(), cnx)));
6313      // Number of entries
6314      int nEntries = replica.getEntries();
6315      if (nEntries >= 0)
6316      {
6317        tableBuilder.appendCell(LocalizableMessage.raw(String.valueOf(nEntries)));
6318      }
6319      else
6320      {
6321        tableBuilder.appendCell(EMPTY_MSG);
6322      }
6323
6324      if (!replica.isReplicated())
6325      {
6326        tableBuilder.appendCell(EMPTY_MSG);
6327      }
6328      else
6329      {
6330        // Replication enabled
6331        tableBuilder.appendCell(
6332          LocalizableMessage.raw(Boolean.toString(replica.isReplicationEnabled())));
6333
6334        // DS instance ID
6335        tableBuilder.appendCell(
6336            LocalizableMessage.raw(Integer.toString(replica.getReplicationId())));
6337
6338        // RS ID and port.
6339        if (replica.getServer().isReplicationServer())
6340        {
6341          tableBuilder.appendCell(Integer.toString(replica.getServer()
6342              .getReplicationServerId()));
6343          tableBuilder.appendCell(LocalizableMessage.raw(String.valueOf(replica
6344              .getServer().getReplicationServerPort())));
6345        }
6346        else
6347        {
6348          if (scriptFriendly)
6349          {
6350            tableBuilder.appendCell(EMPTY_MSG);
6351          }
6352          else
6353          {
6354            tableBuilder.appendCell(
6355              INFO_REPLICATION_STATUS_NOT_A_REPLICATION_SERVER_SHORT.get());
6356          }
6357          tableBuilder.appendCell(EMPTY_MSG);
6358          replicasWithNoReplicationServer.add(replica);
6359        }
6360
6361        // Missing changes
6362        int missingChanges = replica.getMissingChanges();
6363        if (missingChanges >= 0)
6364        {
6365          tableBuilder.appendCell(LocalizableMessage.raw(String.valueOf(missingChanges)));
6366        }
6367        else
6368        {
6369          tableBuilder.appendCell(EMPTY_MSG);
6370        }
6371
6372        // Age of oldest missing change
6373        long ageOfOldestMissingChange = replica.getAgeOfOldestMissingChange();
6374        if (ageOfOldestMissingChange > 0)
6375        {
6376          Date date = new Date(ageOfOldestMissingChange);
6377          tableBuilder.appendCell(LocalizableMessage.raw(date.toString()));
6378        }
6379        else
6380        {
6381          tableBuilder.appendCell(EMPTY_MSG);
6382        }
6383
6384        // Secure
6385        if (!replica.getServer().isReplicationServer())
6386        {
6387          tableBuilder.appendCell(EMPTY_MSG);
6388        }
6389        else
6390        {
6391          tableBuilder.appendCell(
6392            LocalizableMessage.raw(Boolean.toString(
6393              replica.getServer().isReplicationSecure())));
6394        }
6395      }
6396    }
6397
6398    for (ServerDescriptor server : notAddedReplicationServers)
6399    {
6400      tableBuilder.startRow();
6401      serversWithNoReplica.add(server);
6402
6403      // Suffix DN
6404      tableBuilder.appendCell(EMPTY_MSG);
6405      // Server port
6406      tableBuilder.appendCell(LocalizableMessage.raw(getHostPort2(server, cnx)));
6407      // Number of entries
6408      if (scriptFriendly)
6409      {
6410        tableBuilder.appendCell(EMPTY_MSG);
6411      }
6412      else
6413      {
6414        tableBuilder.appendCell(
6415          INFO_REPLICATION_STATUS_NOT_A_REPLICATION_DOMAIN_SHORT.get());
6416      }
6417
6418      // Replication enabled
6419      tableBuilder.appendCell(Boolean.toString(true));
6420
6421      // DS ID
6422      tableBuilder.appendCell(EMPTY_MSG);
6423
6424      // RS ID
6425      tableBuilder.appendCell(
6426        LocalizableMessage.raw(Integer.toString(server.getReplicationServerId())));
6427
6428      // Replication port
6429      int replicationPort = server.getReplicationServerPort();
6430      if (replicationPort >= 0)
6431      {
6432        tableBuilder.appendCell(
6433          LocalizableMessage.raw(String.valueOf(replicationPort)));
6434      }
6435      else
6436      {
6437        tableBuilder.appendCell(EMPTY_MSG);
6438      }
6439
6440      // Missing changes
6441      tableBuilder.appendCell(EMPTY_MSG);
6442
6443      // Age of oldest change
6444      tableBuilder.appendCell(EMPTY_MSG);
6445
6446      // Secure
6447      tableBuilder.appendCell(
6448        LocalizableMessage.raw(Boolean.toString(server.isReplicationSecure())));
6449    }
6450
6451    TablePrinter printer;
6452    PrintStream out = getOutputStream();
6453    if (scriptFriendly)
6454    {
6455      printer = new TabSeparatedTablePrinter(out);
6456    }
6457    else
6458    {
6459      final TextTablePrinter ttPrinter = new TextTablePrinter(out);
6460      ttPrinter.setColumnSeparator(LIST_TABLE_SEPARATOR);
6461      printer = ttPrinter;
6462    }
6463    tableBuilder.print(printer);
6464  }
6465
6466  private boolean isRepServerNotInDomain(Set<ReplicaDescriptor> replicas, ServerDescriptor server)
6467  {
6468    boolean isDomain = false;
6469    boolean isRepServer = false;
6470    String replicationServer = server.getReplicationServerHostPort();
6471    for (ReplicaDescriptor replica : replicas)
6472    {
6473      if (!isRepServer)
6474      {
6475        isRepServer = containsIgnoreCase(replica.getReplicationServers(), replicationServer);
6476      }
6477      if (replica.getServer() == server)
6478      {
6479        isDomain = true;
6480      }
6481      if (isDomain && isRepServer)
6482      {
6483        break;
6484      }
6485    }
6486    return !isDomain && isRepServer;
6487  }
6488
6489  /**
6490   * Displays the replication status of the replication servers provided.  The
6491   * code assumes that all the servers have a replication server and that there
6492   * are associated with no replication domain.
6493   * @param servers the servers
6494   * @param cnx the preferred connections used to connect to the server.
6495   * @param scriptFriendly wheter to display it on script-friendly mode or not.
6496   */
6497  private void displayStatus(Set<ServerDescriptor> servers,
6498      boolean scriptFriendly, Set<PreferredConnection> cnx)
6499  {
6500    TableBuilder tableBuilder = new TableBuilder();
6501    tableBuilder.appendHeading(INFO_REPLICATION_STATUS_HEADER_SERVERPORT.get());
6502    tableBuilder.appendHeading(
6503      INFO_REPLICATION_STATUS_HEADER_REPLICATION_PORT.get());
6504    tableBuilder.appendHeading(INFO_REPLICATION_STATUS_HEADER_SECURE.get());
6505
6506    for (ServerDescriptor server : servers)
6507    {
6508      tableBuilder.startRow();
6509      // Server port
6510      tableBuilder.appendCell(LocalizableMessage.raw(getHostPort2(server, cnx)));
6511      // Replication port
6512      int replicationPort = server.getReplicationServerPort();
6513      if (replicationPort >= 0)
6514      {
6515        tableBuilder.appendCell(LocalizableMessage.raw(String.valueOf(replicationPort)));
6516      }
6517      else
6518      {
6519        tableBuilder.appendCell(EMPTY_MSG);
6520      }
6521      // Secure
6522      tableBuilder.appendCell(LocalizableMessage.raw(Boolean.toString(server.isReplicationSecure())));
6523    }
6524
6525    PrintStream out = getOutputStream();
6526    TablePrinter printer;
6527
6528    if (scriptFriendly)
6529    {
6530      print(INFO_REPLICATION_STATUS_INDEPENDENT_REPLICATION_SERVERS.get());
6531      println();
6532      printer = new TabSeparatedTablePrinter(out);
6533    }
6534    else
6535    {
6536      LocalizableMessage msg = INFO_REPLICATION_STATUS_INDEPENDENT_REPLICATION_SERVERS.get();
6537      print(msg);
6538      println();
6539      int length = msg.length();
6540      StringBuilder buf = new StringBuilder();
6541      for (int i=0; i<length; i++)
6542      {
6543        buf.append("=");
6544      }
6545      print(LocalizableMessage.raw(buf.toString()));
6546      println();
6547
6548      printer = new TextTablePrinter(getOutputStream());
6549      ((TextTablePrinter)printer).setColumnSeparator(
6550        LIST_TABLE_SEPARATOR);
6551    }
6552    tableBuilder.print(printer);
6553  }
6554
6555  /**
6556   * Retrieves all the replication servers for a given baseDN.  The
6557   * ServerDescriptor is used to identify the server where the suffix is
6558   * defined and it cannot be null.  The TopologyCache is used to retrieve
6559   * replication servers defined in other replicas but not in the one we
6560   * get in the ServerDescriptor.
6561   * @param baseDN the base DN.
6562   * @param cache the TopologyCache (might be null).
6563   * @param server the ServerDescriptor.
6564   * @return a Set containing the replication servers currently being used
6565   * to replicate the baseDN defined in the server described by the
6566   * ServerDescriptor.
6567   */
6568  private Set<String> getReplicationServers(String baseDN,
6569      TopologyCache cache, ServerDescriptor server)
6570  {
6571    Set<String> servers = getAllReplicationServers(baseDN, server);
6572    if (cache != null)
6573    {
6574      for (SuffixDescriptor suffix : cache.getSuffixes())
6575      {
6576        if (areDnsEqual(suffix.getDN(), baseDN))
6577        {
6578          Set<String> s = suffix.getReplicationServers();
6579          // Test that at least we share one of the replication servers.
6580          // If we do: we are dealing with the same replication topology
6581          // (we must consider the case of disjoint replication topologies
6582          // replicating the same base DN).
6583          Set<String> copy = new HashSet<>(s);
6584          copy.retainAll(servers);
6585          if (!copy.isEmpty())
6586          {
6587            servers.addAll(s);
6588            break;
6589          }
6590          else if (server.isReplicationServer()
6591              && containsIgnoreCase(s, server.getReplicationServerHostPort()))
6592          {
6593            // this server is acting as replication server with no domain.
6594            servers.addAll(s);
6595            break;
6596          }
6597        }
6598      }
6599    }
6600    return servers;
6601  }
6602
6603  private boolean containsIgnoreCase(Set<String> col, String toFind)
6604  {
6605    for (String s : col)
6606    {
6607      if (s.equalsIgnoreCase(toFind))
6608      {
6609        return true;
6610      }
6611    }
6612    return false;
6613  }
6614
6615  private String findIgnoreCase(Set<String> col, String toFind)
6616  {
6617    for (String s : col)
6618    {
6619      if (toFind.equalsIgnoreCase(s))
6620      {
6621        return s;
6622      }
6623    }
6624    return null;
6625  }
6626
6627  /**
6628   * Retrieves the suffix in the TopologyCache for a given baseDN.  The
6629   * ServerDescriptor is used to identify the server where the suffix is
6630   * defined.
6631   * @param baseDN the base DN.
6632   * @param cache the TopologyCache.
6633   * @param server the ServerDescriptor.
6634   * @return the suffix in the TopologyCache for a given baseDN.
6635   */
6636  private SuffixDescriptor getSuffix(String baseDN, TopologyCache cache,
6637      ServerDescriptor server)
6638  {
6639    String replicationServer = null;
6640    if (server.isReplicationServer())
6641    {
6642      replicationServer = server.getReplicationServerHostPort();
6643    }
6644
6645    SuffixDescriptor returnValue = null;
6646    Set<String> servers = getAllReplicationServers(baseDN, server);
6647    for (SuffixDescriptor suffix : cache.getSuffixes())
6648    {
6649      if (areDnsEqual(suffix.getDN(), baseDN))
6650      {
6651        Set<String> s = suffix.getReplicationServers();
6652        // Test that at least we share one of the replication servers.
6653        // If we do: we are dealing with the same replication topology
6654        // (we must consider the case of disjoint replication topologies
6655        // replicating the same base DN).
6656        HashSet<String> copy = new HashSet<>(s);
6657        copy.retainAll(servers);
6658        if (!copy.isEmpty())
6659        {
6660          return suffix;
6661        }
6662        else if (replicationServer != null && containsIgnoreCase(s, replicationServer))
6663        {
6664          returnValue = suffix;
6665        }
6666      }
6667    }
6668    return returnValue;
6669  }
6670
6671  private Set<String> getAllReplicationServers(String baseDN, ServerDescriptor server)
6672  {
6673    Set<String> servers = new LinkedHashSet<>();
6674    for (ReplicaDescriptor replica : server.getReplicas())
6675    {
6676      if (areDnsEqual(replica.getSuffix().getDN(), baseDN))
6677      {
6678        servers.addAll(replica.getReplicationServers());
6679        break;
6680      }
6681    }
6682    return servers;
6683  }
6684
6685  /**
6686   * Retrieves all the replication domain IDs for a given baseDN in the
6687   * ServerDescriptor.
6688   * @param baseDN the base DN.
6689   * @param server the ServerDescriptor.
6690   * @return a Set containing the replication domain IDs for a given baseDN in
6691   * the ServerDescriptor.
6692   */
6693  private Set<Integer> getReplicationDomainIds(String baseDN,
6694      ServerDescriptor server)
6695  {
6696    Set<Integer> ids = new HashSet<>();
6697    for (ReplicaDescriptor replica : server.getReplicas())
6698    {
6699      if (replica.isReplicated()
6700          && areDnsEqual(replica.getSuffix().getDN(), baseDN))
6701      {
6702        ids.add(replica.getReplicationId());
6703        break;
6704      }
6705    }
6706    return ids;
6707  }
6708
6709  /**
6710   * Configures the server to which the provided InitialLdapContext is connected
6711   * as a replication server.  The replication server listens in the provided
6712   * port.
6713   * @param ctx the context connected to the server that we want to configure.
6714   * @param replicationPort the replication port of the replication server.
6715   * @param useSecureReplication whether to have encrypted communication with
6716   * the replication port or not.
6717   * @param replicationServers the list of replication servers to which the
6718   * replication server will communicate with.
6719   * @param usedReplicationServerIds the set of replication server IDs that
6720   * are already in use.  The set will be updated with the replication ID
6721   * that will be used by the newly configured replication server.
6722   * @throws OpenDsException if there is an error updating the configuration.
6723   */
6724  private void configureAsReplicationServer(InitialLdapContext ctx,
6725      int replicationPort, boolean useSecureReplication,
6726      Set<String> replicationServers,
6727      Set<Integer> usedReplicationServerIds) throws OpenDsException
6728  {
6729    print(formatter.getFormattedWithPoints(
6730        INFO_REPLICATION_ENABLE_CONFIGURING_REPLICATION_SERVER.get(getHostPort(ctx))));
6731
6732    ManagementContext mCtx = LDAPManagementContext.createFromContext(
6733        JNDIDirContextAdaptor.adapt(ctx));
6734    RootCfgClient root = mCtx.getRootConfiguration();
6735
6736    /* Configure Synchronization plugin. */
6737    ReplicationSynchronizationProviderCfgClient sync = null;
6738    try
6739    {
6740      sync = (ReplicationSynchronizationProviderCfgClient)
6741      root.getSynchronizationProvider("Multimaster Synchronization");
6742    }
6743    catch (ManagedObjectNotFoundException monfe)
6744    {
6745      logger.info(LocalizableMessage.raw("Synchronization server does not exist in " + getHostPort(ctx)));
6746    }
6747    if (sync == null)
6748    {
6749      ReplicationSynchronizationProviderCfgDefn provider =
6750        ReplicationSynchronizationProviderCfgDefn.getInstance();
6751      sync = root.createSynchronizationProvider(provider,
6752          "Multimaster Synchronization",
6753          new ArrayList<PropertyException>());
6754      sync.setJavaClass(
6755          org.opends.server.replication.plugin.MultimasterReplication.class.
6756          getName());
6757      sync.setEnabled(Boolean.TRUE);
6758    }
6759    else if (!sync.isEnabled())
6760    {
6761      sync.setEnabled(Boolean.TRUE);
6762    }
6763    sync.commit();
6764
6765    /* Configure the replication server. */
6766    ReplicationServerCfgClient replicationServer;
6767
6768    boolean mustCommit = false;
6769
6770    if (!sync.hasReplicationServer())
6771    {
6772      CryptoManagerCfgClient crypto = root.getCryptoManager();
6773      if (useSecureReplication != crypto.isSSLEncryption())
6774      {
6775        crypto.setSSLEncryption(useSecureReplication);
6776        crypto.commit();
6777      }
6778      int id = InstallerHelper.getReplicationId(usedReplicationServerIds);
6779      usedReplicationServerIds.add(id);
6780      replicationServer = sync.createReplicationServer(
6781          ReplicationServerCfgDefn.getInstance(),
6782          new ArrayList<PropertyException>());
6783      replicationServer.setReplicationServerId(id);
6784      replicationServer.setReplicationPort(replicationPort);
6785      replicationServer.setReplicationServer(replicationServers);
6786      mustCommit = true;
6787    }
6788    else
6789    {
6790      replicationServer = sync.getReplicationServer();
6791      usedReplicationServerIds.add(
6792          replicationServer.getReplicationServerId());
6793      Set<String> servers = replicationServer.getReplicationServer();
6794      if (servers == null)
6795      {
6796        replicationServer.setReplicationServer(replicationServers);
6797        mustCommit = true;
6798      }
6799      else if (!areReplicationServersEqual(servers, replicationServers))
6800      {
6801        replicationServer.setReplicationServer(
6802            mergeReplicationServers(replicationServers, servers));
6803        mustCommit = true;
6804      }
6805    }
6806    if (mustCommit)
6807    {
6808      replicationServer.commit();
6809    }
6810
6811    print(formatter.getFormattedDone());
6812    println();
6813  }
6814
6815  /**
6816   * Updates the configuration of the replication server with the list of
6817   * replication servers provided.
6818   * @param ctx the context connected to the server that we want to update.
6819   * @param replicationServers the list of replication servers to which the
6820   * replication server will communicate with.
6821   * @throws OpenDsException if there is an error updating the configuration.
6822   */
6823  private void updateReplicationServer(InitialLdapContext ctx,
6824      Set<String> replicationServers) throws OpenDsException
6825  {
6826    print(formatter.getFormattedWithPoints(
6827        INFO_REPLICATION_ENABLE_UPDATING_REPLICATION_SERVER.get(getHostPort(ctx))));
6828
6829    ManagementContext mCtx = LDAPManagementContext.createFromContext(
6830        JNDIDirContextAdaptor.adapt(ctx));
6831    RootCfgClient root = mCtx.getRootConfiguration();
6832
6833    ReplicationSynchronizationProviderCfgClient sync =
6834      (ReplicationSynchronizationProviderCfgClient)
6835    root.getSynchronizationProvider("Multimaster Synchronization");
6836    boolean mustCommit = false;
6837    ReplicationServerCfgClient replicationServer = sync.getReplicationServer();
6838    Set<String> servers = replicationServer.getReplicationServer();
6839    if (servers == null)
6840    {
6841      replicationServer.setReplicationServer(replicationServers);
6842      mustCommit = true;
6843    }
6844    else if (!areReplicationServersEqual(servers, replicationServers))
6845    {
6846      replicationServers.addAll(servers);
6847      replicationServer.setReplicationServer(
6848          mergeReplicationServers(replicationServers, servers));
6849      mustCommit = true;
6850    }
6851    if (mustCommit)
6852    {
6853      replicationServer.commit();
6854    }
6855
6856    print(formatter.getFormattedDone());
6857    println();
6858  }
6859
6860  /**
6861   * Returns a Set containing all the replication server ids found in the
6862   * servers of a given TopologyCache object.
6863   * @param cache the TopologyCache object to use.
6864   * @return a Set containing all the replication server ids found in a given
6865   * TopologyCache object.
6866   */
6867  private Set<Integer> getReplicationServerIds(TopologyCache cache)
6868  {
6869    Set<Integer> ids = new HashSet<>();
6870    for (ServerDescriptor server : cache.getServers())
6871    {
6872      if (server.isReplicationServer())
6873      {
6874        ids.add(server.getReplicationServerId());
6875      }
6876    }
6877    return ids;
6878  }
6879
6880  /**
6881   * Configures a replication domain for a given base DN in the server to which
6882   * the provided InitialLdapContext is connected.
6883   * @param ctx the context connected to the server that we want to configure.
6884   * @param baseDN the base DN of the replication domain to configure.
6885   * @param replicationServers the list of replication servers to which the
6886   * replication domain will communicate with.
6887   * @param usedReplicationDomainIds the set of replication domain IDs that
6888   * are already in use.  The set will be updated with the replication ID
6889   * that will be used by the newly configured replication server.
6890   * @throws OpenDsException if there is an error updating the configuration.
6891   */
6892  private void configureToReplicateBaseDN(InitialLdapContext ctx,
6893      String baseDN,
6894      Set<String> replicationServers,
6895      Set<Integer> usedReplicationDomainIds) throws OpenDsException
6896  {
6897    boolean userSpecifiedAdminBaseDN = false;
6898    List<String> l = argParser.getBaseDNs();
6899    if (l != null)
6900    {
6901      userSpecifiedAdminBaseDN = containsDN(l, ADSContext.getAdministrationSuffixDN());
6902    }
6903    if (!userSpecifiedAdminBaseDN
6904        && areDnsEqual(baseDN, ADSContext.getAdministrationSuffixDN()))
6905    {
6906      print(formatter.getFormattedWithPoints(
6907          INFO_REPLICATION_ENABLE_CONFIGURING_ADS.get(getHostPort(ctx))));
6908    }
6909    else
6910    {
6911      print(formatter.getFormattedWithPoints(
6912          INFO_REPLICATION_ENABLE_CONFIGURING_BASEDN.get(baseDN, getHostPort(ctx))));
6913    }
6914    ManagementContext mCtx = LDAPManagementContext.createFromContext(
6915        JNDIDirContextAdaptor.adapt(ctx));
6916    RootCfgClient root = mCtx.getRootConfiguration();
6917
6918    ReplicationSynchronizationProviderCfgClient sync =
6919      (ReplicationSynchronizationProviderCfgClient)
6920      root.getSynchronizationProvider("Multimaster Synchronization");
6921
6922    String[] domainNames = sync.listReplicationDomains();
6923    if (domainNames == null)
6924    {
6925      domainNames = new String[]{};
6926    }
6927    ReplicationDomainCfgClient[] domains =
6928      new ReplicationDomainCfgClient[domainNames.length];
6929    for (int i=0; i<domains.length; i++)
6930    {
6931      domains[i] = sync.getReplicationDomain(domainNames[i]);
6932    }
6933    ReplicationDomainCfgClient domain = null;
6934    for (ReplicationDomainCfgClient domain2 : domains)
6935    {
6936      if (areDnsEqual(baseDN, domain2.getBaseDN().toString()))
6937      {
6938        domain = domain2;
6939        break;
6940      }
6941    }
6942    boolean mustCommit = false;
6943    if (domain == null)
6944    {
6945      int domainId = InstallerHelper.getReplicationId(usedReplicationDomainIds);
6946      usedReplicationDomainIds.add(domainId);
6947      String domainName = InstallerHelper.getDomainName(domainNames, baseDN);
6948      domain = sync.createReplicationDomain(
6949          ReplicationDomainCfgDefn.getInstance(), domainName,
6950          new ArrayList<PropertyException>());
6951      domain.setServerId(domainId);
6952      domain.setBaseDN(DN.valueOf(baseDN));
6953      domain.setReplicationServer(replicationServers);
6954      mustCommit = true;
6955    }
6956    else
6957    {
6958      Set<String> servers = domain.getReplicationServer();
6959      if (servers == null)
6960      {
6961        domain.setReplicationServer(null);
6962        mustCommit = true;
6963      }
6964      else if (!areReplicationServersEqual(servers, replicationServers))
6965      {
6966        domain.setReplicationServer(mergeReplicationServers(replicationServers, servers));
6967        mustCommit = true;
6968      }
6969    }
6970
6971    if (mustCommit)
6972    {
6973      domain.commit();
6974    }
6975
6976    print(formatter.getFormattedDone());
6977    println();
6978  }
6979
6980  /**
6981   * Configures the baseDN to replicate in all the Replicas found in a Topology
6982   * Cache that are replicated with the Replica of the same base DN in the
6983   * provided ServerDescriptor object.
6984   * @param baseDN the base DN to replicate.
6985   * @param repServers the replication servers to be defined in the domain.
6986   * @param usedIds the replication domain Ids already used.  This Set is
6987   * updated with the new domains that are used.
6988   * @param cache the TopologyCache used to retrieve the different defined
6989   * replicas.
6990   * @param server the ServerDescriptor that is used to identify the
6991   * replication topology that we are interested at (we only update the replicas
6992   * that are already replicated with this server).
6993   * @param alreadyConfiguredServers the list of already configured servers.  If
6994   * a server is in this list no updates are performed to the domain.
6995   * @param alreadyConfiguredReplicationServers the list of already configured
6996   * servers.  If a server is in this list no updates are performed to the
6997   * replication server.
6998   * @throws ReplicationCliException if something goes wrong.
6999   */
7000  private void configureToReplicateBaseDN(String baseDN,
7001      Set<String> repServers, Set<Integer> usedIds,
7002      TopologyCache cache, ServerDescriptor server,
7003      Set<String> alreadyConfiguredServers, Set<String> allRepServers,
7004      Set<String> alreadyConfiguredReplicationServers)
7005  throws ReplicationCliException
7006  {
7007    logger.info(LocalizableMessage.raw("Configuring base DN '"+baseDN+
7008        "' the replication servers are "+repServers));
7009    Set<ServerDescriptor> serversToConfigureDomain = new HashSet<>();
7010    Set<ServerDescriptor> replicationServersToConfigure = new HashSet<>();
7011    SuffixDescriptor suffix = getSuffix(baseDN, cache, server);
7012    if (suffix != null)
7013    {
7014      for (ReplicaDescriptor replica: suffix.getReplicas())
7015      {
7016        ServerDescriptor s = replica.getServer();
7017        if (!alreadyConfiguredServers.contains(s.getId()))
7018        {
7019          serversToConfigureDomain.add(s);
7020        }
7021      }
7022    }
7023    // Now check the replication servers.
7024    for (ServerDescriptor s : cache.getServers())
7025    {
7026      if (s.isReplicationServer()
7027          && !alreadyConfiguredReplicationServers.contains(s.getId())
7028          // Check if it is part of the replication topology
7029          && containsIgnoreCase(repServers, s.getReplicationServerHostPort()))
7030      {
7031        replicationServersToConfigure.add(s);
7032      }
7033    }
7034
7035    Set<ServerDescriptor> allServers = new HashSet<>(serversToConfigureDomain);
7036    allServers.addAll(replicationServersToConfigure);
7037
7038    for (ServerDescriptor s : allServers)
7039    {
7040      logger.info(LocalizableMessage.raw("Configuring server "+server.getHostPort(true)));
7041      InitialLdapContext ctx = null;
7042      try
7043      {
7044        ctx = getDirContextForServer(cache, s);
7045        if (serversToConfigureDomain.contains(s))
7046        {
7047          configureToReplicateBaseDN(ctx, baseDN, repServers, usedIds);
7048        }
7049        if (replicationServersToConfigure.contains(s))
7050        {
7051          updateReplicationServer(ctx, allRepServers);
7052        }
7053      }
7054      catch (NamingException ne)
7055      {
7056        String hostPort = getHostPort2(s, cache.getPreferredConnections());
7057        LocalizableMessage msg = getMessageForException(ne, hostPort);
7058        throw new ReplicationCliException(msg, ERROR_CONNECTING, ne);
7059      }
7060      catch (OpenDsException ode)
7061      {
7062        String hostPort = getHostPort2(s, cache.getPreferredConnections());
7063        LocalizableMessage msg = getMessageForEnableException(hostPort, baseDN);
7064        throw new ReplicationCliException(msg,
7065            ERROR_ENABLING_REPLICATION_ON_BASEDN, ode);
7066      }
7067      finally
7068      {
7069        close(ctx);
7070      }
7071      alreadyConfiguredServers.add(s.getId());
7072      alreadyConfiguredReplicationServers.add(s.getId());
7073    }
7074  }
7075
7076  /**
7077   * Returns the Map of properties to be used to update the ADS.
7078   * This map uses the data provided by the user.
7079   * @return the Map of properties to be used to update the ADS.
7080   * This map uses the data provided by the user
7081   */
7082  private Map<ADSContext.AdministratorProperty, Object>
7083  getAdministratorProperties(ReplicationUserData uData)
7084  {
7085    Map<ADSContext.AdministratorProperty, Object> adminProperties = new HashMap<>();
7086    adminProperties.put(ADSContext.AdministratorProperty.UID, uData.getAdminUid());
7087    adminProperties.put(ADSContext.AdministratorProperty.PASSWORD, uData.getAdminPwd());
7088    adminProperties.put(ADSContext.AdministratorProperty.DESCRIPTION,
7089        INFO_GLOBAL_ADMINISTRATOR_DESCRIPTION.get().toString());
7090    return adminProperties;
7091  }
7092
7093  private void initializeSuffix(String baseDN, InitialLdapContext ctxSource,
7094      InitialLdapContext ctxDestination, boolean displayProgress)
7095  throws ReplicationCliException
7096  {
7097    int replicationId = -1;
7098    try
7099    {
7100      TopologyCacheFilter filter = new TopologyCacheFilter();
7101      filter.setSearchMonitoringInformation(false);
7102      filter.addBaseDNToSearch(baseDN);
7103      ServerDescriptor source = ServerDescriptor.createStandalone(ctxSource, filter);
7104      for (ReplicaDescriptor replica : source.getReplicas())
7105      {
7106        if (areDnsEqual(replica.getSuffix().getDN(), baseDN))
7107        {
7108          replicationId = replica.getReplicationId();
7109          break;
7110        }
7111      }
7112    }
7113    catch (NamingException ne)
7114    {
7115      String hostPort = getHostPort(ctxSource);
7116      LocalizableMessage msg = getMessageForException(ne, hostPort);
7117      throw new ReplicationCliException(msg, ERROR_READING_CONFIGURATION, ne);
7118    }
7119
7120    if (replicationId == -1)
7121    {
7122      throw new ReplicationCliException(
7123          ERR_INITIALIZING_REPLICATIONID_NOT_FOUND.get(getHostPort(ctxSource), baseDN),
7124          REPLICATIONID_NOT_FOUND, null);
7125    }
7126
7127    OfflineInstaller installer = new OfflineInstaller();
7128    installer.setProgressMessageFormatter(formatter);
7129    installer.addProgressUpdateListener(new ProgressUpdateListener()
7130    {
7131      @Override
7132      public void progressUpdate(ProgressUpdateEvent ev)
7133      {
7134        LocalizableMessage newLogDetails = ev.getNewLogs();
7135        if (newLogDetails != null && !"".equals(newLogDetails.toString().trim()))
7136        {
7137          print(newLogDetails);
7138          println();
7139        }
7140      }
7141    });
7142    int nTries = 5;
7143    boolean initDone = false;
7144    while (!initDone)
7145    {
7146      try
7147      {
7148        installer.initializeSuffix(ctxDestination, replicationId, baseDN, displayProgress, getHostPort(ctxSource));
7149        initDone = true;
7150      }
7151      catch (PeerNotFoundException pnfe)
7152      {
7153        logger.info(LocalizableMessage.raw("Peer could not be found"));
7154        if (nTries == 1)
7155        {
7156          throw new ReplicationCliException(
7157              ERR_REPLICATION_INITIALIZING_TRIES_COMPLETED.get(
7158                  pnfe.getMessageObject()), INITIALIZING_TRIES_COMPLETED, pnfe);
7159        }
7160        sleepCatchInterrupt((5 - nTries) * 3000);
7161      }
7162      catch (ApplicationException ae)
7163      {
7164        throw new ReplicationCliException(ae.getMessageObject(),
7165            ERROR_INITIALIZING_BASEDN_GENERIC, ae);
7166      }
7167      nTries--;
7168    }
7169  }
7170
7171  /**
7172   * Initializes all the replicas in the topology with the contents of a
7173   * given replica.
7174   * @param ctx the connection to the server where the source replica of the
7175   * initialization is.
7176   * @param baseDN the dn of the suffix.
7177   * @param displayProgress whether we want to display progress or not.
7178   * @throws ReplicationCliException if an unexpected error occurs.
7179   */
7180  public void initializeAllSuffix(String baseDN, InitialLdapContext ctx,
7181  boolean displayProgress) throws ReplicationCliException
7182  {
7183    if (argParser == null)
7184    {
7185      try
7186      {
7187        createArgumenParser();
7188      }
7189      catch (ArgumentException ae)
7190      {
7191        throw new RuntimeException("Error creating argument parser: "+ae, ae);
7192      }
7193    }
7194    int nTries = 5;
7195    boolean initDone = false;
7196    while (!initDone)
7197    {
7198      try
7199      {
7200        initializeAllSuffixTry(baseDN, ctx, displayProgress);
7201        postPreExternalInitialization(baseDN, ctx, false);
7202        initDone = true;
7203      }
7204      catch (PeerNotFoundException pnfe)
7205      {
7206        logger.info(LocalizableMessage.raw("Peer could not be found"));
7207        if (nTries == 1)
7208        {
7209          throw new ReplicationCliException(
7210              ERR_REPLICATION_INITIALIZING_TRIES_COMPLETED.get(
7211                  pnfe.getMessageObject()), INITIALIZING_TRIES_COMPLETED, pnfe);
7212        }
7213        sleepCatchInterrupt((5 - nTries) * 3000);
7214      }
7215      catch (ClientException ae)
7216      {
7217        throw new ReplicationCliException(ae.getMessageObject(),
7218            ERROR_INITIALIZING_BASEDN_GENERIC, ae);
7219      }
7220      nTries--;
7221    }
7222  }
7223
7224  /**
7225   * Launches the pre external initialization operation using the provided
7226   * connection on a given base DN.
7227   * @param baseDN the base DN that we want to reset.
7228   * @param ctx the connection to the server.
7229   * @throws ReplicationCliException if there is an error performing the
7230   * operation.
7231   */
7232  private void preExternalInitialization(String baseDN, InitialLdapContext ctx) throws ReplicationCliException
7233  {
7234    postPreExternalInitialization(baseDN, ctx, true);
7235  }
7236
7237  /**
7238   * Launches the post external initialization operation using the provided
7239   * connection on a given base DN required for replication to work.
7240   * @param baseDN the base DN that we want to reset.
7241   * @param ctx the connection to the server.
7242   * @throws ReplicationCliException if there is an error performing the
7243   * operation.
7244   */
7245  private void postExternalInitialization(String baseDN, InitialLdapContext ctx) throws ReplicationCliException
7246  {
7247    postPreExternalInitialization(baseDN, ctx, false);
7248  }
7249
7250  /**
7251   * Launches the pre or post external initialization operation using the
7252   * provided connection on a given base DN.
7253   * @param baseDN the base DN that we want to reset.
7254   * @param ctx the connection to the server.
7255   * @param isPre whether this is the pre operation or the post operation.
7256   * @throws ReplicationCliException if there is an error performing the
7257   * operation.
7258   */
7259  private void postPreExternalInitialization(String baseDN,
7260      InitialLdapContext ctx, boolean isPre) throws ReplicationCliException
7261  {
7262    boolean isOver = false;
7263    String dn = null;
7264    Map<String, String> attrMap = new TreeMap<>();
7265    if (isPre)
7266    {
7267      attrMap.put("ds-task-reset-generation-id-new-value", "-1");
7268    }
7269    attrMap.put("ds-task-reset-generation-id-domain-base-dn", baseDN);
7270
7271    try {
7272      dn = createServerTask(ctx, "ds-task-reset-generation-id", "org.opends.server.tasks.SetGenerationIdTask",
7273          "dsreplication-reset-generation-id", attrMap);
7274    }
7275    catch (NamingException ne)
7276    {
7277      LocalizableMessage msg = isPre ?
7278          ERR_LAUNCHING_PRE_EXTERNAL_INITIALIZATION.get():
7279          ERR_LAUNCHING_POST_EXTERNAL_INITIALIZATION.get();
7280      ReplicationCliReturnCode code = isPre?
7281          ERROR_LAUNCHING_PRE_EXTERNAL_INITIALIZATION:
7282          ERROR_LAUNCHING_POST_EXTERNAL_INITIALIZATION;
7283      throw new ReplicationCliException(getThrowableMsg(msg, ne), code, ne);
7284    }
7285
7286    String lastLogMsg = null;
7287    while (!isOver)
7288    {
7289      sleepCatchInterrupt(500);
7290      try
7291      {
7292        SearchResult sr = getLastSearchResult(ctx, dn, "ds-task-log-message", "ds-task-state");
7293        String logMsg = getFirstValue(sr, "ds-task-log-message");
7294        if (logMsg != null && !logMsg.equals(lastLogMsg))
7295        {
7296          logger.info(LocalizableMessage.raw(logMsg));
7297          lastLogMsg = logMsg;
7298        }
7299        InstallerHelper helper = new InstallerHelper();
7300        String state = getFirstValue(sr, "ds-task-state");
7301
7302        if (helper.isDone(state) || helper.isStoppedByError(state))
7303        {
7304          isOver = true;
7305          LocalizableMessage errorMsg = getPrePostErrorMsg(lastLogMsg, state, ctx);
7306
7307          if (helper.isCompletedWithErrors(state))
7308          {
7309            logger.warn(LocalizableMessage.raw("Completed with error: "+errorMsg));
7310            errPrintln(errorMsg);
7311          }
7312          else if (!helper.isSuccessful(state) ||
7313              helper.isStoppedByError(state))
7314          {
7315            logger.warn(LocalizableMessage.raw("Error: "+errorMsg));
7316            ReplicationCliReturnCode code = isPre?
7317                ERROR_LAUNCHING_PRE_EXTERNAL_INITIALIZATION:
7318                  ERROR_LAUNCHING_POST_EXTERNAL_INITIALIZATION;
7319            throw new ReplicationCliException(errorMsg, code, null);
7320          }
7321        }
7322      }
7323      catch (NameNotFoundException x)
7324      {
7325        isOver = true;
7326      }
7327      catch (NamingException ne)
7328      {
7329        throw new ReplicationCliException(getThrowableMsg(ERR_READING_SERVER_TASK_PROGRESS.get(), ne),
7330            ERROR_CONNECTING, ne);
7331      }
7332    }
7333  }
7334
7335  private LocalizableMessage getPrePostErrorMsg(String lastLogMsg, String state, InitialLdapContext ctx)
7336  {
7337    String server = getHostPort(ctx);
7338    if (lastLogMsg != null)
7339    {
7340      return ERR_UNEXPECTED_DURING_TASK_WITH_LOG.get(lastLogMsg, state, server);
7341    }
7342    return ERR_UNEXPECTED_DURING_TASK_NO_LOG.get(state, server);
7343  }
7344
7345  private void sleepCatchInterrupt(long millis)
7346  {
7347    try
7348    {
7349      Thread.sleep(millis);
7350    }
7351    catch (InterruptedException e)
7352    {
7353    }
7354  }
7355
7356  /**
7357   * Initializes all the replicas in the topology with the contents of a
7358   * given replica.  This method will try to create the task only once.
7359   * @param ctx the connection to the server where the source replica of the
7360   * initialization is.
7361   * @param baseDN the dn of the suffix.
7362   * @param displayProgress whether we want to display progress or not.
7363   * @throws ClientException if an unexpected error occurs.
7364   * @throws PeerNotFoundException if the replication mechanism cannot find
7365   * a peer.
7366   */
7367  public void initializeAllSuffixTry(String baseDN, InitialLdapContext ctx,
7368      boolean displayProgress)
7369  throws ClientException, PeerNotFoundException
7370  {
7371    boolean isOver = false;
7372    String dn = null;
7373    String serverDisplay = getHostPort(ctx);
7374    Map<String, String> attrsMap = new TreeMap<>();
7375    attrsMap.put("ds-task-initialize-domain-dn", baseDN);
7376    attrsMap.put("ds-task-initialize-replica-server-id", "all");
7377    try
7378    {
7379      dn = createServerTask(ctx, "ds-task-initialize-remote-replica", "org.opends.server.tasks.InitializeTargetTask",
7380          "dsreplication-initialize", attrsMap);
7381    }
7382    catch (NamingException ne)
7383    {
7384      throw new ClientException(ReturnCode.APPLICATION_ERROR,
7385              getThrowableMsg(INFO_ERROR_LAUNCHING_INITIALIZATION.get(serverDisplay), ne), ne);
7386    }
7387
7388    LocalizableMessage lastDisplayedMsg = null;
7389    String lastLogMsg = null;
7390    long lastTimeMsgDisplayed = -1;
7391    long lastTimeMsgLogged = -1;
7392    long totalEntries = 0;
7393    while (!isOver)
7394    {
7395      sleepCatchInterrupt(500);
7396      try
7397      {
7398        SearchResult sr = getLastSearchResult(ctx, dn, "ds-task-unprocessed-entry-count",
7399            "ds-task-processed-entry-count", "ds-task-log-message", "ds-task-state" );
7400
7401        // Get the number of entries that have been handled and a percentage...
7402        String sProcessed = getFirstValue(sr, "ds-task-processed-entry-count");
7403        String sUnprocessed = getFirstValue(sr, "ds-task-unprocessed-entry-count");
7404        long processed = -1;
7405        long unprocessed = -1;
7406        if (sProcessed != null)
7407        {
7408          processed = Integer.parseInt(sProcessed);
7409        }
7410        if (sUnprocessed != null)
7411        {
7412          unprocessed = Integer.parseInt(sUnprocessed);
7413        }
7414        totalEntries = Math.max(totalEntries, processed+unprocessed);
7415
7416        LocalizableMessage msg = getMsg(lastDisplayedMsg, processed, unprocessed);
7417        if (msg != null)
7418        {
7419          long currentTime = System.currentTimeMillis();
7420          /* Refresh period: to avoid having too many lines in the log */
7421          long minRefreshPeriod = getMinRefreshPeriod(totalEntries);
7422          if (currentTime - minRefreshPeriod > lastTimeMsgLogged)
7423          {
7424            lastTimeMsgLogged = currentTime;
7425            logger.info(LocalizableMessage.raw("Progress msg: "+msg));
7426          }
7427          if (displayProgress
7428              && currentTime - minRefreshPeriod > lastTimeMsgDisplayed
7429              && !msg.equals(lastDisplayedMsg))
7430          {
7431            print(msg);
7432            lastDisplayedMsg = msg;
7433            println();
7434            lastTimeMsgDisplayed = currentTime;
7435          }
7436        }
7437
7438        String logMsg = getFirstValue(sr, "ds-task-log-message");
7439        if (logMsg != null && !logMsg.equals(lastLogMsg))
7440        {
7441          logger.info(LocalizableMessage.raw(logMsg));
7442          lastLogMsg = logMsg;
7443        }
7444        InstallerHelper helper = new InstallerHelper();
7445        String state = getFirstValue(sr, "ds-task-state");
7446
7447        if (helper.isDone(state) || helper.isStoppedByError(state))
7448        {
7449          isOver = true;
7450          logger.info(LocalizableMessage.raw("Last task entry: "+sr));
7451          if (displayProgress && msg != null && !msg.equals(lastDisplayedMsg))
7452          {
7453            print(msg);
7454            lastDisplayedMsg = msg;
7455            println();
7456          }
7457
7458          LocalizableMessage errorMsg = getInitializeAllErrorMsg(serverDisplay, lastLogMsg, state);
7459          if (helper.isCompletedWithErrors(state))
7460          {
7461            logger.warn(LocalizableMessage.raw("Processed errorMsg: "+errorMsg));
7462            if (displayProgress)
7463            {
7464              errPrintln(errorMsg);
7465            }
7466          }
7467          else if (!helper.isSuccessful(state) ||
7468              helper.isStoppedByError(state))
7469          {
7470            logger.warn(LocalizableMessage.raw("Processed errorMsg: "+errorMsg));
7471            ClientException ce = new ClientException(
7472                ReturnCode.APPLICATION_ERROR, errorMsg,
7473                null);
7474            if (lastLogMsg == null
7475                || helper.isPeersNotFoundError(lastLogMsg))
7476            {
7477              logger.warn(LocalizableMessage.raw("Throwing peer not found error.  "+
7478                  "Last Log Msg: "+lastLogMsg));
7479              // Assume that this is a peer not found error.
7480              throw new PeerNotFoundException(errorMsg);
7481            }
7482            else
7483            {
7484              logger.error(LocalizableMessage.raw("Throwing ApplicationException."));
7485              throw ce;
7486            }
7487          }
7488          else
7489          {
7490            if (displayProgress)
7491            {
7492              print(INFO_SUFFIX_INITIALIZED_SUCCESSFULLY.get());
7493              println();
7494            }
7495            logger.info(LocalizableMessage.raw("Processed msg: "+errorMsg));
7496            logger.info(LocalizableMessage.raw("Initialization completed successfully."));
7497          }
7498        }
7499      }
7500      catch (NameNotFoundException x)
7501      {
7502        isOver = true;
7503        logger.info(LocalizableMessage.raw("Initialization entry not found."));
7504        if (displayProgress)
7505        {
7506          print(INFO_SUFFIX_INITIALIZED_SUCCESSFULLY.get());
7507          println();
7508        }
7509      }
7510      catch (NamingException ne)
7511      {
7512        throw new ClientException(
7513            ReturnCode.APPLICATION_ERROR,
7514                getThrowableMsg(INFO_ERROR_POOLING_INITIALIZATION.get(
7515                    serverDisplay), ne), ne);
7516      }
7517    }
7518  }
7519
7520  private SearchResult getLastSearchResult(InitialLdapContext ctx, String dn, String... returnedAttributes)
7521      throws NamingException
7522  {
7523    SearchControls searchControls = new SearchControls();
7524    searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
7525    searchControls.setReturningAttributes(returnedAttributes);
7526    NamingEnumeration<SearchResult> res = ctx.search(dn, "objectclass=*", searchControls);
7527    try
7528    {
7529      SearchResult sr = null;
7530      while (res.hasMore())
7531      {
7532        sr = res.next();
7533      }
7534      return sr;
7535    }
7536    finally
7537    {
7538      res.close();
7539    }
7540  }
7541
7542  private String createServerTask(InitialLdapContext ctx, String taskObjectclass,
7543      String taskJavaClass, String taskID, Map<String, String> taskAttrs) throws NamingException
7544  {
7545    int i = 1;
7546    String dn = "";
7547    BasicAttributes attrs = new BasicAttributes();
7548    attrs.put("objectclass", taskObjectclass);
7549    attrs.put("ds-task-class-name", taskJavaClass);
7550    for (Map.Entry<String, String> attr : taskAttrs.entrySet())
7551    {
7552      attrs.put(attr.getKey(), attr.getValue());
7553    }
7554
7555    while (true)
7556    {
7557      String id = taskID + "-" + i;
7558      dn = "ds-task-id=" + id + ",cn=Scheduled Tasks,cn=Tasks";
7559      try
7560      {
7561        DirContext dirCtx = ctx.createSubcontext(dn, attrs);
7562        logger.info(LocalizableMessage.raw("created task entry: " + attrs));
7563        dirCtx.close();
7564        return dn;
7565      }
7566      catch (NameAlreadyBoundException x)
7567      {
7568        logger.warn(LocalizableMessage.raw("A task with dn: " + dn + " already existed."));
7569      }
7570      catch (NamingException ne)
7571      {
7572        logger.error(LocalizableMessage.raw("Error creating task " + attrs, ne));
7573        throw ne;
7574      }
7575      i++;
7576    }
7577  }
7578
7579  private LocalizableMessage getInitializeAllErrorMsg(String serverDisplay, String lastLogMsg, String state)
7580  {
7581    if (lastLogMsg != null)
7582    {
7583      return INFO_ERROR_DURING_INITIALIZATION_LOG.get(serverDisplay, lastLogMsg, state, serverDisplay);
7584    }
7585    return INFO_ERROR_DURING_INITIALIZATION_NO_LOG.get(serverDisplay, state, serverDisplay);
7586  }
7587
7588  private LocalizableMessage getMsg(LocalizableMessage lastDisplayedMsg, long processed, long unprocessed)
7589  {
7590    if (processed != -1 && unprocessed != -1)
7591    {
7592      if (processed + unprocessed > 0)
7593      {
7594        long perc = (100 * processed) / (processed + unprocessed);
7595        return INFO_INITIALIZE_PROGRESS_WITH_PERCENTAGE.get(processed, perc);
7596      }
7597      else
7598      {
7599        // return INFO_NO_ENTRIES_TO_INITIALIZE.get();
7600        return null;
7601      }
7602    }
7603    else if (processed != -1)
7604    {
7605      return INFO_INITIALIZE_PROGRESS_WITH_PROCESSED.get(processed);
7606    }
7607    else if (unprocessed != -1)
7608    {
7609      return INFO_INITIALIZE_PROGRESS_WITH_UNPROCESSED.get(unprocessed);
7610    }
7611    else
7612    {
7613      return lastDisplayedMsg;
7614    }
7615  }
7616
7617  private long getMinRefreshPeriod(long totalEntries)
7618  {
7619    if (totalEntries < 100)
7620    {
7621      return 0;
7622    }
7623    else if (totalEntries < 1000)
7624    {
7625      return 1000;
7626    }
7627    else if (totalEntries < 10000)
7628    {
7629      return 5000;
7630    }
7631    return 10000;
7632  }
7633
7634  /**
7635   * Removes the references to a replication server in the base DNs of a
7636   * given server.
7637   * @param server the server that we want to update.
7638   * @param replicationServer the replication server whose references we want
7639   * to remove.
7640   * @param bindDn the bindDn that must be used to log to the server.
7641   * @param pwd the password that must be used to log to the server.
7642   * @param baseDNs the list of base DNs where we want to remove the references
7643   * to the provided replication server.
7644   * @param updateReplicationServers if references in the replication servers
7645   * must be updated.
7646   * @param cnx the preferred LDAP URLs to be used to connect to the
7647   * server.
7648   * @throws ReplicationCliException if there is an error updating the
7649   * configuration.
7650   */
7651  private void removeReferencesInServer(ServerDescriptor server,
7652      String replicationServer, String bindDn, String pwd,
7653      Collection<String> baseDNs, boolean updateReplicationServers,
7654      Set<PreferredConnection> cnx)
7655  throws ReplicationCliException
7656  {
7657    TopologyCacheFilter filter = new TopologyCacheFilter();
7658    filter.setSearchMonitoringInformation(false);
7659    filter.setSearchBaseDNInformation(false);
7660    ServerLoader loader = new ServerLoader(server.getAdsProperties(), bindDn,
7661        pwd, getTrustManager(sourceServerCI), getConnectTimeout(), cnx, filter);
7662    InitialLdapContext ctx = null;
7663    String lastBaseDN = null;
7664    String hostPort = null;
7665
7666    try
7667    {
7668      ctx = loader.createContext();
7669      hostPort = getHostPort(ctx);
7670      ManagementContext mCtx = LDAPManagementContext.createFromContext(
7671          JNDIDirContextAdaptor.adapt(ctx));
7672      RootCfgClient root = mCtx.getRootConfiguration();
7673      ReplicationSynchronizationProviderCfgClient sync = null;
7674      try
7675      {
7676        sync = (ReplicationSynchronizationProviderCfgClient)
7677        root.getSynchronizationProvider("Multimaster Synchronization");
7678      }
7679      catch (ManagedObjectNotFoundException monfe)
7680      {
7681        // It does not exist.
7682        logger.info(LocalizableMessage.raw("No synchronization found on "+ hostPort +".",
7683            monfe));
7684      }
7685      if (sync != null)
7686      {
7687        String[] domainNames = sync.listReplicationDomains();
7688        if (domainNames != null)
7689        {
7690          for (String domainName : domainNames)
7691          {
7692            ReplicationDomainCfgClient domain =
7693              sync.getReplicationDomain(domainName);
7694            for (String baseDN : baseDNs)
7695            {
7696              lastBaseDN = baseDN;
7697              if (areDnsEqual(domain.getBaseDN().toString(), baseDN))
7698              {
7699                print(formatter.getFormattedWithPoints(
7700                    INFO_REPLICATION_REMOVING_REFERENCES_ON_REMOTE.get(baseDN, hostPort)));
7701                Set<String> replServers = domain.getReplicationServer();
7702                if (replServers != null)
7703                {
7704                  String replServer = findIgnoreCase(replServers, replicationServer);
7705                  if (replServer != null)
7706                  {
7707                    logger.info(LocalizableMessage.raw("Updating references in domain " +
7708                        domain.getBaseDN()+" on " + hostPort + "."));
7709                    replServers.remove(replServer);
7710                    if (!replServers.isEmpty())
7711                    {
7712                      domain.setReplicationServer(replServers);
7713                      domain.commit();
7714                    }
7715                    else
7716                    {
7717                      sync.removeReplicationDomain(domainName);
7718                      sync.commit();
7719                    }
7720                  }
7721                }
7722                print(formatter.getFormattedDone());
7723                println();
7724              }
7725            }
7726          }
7727        }
7728        if (updateReplicationServers && sync.hasReplicationServer())
7729        {
7730          ReplicationServerCfgClient rServerObj = sync.getReplicationServer();
7731          Set<String> replServers = rServerObj.getReplicationServer();
7732          if (replServers != null)
7733          {
7734            String replServer = findIgnoreCase(replServers, replicationServer);
7735            if (replServer != null)
7736            {
7737              replServers.remove(replServer);
7738              if (!replServers.isEmpty())
7739              {
7740                rServerObj.setReplicationServer(replServers);
7741                rServerObj.commit();
7742              }
7743              else
7744              {
7745                sync.removeReplicationServer();
7746                sync.commit();
7747              }
7748            }
7749          }
7750        }
7751      }
7752    }
7753    catch (NamingException ne)
7754    {
7755      hostPort = getHostPort2(server, cnx);
7756      LocalizableMessage msg = getMessageForException(ne, hostPort);
7757      throw new ReplicationCliException(msg, ERROR_CONNECTING, ne);
7758    }
7759    catch (OpenDsException ode)
7760    {
7761      if (lastBaseDN != null)
7762      {
7763        LocalizableMessage msg = getMessageForDisableException(hostPort, lastBaseDN);
7764        throw new ReplicationCliException(msg,
7765          ERROR_DISABLING_REPLICATION_REMOVE_REFERENCE_ON_BASEDN, ode);
7766      }
7767      else
7768      {
7769        LocalizableMessage msg = ERR_REPLICATION_ERROR_READING_CONFIGURATION.get(hostPort,
7770            ode.getMessage());
7771        throw new ReplicationCliException(msg, ERROR_CONNECTING, ode);
7772      }
7773    }
7774    finally
7775    {
7776      close(ctx);
7777    }
7778  }
7779
7780  /**
7781   * Deletes a replication domain in a server for a given base DN (disable
7782   * replication of the base DN).
7783   * @param ctx the connection to the server.
7784   * @param baseDN the base DN of the replication domain that we want to
7785   * delete.
7786   * @throws ReplicationCliException if there is an error updating the
7787   * configuration of the server.
7788   */
7789  private void deleteReplicationDomain(InitialLdapContext ctx,
7790      String baseDN) throws ReplicationCliException
7791  {
7792    String hostPort = getHostPort(ctx);
7793    try
7794    {
7795      ManagementContext mCtx = LDAPManagementContext.createFromContext(
7796          JNDIDirContextAdaptor.adapt(ctx));
7797      RootCfgClient root = mCtx.getRootConfiguration();
7798      ReplicationSynchronizationProviderCfgClient sync = null;
7799      try
7800      {
7801        sync = (ReplicationSynchronizationProviderCfgClient)
7802        root.getSynchronizationProvider("Multimaster Synchronization");
7803      }
7804      catch (ManagedObjectNotFoundException monfe)
7805      {
7806        // It does not exist.
7807        logger.info(LocalizableMessage.raw("No synchronization found on "+ hostPort +".",
7808            monfe));
7809      }
7810      if (sync != null)
7811      {
7812        String[] domainNames = sync.listReplicationDomains();
7813        if (domainNames != null)
7814        {
7815          for (String domainName : domainNames)
7816          {
7817            ReplicationDomainCfgClient domain =
7818              sync.getReplicationDomain(domainName);
7819            if (areDnsEqual(domain.getBaseDN().toString(), baseDN))
7820            {
7821              print(formatter.getFormattedWithPoints(
7822                  INFO_REPLICATION_DISABLING_BASEDN.get(baseDN, hostPort)));
7823              sync.removeReplicationDomain(domainName);
7824              sync.commit();
7825
7826              print(formatter.getFormattedDone());
7827              println();
7828            }
7829          }
7830        }
7831      }
7832    }
7833    catch (OpenDsException ode)
7834    {
7835      LocalizableMessage msg = getMessageForDisableException(hostPort, baseDN);
7836        throw new ReplicationCliException(msg,
7837          ERROR_DISABLING_REPLICATION_REMOVE_REFERENCE_ON_BASEDN, ode);
7838    }
7839  }
7840
7841  /**
7842   * Disables the replication server for a given server.
7843   * @param ctx the connection to the server.
7844   * @throws ReplicationCliException if there is an error updating the
7845   * configuration of the server.
7846   */
7847  private void disableReplicationServer(InitialLdapContext ctx)
7848  throws ReplicationCliException
7849  {
7850    String hostPort = getHostPort(ctx);
7851    try
7852    {
7853      ManagementContext mCtx = LDAPManagementContext.createFromContext(
7854          JNDIDirContextAdaptor.adapt(ctx));
7855      RootCfgClient root = mCtx.getRootConfiguration();
7856      ReplicationSynchronizationProviderCfgClient sync = null;
7857      ReplicationServerCfgClient replicationServer = null;
7858      try
7859      {
7860        sync = (ReplicationSynchronizationProviderCfgClient)
7861        root.getSynchronizationProvider("Multimaster Synchronization");
7862        if (sync.hasReplicationServer())
7863        {
7864          replicationServer = sync.getReplicationServer();
7865        }
7866      }
7867      catch (ManagedObjectNotFoundException monfe)
7868      {
7869        // It does not exist.
7870        logger.info(LocalizableMessage.raw("No synchronization found on "+ hostPort +".",
7871            monfe));
7872      }
7873      if (replicationServer != null)
7874      {
7875        String s = String.valueOf(replicationServer.getReplicationPort());
7876        print(formatter.getFormattedWithPoints(
7877            INFO_REPLICATION_DISABLING_REPLICATION_SERVER.get(s,
7878                hostPort)));
7879
7880        sync.removeReplicationServer();
7881        sync.commit();
7882        print(formatter.getFormattedDone());
7883        println();
7884      }
7885    }
7886    catch (OpenDsException ode)
7887    {
7888      throw new ReplicationCliException(
7889          ERR_REPLICATION_DISABLING_REPLICATIONSERVER.get(hostPort),
7890          ERROR_DISABLING_REPLICATION_SERVER,
7891          ode);
7892    }
7893  }
7894
7895  /**
7896   * Returns a message for a given OpenDsException (we assume that was an
7897   * exception generated updating the configuration of the server) that
7898   * occurred when we were configuring some replication domain (creating
7899   * the replication domain or updating the list of replication servers of
7900   * the replication domain).
7901   * @param hostPort the hostPort representation of the server we were
7902   * contacting when the OpenDsException occurred.
7903   * @return a message for a given OpenDsException (we assume that was an
7904   * exception generated updating the configuration of the server) that
7905   * occurred when we were configuring some replication domain (creating
7906   * the replication domain or updating the list of replication servers of
7907   * the replication domain).
7908   */
7909  private LocalizableMessage getMessageForEnableException(String hostPort, String baseDN)
7910  {
7911    return ERR_REPLICATION_CONFIGURING_BASEDN.get(baseDN, hostPort);
7912  }
7913
7914  /**
7915   * Returns a message for a given OpenDsException (we assume that was an
7916   * exception generated updating the configuration of the server) that
7917   * occurred when we were configuring some replication domain (deleting
7918   * the replication domain or updating the list of replication servers of
7919   * the replication domain).
7920   * @param hostPort the hostPort representation of the server we were
7921   * contacting when the OpenDsException occurred.
7922   * @return a message for a given OpenDsException (we assume that was an
7923   * exception generated updating the configuration of the server) that
7924   * occurred when we were configuring some replication domain (deleting
7925   * the replication domain or updating the list of replication servers of
7926   * the replication domain).
7927   */
7928  private LocalizableMessage getMessageForDisableException(String hostPort, String baseDN)
7929  {
7930    return ERR_REPLICATION_CONFIGURING_BASEDN.get(baseDN, hostPort);
7931  }
7932
7933  /**
7934   * Returns a message informing the user that the provided port cannot be used.
7935   * @param port the port that cannot be used.
7936   * @return a message informing the user that the provided port cannot be used.
7937   */
7938  private LocalizableMessage getCannotBindToPortError(int port)
7939  {
7940    if (SetupUtils.isPrivilegedPort(port))
7941    {
7942      return ERR_CANNOT_BIND_TO_PRIVILEGED_PORT.get(port);
7943    }
7944    return ERR_CANNOT_BIND_TO_PORT.get(port);
7945  }
7946
7947  /**
7948   * Convenience method used to know if one Set of replication servers equals
7949   * another set of replication servers.
7950   * @param s1 the first set of replication servers.
7951   * @param s2 the second set of replication servers.
7952   * @return <CODE>true</CODE> if the two sets represent the same replication
7953   * servers and <CODE>false</CODE> otherwise.
7954   */
7955  private boolean areReplicationServersEqual(Set<String> s1, Set<String> s2)
7956  {
7957    Set<String> c1 = new HashSet<>();
7958    for (String s : s1)
7959    {
7960      c1.add(s.toLowerCase());
7961    }
7962    Set<String> c2 = new HashSet<>();
7963    for (String s : s2)
7964    {
7965      c2.add(s.toLowerCase());
7966    }
7967    return c1.equals(c2);
7968  }
7969
7970  /**
7971   * Convenience method used to merge two Sets of replication servers.
7972   * @param s1 the first set of replication servers.
7973   * @param s2 the second set of replication servers.
7974   * @return a Set of replication servers containing all the replication servers
7975   * specified in the provided Sets.
7976   */
7977  private Set<String> mergeReplicationServers(Set<String> s1, Set<String> s2)
7978  {
7979    Set<String> c1 = new HashSet<>();
7980    for (String s : s1)
7981    {
7982      c1.add(s.toLowerCase());
7983    }
7984    for (String s : s2)
7985    {
7986      c1.add(s.toLowerCase());
7987    }
7988    return c1;
7989  }
7990
7991  /**
7992   * Returns the message that must be displayed to the user for a given
7993   * exception.  This is assumed to be a critical exception that stops all
7994   * the processing.
7995   * @param rce the ReplicationCliException.
7996   * @return a message to be displayed to the user.
7997   */
7998  private LocalizableMessage getCriticalExceptionMessage(ReplicationCliException rce)
7999  {
8000    LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
8001    mb.append(rce.getMessageObject());
8002    File logFile = ControlPanelLog.getLogFile();
8003    if (logFile != null && rce.getErrorCode() != USER_CANCELLED)
8004    {
8005      mb.append(Constants.LINE_SEPARATOR);
8006      mb.append(INFO_GENERAL_SEE_FOR_DETAILS.get(logFile.getPath()));
8007    }
8008    // Check if the cause has already been included in the message
8009    Throwable c = rce.getCause();
8010    if (c != null)
8011    {
8012      String s;
8013      if (c instanceof NamingException)
8014      {
8015        s = ((NamingException)c).toString(true);
8016      }
8017      else if (c instanceof OpenDsException)
8018      {
8019        LocalizableMessage msg = ((OpenDsException)c).getMessageObject();
8020        if (msg != null)
8021        {
8022          s = msg.toString();
8023        }
8024        else
8025        {
8026          s = c.toString();
8027        }
8028      }
8029      else
8030      {
8031        s = c.toString();
8032      }
8033      if (!mb.toString().contains(s))
8034      {
8035        mb.append(Constants.LINE_SEPARATOR);
8036        mb.append(INFO_REPLICATION_CRITICAL_ERROR_DETAILS.get(s));
8037      }
8038    }
8039    return mb.toMessage();
8040  }
8041
8042  private boolean mustInitializeSchema(ServerDescriptor server1,
8043      ServerDescriptor server2, EnableReplicationUserData uData)
8044  {
8045    boolean mustInitializeSchema = false;
8046    if (!argParser.noSchemaReplication())
8047    {
8048      String id1 = server1.getSchemaReplicationID();
8049      String id2 = server2.getSchemaReplicationID();
8050      mustInitializeSchema = id1 == null || !id1.equals(id2);
8051    }
8052    if (mustInitializeSchema)
8053    {
8054      // Check that both will contain replication data
8055      mustInitializeSchema = uData.getServer1().configureReplicationDomain()
8056          && uData.getServer2().configureReplicationDomain();
8057    }
8058    return mustInitializeSchema;
8059  }
8060
8061  /**
8062   * This method registers a server in a given ADSContext.  If the server was
8063   * already registered it unregisters it and registers again (some properties
8064   * might have changed).
8065   * @param adsContext the ADS Context to be used.
8066   * @param serverProperties the properties of the server to be registered.
8067   * @throws ADSContextException if an error occurs during the registration or
8068   * unregistration of the server.
8069   */
8070  private void registerServer(ADSContext adsContext, Map<ServerProperty, Object> serverProperties)
8071      throws ADSContextException
8072  {
8073    try
8074    {
8075      adsContext.registerServer(serverProperties);
8076    }
8077    catch (ADSContextException ade)
8078    {
8079      if (ade.getError() ==
8080        ADSContextException.ErrorType.ALREADY_REGISTERED)
8081      {
8082        logger.warn(LocalizableMessage.raw("The server was already registered: "+
8083            serverProperties));
8084        adsContext.unregisterServer(serverProperties);
8085        adsContext.registerServer(serverProperties);
8086      }
8087      else
8088      {
8089        throw ade;
8090      }
8091    }
8092  }
8093
8094  /** {@inheritDoc} */
8095  @Override
8096  public boolean isAdvancedMode() {
8097    return false;
8098  }
8099
8100  /** {@inheritDoc} */
8101  @Override
8102  public boolean isInteractive() {
8103    return !forceNonInteractive && argParser.isInteractive();
8104  }
8105
8106  /** {@inheritDoc} */
8107  @Override
8108  public boolean isMenuDrivenMode() {
8109    return true;
8110  }
8111
8112  /** {@inheritDoc} */
8113  @Override
8114  public boolean isQuiet()
8115  {
8116    return argParser.isQuiet();
8117  }
8118
8119  /** {@inheritDoc} */
8120  @Override
8121  public boolean isScriptFriendly() {
8122    return argParser.isScriptFriendly();
8123  }
8124
8125  /** {@inheritDoc} */
8126  @Override
8127  public boolean isVerbose() {
8128    return true;
8129  }
8130
8131  /**
8132   * Forces the initialization of the trust manager in the LDAPConnectionInteraction object.
8133   * @param ci the LDAP connection to the server
8134   */
8135  private void forceTrustManagerInitialization(LDAPConnectionConsoleInteraction ci)
8136  {
8137    forceNonInteractive = true;
8138    try
8139    {
8140      ci.initializeTrustManagerIfRequired();
8141    }
8142    catch (ArgumentException ae)
8143    {
8144      logger.warn(LocalizableMessage.raw("Error initializing trust store: "+ae, ae));
8145    }
8146    forceNonInteractive = false;
8147  }
8148
8149  /**
8150   * Method used to compare two server registries.
8151   * @param registry1 the first registry to compare.
8152   * @param registry2 the second registry to compare.
8153   * @return <CODE>true</CODE> if the registries are equal and
8154   * <CODE>false</CODE> otherwise.
8155   */
8156  private boolean areEqual(Set<Map<ServerProperty, Object>> registry1, Set<Map<ServerProperty, Object>> registry2)
8157  {
8158    return registry1.size() == registry2.size()
8159        && equals(registry1, registry2, getPropertiesToCompare());
8160  }
8161
8162  private Set<ServerProperty> getPropertiesToCompare()
8163  {
8164    final Set<ServerProperty> propertiesToCompare = new HashSet<>();
8165    for (ServerProperty property : ServerProperty.values())
8166    {
8167      if (property.getAttributeSyntax() != ADSPropertySyntax.CERTIFICATE_BINARY)
8168      {
8169        propertiesToCompare.add(property);
8170      }
8171    }
8172    return propertiesToCompare;
8173  }
8174
8175  private boolean equals(Set<Map<ServerProperty, Object>> registry1, Set<Map<ServerProperty, Object>> registry2,
8176      Set<ServerProperty> propertiesToCompare)
8177  {
8178    for (Map<ServerProperty, Object> server1 : registry1)
8179    {
8180      if (!exists(registry2, server1, propertiesToCompare))
8181      {
8182        return false;
8183      }
8184    }
8185    return true;
8186  }
8187
8188  private boolean exists(Set<Map<ServerProperty, Object>> registry2, Map<ServerProperty, Object> server1,
8189      Set<ServerProperty> propertiesToCompare)
8190  {
8191    for (Map<ServerProperty, Object> server2 : registry2)
8192    {
8193      if (equals(server1, server2, propertiesToCompare))
8194      {
8195        return true;
8196      }
8197    }
8198    return false;
8199  }
8200
8201  private boolean equals(Map<ServerProperty, Object> server1, Map<ServerProperty, Object> server2,
8202      Set<ServerProperty> propertiesToCompare)
8203  {
8204    for (ServerProperty prop : propertiesToCompare)
8205    {
8206      if (!Objects.equals(server1.get(prop), server2.get(prop)))
8207      {
8208        return false;
8209      }
8210    }
8211    return true;
8212  }
8213
8214  /**
8215   * Tells whether we are trying to disable all the replicated suffixes.
8216   * @param uData the disable replication data provided by the user.
8217   * @return <CODE>true</CODE> if we want to disable all the replicated suffixes
8218   * and <CODE>false</CODE> otherwise.
8219   */
8220  private boolean disableAllBaseDns(InitialLdapContext ctx,
8221      DisableReplicationUserData uData)
8222  {
8223    if (uData.disableAll())
8224    {
8225      return true;
8226    }
8227
8228    Collection<ReplicaDescriptor> replicas = getReplicas(ctx);
8229    Set<String> replicatedSuffixes = new HashSet<>();
8230    for (ReplicaDescriptor rep : replicas)
8231    {
8232      String dn = rep.getSuffix().getDN();
8233      if (rep.isReplicated())
8234      {
8235        replicatedSuffixes.add(dn);
8236      }
8237    }
8238
8239    for (String dn1 : replicatedSuffixes)
8240    {
8241      if (!areDnsEqual(ADSContext.getAdministrationSuffixDN(), dn1)
8242          && !areDnsEqual(Constants.SCHEMA_DN, dn1)
8243          && !containsDN(uData.getBaseDNs(), dn1))
8244      {
8245        return false;
8246      }
8247    }
8248    return true;
8249  }
8250
8251  private boolean containsDN(final Collection<String> dns, String dnToFind)
8252  {
8253    for (String dn : dns)
8254    {
8255      if (areDnsEqual(dn, dnToFind))
8256      {
8257        return true;
8258      }
8259    }
8260    return false;
8261  }
8262
8263  /**
8264   * Returns the host port representation of the server to be used in progress,
8265   * status and error messages.  It takes into account the fact the host and
8266   * port provided by the user.
8267   * @param server the ServerDescriptor.
8268   * @param cnx the preferred connections list.
8269   * @return the host port string representation of the provided server.
8270   */
8271  private String getHostPort2(ServerDescriptor server,
8272      Collection<PreferredConnection> cnx)
8273  {
8274    String hostPort = null;
8275    for (PreferredConnection connection : cnx)
8276    {
8277      String url = connection.getLDAPURL();
8278      if (url.equals(server.getLDAPURL()))
8279      {
8280        hostPort = server.getHostPort(false);
8281      }
8282      else if (url.equals(server.getLDAPsURL()))
8283      {
8284        hostPort = server.getHostPort(true);
8285      }
8286    }
8287    if (hostPort != null)
8288    {
8289      return hostPort;
8290    }
8291    return server.getHostPort(true);
8292  }
8293
8294  /**
8295   * Prompts the user for the subcommand that should be executed.
8296   * @return the subcommand choice of the user.
8297   */
8298  private SubcommandChoice promptForSubcommand()
8299  {
8300    MenuBuilder<SubcommandChoice> builder = new MenuBuilder<>(this);
8301    builder.setPrompt(INFO_REPLICATION_SUBCOMMAND_PROMPT.get());
8302    builder.addCancelOption(false);
8303    for (SubcommandChoice choice : SubcommandChoice.values())
8304    {
8305      if (choice != SubcommandChoice.CANCEL)
8306      {
8307        builder.addNumberedOption(choice.getPrompt(),
8308            MenuResult.success(choice));
8309      }
8310    }
8311    try
8312    {
8313      MenuResult<SubcommandChoice> m = builder.toMenu().run();
8314      if (m.isSuccess())
8315      {
8316        return m.getValue();
8317      }
8318      // The user cancelled
8319      return SubcommandChoice.CANCEL;
8320    }
8321    catch (ClientException ce)
8322    {
8323      logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
8324      return SubcommandChoice.CANCEL;
8325    }
8326  }
8327
8328  private boolean mustPrintCommandBuilder()
8329  {
8330    return argParser.isInteractive() &&
8331        (argParser.displayEquivalentArgument.isPresent() ||
8332        argParser.equivalentCommandFileArgument.isPresent());
8333  }
8334
8335  /**
8336   * Prints the contents of a command builder.  This method has been created
8337   * since SetPropSubCommandHandler calls it.  All the logic of DSConfig is on
8338   * this method.  Currently it simply writes the content of the CommandBuilder
8339   * to the standard output, but if we provide an option to write the content
8340   * to a file only the implementation of this method must be changed.
8341   * @param subCommandName the command builder to be printed.
8342   * @param uData input parameters from cli
8343   */
8344  private void printNewCommandBuilder(String subCommandName, ReplicationUserData uData)
8345  {
8346    try
8347    {
8348      final CommandBuilder commandBuilder = createCommandBuilder(sourceServerCI, subCommandName, uData);
8349      if (argParser.displayEquivalentArgument.isPresent())
8350      {
8351        println();
8352        // We assume that the app we are running is this one.
8353        println(INFO_REPLICATION_NON_INTERACTIVE.get(commandBuilder));
8354      }
8355      if (argParser.equivalentCommandFileArgument.isPresent())
8356      {
8357        // Write to the file.
8358        String file = argParser.equivalentCommandFileArgument.getValue();
8359        try
8360        {
8361          BufferedWriter writer = new BufferedWriter(new FileWriter(file, true));
8362
8363          writer.write(SHELL_COMMENT_SEPARATOR+getCurrentOperationDateMessage());
8364          writer.newLine();
8365
8366          writer.write(commandBuilder.toString());
8367          writer.newLine();
8368          writer.newLine();
8369
8370          writer.flush();
8371          writer.close();
8372        }
8373        catch (IOException ioe)
8374        {
8375          errPrintln(ERR_REPLICATION_ERROR_WRITING_EQUIVALENT_COMMAND_LINE.get(file, ioe));
8376        }
8377      }
8378    }
8379    catch (Throwable t)
8380    {
8381      logger.error(LocalizableMessage.raw("Error printing equivalent command-line: " + t), t);
8382    }
8383  }
8384
8385  /**
8386   * Creates a command builder with the global options: script friendly,
8387   * verbose, etc. for a given subcommand name.  It also adds systematically the
8388   * no-prompt option.
8389   *
8390   * @param ci the LDAP connection to the server
8391   * @param subcommandName the subcommand name.
8392   * @param uData the user data.
8393   * @return the command builder that has been created with the specified
8394   * subcommandName.
8395   */
8396  private CommandBuilder createCommandBuilder(LDAPConnectionConsoleInteraction ci, String subcommandName,
8397      ReplicationUserData uData) throws ArgumentException
8398  {
8399    String commandName = getCommandName();
8400
8401    CommandBuilder commandBuilder = new CommandBuilder(commandName, subcommandName);
8402
8403    if (ENABLE_REPLICATION_SUBCMD_NAME.equals(subcommandName))
8404    {
8405      // All the arguments for enable replication are update here.
8406      updateCommandBuilder(commandBuilder, (EnableReplicationUserData)uData);
8407    }
8408    else if (INITIALIZE_REPLICATION_SUBCMD_NAME.equals(subcommandName) ||
8409        RESET_CHANGE_NUMBER_SUBCMD_NAME.equals(subcommandName))
8410    {
8411      // All the arguments for initialize replication are update here.
8412      updateCommandBuilder(commandBuilder, (SourceDestinationServerUserData)uData);
8413    }
8414    else if (PURGE_HISTORICAL_SUBCMD_NAME.equals(subcommandName))
8415    {
8416      // All the arguments for initialize replication are update here.
8417      updateCommandBuilder(ci, commandBuilder, (PurgeHistoricalUserData)uData);
8418    }
8419    else
8420    {
8421      // Update the arguments used in the console interaction with the
8422      // actual arguments of dsreplication.
8423      updateCommandBuilderWithConsoleInteraction(commandBuilder, ci);
8424    }
8425
8426    if (DISABLE_REPLICATION_SUBCMD_NAME.equals(subcommandName))
8427    {
8428      DisableReplicationUserData disableData =
8429        (DisableReplicationUserData)uData;
8430      if (disableData.disableAll())
8431      {
8432        commandBuilder.addArgument(newBooleanArgument(
8433            argParser.disableAllArg, INFO_DESCRIPTION_DISABLE_ALL));
8434      }
8435      else if (disableData.disableReplicationServer())
8436      {
8437        commandBuilder.addArgument(newBooleanArgument(
8438            argParser.disableReplicationServerArg, INFO_DESCRIPTION_DISABLE_REPLICATION_SERVER));
8439      }
8440    }
8441
8442    addGlobalArguments(commandBuilder, uData);
8443    return commandBuilder;
8444  }
8445
8446  private String getCommandName()
8447  {
8448    String commandName = System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
8449    if (commandName != null)
8450    {
8451      return commandName;
8452    }
8453    return "dsreplication";
8454  }
8455
8456  private void updateCommandBuilderWithConsoleInteraction(CommandBuilder commandBuilder,
8457      LDAPConnectionConsoleInteraction ci) throws ArgumentException
8458  {
8459    if (ci != null && ci.getCommandBuilder() != null)
8460    {
8461      CommandBuilder interactionBuilder = ci.getCommandBuilder();
8462      for (Argument arg : interactionBuilder.getArguments())
8463      {
8464        if (OPTION_LONG_BINDPWD.equals(arg.getLongIdentifier()))
8465        {
8466          commandBuilder.addObfuscatedArgument(getAdminPasswordArg(arg));
8467        }
8468        else if (OPTION_LONG_BINDPWD_FILE.equals(arg.getLongIdentifier()))
8469        {
8470          commandBuilder.addArgument(getAdminPasswordFileArg(arg));
8471        }
8472        else
8473        {
8474          addArgument(commandBuilder, arg, interactionBuilder.isObfuscated(arg));
8475        }
8476      }
8477    }
8478  }
8479
8480  private void updateCommandBuilder(LDAPConnectionConsoleInteraction ci, CommandBuilder commandBuilder,
8481      PurgeHistoricalUserData uData) throws ArgumentException
8482  {
8483    if (uData.isOnline())
8484    {
8485      updateCommandBuilderWithConsoleInteraction(commandBuilder, ci);
8486      if (uData.getTaskSchedule() != null)
8487      {
8488        updateCommandBuilderWithTaskSchedule(commandBuilder,
8489            uData.getTaskSchedule());
8490      }
8491    }
8492
8493    IntegerArgument maximumDurationArg = IntegerArgument.builder(argParser.maximumDurationArg.getLongIdentifier())
8494            .shortIdentifier(argParser.maximumDurationArg.getShortIdentifier())
8495            .description(argParser.maximumDurationArg.getDescription())
8496            .required()
8497            .defaultValue(PurgeConflictsHistoricalTask.DEFAULT_MAX_DURATION)
8498            .valuePlaceholder(argParser.maximumDurationArg.getValuePlaceholder())
8499            .buildArgument();
8500    maximumDurationArg.addValue(String.valueOf(uData.getMaximumDuration()));
8501    commandBuilder.addArgument(maximumDurationArg);
8502  }
8503
8504  private void updateCommandBuilderWithTaskSchedule(
8505      CommandBuilder commandBuilder,
8506      TaskScheduleUserData taskSchedule)
8507  {
8508    TaskScheduleUserData.updateCommandBuilderWithTaskSchedule(
8509        commandBuilder, taskSchedule);
8510  }
8511
8512  private void addGlobalArguments(CommandBuilder commandBuilder, ReplicationUserData uData)
8513  throws ArgumentException
8514  {
8515    List<String> baseDNs = uData.getBaseDNs();
8516    StringArgument baseDNsArg =
8517            StringArgument.builder(OPTION_LONG_BASEDN)
8518                    .shortIdentifier(OPTION_SHORT_BASEDN)
8519                    .description(INFO_DESCRIPTION_REPLICATION_BASEDNS.get())
8520                    .multiValued()
8521                    .valuePlaceholder(INFO_BASEDN_PLACEHOLDER.get())
8522                    .buildArgument();
8523    for (String baseDN : baseDNs)
8524    {
8525      baseDNsArg.addValue(baseDN);
8526    }
8527    commandBuilder.addArgument(baseDNsArg);
8528
8529    if (argParser.resetChangeNumber.isPresent())
8530    {
8531      commandBuilder.addArgument(argParser.resetChangeNumber);
8532    }
8533
8534    // Try to find some arguments and put them at the end.
8535    String[] identifiersToMove ={
8536        OPTION_LONG_ADMIN_UID,
8537        "adminPassword",
8538        "adminPasswordFile",
8539        OPTION_LONG_SASLOPTION,
8540        OPTION_LONG_TRUSTALL,
8541        OPTION_LONG_TRUSTSTOREPATH,
8542        OPTION_LONG_TRUSTSTORE_PWD,
8543        OPTION_LONG_TRUSTSTORE_PWD_FILE,
8544        OPTION_LONG_KEYSTOREPATH,
8545        OPTION_LONG_KEYSTORE_PWD,
8546        OPTION_LONG_KEYSTORE_PWD_FILE,
8547        OPTION_LONG_CERT_NICKNAME
8548    };
8549
8550    ArrayList<Argument> toMoveArgs = new ArrayList<>();
8551    for (String longID : identifiersToMove)
8552    {
8553      final Argument arg = findArg(commandBuilder, longID);
8554      if (arg != null)
8555      {
8556        toMoveArgs.add(arg);
8557      }
8558    }
8559    for (Argument argToMove : toMoveArgs)
8560    {
8561      boolean toObfuscate = commandBuilder.isObfuscated(argToMove);
8562      commandBuilder.removeArgument(argToMove);
8563      if (toObfuscate)
8564      {
8565        commandBuilder.addObfuscatedArgument(argToMove);
8566      }
8567      else
8568      {
8569        commandBuilder.addArgument(argToMove);
8570      }
8571    }
8572
8573    if (argParser.isVerbose())
8574    {
8575      commandBuilder.addArgument(
8576              BooleanArgument.builder(OPTION_LONG_VERBOSE)
8577                      .shortIdentifier(OPTION_SHORT_VERBOSE)
8578                      .description(INFO_DESCRIPTION_VERBOSE.get())
8579                      .buildArgument());
8580    }
8581
8582    if (argParser.isScriptFriendly())
8583    {
8584      commandBuilder.addArgument(argParser.scriptFriendlyArg);
8585    }
8586
8587    commandBuilder.addArgument(argParser.noPromptArg);
8588
8589    if (argParser.propertiesFileArgument.isPresent())
8590    {
8591      commandBuilder.addArgument(argParser.propertiesFileArgument);
8592    }
8593
8594    if (argParser.noPropertiesFileArgument.isPresent())
8595    {
8596      commandBuilder.addArgument(argParser.noPropertiesFileArgument);
8597    }
8598  }
8599
8600  private Argument findArg(CommandBuilder commandBuilder, String longIdentifier)
8601  {
8602    for (Argument arg : commandBuilder.getArguments())
8603    {
8604      if (longIdentifier.equals(arg.getLongIdentifier()))
8605      {
8606        return arg;
8607      }
8608    }
8609    return null;
8610  }
8611
8612  private boolean existsArg(CommandBuilder commandBuilder, String longIdentifier)
8613  {
8614    return findArg(commandBuilder, longIdentifier) != null;
8615  }
8616
8617  private void addArgument(CommandBuilder commandBuilder, Argument arg, boolean isObfuscated)
8618  {
8619    if (isObfuscated)
8620    {
8621      commandBuilder.addObfuscatedArgument(arg);
8622    }
8623    else
8624    {
8625      commandBuilder.addArgument(arg);
8626    }
8627  }
8628
8629  private void updateCommandBuilder(CommandBuilder commandBuilder, EnableReplicationUserData uData)
8630      throws ArgumentException
8631  {
8632    // Update the arguments used in the console interaction with the
8633    // actual arguments of dsreplication.
8634    boolean adminInformationAdded = false;
8635
8636    EnableReplicationServerData server1 = uData.getServer1();
8637    if (firstServerCommandBuilder != null)
8638    {
8639      boolean useAdminUID = existsArg(firstServerCommandBuilder, OPTION_LONG_ADMIN_UID);
8640      // This is required when both the bindDN and the admin UID are provided
8641      // in the command-line.
8642      boolean forceAddBindDN1 = false;
8643      boolean forceAddBindPwdFile1 = false;
8644      if (useAdminUID)
8645      {
8646        String bindDN1 = server1.getBindDn();
8647        String adminUID = uData.getAdminUid();
8648        if (bindDN1 != null
8649            && adminUID != null
8650            && !areDnsEqual(getAdministratorDN(adminUID), bindDN1))
8651        {
8652          forceAddBindDN1 = true;
8653          forceAddBindPwdFile1 = existsArg(firstServerCommandBuilder, OPTION_LONG_BINDPWD_FILE);
8654        }
8655      }
8656      for (Argument arg : firstServerCommandBuilder.getArguments())
8657      {
8658        if (OPTION_LONG_HOST.equals(arg.getLongIdentifier()))
8659        {
8660          commandBuilder.addArgument(getHostArg("host1", OPTION_SHORT_HOST, server1.getHostName(),
8661              INFO_DESCRIPTION_ENABLE_REPLICATION_HOST1));
8662        }
8663        else if (OPTION_LONG_PORT.equals(arg.getLongIdentifier()))
8664        {
8665          commandBuilder.addArgument(getPortArg("port1", OPTION_SHORT_PORT, server1.getPort(),
8666              INFO_DESCRIPTION_ENABLE_REPLICATION_SERVER_PORT1));
8667
8668          if (forceAddBindDN1)
8669          {
8670            commandBuilder.addArgument(getBindDN1Arg(uData));
8671            if (forceAddBindPwdFile1)
8672            {
8673              FileBasedArgument bindPasswordFileArg = getBindPasswordFile1Arg();
8674              bindPasswordFileArg.getNameToValueMap().put("{password file}",
8675                  "{password file}");
8676              commandBuilder.addArgument(bindPasswordFileArg);
8677            }
8678            else
8679            {
8680              commandBuilder.addObfuscatedArgument(getBindPassword1Arg(arg));
8681            }
8682          }
8683        }
8684        else if (OPTION_LONG_BINDDN.equals(arg.getLongIdentifier()))
8685        {
8686          commandBuilder.addArgument(getBindDN1Arg(uData));
8687        }
8688        else if (OPTION_LONG_BINDPWD.equals(arg.getLongIdentifier()))
8689        {
8690          if (useAdminUID)
8691          {
8692            adminInformationAdded = true;
8693            commandBuilder.addObfuscatedArgument(getAdminPasswordArg(arg));
8694          }
8695          else
8696          {
8697            commandBuilder.addObfuscatedArgument(getBindPassword1Arg(arg));
8698          }
8699        }
8700        else if (OPTION_LONG_BINDPWD_FILE.equals(arg.getLongIdentifier()))
8701        {
8702          if (useAdminUID)
8703          {
8704            commandBuilder.addArgument(getAdminPasswordFileArg(arg));
8705          }
8706          else
8707          {
8708            FileBasedArgument bindPasswordFileArg = getBindPasswordFile1Arg();
8709            bindPasswordFileArg.getNameToValueMap().putAll(
8710                ((FileBasedArgument)arg).getNameToValueMap());
8711            commandBuilder.addArgument(bindPasswordFileArg);
8712          }
8713        }
8714        else
8715        {
8716          if (OPTION_LONG_ADMIN_UID.equals(arg.getLongIdentifier()))
8717          {
8718            adminInformationAdded = true;
8719          }
8720
8721          addArgument(commandBuilder, arg, firstServerCommandBuilder.isObfuscated(arg));
8722        }
8723      }
8724    }
8725
8726    EnableReplicationServerData server2 = uData.getServer2();
8727    if (sourceServerCI != null && sourceServerCI.getCommandBuilder() != null)
8728    {
8729      CommandBuilder interactionBuilder = sourceServerCI.getCommandBuilder();
8730      boolean useAdminUID = existsArg(interactionBuilder, OPTION_LONG_ADMIN_UID);
8731      boolean hasBindDN = existsArg(interactionBuilder, OPTION_LONG_BINDDN);
8732//    This is required when both the bindDN and the admin UID are provided
8733      // in the command-line.
8734      boolean forceAddBindDN2 = false;
8735      boolean forceAddBindPwdFile2 = false;
8736      if (useAdminUID)
8737      {
8738        String bindDN2 = server2.getBindDn();
8739        String adminUID = uData.getAdminUid();
8740        if (bindDN2 != null
8741            && adminUID != null
8742            && !areDnsEqual(getAdministratorDN(adminUID), bindDN2))
8743        {
8744          forceAddBindDN2 = true;
8745          forceAddBindPwdFile2 = existsArg(interactionBuilder, OPTION_LONG_BINDPWD_FILE);
8746        }
8747      }
8748      ArrayList<Argument> argsToAnalyze = new ArrayList<>();
8749      for (Argument arg : interactionBuilder.getArguments())
8750      {
8751        if (OPTION_LONG_HOST.equals(arg.getLongIdentifier()))
8752        {
8753          commandBuilder.addArgument(
8754              getHostArg("host2", 'O', server2.getHostName(), INFO_DESCRIPTION_ENABLE_REPLICATION_HOST2));
8755        }
8756        else if (OPTION_LONG_PORT.equals(arg.getLongIdentifier()))
8757        {
8758          commandBuilder.addArgument(getPortArg("port2", null, server2.getPort(),
8759              INFO_DESCRIPTION_ENABLE_REPLICATION_SERVER_PORT2));
8760
8761          if (forceAddBindDN2)
8762          {
8763            commandBuilder.addArgument(getBindDN2Arg(uData, OPTION_SHORT_BINDDN));
8764            if (forceAddBindPwdFile2)
8765            {
8766              FileBasedArgument bindPasswordFileArg = getBindPasswordFile2Arg();
8767              bindPasswordFileArg.getNameToValueMap().put("{password file}",
8768                  "{password file}");
8769              commandBuilder.addArgument(bindPasswordFileArg);
8770            }
8771            else
8772            {
8773              commandBuilder.addObfuscatedArgument(getBindPassword2Arg(arg));
8774            }
8775          }
8776        }
8777        else if (OPTION_LONG_BINDDN.equals(arg.getLongIdentifier()))
8778        {
8779          commandBuilder.addArgument(getBindDN2Arg(uData, null));
8780        }
8781        else if (OPTION_LONG_BINDPWD.equals(arg.getLongIdentifier()))
8782        {
8783          if (useAdminUID && !adminInformationAdded)
8784          {
8785            adminInformationAdded = true;
8786            commandBuilder.addObfuscatedArgument(getAdminPasswordArg(arg));
8787          }
8788          else if (hasBindDN)
8789          {
8790            commandBuilder.addObfuscatedArgument(getBindPassword2Arg(arg));
8791          }
8792        }
8793        else if (OPTION_LONG_BINDPWD_FILE.equals(arg.getLongIdentifier()))
8794        {
8795          if (useAdminUID && !adminInformationAdded)
8796          {
8797            adminInformationAdded = true;
8798            commandBuilder.addArgument(getAdminPasswordFileArg(arg));
8799          }
8800          else if (hasBindDN)
8801          {
8802            FileBasedArgument bindPasswordFileArg = getBindPasswordFile2Arg();
8803            bindPasswordFileArg.getNameToValueMap().putAll(
8804                ((FileBasedArgument)arg).getNameToValueMap());
8805            commandBuilder.addArgument(bindPasswordFileArg);
8806          }
8807        }
8808        else
8809        {
8810          argsToAnalyze.add(arg);
8811        }
8812      }
8813
8814      for (Argument arg : argsToAnalyze)
8815      {
8816        // Just check that the arguments have not already been added.
8817        if (!existsArg(commandBuilder, arg.getLongIdentifier()))
8818        {
8819          addArgument(commandBuilder, arg, interactionBuilder.isObfuscated(arg));
8820        }
8821      }
8822    }
8823
8824    // Try to add the new administration information.
8825    if (!adminInformationAdded)
8826    {
8827      if (uData.getAdminUid() != null)
8828      {
8829        final StringArgument adminUID = adminUid(
8830                INFO_DESCRIPTION_REPLICATION_ADMIN_UID.get(ENABLE_REPLICATION_SUBCMD_NAME));
8831        adminUID.addValue(uData.getAdminUid());
8832        commandBuilder.addArgument(adminUID);
8833      }
8834
8835      if (userProvidedAdminPwdFile != null)
8836      {
8837        commandBuilder.addArgument(userProvidedAdminPwdFile);
8838      }
8839      else if (uData.getAdminPwd() != null)
8840      {
8841        Argument bindPasswordArg = getAdminPasswordArg();
8842        bindPasswordArg.addValue(uData.getAdminPwd());
8843        commandBuilder.addObfuscatedArgument(bindPasswordArg);
8844      }
8845    }
8846
8847    if (server1.configureReplicationServer() &&
8848        !server1.configureReplicationDomain())
8849    {
8850      commandBuilder.addArgument(newBooleanArgument(
8851          argParser.server1.onlyReplicationServerArg, INFO_DESCRIPTION_ENABLE_REPLICATION_ONLY_REPLICATION_SERVER1));
8852    }
8853
8854    if (!server1.configureReplicationServer() &&
8855        server1.configureReplicationDomain())
8856    {
8857      commandBuilder.addArgument(newBooleanArgument(
8858          argParser.server1.noReplicationServerArg, INFO_DESCRIPTION_ENABLE_REPLICATION_NO_REPLICATION_SERVER1));
8859    }
8860
8861    if (server1.configureReplicationServer() &&
8862        server1.getReplicationPort() > 0)
8863    {
8864      commandBuilder.addArgument(getReplicationPortArg(
8865          "replicationPort1", server1, 8989, INFO_DESCRIPTION_ENABLE_REPLICATION_PORT1));
8866    }
8867    if (server1.isSecureReplication())
8868    {
8869      commandBuilder.addArgument(
8870          newBooleanArgument("secureReplication1", INFO_DESCRIPTION_ENABLE_SECURE_REPLICATION1));
8871    }
8872
8873    if (server2.configureReplicationServer() &&
8874        !server2.configureReplicationDomain())
8875    {
8876      commandBuilder.addArgument(newBooleanArgument(
8877          argParser.server2.onlyReplicationServerArg, INFO_DESCRIPTION_ENABLE_REPLICATION_ONLY_REPLICATION_SERVER2));
8878    }
8879
8880    if (!server2.configureReplicationServer() &&
8881        server2.configureReplicationDomain())
8882    {
8883      commandBuilder.addArgument(newBooleanArgument(
8884          argParser.server2.noReplicationServerArg, INFO_DESCRIPTION_ENABLE_REPLICATION_NO_REPLICATION_SERVER2));
8885    }
8886    if (server2.configureReplicationServer() &&
8887        server2.getReplicationPort() > 0)
8888    {
8889      commandBuilder.addArgument(getReplicationPortArg(
8890          "replicationPort2", server2, server2.getReplicationPort(), INFO_DESCRIPTION_ENABLE_REPLICATION_PORT2));
8891    }
8892    if (server2.isSecureReplication())
8893    {
8894      commandBuilder.addArgument(
8895          newBooleanArgument("secureReplication2", INFO_DESCRIPTION_ENABLE_SECURE_REPLICATION2));
8896    }
8897
8898    if (!uData.replicateSchema())
8899    {
8900      commandBuilder.addArgument(
8901              BooleanArgument.builder("noSchemaReplication")
8902                      .description(INFO_DESCRIPTION_ENABLE_REPLICATION_NO_SCHEMA_REPLICATION.get())
8903                      .buildArgument());
8904    }
8905    if (argParser.skipReplicationPortCheck())
8906    {
8907      commandBuilder.addArgument(
8908              BooleanArgument.builder("skipPortCheck")
8909                      .shortIdentifier('S')
8910                      .description(INFO_DESCRIPTION_ENABLE_REPLICATION_SKIPPORT.get())
8911                      .buildArgument());
8912    }
8913    if (argParser.useSecondServerAsSchemaSource())
8914    {
8915      commandBuilder.addArgument(
8916              BooleanArgument.builder("useSecondServerAsSchemaSource")
8917                      .description(INFO_DESCRIPTION_ENABLE_REPLICATION_USE_SECOND_AS_SCHEMA_SOURCE.get(
8918                              "--" + argParser.noSchemaReplicationArg.getLongIdentifier()))
8919                      .buildArgument());
8920    }
8921  }
8922
8923  private IntegerArgument getReplicationPortArg(
8924      String name, EnableReplicationServerData server, int defaultValue, Arg0 description) throws ArgumentException
8925  {
8926    IntegerArgument replicationPort =
8927            IntegerArgument.builder(name)
8928                    .shortIdentifier('r')
8929                    .description(description.get())
8930                    .defaultValue(defaultValue)
8931                    .valuePlaceholder(INFO_PORT_PLACEHOLDER.get())
8932                    .buildArgument();
8933    replicationPort.addValue(String.valueOf(server.getReplicationPort()));
8934    return replicationPort;
8935  }
8936
8937  private BooleanArgument newBooleanArgument(String name, Arg0 msg) throws ArgumentException
8938  {
8939    return BooleanArgument.builder(name)
8940            .description(msg.get())
8941            .buildArgument();
8942  }
8943
8944  private BooleanArgument newBooleanArgument(BooleanArgument arg, Arg0 msg) throws ArgumentException
8945  {
8946    return BooleanArgument.builder(arg.getLongIdentifier())
8947            .shortIdentifier(arg.getShortIdentifier())
8948            .description(msg.get())
8949            .buildArgument();
8950  }
8951
8952  private StringArgument getBindPassword1Arg(Argument arg) throws ArgumentException
8953  {
8954    return getBindPasswordArg("bindPassword1", arg, INFO_DESCRIPTION_ENABLE_REPLICATION_BINDPASSWORD1);
8955  }
8956
8957  private StringArgument getBindPassword2Arg(Argument arg) throws ArgumentException
8958  {
8959    return getBindPasswordArg("bindPassword2", arg, INFO_DESCRIPTION_ENABLE_REPLICATION_BINDPASSWORD2);
8960  }
8961
8962  private StringArgument getBindPasswordArg(String name, Argument arg, Arg0 bindPwdMsg) throws ArgumentException
8963  {
8964    StringArgument bindPasswordArg =
8965            StringArgument.builder(name)
8966                    .description(bindPwdMsg.get())
8967                    .valuePlaceholder(INFO_BINDPWD_PLACEHOLDER.get())
8968                    .buildArgument();
8969    bindPasswordArg.addValue(arg.getValue());
8970    return bindPasswordArg;
8971  }
8972
8973  private FileBasedArgument getBindPasswordFile1Arg() throws ArgumentException
8974  {
8975    return FileBasedArgument.builder("bindPasswordFile1")
8976            .description(INFO_DESCRIPTION_ENABLE_REPLICATION_BINDPASSWORDFILE1.get())
8977            .valuePlaceholder(INFO_BINDPWD_FILE_PLACEHOLDER.get())
8978            .buildArgument();
8979  }
8980
8981  private FileBasedArgument getBindPasswordFile2Arg() throws ArgumentException
8982  {
8983    return FileBasedArgument.builder("bindPasswordFile2")
8984            .description(INFO_DESCRIPTION_ENABLE_REPLICATION_BINDPASSWORDFILE2.get())
8985            .valuePlaceholder(INFO_BINDPWD_FILE_PLACEHOLDER.get())
8986            .buildArgument();
8987  }
8988
8989  private StringArgument getBindDN1Arg(EnableReplicationUserData uData) throws ArgumentException
8990  {
8991    StringArgument bindDN =
8992            StringArgument.builder("bindDN1")
8993                    .shortIdentifier(OPTION_SHORT_BINDDN)
8994                    .description(INFO_DESCRIPTION_ENABLE_REPLICATION_BINDDN1.get())
8995                    .defaultValue("cn=Directory Manager")
8996                    .valuePlaceholder(INFO_BINDDN_PLACEHOLDER.get())
8997                    .buildArgument();
8998    bindDN.addValue(uData.getServer1().getBindDn());
8999    return bindDN;
9000  }
9001
9002  private StringArgument getBindDN2Arg(EnableReplicationUserData uData, Character shortIdentifier)
9003      throws ArgumentException
9004  {
9005    StringArgument bindDN =
9006            StringArgument.builder("bindDN2")
9007                    .shortIdentifier(shortIdentifier)
9008                    .description(INFO_DESCRIPTION_ENABLE_REPLICATION_BINDDN2.get())
9009                    .defaultValue("cn=Directory Manager")
9010                    .valuePlaceholder(INFO_BINDDN_PLACEHOLDER.get())
9011                    .buildArgument();
9012    bindDN.addValue(uData.getServer2().getBindDn());
9013    return bindDN;
9014  }
9015
9016  private void updateCommandBuilder(CommandBuilder commandBuilder,
9017      SourceDestinationServerUserData uData)
9018  throws ArgumentException
9019  {
9020    // Update the arguments used in the console interaction with the
9021    // actual arguments of dsreplication.
9022
9023    if (firstServerCommandBuilder != null)
9024    {
9025      for (Argument arg : firstServerCommandBuilder.getArguments())
9026      {
9027        if (OPTION_LONG_HOST.equals(arg.getLongIdentifier()))
9028        {
9029          commandBuilder.addArgument(getHostArg("hostSource", 'O', uData.getHostNameSource(),
9030              INFO_DESCRIPTION_INITIALIZE_REPLICATION_HOST_SOURCE));
9031        }
9032        else if (OPTION_LONG_PORT.equals(arg.getLongIdentifier()))
9033        {
9034          commandBuilder.addArgument(getPortArg("portSource", null, uData.getPortSource(),
9035              INFO_DESCRIPTION_INITIALIZE_REPLICATION_SERVER_PORT_SOURCE));
9036        }
9037        else if (OPTION_LONG_BINDPWD.equals(arg.getLongIdentifier()))
9038        {
9039          commandBuilder.addObfuscatedArgument(getAdminPasswordArg(arg));
9040        }
9041        else if (OPTION_LONG_BINDPWD_FILE.equals(arg.getLongIdentifier()))
9042        {
9043          commandBuilder.addArgument(getAdminPasswordFileArg(arg));
9044        }
9045        else
9046        {
9047          addArgument(commandBuilder, arg, firstServerCommandBuilder.isObfuscated(arg));
9048        }
9049      }
9050    }
9051
9052    if (sourceServerCI != null && sourceServerCI.getCommandBuilder() != null)
9053    {
9054      CommandBuilder interactionBuilder = sourceServerCI.getCommandBuilder();
9055      for (Argument arg : interactionBuilder.getArguments())
9056      {
9057        if (OPTION_LONG_HOST.equals(arg.getLongIdentifier()))
9058        {
9059          commandBuilder.addArgument(getHostArg("hostDestination", 'O', uData.getHostNameDestination(),
9060              INFO_DESCRIPTION_INITIALIZE_REPLICATION_HOST_DESTINATION));
9061        }
9062        else if (OPTION_LONG_PORT.equals(arg.getLongIdentifier()))
9063        {
9064          commandBuilder.addArgument(getPortArg("portDestination", null, uData.getPortDestination(),
9065              INFO_DESCRIPTION_INITIALIZE_REPLICATION_SERVER_PORT_DESTINATION));
9066        }
9067      }
9068    }
9069  }
9070
9071  private StringArgument getAdminPasswordArg(Argument arg) throws ArgumentException
9072  {
9073    StringArgument sArg = getAdminPasswordArg();
9074    sArg.addValue(arg.getValue());
9075    return sArg;
9076  }
9077
9078  private StringArgument getAdminPasswordArg() throws ArgumentException
9079  {
9080    return StringArgument.builder("adminPassword")
9081            .shortIdentifier(OPTION_SHORT_BINDPWD)
9082            .description(INFO_DESCRIPTION_REPLICATION_ADMIN_BINDPASSWORD.get())
9083            .valuePlaceholder(INFO_BINDPWD_PLACEHOLDER.get())
9084            .buildArgument();
9085  }
9086
9087  private FileBasedArgument getAdminPasswordFileArg(Argument arg) throws ArgumentException
9088  {
9089    FileBasedArgument fbArg =
9090            FileBasedArgument.builder("adminPasswordFile")
9091                    .shortIdentifier(OPTION_SHORT_BINDPWD_FILE)
9092                    .description(INFO_DESCRIPTION_REPLICATION_ADMIN_BINDPASSWORDFILE.get())
9093                    .valuePlaceholder(INFO_BINDPWD_FILE_PLACEHOLDER.get())
9094                    .buildArgument();
9095    fbArg.getNameToValueMap().putAll(((FileBasedArgument) arg).getNameToValueMap());
9096    return fbArg;
9097  }
9098
9099  private IntegerArgument getPortArg(String longIdentifier, Character shortIdentifier, int value, Arg0 arg)
9100      throws ArgumentException
9101  {
9102    IntegerArgument iArg =
9103            IntegerArgument.builder(longIdentifier)
9104                    .shortIdentifier(shortIdentifier)
9105                    .description(arg.get())
9106                    .defaultValue(4444)
9107                    .valuePlaceholder(INFO_PORT_PLACEHOLDER.get())
9108                    .buildArgument();
9109    iArg.addValue(String.valueOf(value));
9110    return iArg;
9111  }
9112
9113  private StringArgument getHostArg(String longIdentifier, char shortIdentifier, String value,
9114      Arg0 description) throws ArgumentException
9115  {
9116    StringArgument sArg =
9117            StringArgument.builder(longIdentifier)
9118                    .shortIdentifier(shortIdentifier)
9119                    .description(description.get())
9120                    .valuePlaceholder(INFO_HOST_PLACEHOLDER.get())
9121                    .buildArgument();
9122    sArg.addValue(value);
9123    return sArg;
9124  }
9125
9126  private void updateAvailableAndReplicatedSuffixesForOneDomain(
9127      InitialLdapContext ctxDomain, InitialLdapContext ctxOther,
9128      Set<String> availableSuffixes, Set<String> alreadyReplicatedSuffixes)
9129  {
9130    Collection<ReplicaDescriptor> replicas = getReplicas(ctxDomain);
9131    int replicationPort = getReplicationPort(ctxOther);
9132    boolean isReplicationServerConfigured = replicationPort != -1;
9133    String replicationServer = getReplicationServer(getHostName(ctxOther), replicationPort);
9134    for (ReplicaDescriptor replica : replicas)
9135    {
9136      if (!isReplicationServerConfigured)
9137      {
9138        if (replica.isReplicated())
9139        {
9140          alreadyReplicatedSuffixes.add(replica.getSuffix().getDN());
9141        }
9142        availableSuffixes.add(replica.getSuffix().getDN());
9143      }
9144
9145      if (!isReplicationServerConfigured)
9146      {
9147        availableSuffixes.add(replica.getSuffix().getDN());
9148      }
9149      else if (!replica.isReplicated())
9150      {
9151        availableSuffixes.add(replica.getSuffix().getDN());
9152      }
9153      else if (containsIgnoreCase(replica.getReplicationServers(), replicationServer))
9154      {
9155        alreadyReplicatedSuffixes.add(replica.getSuffix().getDN());
9156      }
9157      else
9158      {
9159        availableSuffixes.add(replica.getSuffix().getDN());
9160      }
9161    }
9162  }
9163
9164  private void updateAvailableAndReplicatedSuffixesForNoDomain(
9165      InitialLdapContext ctx1, InitialLdapContext ctx2,
9166      Set<String> availableSuffixes, Set<String> alreadyReplicatedSuffixes)
9167  {
9168    int replicationPort1 = getReplicationPort(ctx1);
9169    boolean isReplicationServer1Configured = replicationPort1 != -1;
9170    String replicationServer1 = getReplicationServer(getHostName(ctx1), replicationPort1);
9171
9172    int replicationPort2 = getReplicationPort(ctx2);
9173    boolean isReplicationServer2Configured = replicationPort2 != -1;
9174    String replicationServer2 = getReplicationServer(getHostName(ctx2), replicationPort2);
9175
9176    TopologyCache cache1 = isReplicationServer1Configured ? createTopologyCache(ctx1) : null;
9177    TopologyCache cache2 = isReplicationServer2Configured ? createTopologyCache(ctx2) : null;
9178    if (cache1 != null && cache2 != null)
9179    {
9180      updateAvailableAndReplicatedSuffixesForNoDomainOneSense(cache1, cache2,
9181          replicationServer1, replicationServer2, availableSuffixes,
9182          alreadyReplicatedSuffixes);
9183      updateAvailableAndReplicatedSuffixesForNoDomainOneSense(cache2, cache1,
9184          replicationServer2, replicationServer1, availableSuffixes,
9185          alreadyReplicatedSuffixes);
9186    }
9187    else if (cache1 != null)
9188    {
9189      addAllAvailableSuffixes(availableSuffixes, cache1.getSuffixes(), replicationServer1);
9190    }
9191    else if (cache2 != null)
9192    {
9193      addAllAvailableSuffixes(availableSuffixes, cache2.getSuffixes(), replicationServer2);
9194    }
9195  }
9196
9197  private TopologyCache createTopologyCache(InitialLdapContext ctx)
9198  {
9199    try
9200    {
9201      ADSContext adsContext = new ADSContext(ctx);
9202      if (adsContext.hasAdminData())
9203      {
9204        TopologyCache cache = new TopologyCache(adsContext, getTrustManager(sourceServerCI), getConnectTimeout());
9205        cache.getFilter().setSearchMonitoringInformation(false);
9206        cache.setPreferredConnections(getPreferredConnections(ctx));
9207        cache.reloadTopology();
9208        return cache;
9209      }
9210    }
9211    catch (Throwable t)
9212    {
9213      logger.warn(LocalizableMessage.raw("Error loading topology cache in " + getLdapUrl(ctx) + ": " + t, t));
9214    }
9215    return null;
9216  }
9217
9218  private void addAllAvailableSuffixes(Collection<String> availableSuffixes,
9219      Set<SuffixDescriptor> suffixes, String rsToFind)
9220  {
9221    for (SuffixDescriptor suffix : suffixes)
9222    {
9223      for (String rs : suffix.getReplicationServers())
9224      {
9225        if (rs.equalsIgnoreCase(rsToFind))
9226        {
9227          availableSuffixes.add(suffix.getDN());
9228        }
9229      }
9230    }
9231  }
9232
9233  private void updateAvailableAndReplicatedSuffixesForNoDomainOneSense(
9234      TopologyCache cache1, TopologyCache cache2, String replicationServer1,
9235      String replicationServer2,
9236      Set<String> availableSuffixes, Set<String> alreadyReplicatedSuffixes)
9237  {
9238    for (SuffixDescriptor suffix : cache1.getSuffixes())
9239    {
9240      for (String rServer : suffix.getReplicationServers())
9241      {
9242        if (rServer.equalsIgnoreCase(replicationServer1))
9243        {
9244          boolean isSecondReplicatedInSameTopology = false;
9245          boolean isSecondReplicated = false;
9246          boolean isFirstReplicated = false;
9247          for (SuffixDescriptor suffix2 : cache2.getSuffixes())
9248          {
9249            if (areDnsEqual(suffix.getDN(), suffix2.getDN()))
9250            {
9251              for (String rServer2 : suffix2.getReplicationServers())
9252              {
9253                if (rServer2.equalsIgnoreCase(replicationServer2))
9254                {
9255                  isSecondReplicated = true;
9256                }
9257                if (rServer.equalsIgnoreCase(replicationServer2))
9258                {
9259                  isFirstReplicated = true;
9260                }
9261                if (isFirstReplicated && isSecondReplicated)
9262                {
9263                  isSecondReplicatedInSameTopology = true;
9264                  break;
9265                }
9266              }
9267              break;
9268            }
9269          }
9270          if (!isSecondReplicatedInSameTopology)
9271          {
9272            availableSuffixes.add(suffix.getDN());
9273          }
9274          else
9275          {
9276            alreadyReplicatedSuffixes.add(suffix.getDN());
9277          }
9278          break;
9279        }
9280      }
9281    }
9282  }
9283
9284  private void updateBaseDnsWithNotEnoughReplicationServer(ADSContext adsCtx1,
9285      ADSContext adsCtx2, EnableReplicationUserData uData,
9286      Set<String> baseDNsWithNoReplicationServer,
9287      Set<String> baseDNsWithOneReplicationServer)
9288  {
9289    EnableReplicationServerData server1 = uData.getServer1();
9290    EnableReplicationServerData server2 = uData.getServer2();
9291    if (server1.configureReplicationServer() &&
9292        server2.configureReplicationServer())
9293    {
9294      return;
9295    }
9296
9297    Set<SuffixDescriptor> suffixes = new HashSet<>();
9298    createTopologyCache(adsCtx1, uData, suffixes);
9299    createTopologyCache(adsCtx2, uData, suffixes);
9300
9301    int repPort1 = getReplicationPort(adsCtx1.getDirContext());
9302    String repServer1 =  getReplicationServer(server1.getHostName(), repPort1);
9303    int repPort2 = getReplicationPort(adsCtx2.getDirContext());
9304    String repServer2 =  getReplicationServer(server2.getHostName(), repPort2);
9305    for (String baseDN : uData.getBaseDNs())
9306    {
9307      int nReplicationServers = 0;
9308      for (SuffixDescriptor suffix : suffixes)
9309      {
9310        if (areDnsEqual(suffix.getDN(), baseDN))
9311        {
9312          Set<String> replicationServers = suffix.getReplicationServers();
9313          nReplicationServers += replicationServers.size();
9314          for (String repServer : replicationServers)
9315          {
9316            if (server1.configureReplicationServer() &&
9317                repServer.equalsIgnoreCase(repServer1))
9318            {
9319              nReplicationServers --;
9320            }
9321            if (server2.configureReplicationServer() &&
9322                repServer.equalsIgnoreCase(repServer2))
9323            {
9324              nReplicationServers --;
9325            }
9326          }
9327        }
9328      }
9329      if (server1.configureReplicationServer())
9330      {
9331        nReplicationServers ++;
9332      }
9333      if (server2.configureReplicationServer())
9334      {
9335        nReplicationServers ++;
9336      }
9337      if (nReplicationServers == 1)
9338      {
9339        baseDNsWithOneReplicationServer.add(baseDN);
9340      }
9341      else if (nReplicationServers == 0)
9342      {
9343        baseDNsWithNoReplicationServer.add(baseDN);
9344      }
9345    }
9346  }
9347
9348  private void createTopologyCache(ADSContext adsCtx, ReplicationUserData uData, Set<SuffixDescriptor> suffixes)
9349  {
9350    try
9351    {
9352      if (adsCtx.hasAdminData())
9353      {
9354        TopologyCache cache = new TopologyCache(adsCtx, getTrustManager(sourceServerCI), getConnectTimeout());
9355        cache.getFilter().setSearchMonitoringInformation(false);
9356        addBaseDNs(cache.getFilter(), uData.getBaseDNs());
9357        cache.reloadTopology();
9358        suffixes.addAll(cache.getSuffixes());
9359      }
9360    }
9361    catch (Throwable t)
9362    {
9363      String msg = "Error loading topology cache from " + getHostPort(adsCtx.getDirContext()) + ": " + t;
9364      logger.warn(LocalizableMessage.raw(msg, t));
9365    }
9366  }
9367
9368  /**
9369   * Merge the contents of the two registries but only does it partially.
9370   * Only one of the two ADSContext will be updated (in terms of data in
9371   * cn=admin data), while the other registry's replication servers will have
9372   * their truststore updated to be able to initialize all the contents.
9373   *
9374   * This method does NOT configure replication between topologies or initialize
9375   * replication.
9376   *
9377   * @param adsCtx1 the ADSContext of the first registry.
9378   * @param adsCtx2 the ADSContext of the second registry.
9379   * @return <CODE>true</CODE> if the registry containing all the data is
9380   * the first registry and <CODE>false</CODE> otherwise.
9381   * @throws ReplicationCliException if there is a problem reading or updating
9382   * the registries.
9383   */
9384  private boolean mergeRegistries(ADSContext adsCtx1, ADSContext adsCtx2)
9385  throws ReplicationCliException
9386  {
9387    PointAdder pointAdder = new PointAdder(this);
9388    try
9389    {
9390      Set<PreferredConnection> cnx = new LinkedHashSet<>(getPreferredConnections(adsCtx1.getDirContext()));
9391      cnx.addAll(getPreferredConnections(adsCtx2.getDirContext()));
9392      TopologyCache cache1 = createTopologyCache(adsCtx1, cnx);
9393      TopologyCache cache2 = createTopologyCache(adsCtx2, cnx);
9394
9395      // Look for the cache with biggest number of replication servers:
9396      // that one is going to be source.
9397      int nRepServers1 = countReplicationServers(cache1);
9398      int nRepServers2 = countReplicationServers(cache2);
9399
9400      InitialLdapContext ctxSource;
9401      InitialLdapContext ctxDestination;
9402      if (nRepServers1 >= nRepServers2)
9403      {
9404        ctxSource = adsCtx1.getDirContext();
9405        ctxDestination = adsCtx2.getDirContext();
9406      }
9407      else
9408      {
9409        ctxSource = adsCtx2.getDirContext();
9410        ctxDestination = adsCtx1.getDirContext();
9411      }
9412
9413      String hostPortSource = getHostPort(ctxSource);
9414      String hostPortDestination = getHostPort(ctxDestination);
9415      if (isInteractive())
9416      {
9417        LocalizableMessage msg = INFO_REPLICATION_MERGING_REGISTRIES_CONFIRMATION.get(hostPortSource,
9418            hostPortDestination, hostPortSource, hostPortDestination);
9419        if (!askConfirmation(msg, true))
9420        {
9421          throw new ReplicationCliException(ERR_REPLICATION_USER_CANCELLED.get(), USER_CANCELLED, null);
9422        }
9423      }
9424      else
9425      {
9426        LocalizableMessage msg = INFO_REPLICATION_MERGING_REGISTRIES_DESCRIPTION.get(hostPortSource,
9427            hostPortDestination, hostPortSource, hostPortDestination);
9428        println(msg);
9429        println();
9430      }
9431
9432      print(INFO_REPLICATION_MERGING_REGISTRIES_PROGRESS.get());
9433      pointAdder.start();
9434
9435      checkCanMergeReplicationTopologies(adsCtx1, cache1);
9436      checkCanMergeReplicationTopologies(adsCtx2, cache2);
9437
9438      Set<LocalizableMessage> commonRepServerIDErrors = new HashSet<>();
9439      for (ServerDescriptor server1 : cache1.getServers())
9440      {
9441        if (findSameReplicationServer(server1, cache2.getServers(), commonRepServerIDErrors))
9442        {
9443          break;
9444        }
9445      }
9446      Set<LocalizableMessage> commonDomainIDErrors = new HashSet<>();
9447      for (SuffixDescriptor suffix1 : cache1.getSuffixes())
9448      {
9449        for (ReplicaDescriptor replica1 : suffix1.getReplicas())
9450        {
9451          if (replica1.isReplicated())
9452          {
9453            for (SuffixDescriptor suffix2 : cache2.getSuffixes())
9454            {
9455              if (findReplicaInSuffix2(replica1, suffix2, suffix1.getDN(), commonDomainIDErrors))
9456              {
9457                break;
9458              }
9459            }
9460          }
9461        }
9462      }
9463      if (!commonRepServerIDErrors.isEmpty() || !commonDomainIDErrors.isEmpty())
9464      {
9465        LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
9466        if (!commonRepServerIDErrors.isEmpty())
9467        {
9468          mb.append(ERR_REPLICATION_ENABLE_COMMON_REPLICATION_SERVER_ID.get(
9469              getMessageFromCollection(commonRepServerIDErrors, Constants.LINE_SEPARATOR)));
9470        }
9471        if (!commonDomainIDErrors.isEmpty())
9472        {
9473          if (mb.length() > 0)
9474          {
9475            mb.append(Constants.LINE_SEPARATOR);
9476          }
9477          mb.append(ERR_REPLICATION_ENABLE_COMMON_DOMAIN_ID.get(
9478              getMessageFromCollection(commonDomainIDErrors, Constants.LINE_SEPARATOR)));
9479        }
9480        throw new ReplicationCliException(mb.toMessage(),
9481            REPLICATION_ADS_MERGE_NOT_SUPPORTED, null);
9482      }
9483
9484      ADSContext adsCtxSource;
9485      ADSContext adsCtxDestination;
9486      TopologyCache cacheDestination;
9487      if (nRepServers1 >= nRepServers2)
9488      {
9489        adsCtxSource = adsCtx1;
9490        adsCtxDestination = adsCtx2;
9491        cacheDestination = cache2;
9492      }
9493      else
9494      {
9495        adsCtxSource = adsCtx2;
9496        adsCtxDestination = adsCtx1;
9497        cacheDestination = cache1;
9498      }
9499
9500      try
9501      {
9502        adsCtxSource.mergeWithRegistry(adsCtxDestination);
9503      }
9504      catch (ADSContextException adce)
9505      {
9506        logger.error(LocalizableMessage.raw("Error merging registry of "+
9507            getHostPort(adsCtxSource.getDirContext())+
9508            " with registry of "+
9509            getHostPort(adsCtxDestination.getDirContext())+" "+
9510            adce, adce));
9511        if (adce.getError() == ADSContextException.ErrorType.ERROR_MERGING)
9512        {
9513          throw new ReplicationCliException(adce.getMessageObject(),
9514          REPLICATION_ADS_MERGE_NOT_SUPPORTED, adce);
9515        }
9516        else
9517        {
9518          throw new ReplicationCliException(
9519              ERR_REPLICATION_UPDATING_ADS.get(adce.getMessageObject()),
9520              ERROR_UPDATING_ADS, adce);
9521        }
9522      }
9523
9524      try
9525      {
9526        for (ServerDescriptor server : cacheDestination.getServers())
9527        {
9528          if (server.isReplicationServer())
9529          {
9530            logger.info(LocalizableMessage.raw("Seeding to replication server on "+
9531                server.getHostPort(true)+" with certificates of "+
9532                getHostPort(adsCtxSource.getDirContext())));
9533            InitialLdapContext ctx = null;
9534            try
9535            {
9536              ctx = getDirContextForServer(cacheDestination, server);
9537              ServerDescriptor.seedAdsTrustStore(ctx,
9538                  adsCtxSource.getTrustedCertificates());
9539            }
9540            finally
9541            {
9542              close(ctx);
9543            }
9544          }
9545        }
9546      }
9547      catch (Throwable t)
9548      {
9549        logger.error(LocalizableMessage.raw("Error seeding truststore: "+t, t));
9550        LocalizableMessage msg = ERR_REPLICATION_ENABLE_SEEDING_TRUSTSTORE.get(getHostPort(adsCtx2.getDirContext()),
9551            getHostPort(adsCtx1.getDirContext()), toString(t));
9552        throw new ReplicationCliException(msg, ERROR_SEEDING_TRUSTORE, t);
9553      }
9554      pointAdder.stop();
9555      print(formatter.getSpace());
9556      print(formatter.getFormattedDone());
9557      println();
9558
9559      return adsCtxSource == adsCtx1;
9560    }
9561    finally
9562    {
9563      pointAdder.stop();
9564    }
9565  }
9566
9567  private int countReplicationServers(TopologyCache cache)
9568  {
9569    int nbRepServers = 0;
9570    for (ServerDescriptor server : cache.getServers())
9571    {
9572      if (server.isReplicationServer())
9573      {
9574        nbRepServers++;
9575      }
9576    }
9577    return nbRepServers;
9578  }
9579
9580  private void checkCanMergeReplicationTopologies(ADSContext adsCtx, TopologyCache cache)
9581      throws ReplicationCliException
9582  {
9583    Set<LocalizableMessage> cacheErrors = cache.getErrorMessages();
9584    if (!cacheErrors.isEmpty())
9585    {
9586      LocalizableMessage msg = getMessageFromCollection(cacheErrors, Constants.LINE_SEPARATOR);
9587      throw new ReplicationCliException(
9588          ERR_REPLICATION_CANNOT_MERGE_WITH_ERRORS.get(getHostPort(adsCtx.getDirContext()), msg),
9589          ERROR_READING_ADS, null);
9590    }
9591  }
9592
9593  private boolean findSameReplicationServer(ServerDescriptor serverToFind, Set<ServerDescriptor> servers,
9594      Set<LocalizableMessage> commonRepServerIDErrors)
9595  {
9596    if (!serverToFind.isReplicationServer())
9597    {
9598      return false;
9599    }
9600
9601    int replicationID1 = serverToFind.getReplicationServerId();
9602    String replServerHostPort1 = serverToFind.getReplicationServerHostPort();
9603    for (ServerDescriptor server2 : servers)
9604    {
9605      if (server2.isReplicationServer() && server2.getReplicationServerId() == replicationID1
9606          && !server2.getReplicationServerHostPort().equalsIgnoreCase(replServerHostPort1))
9607      {
9608        commonRepServerIDErrors.add(ERR_REPLICATION_ENABLE_COMMON_REPLICATION_SERVER_ID_ARG.get(
9609            serverToFind.getHostPort(true), server2.getHostPort(true), replicationID1));
9610        return true;
9611      }
9612    }
9613    return false;
9614  }
9615
9616  private boolean findReplicaInSuffix2(ReplicaDescriptor replica1, SuffixDescriptor suffix2, String suffix1DN,
9617      Set<LocalizableMessage> commonDomainIDErrors)
9618  {
9619    if (!areDnsEqual(suffix2.getDN(), replica1.getSuffix().getDN()))
9620    {
9621      // Conflicting domain names must apply to same suffix.
9622      return false;
9623    }
9624
9625    int domain1Id = replica1.getReplicationId();
9626    for (ReplicaDescriptor replica2 : suffix2.getReplicas())
9627    {
9628      if (replica2.isReplicated()
9629          && domain1Id == replica2.getReplicationId())
9630      {
9631        commonDomainIDErrors.add(
9632            ERR_REPLICATION_ENABLE_COMMON_DOMAIN_ID_ARG.get(replica1.getServer().getHostPort(true), suffix1DN,
9633                replica2.getServer().getHostPort(true), suffix2.getDN(), domain1Id));
9634        return true;
9635      }
9636    }
9637    return false;
9638  }
9639
9640  private String toString(Throwable t)
9641  {
9642    return (t instanceof OpenDsException) ?
9643        ((OpenDsException) t).getMessageObject().toString() : t.toString();
9644  }
9645
9646  private TopologyCache createTopologyCache(ADSContext adsCtx, Set<PreferredConnection> cnx)
9647      throws ReplicationCliException
9648  {
9649    TopologyCache cache = new TopologyCache(adsCtx, getTrustManager(sourceServerCI), getConnectTimeout());
9650    cache.setPreferredConnections(cnx);
9651    cache.getFilter().setSearchBaseDNInformation(false);
9652    try
9653    {
9654      cache.reloadTopology();
9655      return cache;
9656    }
9657    catch (TopologyCacheException te)
9658    {
9659      logger.error(LocalizableMessage.raw(
9660          "Error reading topology cache of " + getHostPort(adsCtx.getDirContext()) + " " + te, te));
9661      throw new ReplicationCliException(ERR_REPLICATION_READING_ADS.get(te.getMessageObject()), ERROR_UPDATING_ADS, te);
9662    }
9663  }
9664
9665  private InitialLdapContext getDirContextForServer(TopologyCache cache, ServerDescriptor server)
9666      throws NamingException
9667  {
9668    String dn = getBindDN(cache.getAdsContext().getDirContext());
9669    String pwd = getBindPassword(cache.getAdsContext().getDirContext());
9670    TopologyCacheFilter filter = new TopologyCacheFilter();
9671    filter.setSearchMonitoringInformation(false);
9672    filter.setSearchBaseDNInformation(false);
9673    ServerLoader loader = new ServerLoader(server.getAdsProperties(),
9674        dn, pwd, getTrustManager(sourceServerCI), getConnectTimeout(),
9675        cache.getPreferredConnections(), filter);
9676    return loader.createContext();
9677  }
9678
9679  /**
9680   * Returns <CODE>true</CODE> if the provided baseDN is replicated in the
9681   * provided server, <CODE>false</CODE> otherwise.
9682   * @param server the server.
9683   * @param baseDN the base DN.
9684   * @return <CODE>true</CODE> if the provided baseDN is replicated in the
9685   * provided server, <CODE>false</CODE> otherwise.
9686   */
9687  private boolean isBaseDNReplicated(ServerDescriptor server, String baseDN)
9688  {
9689    return findReplicated(server.getReplicas(), baseDN) != null;
9690  }
9691
9692  /**
9693   * Returns <CODE>true</CODE> if the provided baseDN is replicated between
9694   * both servers, <CODE>false</CODE> otherwise.
9695   * @param server1 the first server.
9696   * @param server2 the second server.
9697   * @param baseDN the base DN.
9698   * @return <CODE>true</CODE> if the provided baseDN is replicated between
9699   * both servers, <CODE>false</CODE> otherwise.
9700   */
9701  private boolean isBaseDNReplicated(ServerDescriptor server1,
9702      ServerDescriptor server2, String baseDN)
9703  {
9704    final ReplicaDescriptor replica1 = findReplicated(server1.getReplicas(), baseDN);
9705    final ReplicaDescriptor replica2 = findReplicated(server2.getReplicas(), baseDN);
9706    if (replica1 != null && replica2 != null)
9707    {
9708      Set<String> replServers1 = replica1.getSuffix().getReplicationServers();
9709      Set<String> replServers2 = replica1.getSuffix().getReplicationServers();
9710      for (String replServer1 : replServers1)
9711      {
9712        if (containsIgnoreCase(replServers2, replServer1))
9713        {
9714          // it is replicated in both
9715          return true;
9716        }
9717      }
9718    }
9719    return false;
9720  }
9721
9722  private ReplicaDescriptor findReplicated(Set<ReplicaDescriptor> replicas, String baseDN)
9723  {
9724    for (ReplicaDescriptor replica : replicas)
9725    {
9726      if (areDnsEqual(replica.getSuffix().getDN(), baseDN))
9727      {
9728        return replica;
9729      }
9730    }
9731    return null;
9732  }
9733
9734  private boolean displayLogFileAtEnd(String subCommand)
9735  {
9736    final List<String> subCommands = Arrays.asList(ENABLE_REPLICATION_SUBCMD_NAME, DISABLE_REPLICATION_SUBCMD_NAME,
9737        INITIALIZE_ALL_REPLICATION_SUBCMD_NAME, INITIALIZE_REPLICATION_SUBCMD_NAME, RESET_CHANGE_NUMBER_SUBCMD_NAME);
9738    return subCommands.contains(subCommand);
9739  }
9740
9741  /**
9742   * Returns the timeout to be used to connect in milliseconds.  The method
9743   * must be called after parsing the arguments.
9744   * @return the timeout to be used to connect in milliseconds.  Returns
9745   * {@code 0} if there is no timeout.
9746   */
9747  private int getConnectTimeout()
9748  {
9749    return argParser.getConnectTimeout();
9750  }
9751
9752  private String binDir;
9753
9754  /**
9755   * Returns the binary/script directory.
9756   * @return the binary/script directory.
9757   */
9758  private String getBinaryDir()
9759  {
9760    if (binDir == null)
9761    {
9762      File f = Installation.getLocal().getBinariesDirectory();
9763      try
9764      {
9765        binDir = f.getCanonicalPath();
9766      }
9767      catch (Throwable t)
9768      {
9769        binDir = f.getAbsolutePath();
9770      }
9771      if (binDir.lastIndexOf(File.separatorChar) != binDir.length() - 1)
9772      {
9773        binDir += File.separatorChar;
9774      }
9775    }
9776    return binDir;
9777  }
9778
9779  /**
9780   * Returns the full path of the command-line for a given script name.
9781   * @param scriptBasicName the script basic name (with no extension).
9782   * @return the full path of the command-line for a given script name.
9783   */
9784  private String getCommandLinePath(String scriptBasicName)
9785  {
9786    if (isWindows())
9787    {
9788      return getBinaryDir() + scriptBasicName + ".bat";
9789    }
9790    return getBinaryDir() + scriptBasicName;
9791  }
9792}
9793
9794/** Class used to compare replication servers. */
9795class ReplicationServerComparator implements Comparator<ServerDescriptor>
9796{
9797  /** {@inheritDoc} */
9798  @Override
9799  public int compare(ServerDescriptor s1, ServerDescriptor s2)
9800  {
9801    int compare = s1.getHostName().compareTo(s2.getHostName());
9802    if (compare == 0)
9803    {
9804      if (s1.getReplicationServerPort() > s2.getReplicationServerPort())
9805      {
9806        return 1;
9807      }
9808      else if (s1.getReplicationServerPort() < s2.getReplicationServerPort())
9809      {
9810        return -1;
9811      }
9812    }
9813    return compare;
9814  }
9815}
9816
9817/** Class used to compare suffixes. */
9818class SuffixComparator implements Comparator<SuffixDescriptor>
9819{
9820  @Override
9821  public int compare(SuffixDescriptor s1, SuffixDescriptor s2)
9822  {
9823    return s1.getId().compareTo(s2.getId());
9824  }
9825}
9826
9827/** Class used to compare servers. */
9828class ServerComparator implements Comparator<ServerDescriptor>
9829{
9830  @Override
9831  public int compare(ServerDescriptor s1, ServerDescriptor s2)
9832  {
9833    return s1.getId().compareTo(s2.getId());
9834  }
9835}