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