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