/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2015 ForgeRock AS
 */
package org.opends.quicksetup.installer;

import static org.forgerock.util.Utils.*;
import static org.opends.admin.ads.ServerDescriptor.*;
import static org.opends.admin.ads.ServerDescriptor.ServerProperty.*;
import static org.opends.admin.ads.util.ConnectionUtils.*;
import static org.opends.messages.QuickSetupMessages.*;
import static org.opends.quicksetup.Step.*;
import static org.opends.quicksetup.installer.DataReplicationOptions.Type.*;
import static org.opends.quicksetup.installer.InstallProgressStep.*;
import static org.opends.quicksetup.util.Utils.*;

import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.*;

import java.awt.event.WindowEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.naming.NameAlreadyBoundException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NamingSecurityException;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.Rdn;
import javax.swing.JPanel;

import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.ManagedObjectDefinition;
import org.forgerock.opendj.server.config.client.BackendCfgClient;
import org.forgerock.opendj.server.config.server.BackendCfg;
import org.opends.admin.ads.ADSContext;
import org.opends.admin.ads.ADSContextException;
import org.opends.admin.ads.ReplicaDescriptor;
import org.opends.admin.ads.ServerDescriptor;
import org.opends.admin.ads.SuffixDescriptor;
import org.opends.admin.ads.TopologyCache;
import org.opends.admin.ads.TopologyCacheException;
import org.opends.admin.ads.TopologyCacheFilter;
import org.opends.admin.ads.util.ApplicationTrustManager;
import org.opends.admin.ads.util.ConnectionUtils;
import org.opends.admin.ads.util.PreferredConnection;
import org.opends.quicksetup.ApplicationException;
import org.opends.quicksetup.ButtonName;
import org.opends.quicksetup.Constants;
import org.opends.quicksetup.Installation;
import org.opends.quicksetup.JavaArguments;
import org.opends.quicksetup.LicenseFile;
import org.opends.quicksetup.ProgressStep;
import org.opends.quicksetup.QuickSetupLog;
import org.opends.quicksetup.ReturnCode;
import org.opends.quicksetup.SecurityOptions;
import org.opends.quicksetup.Step;
import org.opends.quicksetup.UserData;
import org.opends.quicksetup.UserDataCertificateException;
import org.opends.quicksetup.UserDataConfirmationException;
import org.opends.quicksetup.UserDataException;
import org.opends.quicksetup.WizardStep;
import org.opends.quicksetup.event.ButtonActionListener;
import org.opends.quicksetup.event.ButtonEvent;
import org.opends.quicksetup.installer.ui.DataOptionsPanel;
import org.opends.quicksetup.installer.ui.DataReplicationPanel;
import org.opends.quicksetup.installer.ui.GlobalAdministratorPanel;
import org.opends.quicksetup.installer.ui.InstallLicensePanel;
import org.opends.quicksetup.installer.ui.InstallReviewPanel;
import org.opends.quicksetup.installer.ui.InstallWelcomePanel;
import org.opends.quicksetup.installer.ui.RemoteReplicationPortsPanel;
import org.opends.quicksetup.installer.ui.RuntimeOptionsPanel;
import org.opends.quicksetup.installer.ui.ServerSettingsPanel;
import org.opends.quicksetup.installer.ui.SuffixesToReplicatePanel;
import org.opends.quicksetup.ui.FieldName;
import org.opends.quicksetup.ui.FinishedPanel;
import org.opends.quicksetup.ui.GuiApplication;
import org.opends.quicksetup.ui.ProgressPanel;
import org.opends.quicksetup.ui.QuickSetup;
import org.opends.quicksetup.ui.QuickSetupDialog;
import org.opends.quicksetup.ui.QuickSetupErrorPanel;
import org.opends.quicksetup.ui.QuickSetupStepPanel;
import org.opends.quicksetup.ui.UIFactory;
import org.opends.quicksetup.util.FileManager;
import org.opends.quicksetup.util.IncompatibleVersionException;
import org.opends.quicksetup.util.Utils;
import org.opends.server.tools.BackendTypeHelper;
import org.opends.server.tools.BackendTypeHelper.BackendTypeUIAdapter;
import org.opends.server.util.CertificateManager;
import org.opends.server.util.DynamicConstants;
import org.opends.server.util.SetupUtils;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.Platform.KeyType;

import com.forgerock.opendj.util.OperatingSystem;

/**
 * This is an abstract class that is in charge of actually performing the
 * installation.
 *
 * It just takes a UserData object and based on that installs OpenDJ.
 *
 * When there is an update during the installation it will notify the
 * ProgressUpdateListener objects that have been added to it.  The
 * notification will send a ProgressUpdateEvent.
 *
 * This class is supposed to be fully independent of the graphical layout.
 *
 * Note that we can use freely the class org.opends.server.util.SetupUtils as
 * it is included in quicksetup.jar.
 */
public abstract class Installer extends GuiApplication
{
  /** The minimum integer value that can be used for a port. */
  public static final int MIN_PORT_VALUE = 1;
  /** The maximum integer value that can be used for a port. */
  public static final int MAX_PORT_VALUE = 65535;

  /** The name of the backend created on setup. */
  public static final String ROOT_BACKEND_NAME = "userRoot";

  /** Constants used to do checks. */
  private static final int MIN_DIRECTORY_MANAGER_PWD = 1;

  private static final int MIN_NUMBER_ENTRIES = 1;
  private static final int MAX_NUMBER_ENTRIES = 10000000;

  /**
   * If the user decides to import more than this number of entries, the import
   * process of automatically generated data will be verbose.
   */
  private static final int THRESHOLD_AUTOMATIC_DATA_VERBOSE = 20000;

  /**
   * If the user decides to import a number of entries higher than this
   * threshold, the start process will be verbose.
   */
  private static final int THRESHOLD_VERBOSE_START = 100000;

  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  private TopologyCache lastLoadedCache;

  /** Indicates that we've detected that there is something installed. */
  boolean forceToDisplaySetup;

  /** When true indicates that the user has canceled this operation. */
  protected boolean canceled;

  private boolean javaVersionCheckFailed;

  /** Map containing information about what has been configured remotely. */
  private final Map<ServerDescriptor, ConfiguredReplication> hmConfiguredRemoteReplication = new HashMap<>();

  /** Set of progress steps that have been completed. */
  protected Set<InstallProgressStep> completedProgress = new HashSet<>();

  private final List<WizardStep> lstSteps = new ArrayList<>();

  private final Set<WizardStep> SUBSTEPS = new HashSet<>();
  {
    SUBSTEPS.add(Step.CREATE_GLOBAL_ADMINISTRATOR);
    SUBSTEPS.add(Step.SUFFIXES_OPTIONS);
    SUBSTEPS.add(Step.NEW_SUFFIX_OPTIONS);
    SUBSTEPS.add(Step.REMOTE_REPLICATION_PORTS);
  }

  private final Map<WizardStep, WizardStep> hmPreviousSteps = new HashMap<>();

  private char[] selfSignedCertPw;

  private boolean registeredNewServerOnRemote;
  private boolean createdAdministrator;
  private boolean createdRemoteAds;
  private String lastImportProgress;

  /** A static String that contains the class name of ConfigFileHandler. */
  protected static final String DEFAULT_CONFIG_CLASS_NAME = "org.opends.server.extensions.ConfigFileHandler";

  /** Aliases of self-signed certificates. */
  protected static final String SELF_SIGNED_CERT_ALIASES[] = new String[] {
    SecurityOptions.SELF_SIGNED_CERT_ALIAS,
    SecurityOptions.SELF_SIGNED_EC_CERT_ALIAS };

  /**
   * The threshold in minutes used to know whether we must display a warning
   * informing that there is a server clock difference between two servers whose
   * contents are being replicated.
   */
  public static final int THRESHOLD_CLOCK_DIFFERENCE_WARNING = 5;

  /** Creates a default instance. */
  public Installer()
  {
    addStepsInOrder(lstSteps, LicenseFile.exists());
    try
    {
      if (!QuickSetupLog.isInitialized())
      {
        QuickSetupLog.initLogFileHandler(File.createTempFile(Constants.LOG_FILE_PREFIX, Constants.LOG_FILE_SUFFIX));
      }
    }
    catch (IOException e)
    {
      System.err.println("Failed to initialize log");
    }
  }

  @Override
  public boolean isCancellable()
  {
    return true;
  }

  @Override
  public UserData createUserData()
  {
    UserData ud = new UserData();
    ud.setServerLocation(getDefaultServerLocation());
    initializeUserDataWithUserArguments(ud, getUserArguments());
    return ud;
  }

  private void initializeUserDataWithUserArguments(UserData ud, String[] userArguments)
  {
    for (int i = 0; i < userArguments.length; i++)
    {
      if ("--connectTimeout".equalsIgnoreCase(userArguments[i]))
      {
        if (i < userArguments.length - 1)
        {
          String sTimeout = userArguments[i + 1];
          try
          {
            ud.setConnectTimeout(Integer.valueOf(sTimeout));
          }
          catch (Throwable t)
          {
            logger.warn(LocalizableMessage.raw("Error getting connect timeout: " + t, t));
          }
        }
        break;
      }
    }
  }

  @Override
  public void forceToDisplay()
  {
    forceToDisplaySetup = true;
  }

  @Override
  public boolean canGoBack(WizardStep step)
  {
    return step != WELCOME && step != PROGRESS && step != FINISHED;
  }

  @Override
  public boolean canGoForward(WizardStep step)
  {
    return step != REVIEW && step != PROGRESS && step != FINISHED;
  }

  @Override
  public boolean canFinish(WizardStep step)
  {
    return step == REVIEW;
  }

    @Override
  public boolean isSubStep(WizardStep step)
  {
    return SUBSTEPS.contains(step);
  }

  @Override
  public boolean isVisible(WizardStep step, UserData userData)
  {
    if (step == CREATE_GLOBAL_ADMINISTRATOR)
    {
      return userData.mustCreateAdministrator();
    }
    else if (step == NEW_SUFFIX_OPTIONS)
    {
      SuffixesToReplicateOptions suf = userData.getSuffixesToReplicateOptions();
      return suf != null && suf.getType() != SuffixesToReplicateOptions.Type.REPLICATE_WITH_EXISTING_SUFFIXES;
    }
    else if (step == SUFFIXES_OPTIONS)
    {
      DataReplicationOptions repl = userData.getReplicationOptions();
      return repl != null && repl.getType() != DataReplicationOptions.Type.STANDALONE
          && repl.getType() != DataReplicationOptions.Type.FIRST_IN_TOPOLOGY;
    }
    else if (step == REMOTE_REPLICATION_PORTS)
    {
      return isVisible(SUFFIXES_OPTIONS, userData)
          && !userData.getRemoteWithNoReplicationPort().isEmpty()
          && userData.getSuffixesToReplicateOptions().getType() ==
              SuffixesToReplicateOptions.Type.REPLICATE_WITH_EXISTING_SUFFIXES;
    }
    return true;
  }

  @Override
  public boolean isVisible(WizardStep step, QuickSetup qs)
  {
    return isVisible(step, getUserData());
  }

  @Override
  public boolean finishClicked(final WizardStep cStep, final QuickSetup qs)
  {
    if (cStep != Step.REVIEW)
    {
      throw new IllegalStateException("Cannot click on finish when we are not in the Review window");
    }

    updateUserDataForReviewPanel(qs);
    qs.launch();
    qs.setCurrentStep(Step.PROGRESS);
    // Installer responsible for updating the user data and launching
    return false;
  }

    @Override
  public void nextClicked(WizardStep cStep, QuickSetup qs)
  {
    if (cStep == PROGRESS)
    {
      throw new IllegalStateException("Cannot click on next from progress step");
    }
    else if (cStep == REVIEW)
    {
      throw new IllegalStateException("Cannot click on next from review step");
    }
    else if (cStep == FINISHED)
    {
      throw new IllegalStateException("Cannot click on next from finished step");
    }
  }

  @Override
  public void closeClicked(WizardStep cStep, QuickSetup qs)
  {
    if (cStep == PROGRESS)
    {
      if (isFinished()
          || qs.displayConfirmation(INFO_CONFIRM_CLOSE_INSTALL_MSG.get(), INFO_CONFIRM_CLOSE_INSTALL_TITLE.get()))
      {
        qs.quit();
      }
    }
    else if (cStep == FINISHED)
    {
      qs.quit();
    }
    else
    {
      throw new IllegalStateException("Close only can be clicked on PROGRESS step");
    }
  }

  @Override
  public boolean isFinished()
  {
    return getCurrentProgressStep() == InstallProgressStep.FINISHED_SUCCESSFULLY
        || getCurrentProgressStep() == InstallProgressStep.FINISHED_CANCELED
        || getCurrentProgressStep() == InstallProgressStep.FINISHED_WITH_ERROR;
  }

  @Override
  public void cancel()
  {
    setCurrentProgressStep(InstallProgressStep.WAITING_TO_CANCEL);
    notifyListeners(null);
    this.canceled = true;
  }

  @Override
  public void quitClicked(WizardStep cStep, QuickSetup qs)
  {
    if (cStep == FINISHED)
    {
      qs.quit();
    }
    else if (cStep == PROGRESS)
    {
      throw new IllegalStateException("Cannot click on quit from progress step");
    }
    else if (installStatus.isInstalled())
    {
      qs.quit();
    }
    else if (javaVersionCheckFailed)
    {
      qs.quit();
    }
    else if (qs.displayConfirmation(INFO_CONFIRM_QUIT_INSTALL_MSG.get(), INFO_CONFIRM_QUIT_INSTALL_TITLE.get()))
    {
      qs.quit();
    }
  }

  @Override
  public ButtonName getInitialFocusButtonName()
  {
    if (!installStatus.isInstalled() || forceToDisplaySetup)
    {
      return ButtonName.NEXT;
    }
    else if (installStatus.canOverwriteCurrentInstall())
    {
      return ButtonName.CONTINUE_INSTALL;
    }
    else
    {
      return ButtonName.QUIT;
    }
  }

  @Override
  public JPanel createFramePanel(QuickSetupDialog dlg)
  {
    JPanel p;
    javaVersionCheckFailed = true;
    try
    {
      Utils.checkJavaVersion();
      javaVersionCheckFailed = false;
      if (installStatus.isInstalled() && !forceToDisplaySetup)
      {
        p = dlg.getInstalledPanel();
      }
      else
      {
        p = super.createFramePanel(dlg);
      }
    }
    catch (IncompatibleVersionException ijv)
    {
      LocalizableMessageBuilder sb = new LocalizableMessageBuilder();
      sb.append(Utils.breakHtmlString(Utils.getHtml(ijv.getMessageObject().toString()),
          Constants.MAX_CHARS_PER_LINE_IN_DIALOG));
      QuickSetupErrorPanel errPanel = new QuickSetupErrorPanel(this, sb.toMessage());
      final QuickSetupDialog fDlg = dlg;
      errPanel.addButtonActionListener(new ButtonActionListener()
      {
        /**
         * ButtonActionListener implementation. It assumes that we are called in
         * the event thread.
         *
         * @param ev
         *          the ButtonEvent we receive.
         */
        @Override
        public void buttonActionPerformed(ButtonEvent ev)
        {
          // Simulate a close button event
          fDlg.notifyButtonEvent(ButtonName.QUIT);
        }
      });
      p = errPanel;
    }
    return p;
  }

  @Override
  public Set<? extends WizardStep> getWizardSteps()
  {
    return Collections.unmodifiableSet(new HashSet<WizardStep>(lstSteps));
  }

  @Override
  public QuickSetupStepPanel createWizardStepPanel(WizardStep step)
  {
    if (step instanceof Step)
    {
      switch ((Step) step)
      {
      case WELCOME:
        return new InstallWelcomePanel(this);
      case LICENSE:
        return new InstallLicensePanel(this);
      case SERVER_SETTINGS:
        return new ServerSettingsPanel(this);
      case REPLICATION_OPTIONS:
        return new DataReplicationPanel(this);
      case CREATE_GLOBAL_ADMINISTRATOR:
        return new GlobalAdministratorPanel(this);
      case SUFFIXES_OPTIONS:
        return new SuffixesToReplicatePanel(this);
      case REMOTE_REPLICATION_PORTS:
        return new RemoteReplicationPortsPanel(this);
      case NEW_SUFFIX_OPTIONS:
        return new DataOptionsPanel(this);
      case RUNTIME_OPTIONS:
        return new RuntimeOptionsPanel(this);
      case REVIEW:
        return new InstallReviewPanel(this);
      case PROGRESS:
        return new ProgressPanel(this);
      case FINISHED:
        return new FinishedPanel(this);
      }
    }
    return null;
  }

  @Override
  public void windowClosing(QuickSetupDialog dlg, WindowEvent evt)
  {
    if (installStatus.isInstalled() && forceToDisplaySetup)
    {
      // Simulate a close button event
      dlg.notifyButtonEvent(ButtonName.QUIT);
    }
    else if (dlg.getDisplayedStep() == Step.PROGRESS)
    {
      // Simulate a close button event
      dlg.notifyButtonEvent(ButtonName.CLOSE);
    }
    else
    {
      // Simulate a quit button event
      dlg.notifyButtonEvent(ButtonName.QUIT);
    }
  }

  @Override
  public LocalizableMessage getCloseButtonToolTip()
  {
    return INFO_CLOSE_BUTTON_INSTALL_TOOLTIP.get();
  }

  @Override
  public LocalizableMessage getQuitButtonToolTip()
  {
    return INFO_QUIT_BUTTON_INSTALL_TOOLTIP.get();
  }

  @Override
  public LocalizableMessage getFinishButtonToolTip()
  {
    return INFO_FINISH_BUTTON_INSTALL_TOOLTIP.get();
  }

  @Override
  public int getExtraDialogHeight()
  {
    return UIFactory.EXTRA_DIALOG_HEIGHT;
  }

  @Override
  public void previousClicked(WizardStep cStep, QuickSetup qs)
  {
    if (cStep == WELCOME)
    {
      throw new IllegalStateException("Cannot click on previous from progress step");
    }
    else if (cStep == PROGRESS)
    {
      throw new IllegalStateException("Cannot click on previous from progress step");
    }
    else if (cStep == FINISHED)
    {
      throw new IllegalStateException("Cannot click on previous from finished step");
    }
  }

  @Override
  public LocalizableMessage getFrameTitle()
  {
    return Utils.getCustomizedObject("INFO_FRAME_INSTALL_TITLE", INFO_FRAME_INSTALL_TITLE
        .get(DynamicConstants.PRODUCT_NAME), LocalizableMessage.class);
  }

  /** Indicates the current progress step. */
  private InstallProgressStep currentProgressStep = InstallProgressStep.NOT_STARTED;

  @Override
  public void setWizardDialogState(QuickSetupDialog dlg, UserData userData, WizardStep step)
  {
    if (!installStatus.isInstalled() || forceToDisplaySetup)
    {
      // Set the default button for the frame
      if (step == REVIEW)
      {
        dlg.setFocusOnButton(ButtonName.FINISH);
        dlg.setDefaultButton(ButtonName.FINISH);
      }
      else if (step == WELCOME)
      {
        dlg.setDefaultButton(ButtonName.NEXT);
        dlg.setFocusOnButton(ButtonName.NEXT);
      }
      else if (step == PROGRESS || step == FINISHED)
      {
        dlg.setDefaultButton(ButtonName.CLOSE);
        dlg.setFocusOnButton(ButtonName.CLOSE);
      }
      else
      {
        dlg.setDefaultButton(ButtonName.NEXT);
      }
    }
  }

  @Override
  public ProgressStep getCurrentProgressStep()
  {
    return currentProgressStep;
  }

  @Override
  public WizardStep getFirstWizardStep()
  {
    return WELCOME;
  }

  @Override
  public WizardStep getNextWizardStep(WizardStep step)
  {
    WizardStep next = getNextWizardStep0(step);
    if (next != null)
    {
      hmPreviousSteps.put(next, step);
    }
    return next;
  }

  private WizardStep getNextWizardStep0(WizardStep step)
  {
    if (step == Step.REPLICATION_OPTIONS)
    {
      if (getUserData().mustCreateAdministrator())
      {
        return Step.CREATE_GLOBAL_ADMINISTRATOR;
      }

      switch (getUserData().getReplicationOptions().getType())
      {
      case FIRST_IN_TOPOLOGY:
      case STANDALONE:
        return Step.NEW_SUFFIX_OPTIONS;
      default:
        return Step.SUFFIXES_OPTIONS;
      }
    }
    else if (step == Step.SUFFIXES_OPTIONS)
    {
      switch (getUserData().getSuffixesToReplicateOptions().getType())
      {
      case REPLICATE_WITH_EXISTING_SUFFIXES:
        if (!getUserData().getRemoteWithNoReplicationPort().isEmpty())
        {
          return Step.REMOTE_REPLICATION_PORTS;
        }
        return Step.RUNTIME_OPTIONS;
      default:
        return Step.NEW_SUFFIX_OPTIONS;
      }
    }
    else if (step == Step.REMOTE_REPLICATION_PORTS)
    {
      return Step.RUNTIME_OPTIONS;
    }
    else
    {
      int i = lstSteps.indexOf(step);
      if (i != -1 && i + 1 < lstSteps.size())
      {
        return lstSteps.get(i + 1);
      }
    }
    return null;
  }

  @Override
  public LinkedHashSet<WizardStep> getOrderedSteps()
  {
    LinkedHashSet<WizardStep> orderedSteps = new LinkedHashSet<>();
    addStepsInOrder(orderedSteps, lstSteps.contains(LICENSE));
    return orderedSteps;
  }

  private void addStepsInOrder(Collection<WizardStep> steps, boolean licenseExists)
  {
    steps.add(WELCOME);
    if (licenseExists)
    {
      steps.add(LICENSE);
    }
    steps.add(SERVER_SETTINGS);
    steps.add(REPLICATION_OPTIONS);
    steps.add(CREATE_GLOBAL_ADMINISTRATOR);
    steps.add(SUFFIXES_OPTIONS);
    steps.add(REMOTE_REPLICATION_PORTS);
    steps.add(NEW_SUFFIX_OPTIONS);
    steps.add(RUNTIME_OPTIONS);
    steps.add(REVIEW);
    steps.add(PROGRESS);
    steps.add(FINISHED);
  }

  @Override
  public WizardStep getPreviousWizardStep(WizardStep step)
  {
    //  Try with the steps calculated in method getNextWizardStep.
    WizardStep prev = hmPreviousSteps.get(step);

    if (prev == null)
    {
      int i = lstSteps.indexOf(step);
      if (i != -1 && i > 0)
      {
        prev = lstSteps.get(i - 1);
      }
    }
    return prev;
  }

  @Override
  public WizardStep getFinishedStep()
  {
    return Step.FINISHED;
  }

  /**
   * Uninstalls installed services. This is to be used when the user has elected
   * to cancel an installation.
   */
  protected void uninstallServices()
  {
    if (completedProgress.contains(InstallProgressStep.ENABLING_WINDOWS_SERVICE))
    {
      try
      {
        new InstallerHelper().disableWindowsService();
      }
      catch (ApplicationException ae)
      {
        logger.info(LocalizableMessage.raw("Error disabling Windows service", ae));
      }
    }

    unconfigureRemote();
  }

  /**
   * Creates the template files based in the contents of the UserData object.
   * These templates files are used to generate automatically data. To generate
   * the template file the code will basically take into account the value of
   * the base dn and the number of entries to be generated.
   *
   * @return a list of file objects pointing to the create template files.
   * @throws ApplicationException
   *           if an error occurs.
   */
  private File createTemplateFile() throws ApplicationException
  {
    try
    {
      Set<String> baseDNs = new LinkedHashSet<>(getUserData().getNewSuffixOptions().getBaseDns());
      int nEntries = getUserData().getNewSuffixOptions().getNumberEntries();
      return SetupUtils.createTemplateFile(baseDNs, nEntries);
    }
    catch (IOException ioe)
    {
      LocalizableMessage failedMsg = getThrowableMsg(INFO_ERROR_CREATING_TEMP_FILE.get(), ioe);
      throw new ApplicationException(ReturnCode.FILE_SYSTEM_ACCESS_ERROR, failedMsg, ioe);
    }
  }

  /**
   * This methods configures the server based on the contents of the UserData
   * object provided in the constructor.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  protected void configureServer() throws ApplicationException
  {
    notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CONFIGURING.get()));
    copyTemplateInstance();
    writeOpenDSJavaHome();
    writeHostName();
    checkAbort();

    List<String> argList = new ArrayList<>();
    argList.add("-C");
    argList.add(getConfigurationClassName());

    argList.add("-c");
    argList.add(getConfigurationFile());
    argList.add("-h");
    argList.add(getUserData().getHostName());
    argList.add("-p");
    argList.add(String.valueOf(getUserData().getServerPort()));
    argList.add("--adminConnectorPort");
    argList.add(String.valueOf(getUserData().getAdminConnectorPort()));

    final SecurityOptions sec = getUserData().getSecurityOptions();
    // TODO: even if the user does not configure SSL maybe we should choose
    // a secure port that is not being used and that we can actually use.
    if (sec.getEnableSSL())
    {
      argList.add("-P");
      argList.add(String.valueOf(sec.getSslPort()));
    }

    if (sec.getEnableStartTLS())
    {
      argList.add("-q");
    }

    addCertificateArguments(sec, argList);
    // For the moment do not enable JMX
    if (getUserData().getServerJMXPort() > 0)
    {
      argList.add("-x");
      argList.add(String.valueOf(getUserData().getServerJMXPort()));
    }

    argList.add("-D");
    argList.add(getUserData().getDirectoryManagerDn());

    argList.add("-w");
    argList.add(getUserData().getDirectoryManagerPwd());

    final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backendType =
        getUserData().getBackendType();
    if (backendType != null)
    {
      argList.add("--" + OPTION_LONG_BACKEND_TYPE);
      argList.add(BackendTypeHelper.filterSchemaBackendName(backendType.getName()));
    }

    if (createNotReplicatedSuffix())
    {
      for (String baseDn : getUserData().getNewSuffixOptions().getBaseDns())
      {
        argList.add("-b");
        argList.add(baseDn);
      }
    }

    argList.add("-R");
    argList.add(getInstallation().getRootDirectory().getAbsolutePath());

    final String[] args = new String[argList.size()];
    argList.toArray(args);
    StringBuilder cmd = new StringBuilder();
    boolean nextPassword = false;
    for (String s : argList)
    {
      if (cmd.length() > 0)
      {
        cmd.append(" ");
      }
      if (nextPassword)
      {
        cmd.append("{rootUserPassword}");
      }
      else
      {
        cmd.append(s);
      }
      nextPassword = "-w".equals(s);
    }
    logger.info(LocalizableMessage.raw("configure DS cmd: " + cmd));
    final InstallerHelper helper = new InstallerHelper();
    setNotifyListeners(false);
    InvokeThread thread = new InvokeThread()
    {
      @Override
      public void run()
      {
        try
        {
          if (helper.invokeConfigureServer(args) != 0)
          {
            ae = new ApplicationException(ReturnCode.CONFIGURATION_ERROR, INFO_ERROR_CONFIGURING.get(), null);
          }
          else if (getUserData().getNewSuffixOptions().getBaseDns().isEmpty())
          {
            helper.deleteBackend(ROOT_BACKEND_NAME);
          }
        }
        catch (ApplicationException aex)
        {
          ae = aex;
        }
        catch (Throwable t)
        {
          ae = new ApplicationException(
              ReturnCode.CONFIGURATION_ERROR, getThrowableMsg(INFO_ERROR_CONFIGURING.get(), t), t);
        }
        finally
        {
          setNotifyListeners(true);
        }
        isOver = true;
      }

      @Override
      public void abort()
      {
        // TODO: implement the abort
      }
    };
    invokeLongOperation(thread);
    notifyListeners(getFormattedDoneWithLineBreak());
    checkAbort();
    configureCertificate(sec);
  }

  private void configureCertificate(SecurityOptions sec) throws ApplicationException
  {
    try
    {
      SecurityOptions.CertificateType certType = sec.getCertificateType();
      if (certType != SecurityOptions.CertificateType.NO_CERTIFICATE)
      {
        notifyListeners(getFormattedWithPoints(INFO_PROGRESS_UPDATING_CERTIFICATES.get()));
      }

      switch (certType)
      {
      case NO_CERTIFICATE:
        // Nothing to do
        break;
      case SELF_SIGNED_CERTIFICATE:
        String pwd = getSelfSignedCertificatePwd();
        final CertificateManager certManager =
            new CertificateManager(getSelfSignedKeystorePath(), CertificateManager.KEY_STORE_TYPE_JKS, pwd);
        for (String alias : sec.getAliasesToUse())
        {
          final KeyType keyType = KeyType.getTypeOrDefault(alias);
          certManager.generateSelfSignedCertificate(keyType, alias, getSelfSignedCertificateSubjectDN(keyType),
              getSelfSignedCertificateValidity());
          SetupUtils.exportCertificate(certManager, alias, getTemporaryCertificatePath());
          configureTrustStore(CertificateManager.KEY_STORE_TYPE_JKS, alias, pwd);
        }
        break;

      case JKS:
        configureKeyAndTrustStore(sec.getKeystorePath(), CertificateManager.KEY_STORE_TYPE_JKS,
            CertificateManager.KEY_STORE_TYPE_JKS, sec);
        break;

      case JCEKS:
        configureKeyAndTrustStore(sec.getKeystorePath(), CertificateManager.KEY_STORE_TYPE_JCEKS,
            CertificateManager.KEY_STORE_TYPE_JCEKS, sec);
        break;

      case PKCS12:
        configureKeyAndTrustStore(sec.getKeystorePath(), CertificateManager.KEY_STORE_TYPE_PKCS12,
            CertificateManager.KEY_STORE_TYPE_JKS, sec);
        break;

      case PKCS11:
        configureKeyAndTrustStore(CertificateManager.KEY_STORE_PATH_PKCS11, CertificateManager.KEY_STORE_TYPE_PKCS11,
            CertificateManager.KEY_STORE_TYPE_JKS, sec);
        break;

      default:
        throw new IllegalStateException("Unknown certificate type: " + certType);
      }

      if (certType != SecurityOptions.CertificateType.NO_CERTIFICATE)
      {
        notifyListeners(getFormattedDoneWithLineBreak());
      }
    }
    catch (Throwable t)
    {
      logger.error(LocalizableMessage.raw("Error configuring certificate: " + t, t));
      throw new ApplicationException(
          ReturnCode.CONFIGURATION_ERROR, getThrowableMsg(INFO_ERROR_CONFIGURING_CERTIFICATE.get(), t), t);
    }
  }

  private void configureKeyAndTrustStore(final String keyStorePath, final String keyStoreType,
      final String trustStoreType, final SecurityOptions sec) throws Exception
  {
    final String keystorePassword = sec.getKeystorePassword();
    CertificateManager certManager = new CertificateManager(keyStorePath, keyStoreType, keystorePassword);
    for (String keyStoreAlias : sec.getAliasesToUse())
    {
      SetupUtils.exportCertificate(certManager, keyStoreAlias, getTemporaryCertificatePath());
      configureTrustStore(trustStoreType, keyStoreAlias, keystorePassword);
    }
  }

  private void configureTrustStore(final String type, final String keyStoreAlias, final String password)
      throws Exception
  {
    final String alias = keyStoreAlias != null ? keyStoreAlias : SELF_SIGNED_CERT_ALIASES[0];
    final CertificateManager trustMgr = new CertificateManager(getTrustManagerPath(), type, password);
    trustMgr.addCertificate(alias, new File(getTemporaryCertificatePath()));

    createProtectedFile(getKeystorePinPath(), password);
    final File f = new File(getTemporaryCertificatePath());
    f.delete();
  }

  private void addCertificateArguments(SecurityOptions sec, List<String> argList)
  {
    final Collection<String> aliasInKeyStore = sec.getAliasesToUse();

    switch (sec.getCertificateType())
    {
    case SELF_SIGNED_CERTIFICATE:
      argList.add("-k");
      argList.add("cn=JKS,cn=Key Manager Providers,cn=config");
      argList.add("-t");
      argList.add("cn=JKS,cn=Trust Manager Providers,cn=config");
      break;
    case JKS:
      addCertificateArguments(argList, sec, aliasInKeyStore, "cn=JKS,cn=Key Manager Providers,cn=config",
          "cn=JKS,cn=Trust Manager Providers,cn=config");
      break;
    case JCEKS:
      addCertificateArguments(argList, sec, aliasInKeyStore, "cn=JCEKS,cn=Key Manager Providers,cn=config",
          "cn=JCEKS,cn=Trust Manager Providers,cn=config");
      break;
    case PKCS12:
      addCertificateArguments(argList, sec, aliasInKeyStore, "cn=PKCS12,cn=Key Manager Providers,cn=config",
          "cn=JKS,cn=Trust Manager Providers,cn=config");
      break;
    case PKCS11:
      addCertificateArguments(argList, null, aliasInKeyStore, "cn=PKCS11,cn=Key Manager Providers,cn=config",
          "cn=JKS,cn=Trust Manager Providers,cn=config");
      break;
    case NO_CERTIFICATE:
      // Nothing to do.
      break;
    default:
      throw new IllegalStateException("Unknown certificate type: " + sec.getCertificateType());
    }
  }

  private static void addCertificateArguments(List<String> argList, SecurityOptions sec,
      Collection<String> aliasesInKeyStore, String keyStoreDN, String trustStoreDN)
  {
    argList.add("-k");
    argList.add(keyStoreDN);
    argList.add("-t");
    argList.add(trustStoreDN);
    if (sec != null)
    {
      argList.add("-m");
      argList.add(sec.getKeystorePath());
    }
    for(String alias : aliasesInKeyStore)
    {
      argList.add("-a");
      argList.add(alias);
    }
  }

  /**
   * This methods creates the base entry for the suffix based on the contents of
   * the UserData object provided in the constructor.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  private void createBaseEntry() throws ApplicationException
  {
    LinkedList<String> baseDns = getUserData().getNewSuffixOptions().getBaseDns();
    if (baseDns.size() == 1)
    {
      notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CREATING_BASE_ENTRY.get(baseDns.getFirst())));
    }
    else
    {
      notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CREATING_BASE_ENTRIES.get()));
    }

    final InstallerHelper helper = new InstallerHelper();

    LinkedList<File> ldifFiles = new LinkedList<>();

    for (String baseDn : baseDns)
    {
      ldifFiles.add(helper.createBaseEntryTempFile(baseDn));
    }
    checkAbort();

    List<String> argList = new ArrayList<>();
    argList.add("-n");
    argList.add(ROOT_BACKEND_NAME);
    for (File f : ldifFiles)
    {
      argList.add("-l");
      argList.add(f.getAbsolutePath());
    }
    argList.add("-F");
    argList.add("-Q");
    argList.add("--noPropertiesFile");

    final String[] args = new String[argList.size()];
    argList.toArray(args);

    setNotifyListeners(false);

    InvokeThread thread = new InvokeThread()
    {
      @Override
      public void run()
      {
        try
        {
          int result = helper.invokeImportLDIF(Installer.this, args);

          if (result != 0)
          {
            ae = new ApplicationException(ReturnCode.IMPORT_ERROR, INFO_ERROR_CREATING_BASE_ENTRY.get(), null);
          }
        }
        catch (Throwable t)
        {
          ae =
              new ApplicationException(ReturnCode.IMPORT_ERROR,
                  getThrowableMsg(INFO_ERROR_CREATING_BASE_ENTRY.get(), t), t);
        }
        finally
        {
          setNotifyListeners(true);
        }
        isOver = true;
      }

      @Override
      public void abort()
      {
        // TODO: implement the abort
      }
    };
    invokeLongOperation(thread);
    notifyListeners(getFormattedDoneWithLineBreak());
  }

  /**
   * This methods imports the contents of an LDIF file based on the contents of
   * the UserData object provided in the constructor.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  private void importLDIF() throws ApplicationException
  {
    LinkedList<String> ldifPaths = getUserData().getNewSuffixOptions().getLDIFPaths();
    LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
    if (ldifPaths.size() > 1)
    {
      if (isVerbose())
      {
        mb.append(getFormattedProgress(INFO_PROGRESS_IMPORTING_LDIFS.get(joinAsString(", ", ldifPaths))));
        mb.append(getLineBreak());
      }
      else
      {
        mb.append(getFormattedProgress(INFO_PROGRESS_IMPORTING_LDIFS_NON_VERBOSE.get(joinAsString(", ", ldifPaths))));
      }
    }
    else if (isVerbose())
    {
      mb.append(getFormattedProgress(INFO_PROGRESS_IMPORTING_LDIF.get(ldifPaths.getFirst())));
      mb.append(getLineBreak());
    }
    else
    {
      mb.append(getFormattedProgress(INFO_PROGRESS_IMPORTING_LDIF_NON_VERBOSE.get(ldifPaths.getFirst())));
    }
    notifyListeners(mb.toMessage());

    final PointAdder pointAdder = new PointAdder();

    if (!isVerbose())
    {
      setNotifyListeners(false);
      pointAdder.start();
    }

    List<String> argList = new ArrayList<>();
    argList.add("-n");
    argList.add(ROOT_BACKEND_NAME);
    for (String ldifPath : ldifPaths)
    {
      argList.add("-l");
      argList.add(ldifPath);
    }
    argList.add("-F");
    String rejectedFile = getUserData().getNewSuffixOptions().getRejectedFile();
    if (rejectedFile != null)
    {
      argList.add("-R");
      argList.add(rejectedFile);
    }
    String skippedFile = getUserData().getNewSuffixOptions().getSkippedFile();
    if (skippedFile != null)
    {
      argList.add("--skipFile");
      argList.add(skippedFile);
    }

    argList.add("--noPropertiesFile");

    final String[] args = new String[argList.size()];
    argList.toArray(args);

    InvokeThread thread = new InvokeThread()
    {
      @Override
      public void run()
      {
        try
        {
          InstallerHelper helper = new InstallerHelper();
          int result = helper.invokeImportLDIF(Installer.this, args);

          if (result != 0)
          {
            ae = new ApplicationException(ReturnCode.IMPORT_ERROR, INFO_ERROR_IMPORTING_LDIF.get(), null);
          }
        }
        catch (Throwable t)
        {
          ae = new ApplicationException(
              ReturnCode.IMPORT_ERROR, getThrowableMsg(INFO_ERROR_IMPORTING_LDIF.get(), t), t);
        }
        finally
        {
          if (!isVerbose())
          {
            setNotifyListeners(true);
            pointAdder.stop();
          }
        }
        isOver = true;
      }

      @Override
      public void abort()
      {
        // TODO: implement the abort
      }
    };
    try
    {
      invokeLongOperation(thread);
    }
    catch (ApplicationException ae)
    {
      if (!isVerbose() && lastImportProgress != null)
      {
        notifyListeners(getFormattedProgress(LocalizableMessage.raw(lastImportProgress)));
        notifyListeners(getLineBreak());
      }
      throw ae;
    }
    if (!isVerbose())
    {
      if (lastImportProgress == null)
      {
        notifyListeners(getFormattedDoneWithLineBreak());
      }
      else
      {
        notifyListeners(getFormattedProgress(LocalizableMessage.raw(lastImportProgress)));
        notifyListeners(getLineBreak());
      }
    }
  }

  /**
   * This methods imports automatically generated data based on the contents of
   * the UserData object provided in the constructor.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  private void importAutomaticallyGenerated() throws ApplicationException
  {
    File templatePath = createTemplateFile();
    int nEntries = getUserData().getNewSuffixOptions().getNumberEntries();
    LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
    if (isVerbose() || nEntries > THRESHOLD_AUTOMATIC_DATA_VERBOSE)
    {
      mb.append(getFormattedProgress(INFO_PROGRESS_IMPORT_AUTOMATICALLY_GENERATED.get(nEntries)));
      mb.append(getLineBreak());
    }
    else
    {
      mb.append(getFormattedProgress(INFO_PROGRESS_IMPORT_AUTOMATICALLY_GENERATED_NON_VERBOSE.get(nEntries)));
    }
    notifyListeners(mb.toMessage());

    final PointAdder pointAdder = new PointAdder();
    if (!isVerbose())
    {
      pointAdder.start();
    }

    if (!isVerbose())
    {
      setNotifyListeners(false);
    }
    final List<String> argList = new ArrayList<>();
    argList.add("-n");
    argList.add(ROOT_BACKEND_NAME);
    argList.add("-A");
    argList.add(templatePath.getAbsolutePath());
    argList.add("-s"); // seed
    argList.add("0");
    argList.add("-F");
    argList.add("--noPropertiesFile");

    final String[] args = new String[argList.size()];
    argList.toArray(args);

    InvokeThread thread = new InvokeThread()
    {
      @Override
      public void run()
      {
        try
        {
          InstallerHelper helper = new InstallerHelper();
          int result = helper.invokeImportLDIF(Installer.this, args);

          if (result != 0)
          {
            ae = new ApplicationException(
                ReturnCode.IMPORT_ERROR, INFO_ERROR_IMPORT_LDIF_TOOL_RETURN_CODE.get(result), null);
          }
        }
        catch (Throwable t)
        {
          ae = new ApplicationException(ReturnCode.IMPORT_ERROR, getThrowableMsg(
                      INFO_ERROR_IMPORT_AUTOMATICALLY_GENERATED.get(joinAsString(" ", argList),
                      t.getLocalizedMessage()), t), t);
        }
        finally
        {
          if (!isVerbose())
          {
            setNotifyListeners(true);
            if (ae != null)
            {
              pointAdder.stop();
            }
          }
        }
        isOver = true;
      }

      @Override
      public void abort()
      {
        // TODO: implement the abort
      }
    };
    invokeLongOperation(thread);
    if (!isVerbose())
    {
      pointAdder.stop();
      notifyListeners(getFormattedDoneWithLineBreak());
    }
  }

  /**
   * This method undoes the modifications made in other servers in terms of
   * replication. This method assumes that we are aborting the Installer and
   * that is why it does not call checkAbort.
   */
  private void unconfigureRemote()
  {
    InitialLdapContext ctx = null;
    if (registeredNewServerOnRemote || createdAdministrator || createdRemoteAds)
    {
      // Try to connect
      DataReplicationOptions repl = getUserData().getReplicationOptions();
      AuthenticationData auth = repl.getAuthenticationData();
      if (isVerbose())
      {
        notifyListeners(getFormattedWithPoints(INFO_PROGRESS_UNCONFIGURING_ADS_ON_REMOTE.get(getHostDisplay(auth))));
      }
      try
      {
        ctx = createInitialLdapContext(auth);

        ADSContext adsContext = new ADSContext(ctx);
        if (createdRemoteAds)
        {
          adsContext.removeAdminData(true);
        }
        else
        {
          if (registeredNewServerOnRemote)
          {
            try
            {
              adsContext.unregisterServer(getNewServerAdsProperties(getUserData()));
            }
            catch (ADSContextException ace)
            {
              if (ace.getError() != ADSContextException.ErrorType.NOT_YET_REGISTERED)
              {
                throw ace;
              }
              // Else, nothing to do: this may occur if the new server has been
              // unregistered on another server and the modification has been
              // already propagated by replication.
            }
          }
          if (createdAdministrator)
          {
            adsContext.deleteAdministrator(getAdministratorProperties(getUserData()));
          }
        }
        if (isVerbose())
        {
          notifyListeners(getFormattedDoneWithLineBreak());
        }
      }
      catch (Throwable t)
      {
        notifyListeners(getFormattedError(t, true));
      }
      finally
      {
        StaticUtils.close(ctx);
      }
    }
    InstallerHelper helper = new InstallerHelper();
    for (ServerDescriptor server : hmConfiguredRemoteReplication.keySet())
    {
      notifyListeners(getFormattedWithPoints(INFO_PROGRESS_UNCONFIGURING_REPLICATION_REMOTE.get(getHostPort(server))));
      try
      {
        ctx = getRemoteConnection(server, getTrustManager(), getPreferredConnections());
        helper.unconfigureReplication(ctx, hmConfiguredRemoteReplication.get(server), ConnectionUtils.getHostPort(ctx));
      }
      catch (ApplicationException ae)
      {
        notifyListeners(getFormattedError(ae, true));
      }
      finally
      {
        StaticUtils.close(ctx);
      }
      notifyListeners(getFormattedDoneWithLineBreak());
    }
  }

  /**
   * This method configures the backends and suffixes that must be replicated.
   * The setup uses the same backend names as in the remote servers. If userRoot
   * is not one of the backends defined in the remote servers, it deletes it
   * from the configuration. NOTE: this method assumes that the server is
   * running.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  protected void createReplicatedBackendsIfRequired() throws ApplicationException
  {
    if (FIRST_IN_TOPOLOGY == getUserData().getReplicationOptions().getType())
    {
      return;
    }
    notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CREATING_REPLICATED_BACKENDS.get()));

    // The keys are the backend IDs and the values the list of base DNs.
    final Map<String, Set<String>> hmBackendSuffix = new HashMap<>();
    final SuffixesToReplicateOptions suffixData = getUserData().getSuffixesToReplicateOptions();
    populateBackendsToCreate(hmBackendSuffix, suffixData.getSuffixes());
    createReplicatedBackends(hmBackendSuffix, suffixData.getSuffixBackendTypes());
    notifyListeners(getFormattedDoneWithLineBreak());
    checkAbort();
  }

  /**
   * The criteria to choose the name of the backend is to try to have the
   * configuration of the other server. The algorithm consists on putting the
   * remote servers in a list and pick the backend as they appear on the list.
   */
  private void populateBackendsToCreate(Map<String, Set<String>> hmBackendSuffix, Set<SuffixDescriptor> suffixes)
  {
    Set<ServerDescriptor> serverList = getServerListFromSuffixes(suffixes);
    for (SuffixDescriptor suffix : suffixes)
    {
      final ReplicaDescriptor replica = retrieveReplicaForSuffix(serverList, suffix);
      if (replica != null)
      {
        final String backendNameKey = getOrAddBackend(hmBackendSuffix, replica.getBackendName());
        hmBackendSuffix.get(backendNameKey).add(suffix.getDN());
      }
    }
  }

  private Set<ServerDescriptor> getServerListFromSuffixes(Set<SuffixDescriptor> suffixes)
  {
    Set<ServerDescriptor> serverList = new LinkedHashSet<>();
    for (SuffixDescriptor suffix : suffixes)
    {
      for (ReplicaDescriptor replica : suffix.getReplicas())
      {
        serverList.add(replica.getServer());
      }
    }
    return serverList;
  }

  private ReplicaDescriptor retrieveReplicaForSuffix(Set<ServerDescriptor> serverList, SuffixDescriptor suffix)
  {
    for (ServerDescriptor server : serverList)
    {
      for (ReplicaDescriptor replica : suffix.getReplicas())
      {
        if (replica.getServer() == server)
        {
          return replica;
        }
      }
    }
    return null;
  }

  private String getOrAddBackend(Map<String, Set<String>> hmBackendSuffix, String backendName)
  {
    for (String storedBackend : hmBackendSuffix.keySet())
    {
      if (storedBackend.equalsIgnoreCase(backendName))
      {
        return storedBackend;
      }
    }
    hmBackendSuffix.put(backendName, new HashSet<String>());
    return backendName;
  }

  private void createReplicatedBackends(final Map<String, Set<String>> hmBackendSuffix,
      final Map<String, BackendTypeUIAdapter> backendTypes) throws ApplicationException
  {
    InitialLdapContext ctx = null;
    try
    {
      ctx = createLocalContext();
      final InstallerHelper helper = new InstallerHelper();
      for (String backendName : hmBackendSuffix.keySet())
      {
        helper.createBackend(ctx, backendName, hmBackendSuffix.get(backendName), ConnectionUtils.getHostPort(ctx),
            backendTypes.get(backendName).getLegacyConfigurationFrameworkBackend());
      }
    }
    catch (NamingException ne)
    {
      LocalizableMessage failedMsg = getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), ne);
      throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, failedMsg, ne);
    }
    finally
    {
      StaticUtils.close(ctx);
    }
  }

  /**
   * This method creates the replication configuration for the suffixes on the
   * the local server (and eventually in the remote servers) to synchronize
   * things. NOTE: this method assumes that the server is running.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  protected void configureReplication() throws ApplicationException
  {
    notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CONFIGURING_REPLICATION.get()));

    InstallerHelper helper = new InstallerHelper();
    Set<Integer> knownServerIds = new HashSet<>();
    Set<Integer> knownReplicationServerIds = new HashSet<>();
    if (lastLoadedCache != null)
    {
      for (SuffixDescriptor suffix : lastLoadedCache.getSuffixes())
      {
        for (ReplicaDescriptor replica : suffix.getReplicas())
        {
          knownServerIds.add(replica.getReplicationId());
        }
      }
      for (ServerDescriptor server : lastLoadedCache.getServers())
      {
        Object v = server.getServerProperties().get(REPLICATION_SERVER_ID);
        if (v != null)
        {
          knownReplicationServerIds.add((Integer) v);
        }
      }
    }
    else
    {
      /* There is no ADS anywhere. Just use the SuffixDescriptors we found */
      for (SuffixDescriptor suffix : getUserData().getSuffixesToReplicateOptions().getAvailableSuffixes())
      {
        for (ReplicaDescriptor replica : suffix.getReplicas())
        {
          knownServerIds.add(replica.getReplicationId());
          Object v = replica.getServer().getServerProperties().get(REPLICATION_SERVER_ID);
          if (v != null)
          {
            knownReplicationServerIds.add((Integer) v);
          }
        }
      }
    }

    /*
     * For each suffix specified by the user, create a map from the suffix DN to
     * the set of replication servers. The initial instance in a topology is a
     * degenerate case. Also, collect a set of all observed replication servers
     * as the set of ADS suffix replicas (all instances hosting the replication
     * server also replicate ADS).
     */
    Map<String, Set<String>> replicationServers = new HashMap<>();
    Set<String> adsServers = new HashSet<>();

    if (getUserData().getReplicationOptions().getType() == DataReplicationOptions.Type.FIRST_IN_TOPOLOGY)
    {
      List<String> baseDns = getUserData().getNewSuffixOptions().getBaseDns();
      Set<String> h = new HashSet<>();
      h.add(getLocalReplicationServer());
      adsServers.add(getLocalReplicationServer());
      for (String dn : baseDns)
      {
        replicationServers.put(dn, new HashSet<String>(h));
      }
    }
    else
    {
      Set<SuffixDescriptor> suffixes = getUserData().getSuffixesToReplicateOptions().getSuffixes();
      for (SuffixDescriptor suffix : suffixes)
      {
        Set<String> h = new HashSet<>(suffix.getReplicationServers());
        adsServers.addAll(suffix.getReplicationServers());
        h.add(getLocalReplicationServer());
        adsServers.add(getLocalReplicationServer());
        for (ReplicaDescriptor replica : suffix.getReplicas())
        {
          ServerDescriptor server = replica.getServer();
          AuthenticationData repPort = getUserData().getRemoteWithNoReplicationPort().get(server);
          if (repPort != null)
          {
            h.add(server.getHostName() + ":" + repPort.getPort());
            adsServers.add(server.getHostName() + ":" + repPort.getPort());
          }
        }
        replicationServers.put(suffix.getDN(), h);
      }
    }
    replicationServers.put(ADSContext.getAdministrationSuffixDN(), adsServers);
    replicationServers.put(Constants.SCHEMA_DN, new HashSet<String>(adsServers));

    InitialLdapContext ctx = null;
    long localTime = -1;
    long localTimeMeasureTime = -1;
    String localServerDisplay = null;
    try
    {
      ctx = createLocalContext();
      helper.configureReplication(ctx, replicationServers,
          getUserData().getReplicationOptions().getReplicationPort(),
          getUserData().getReplicationOptions().useSecureReplication(),
          getLocalHostPort(),
          knownReplicationServerIds, knownServerIds);
      localTimeMeasureTime = System.currentTimeMillis();
      localTime = Utils.getServerClock(ctx);
      localServerDisplay = ConnectionUtils.getHostPort(ctx);
    }
    catch (NamingException ne)
    {
      LocalizableMessage failedMsg = getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), ne);
      throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, failedMsg, ne);
    }
    finally
    {
      StaticUtils.close(ctx);
    }
    notifyListeners(getFormattedDoneWithLineBreak());
    checkAbort();

    if (getUserData().getReplicationOptions().getType() == DataReplicationOptions.Type.IN_EXISTING_TOPOLOGY)
    {
      Map<ServerDescriptor, Set<ReplicaDescriptor>> hm = new HashMap<>();
      for (SuffixDescriptor suffix : getUserData().getSuffixesToReplicateOptions().getSuffixes())
      {
        for (ReplicaDescriptor replica : suffix.getReplicas())
        {
          Set<ReplicaDescriptor> replicas = hm.get(replica.getServer());
          if (replicas == null)
          {
            replicas = new HashSet<>();
            hm.put(replica.getServer(), replicas);
          }
          replicas.add(replica);
        }
      }
      for (ServerDescriptor server : hm.keySet())
      {
        notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CONFIGURING_REPLICATION_REMOTE.get(getHostPort(server))));
        Integer v = (Integer) server.getServerProperties().get(REPLICATION_SERVER_PORT);
        int replicationPort;
        boolean enableSecureReplication;
        if (v != null)
        {
          replicationPort = v;
          enableSecureReplication = false;
        }
        else
        {
          AuthenticationData authData = getUserData().getRemoteWithNoReplicationPort().get(server);
          if (authData != null)
          {
            replicationPort = authData.getPort();
            enableSecureReplication = authData.useSecureConnection();
          }
          else
          {
            replicationPort = Constants.DEFAULT_REPLICATION_PORT;
            enableSecureReplication = false;
            logger.warn(LocalizableMessage.raw("Could not find replication port for: " + getHostPort(server)));
          }
        }
        Set<String> dns = new HashSet<>();
        for (ReplicaDescriptor replica : hm.get(server))
        {
          dns.add(replica.getSuffix().getDN());
        }
        dns.add(ADSContext.getAdministrationSuffixDN());
        dns.add(Constants.SCHEMA_DN);
        Map<String, Set<String>> remoteReplicationServers = new HashMap<>();
        for (String dn : dns)
        {
          Set<String> repServer = replicationServers.get(dn);
          if (repServer == null)
          {
            // Do the comparison manually
            for (String dn1 : replicationServers.keySet())
            {
              if (Utils.areDnsEqual(dn, dn1))
              {
                repServer = replicationServers.get(dn1);
                dn = dn1;
                break;
              }
            }
          }
          if (repServer != null)
          {
            remoteReplicationServers.put(dn, repServer);
          }
          else
          {
            logger.warn(LocalizableMessage.raw("Could not find replication server for: " + dn));
          }
        }

        ctx = getRemoteConnection(server, getTrustManager(), getPreferredConnections());
        ConfiguredReplication repl =
            helper.configureReplication(ctx, remoteReplicationServers, replicationPort, enableSecureReplication,
                ConnectionUtils.getHostPort(ctx), knownReplicationServerIds, knownServerIds);
        long remoteTimeMeasureTime = System.currentTimeMillis();
        long remoteTime = Utils.getServerClock(ctx);
        if (localTime != -1
            && remoteTime != -1
            && Math.abs(localTime - remoteTime - localTimeMeasureTime + remoteTimeMeasureTime) >
               THRESHOLD_CLOCK_DIFFERENCE_WARNING * 60 * 1000)
        {
          notifyListeners(getFormattedWarning(INFO_WARNING_SERVERS_CLOCK_DIFFERENCE.get(localServerDisplay,
              ConnectionUtils.getHostPort(ctx), THRESHOLD_CLOCK_DIFFERENCE_WARNING)));
        }

        hmConfiguredRemoteReplication.put(server, repl);

        StaticUtils.close(ctx);
        notifyListeners(getFormattedDoneWithLineBreak());
        checkAbort();
      }
    }
  }

  /**
   * This methods enables this server as a Windows service.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  protected void enableWindowsService() throws ApplicationException
  {
    notifyListeners(getFormattedWithPoints(INFO_PROGRESS_ENABLING_WINDOWS_SERVICE.get()));
    InstallerHelper helper = new InstallerHelper();
    helper.enableWindowsService();
    notifyListeners(getLineBreak());
  }

  /**
   * Updates the contents of the provided map with the localized summary
   * strings.
   *
   * @param hmSummary
   *          the Map to be updated.
   * @param isCli
   *          a boolean to indicate if the install is using CLI or GUI
   */
  protected void initSummaryMap(Map<ProgressStep, LocalizableMessage> hmSummary, boolean isCli)
  {
    put(hmSummary, NOT_STARTED, INFO_SUMMARY_INSTALL_NOT_STARTED);
    put(hmSummary, CONFIGURING_SERVER, INFO_SUMMARY_CONFIGURING);
    put(hmSummary, CREATING_BASE_ENTRY, INFO_SUMMARY_CREATING_BASE_ENTRY);
    put(hmSummary, IMPORTING_LDIF, INFO_SUMMARY_IMPORTING_LDIF);
    put(hmSummary, IMPORTING_AUTOMATICALLY_GENERATED, INFO_SUMMARY_IMPORTING_AUTOMATICALLY_GENERATED);
    put(hmSummary, CONFIGURING_REPLICATION, INFO_SUMMARY_CONFIGURING_REPLICATION);
    put(hmSummary, STARTING_SERVER, INFO_SUMMARY_STARTING);
    put(hmSummary, STOPPING_SERVER, INFO_SUMMARY_STOPPING);
    put(hmSummary, CONFIGURING_ADS, INFO_SUMMARY_CONFIGURING_ADS);
    put(hmSummary, INITIALIZE_REPLICATED_SUFFIXES, INFO_SUMMARY_INITIALIZE_REPLICATED_SUFFIXES);
    put(hmSummary, ENABLING_WINDOWS_SERVICE, INFO_SUMMARY_ENABLING_WINDOWS_SERVICE);
    put(hmSummary, WAITING_TO_CANCEL, INFO_SUMMARY_WAITING_TO_CANCEL);
    put(hmSummary, CANCELING, INFO_SUMMARY_CANCELING);

    Installation installation = getInstallation();
    String cmd = Utils.addWordBreaks(getPath(installation.getControlPanelCommandFile()), 60, 5);
    if (!isCli)
    {
      cmd = UIFactory.applyFontToHtml(cmd, UIFactory.INSTRUCTIONS_MONOSPACE_FONT);
    }
    String formattedPath =
        Utils.addWordBreaks(formatter.getFormattedText(LocalizableMessage.raw(getPath(new File(getInstancePath()))))
            .toString(), 60, 5);
    LocalizableMessage successMessage =
        Utils.getCustomizedObject("INFO_SUMMARY_INSTALL_FINISHED_SUCCESSFULLY",
            INFO_SUMMARY_INSTALL_FINISHED_SUCCESSFULLY.get(DynamicConstants.PRODUCT_NAME,
                DynamicConstants.PRODUCT_NAME, formattedPath, INFO_GENERAL_SERVER_STOPPED.get(),
                DynamicConstants.DOC_QUICK_REFERENCE_GUIDE, DynamicConstants.PRODUCT_NAME, cmd),
            LocalizableMessage.class);
    hmSummary.put(FINISHED_SUCCESSFULLY, getFormattedSuccess(successMessage));
    hmSummary.put(FINISHED_CANCELED, getFormattedSuccess(INFO_SUMMARY_INSTALL_FINISHED_CANCELED.get()));
    hmSummary.put(FINISHED_WITH_ERROR,
        getFormattedError(INFO_SUMMARY_INSTALL_FINISHED_WITH_ERROR.get(INFO_GENERAL_SERVER_STOPPED.get(), cmd)));
  }

  private void put(Map<ProgressStep, LocalizableMessage> hmSummary, InstallProgressStep step, Arg0 msg)
  {
    hmSummary.put(step, getFormattedSummary(msg.get()));
  }

  /**
   * Updates the messages in the summary with the state of the server.
   *
   * @param hmSummary
   *          the Map containing the messages.
   * @param isCli
   *          a boolean to indicate if the install is using CLI or GUI
   */
  protected void updateSummaryWithServerState(Map<ProgressStep, LocalizableMessage> hmSummary, Boolean isCli)
  {
    Installation installation = getInstallation();
    String cmd = getPath(installation.getControlPanelCommandFile());
    if (!isCli)
    {
      cmd = Utils.addWordBreaks(UIFactory.applyFontToHtml(cmd, UIFactory.INSTRUCTIONS_MONOSPACE_FONT), 60, 5);
    }
    LocalizableMessage status;
    if (installation.getStatus().isServerRunning())
    {
      status = INFO_GENERAL_SERVER_STARTED.get();
    }
    else
    {
      status = INFO_GENERAL_SERVER_STOPPED.get();
    }
    String formattedPath =
        Utils.addWordBreaks(formatter.getFormattedText(LocalizableMessage.raw(getPath(new File(getInstancePath()))))
            .toString(), 60, 5);
    LocalizableMessage successMessage =
        Utils.getCustomizedObject("INFO_SUMMARY_INSTALL_FINISHED_SUCCESSFULLY",
            INFO_SUMMARY_INSTALL_FINISHED_SUCCESSFULLY.get(DynamicConstants.PRODUCT_NAME,
                DynamicConstants.PRODUCT_NAME, formattedPath, status, DynamicConstants.DOC_QUICK_REFERENCE_GUIDE,
                DynamicConstants.PRODUCT_NAME, cmd), LocalizableMessage.class);
    hmSummary.put(InstallProgressStep.FINISHED_SUCCESSFULLY, getFormattedSuccess(successMessage));
    hmSummary.put(InstallProgressStep.FINISHED_WITH_ERROR, getFormattedError(INFO_SUMMARY_INSTALL_FINISHED_WITH_ERROR
        .get(status, cmd)));
  }

  /**
   * Checks the value of <code>canceled</code> field and throws an
   * ApplicationException if true. This indicates that the user has canceled
   * this operation and the process of aborting should begin as soon as
   * possible.
   *
   * @throws ApplicationException
   *           thrown if <code>canceled</code>
   */
  @Override
  public void checkAbort() throws ApplicationException
  {
    if (canceled)
    {
      setCurrentProgressStep(InstallProgressStep.CANCELING);
      notifyListeners(null);
      throw new ApplicationException(ReturnCode.CANCELED, INFO_INSTALL_CANCELED.get(), null);
    }
  }

  /**
   * Writes the host name to a file that will be used by the server to generate
   * a self-signed certificate.
   */
  private void writeHostName()
  {
    BufferedWriter writer = null;
    try
    {
      writer = new BufferedWriter(new FileWriter(getHostNameFile(), false));
      writer.append(getUserData().getHostName());
    }
    catch (IOException ioe)
    {
      logger.warn(LocalizableMessage.raw("Error writing host name file: " + ioe, ioe));
    }
    finally
    {
      StaticUtils.close(writer);
    }
  }

  /**
   * Returns the file path where the host name is to be written.
   *
   * @return the file path where the host name is to be written.
   */
  private String getHostNameFile()
  {
    return Utils.getPath(getInstallation().getRootDirectory().getAbsolutePath(), SetupUtils.HOST_NAME_FILE);
  }

  /**
   * Writes the java home that we are using for the setup in a file. This way we
   * can use this java home even if the user has not set OPENDJ_JAVA_HOME when
   * running the different scripts.
   */
  private void writeOpenDSJavaHome()
  {
    try
    {
      // This isn't likely to happen, and it's not a serious problem even if
      // it does.
      InstallerHelper helper = new InstallerHelper();
      helper.writeSetOpenDSJavaHome(getUserData(), getInstallationPath());
    }
    catch (Exception e)
    {
      logger.warn(LocalizableMessage.raw("Error writing OpenDJ Java Home file: " + e, e));
    }
  }

  /**
   * These methods validate the data provided by the user in the panels and
   * update the userData object according to that content.
   *
   * @param cStep
   *          the current step of the wizard
   * @param qs
   *          QuickStart controller
   * @throws UserDataException
   *           if the data provided by the user is not valid.
   */
  @Override
  public void updateUserData(WizardStep cStep, QuickSetup qs) throws UserDataException
  {
    if (cStep == SERVER_SETTINGS)
    {
      updateUserDataForServerSettingsPanel(qs);
    }
    else if (cStep == REPLICATION_OPTIONS)
    {
      updateUserDataForReplicationOptionsPanel(qs);
    }
    else if (cStep == CREATE_GLOBAL_ADMINISTRATOR)
    {
      updateUserDataForCreateAdministratorPanel(qs);
    }
    else if (cStep == SUFFIXES_OPTIONS)
    {
      updateUserDataForSuffixesOptionsPanel(qs);
    }
    else if (cStep == REMOTE_REPLICATION_PORTS)
    {
      updateUserDataForRemoteReplicationPorts(qs);
    }
    else if (cStep == NEW_SUFFIX_OPTIONS)
    {
      updateUserDataForNewSuffixOptionsPanel(qs);
    }
    else if (cStep == RUNTIME_OPTIONS)
    {
      updateUserDataForRuntimeOptionsPanel(qs);
    }
    else if (cStep == REVIEW)
    {
      updateUserDataForReviewPanel(qs);
    }
  }

  /**
   * Sets the current progress step of the installation process.
   *
   * @param currentProgressStep
   *          the current progress step of the installation process.
   */
  protected void setCurrentProgressStep(InstallProgressStep currentProgressStep)
  {
    if (currentProgressStep != null)
    {
      this.completedProgress.add(currentProgressStep);
    }
    this.currentProgressStep = currentProgressStep;
  }

  /**
   * This methods updates the data on the server based on the contents of the
   * UserData object provided in the constructor.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  protected void createData() throws ApplicationException
  {
    if (createNotReplicatedSuffix()
        && NewSuffixOptions.Type.LEAVE_DATABASE_EMPTY != getUserData().getNewSuffixOptions().getType())
    {
      currentProgressStep = getUserData().getNewSuffixOptions().getInstallProgressStep();
      if (isVerbose())
      {
        notifyListeners(getTaskSeparator());
      }

      switch (getUserData().getNewSuffixOptions().getType())
      {
      case CREATE_BASE_ENTRY:
        createBaseEntry();
        break;
      case IMPORT_FROM_LDIF_FILE:
        importLDIF();
        break;
      case IMPORT_AUTOMATICALLY_GENERATED_DATA:
        importAutomaticallyGenerated();
        break;
      default:
        break;
      }
    }
  }

  /**
   * This method initialize the contents of the synchronized servers with the
   * contents of the first server we find.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  protected void initializeSuffixes() throws ApplicationException
  {
    InitialLdapContext ctx = null;
    try
    {
      ctx = createLocalContext();
    }
    catch (Throwable t)
    {
      LocalizableMessage failedMsg = getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), t);
      StaticUtils.close(ctx);
      throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, failedMsg, t);
    }

    Set<SuffixDescriptor> suffixes = getUserData().getSuffixesToReplicateOptions().getSuffixes();

    /* Initialize local ADS and schema contents using any replica. */
    {
      ServerDescriptor server = suffixes.iterator().next().getReplicas().iterator().next().getServer();
      InitialLdapContext rCtx = null;
      try
      {
        rCtx = getRemoteConnection(server, getTrustManager(), getPreferredConnections());
        TopologyCacheFilter filter = new TopologyCacheFilter();
        filter.setSearchMonitoringInformation(false);
        filter.addBaseDNToSearch(ADSContext.getAdministrationSuffixDN());
        filter.addBaseDNToSearch(Constants.SCHEMA_DN);
        ServerDescriptor s = createStandalone(rCtx, filter);
        for (ReplicaDescriptor replica : s.getReplicas())
        {
          String dn = replica.getSuffix().getDN();
          if (areDnsEqual(dn, ADSContext.getAdministrationSuffixDN()))
          {
            suffixes.add(replica.getSuffix());
          }
          else if (areDnsEqual(dn, Constants.SCHEMA_DN))
          {
            suffixes.add(replica.getSuffix());
          }
        }
      }
      catch (NamingException ne)
      {
        LocalizableMessage msg;
        if (isCertificateException(ne))
        {
          msg = INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE_SERVER.get(getHostPort(server), ne.toString(true));
        }
        else
        {
          msg = INFO_CANNOT_CONNECT_TO_REMOTE_GENERIC.get(getHostPort(server), ne.toString(true));
        }
        throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, msg, ne);
      }
      finally
      {
        StaticUtils.close(rCtx);
      }
    }

    for (SuffixDescriptor suffix : suffixes)
    {
      String dn = suffix.getDN();

      ReplicaDescriptor replica = suffix.getReplicas().iterator().next();
      ServerDescriptor server = replica.getServer();
      String hostPort = getHostPort(server);

      boolean isADS = areDnsEqual(dn, ADSContext.getAdministrationSuffixDN());
      boolean isSchema = areDnsEqual(dn, Constants.SCHEMA_DN);
      if (isADS)
      {
        if (isVerbose())
        {
          notifyListeners(getFormattedWithPoints(INFO_PROGRESS_INITIALIZING_ADS.get()));
        }
      }
      else if (isSchema)
      {
        if (isVerbose())
        {
          notifyListeners(getFormattedWithPoints(INFO_PROGRESS_INITIALIZING_SCHEMA.get()));
        }
      }
      else
      {
        notifyListeners(getFormattedProgress(INFO_PROGRESS_INITIALIZING_SUFFIX.get(dn, hostPort)));
        notifyListeners(getLineBreak());
      }
      try
      {
        int replicationId = replica.getReplicationId();
        if (replicationId == -1)
        {
          // This occurs if the remote server had not replication configured.
          InitialLdapContext rCtx = null;
          try
          {
            rCtx = getRemoteConnection(server, getTrustManager(), getPreferredConnections());
            TopologyCacheFilter filter = new TopologyCacheFilter();
            filter.setSearchMonitoringInformation(false);
            filter.addBaseDNToSearch(dn);
            ServerDescriptor s = createStandalone(rCtx, filter);
            for (ReplicaDescriptor r : s.getReplicas())
            {
              if (areDnsEqual(r.getSuffix().getDN(), dn))
              {
                replicationId = r.getReplicationId();
              }
            }
          }
          catch (NamingException ne)
          {
            LocalizableMessage msg;
            if (isCertificateException(ne))
            {
              msg = INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE_SERVER.get(getHostPort(server), ne.toString(true));
            }
            else
            {
              msg = INFO_CANNOT_CONNECT_TO_REMOTE_GENERIC.get(getHostPort(server), ne.toString(true));
            }
            throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, msg, ne);
          }
          finally
          {
            StaticUtils.close(rCtx);
          }
        }
        if (replicationId == -1)
        {
          throw new ApplicationException(ReturnCode.APPLICATION_ERROR, ERR_COULD_NOT_FIND_REPLICATIONID.get(dn), null);
        }
        StaticUtils.sleep(3000);
        int nTries = 5;
        boolean initDone = false;
        while (!initDone)
        {
          try
          {
            logger.info(LocalizableMessage.raw("Calling initializeSuffix with base DN: " + dn));
            logger.info(LocalizableMessage.raw("Try number: " + (6 - nTries)));
            logger.info(LocalizableMessage.raw("replicationId of source replica: " + replicationId));
            initializeSuffix(ctx, replicationId, dn, !isADS && !isSchema, hostPort);
            initDone = true;
          }
          catch (PeerNotFoundException pnfe)
          {
            logger.info(LocalizableMessage.raw("Peer could not be found"));
            if (nTries == 1)
            {
              throw new ApplicationException(ReturnCode.APPLICATION_ERROR, pnfe.getMessageObject(), null);
            }
            StaticUtils.sleep((5 - nTries) * 3000);
          }
          nTries--;
        }
      }
      catch (ApplicationException ae)
      {
        StaticUtils.close(ctx);
        throw ae;
      }
      if ((isADS || isSchema) && isVerbose())
      {
        notifyListeners(getFormattedDone());
        notifyListeners(getLineBreak());
      }
      checkAbort();
    }
  }

  /**
   * This method updates the ADS contents (and creates the according suffixes).
   * If the user specified an existing topology, the new instance is registered
   * with that ADS (the ADS might need to be created), and the local ADS will be
   * populated when the local server is added to the remote server's ADS
   * replication domain in a subsequent step. Otherwise, an ADS is created on
   * the new instance and the server is registered with the new ADS. NOTE: this
   * method assumes that the local server and any remote server are running.
   *
   * @throws ApplicationException
   *           if something goes wrong.
   */
  protected void updateADS() throws ApplicationException
  {
    DataReplicationOptions repl = getUserData().getReplicationOptions();
    boolean isRemoteServer = repl.getType() == DataReplicationOptions.Type.IN_EXISTING_TOPOLOGY;
    AuthenticationData auth = isRemoteServer ? repl.getAuthenticationData() : null;
    InitialLdapContext remoteCtx = null; // Bound to remote ADS host (if any).
    InitialLdapContext localCtx = null; // Bound to local server.
    ADSContext adsContext = null; // Bound to ADS host (via one of above).

    /*
     * Outer try-catch-finally to convert occurrences of NamingException and
     * ADSContextException to ApplicationException and clean up JNDI contexts.
     */
    try
    {
      if (isRemoteServer)
      {
        remoteCtx = createInitialLdapContext(auth);
        adsContext = new ADSContext(remoteCtx); // adsContext owns remoteCtx

        /*
         * Check the remote server for ADS. If it does not exist, create the
         * initial ADS there and register the server with itself.
         */
        if (!adsContext.hasAdminData())
        {
          if (isVerbose())
          {
            notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CREATING_ADS_ON_REMOTE.get(getHostDisplay(auth))));
          }

          adsContext.createAdminData(null);
          TopologyCacheFilter filter = new TopologyCacheFilter();
          filter.setSearchMonitoringInformation(false);
          filter.setSearchBaseDNInformation(false);
          ServerDescriptor server = createStandalone(remoteCtx, filter);
          server.updateAdsPropertiesWithServerProperties();
          adsContext.registerServer(server.getAdsProperties());
          createdRemoteAds = true;
          if (isVerbose())
          {
            notifyListeners(getFormattedDoneWithLineBreak());
          }
          checkAbort();
        }
      }

      /* Act on local server depending on if using remote or local ADS */
      if (isVerbose())
      {
        notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CREATING_ADS.get()));
      }
      localCtx = createLocalContext();
      //      if (isRemoteServer)
      //      {
      //        /* Create an empty ADS suffix on the local server. */
      //        ADSContext localAdsContext = new ADSContext(localCtx);
      //        localAdsContext.createAdministrationSuffix(null);
      //      }
      if (!isRemoteServer)
      {
        /* Configure local server to have an ADS */
        adsContext = new ADSContext(localCtx); // adsContext owns localCtx
        adsContext.createAdminData(null);
      }
      /* Register new server in ADS. */
      TopologyCacheFilter filter = new TopologyCacheFilter();
      filter.setSearchMonitoringInformation(false);
      filter.setSearchBaseDNInformation(false);
      ServerDescriptor server = createStandalone(localCtx, filter);
      server.updateAdsPropertiesWithServerProperties();
      if (0 == adsContext.registerOrUpdateServer(server.getAdsProperties()))
      {
        if (isRemoteServer)
        {
          registeredNewServerOnRemote = true;
        }
      }
      else
      {
        logger.warn(LocalizableMessage.raw("Server was already registered. Updating " + "server registration."));
      }
      if (isRemoteServer)
      {
        seedAdsTrustStore(localCtx, adsContext.getTrustedCertificates());
      }
      if (isVerbose())
      {
        notifyListeners(getFormattedDoneWithLineBreak());
      }
      checkAbort();

      /* Add global administrator if the user specified one. */
      if (getUserData().mustCreateAdministrator())
      {
        try
        {
          if (isVerbose())
          {
            notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CREATING_ADMINISTRATOR.get()));
          }
          adsContext.createAdministrator(getAdministratorProperties(getUserData()));
          if (isRemoteServer && !createdRemoteAds)
          {
            createdAdministrator = true;
          }
          if (isVerbose())
          {
            notifyListeners(getFormattedDoneWithLineBreak());
          }
          checkAbort();
        }
        catch (ADSContextException ade)
        {
          if (ade.getError() == ADSContextException.ErrorType.ALREADY_REGISTERED)
          {
            notifyListeners(getFormattedWarning(INFO_ADMINISTRATOR_ALREADY_REGISTERED.get()));
            adsContext.unregisterServer(server.getAdsProperties());
            adsContext.registerServer(server.getAdsProperties());
          }
          else
          {
            throw ade;
          }
        }
      }
    }
    catch (NamingException ne)
    {
      LocalizableMessage msg;
      if (isRemoteServer)
      {
        msg = getMessageForException(ne, getHostDisplay(auth));
      }
      else
      {
        msg = Utils.getMessageForException(ne);
      }
      throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, msg, ne);
    }
    catch (ADSContextException ace)
    {
      throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, (isRemoteServer ? INFO_REMOTE_ADS_EXCEPTION.get(
          getHostDisplay(auth), ace.getMessageObject()) : INFO_ADS_EXCEPTION.get(ace)), ace);
    }
    finally
    {
      StaticUtils.close(remoteCtx, localCtx);
    }
  }

  private InitialLdapContext createInitialLdapContext(AuthenticationData auth) throws NamingException
  {
    String ldapUrl = getLdapUrl(auth);
    String dn = auth.getDn();
    String pwd = auth.getPwd();

    if (auth.useSecureConnection())
    {
      ApplicationTrustManager trustManager = getTrustManager();
      trustManager.setHost(auth.getHostName());
      return createLdapsContext(ldapUrl, dn, pwd, getConnectTimeout(), null, trustManager, null);
    }
    return createLdapContext(ldapUrl, dn, pwd, getConnectTimeout(), null);
  }

  /**
   * Tells whether we must create a suffix that we are not going to replicate
   * with other servers or not.
   *
   * @return <CODE>true</CODE> if we must create a new suffix and
   *         <CODE>false</CODE> otherwise.
   */
  protected boolean createNotReplicatedSuffix()
  {
    DataReplicationOptions repl = getUserData().getReplicationOptions();

    SuffixesToReplicateOptions suf = getUserData().getSuffixesToReplicateOptions();

    return repl.getType() == DataReplicationOptions.Type.FIRST_IN_TOPOLOGY
        || repl.getType() == DataReplicationOptions.Type.STANDALONE
        || suf.getType() == SuffixesToReplicateOptions.Type.NEW_SUFFIX_IN_TOPOLOGY;
  }

  /**
   * Returns <CODE>true</CODE> if we must configure replication and
   * <CODE>false</CODE> otherwise.
   *
   * @return <CODE>true</CODE> if we must configure replication and
   *         <CODE>false</CODE> otherwise.
   */
  protected boolean mustConfigureReplication()
  {
    return getUserData().getReplicationOptions().getType() != DataReplicationOptions.Type.STANDALONE;
  }

  /**
   * Returns <CODE>true</CODE> if we must create the ADS and <CODE>false</CODE>
   * otherwise.
   *
   * @return <CODE>true</CODE> if we must create the ADS and <CODE>false</CODE>
   *         otherwise.
   */
  protected boolean mustCreateAds()
  {
    return getUserData().getReplicationOptions().getType() != DataReplicationOptions.Type.STANDALONE;
  }

  /**
   * Returns <CODE>true</CODE> if we must start the server and
   * <CODE>false</CODE> otherwise.
   *
   * @return <CODE>true</CODE> if we must start the server and
   *         <CODE>false</CODE> otherwise.
   */
  protected boolean mustStart()
  {
    return getUserData().getStartServer() || mustCreateAds();
  }

  /**
   * Returns <CODE>true</CODE> if the start server must be launched in verbose
   * mode and <CODE>false</CODE> otherwise. The verbose flag is not enough
   * because in the case where many entries have been imported, the startup
   * phase can take long.
   *
   * @return <CODE>true</CODE> if the start server must be launched in verbose
   *         mode and <CODE>false</CODE> otherwise.
   */
  protected boolean isStartVerbose()
  {
    if (isVerbose())
    {
      return true;
    }
    boolean manyEntriesToImport = false;
    NewSuffixOptions.Type type = getUserData().getNewSuffixOptions().getType();
    if (type == NewSuffixOptions.Type.IMPORT_FROM_LDIF_FILE)
    {
      long mbTotalSize = 0;
      LinkedList<String> ldifPaths = getUserData().getNewSuffixOptions().getLDIFPaths();
      for (String ldifPath : ldifPaths)
      {
        File f = new File(ldifPath);
        mbTotalSize += f.length();
      }
      // Assume entries of 1kb
      if (mbTotalSize > THRESHOLD_VERBOSE_START * 1024)
      {
        manyEntriesToImport = true;
      }
    }
    else if (type == NewSuffixOptions.Type.IMPORT_AUTOMATICALLY_GENERATED_DATA)
    {
      int nEntries = getUserData().getNewSuffixOptions().getNumberEntries();
      if (nEntries > THRESHOLD_VERBOSE_START)
      {
        manyEntriesToImport = true;
      }
    }
    return manyEntriesToImport;
  }

  /**
   * Returns <CODE>true</CODE> if we must stop the server and <CODE>false</CODE>
   * otherwise. The server might be stopped if the user asked not to start it at
   * the end of the installation and it was started temporarily to update its
   * configuration.
   *
   * @return <CODE>true</CODE> if we must stop the server and <CODE>false</CODE>
   *         otherwise.
   */
  protected boolean mustStop()
  {
    return !getUserData().getStartServer() && mustCreateAds();
  }

  /**
   * Returns <CODE>true</CODE> if we must initialize suffixes and
   * <CODE>false</CODE> otherwise.
   *
   * @return <CODE>true</CODE> if we must initialize suffixes and
   *         <CODE>false</CODE> otherwise.
   */
  protected boolean mustInitializeSuffixes()
  {
    return getUserData().getReplicationOptions().getType() == DataReplicationOptions.Type.IN_EXISTING_TOPOLOGY;
  }

  /**
   * Returns the list of preferred URLs to connect to remote servers. In fact it
   * returns only the URL to the remote server specified by the user in the
   * replication options panel. The method returns a list for convenience with
   * other interfaces.
   * <p>
   * NOTE: this method assumes that the UserData object has
   * already been updated with the host and port of the remote server.
   *
   * @return the list of preferred URLs to connect to remote servers.
   */
  private Set<PreferredConnection> getPreferredConnections()
  {
    Set<PreferredConnection> cnx = new LinkedHashSet<>();
    DataReplicationOptions repl = getUserData().getReplicationOptions();
    if (repl.getType() == DataReplicationOptions.Type.IN_EXISTING_TOPOLOGY)
    {
      AuthenticationData auth = repl.getAuthenticationData();
      if (auth != null)
      {
        PreferredConnection.Type type;
        if (auth.useSecureConnection())
        {
          type = PreferredConnection.Type.LDAPS;
        }
        else
        {
          type = PreferredConnection.Type.LDAP;
        }
        cnx.add(new PreferredConnection(getLdapUrl(auth), type));
      }
    }
    return cnx;
  }

  private String getLdapUrl(AuthenticationData auth)
  {
    if (auth.useSecureConnection())
    {
      return "ldaps://" + auth.getHostName() + ":" + auth.getPort();
    }
    return "ldap://" + auth.getHostName() + ":" + auth.getPort();
  }

  private String getHostDisplay(AuthenticationData auth)
  {
    return auth.getHostName() + ":" + auth.getPort();
  }

  private Map<ADSContext.ServerProperty, Object> getNewServerAdsProperties(UserData userData)
  {
    Map<ADSContext.ServerProperty, Object> serverProperties = new HashMap<>();
    serverProperties.put(ADSContext.ServerProperty.HOST_NAME, userData.getHostName());
    serverProperties.put(ADSContext.ServerProperty.LDAP_PORT, String.valueOf(userData.getServerPort()));
    serverProperties.put(ADSContext.ServerProperty.LDAP_ENABLED, "true");

    // TODO: even if the user does not configure SSL maybe we should choose
    // a secure port that is not being used and that we can actually use.
    SecurityOptions sec = userData.getSecurityOptions();
    if (sec.getEnableSSL())
    {
      serverProperties.put(ADSContext.ServerProperty.LDAPS_PORT, String.valueOf(sec.getSslPort()));
      serverProperties.put(ADSContext.ServerProperty.LDAPS_ENABLED, "true");
    }
    else
    {
      serverProperties.put(ADSContext.ServerProperty.LDAPS_PORT, "636");
      serverProperties.put(ADSContext.ServerProperty.LDAPS_ENABLED, "false");
    }

    if (sec.getEnableStartTLS())
    {
      serverProperties.put(ADSContext.ServerProperty.STARTTLS_ENABLED, "true");
    }
    else
    {
      serverProperties.put(ADSContext.ServerProperty.STARTTLS_ENABLED, "false");
    }

    serverProperties.put(ADSContext.ServerProperty.JMX_PORT, "1689");
    serverProperties.put(ADSContext.ServerProperty.JMX_ENABLED, "false");

    serverProperties.put(ADSContext.ServerProperty.INSTANCE_PATH, getInstallPathFromClasspath());

    String serverID = serverProperties.get(ADSContext.ServerProperty.HOST_NAME) + ":" + userData.getServerPort();

    /* TODO: do we want to ask this specifically to the user? */
    serverProperties.put(ADSContext.ServerProperty.ID, serverID);
    serverProperties.put(ADSContext.ServerProperty.HOST_OS, OperatingSystem.getOperatingSystem().toString());

    return serverProperties;
  }

  private Map<ADSContext.AdministratorProperty, Object> getAdministratorProperties(UserData userData)
  {
    Map<ADSContext.AdministratorProperty, Object> adminProperties = new HashMap<>();
    adminProperties.put(ADSContext.AdministratorProperty.UID, userData.getGlobalAdministratorUID());
    adminProperties.put(ADSContext.AdministratorProperty.PASSWORD, userData.getGlobalAdministratorPassword());
    adminProperties.put(ADSContext.AdministratorProperty.DESCRIPTION,
                        INFO_GLOBAL_ADMINISTRATOR_DESCRIPTION.get().toString());
    return adminProperties;
  }

  /**
   * Validate the data provided by the user in the server settings panel and
   * update the userData object according to that content.
   *
   * @throws UserDataException
   *           if the data provided by the user is not valid.
   */
  private void updateUserDataForServerSettingsPanel(QuickSetup qs) throws UserDataException
  {
    List<LocalizableMessage> errorMsgs = new ArrayList<>();
    LocalizableMessage confirmationMsg = null;

    // Check the host is not empty.
    // TODO: check that the host name is valid...
    String hostName = qs.getFieldStringValue(FieldName.HOST_NAME);
    if (hostName == null || hostName.trim().length() == 0)
    {
      errorMsgs.add(INFO_EMPTY_HOST_NAME.get());
      qs.displayFieldInvalid(FieldName.HOST_NAME, true);
    }
    else
    {
      qs.displayFieldInvalid(FieldName.HOST_NAME, false);
      getUserData().setHostName(hostName);
    }

    // Check the port
    String sPort = qs.getFieldStringValue(FieldName.SERVER_PORT);
    int port = -1;
    try
    {
      port = Integer.parseInt(sPort);
      if (port < MIN_PORT_VALUE || port > MAX_PORT_VALUE)
      {
        errorMsgs.add(INFO_INVALID_PORT_VALUE_RANGE.get(MIN_PORT_VALUE, MAX_PORT_VALUE));
        qs.displayFieldInvalid(FieldName.SERVER_PORT, true);
      }
      else if (!canUseAsPort(port))
      {
        errorMsgs.add(getCannotBindErrorMessage(port));
        qs.displayFieldInvalid(FieldName.SERVER_PORT, true);
      }
      else
      {
        getUserData().setServerPort(port);
        qs.displayFieldInvalid(FieldName.SERVER_PORT, false);
      }
    }
    catch (NumberFormatException nfe)
    {
      errorMsgs.add(INFO_INVALID_PORT_VALUE_RANGE.get(MIN_PORT_VALUE, MAX_PORT_VALUE));
      qs.displayFieldInvalid(FieldName.SERVER_PORT, true);
    }

    //  Check the admin connector port
    sPort = qs.getFieldStringValue(FieldName.ADMIN_CONNECTOR_PORT);
    int adminConnectorPort = -1;
    try
    {
      adminConnectorPort = Integer.parseInt(sPort);
      if (adminConnectorPort < MIN_PORT_VALUE || adminConnectorPort > MAX_PORT_VALUE)
      {
        errorMsgs.add(INFO_INVALID_PORT_VALUE_RANGE.get(MIN_PORT_VALUE, MAX_PORT_VALUE));
        qs.displayFieldInvalid(FieldName.ADMIN_CONNECTOR_PORT, true);
      }
      else if (!canUseAsPort(adminConnectorPort))
      {
        errorMsgs.add(getCannotBindErrorMessage(adminConnectorPort));
        qs.displayFieldInvalid(FieldName.ADMIN_CONNECTOR_PORT, true);
      }
      else if (adminConnectorPort == port)
      {
        errorMsgs.add(INFO_ADMIN_CONNECTOR_VALUE_SEVERAL_TIMES.get());
        qs.displayFieldInvalid(FieldName.SERVER_PORT, true);
        qs.displayFieldInvalid(FieldName.ADMIN_CONNECTOR_PORT, true);
      }
      else
      {
        getUserData().setAdminConnectorPort(adminConnectorPort);
        qs.displayFieldInvalid(FieldName.ADMIN_CONNECTOR_PORT, false);
      }
    }
    catch (NumberFormatException nfe)
    {
      errorMsgs.add(INFO_INVALID_PORT_VALUE_RANGE.get(MIN_PORT_VALUE, MAX_PORT_VALUE));
      qs.displayFieldInvalid(FieldName.ADMIN_CONNECTOR_PORT, true);
    }

    // Check the secure port
    SecurityOptions sec = (SecurityOptions) qs.getFieldValue(FieldName.SECURITY_OPTIONS);
    int securePort = sec.getSslPort();
    if (sec.getEnableSSL())
    {
      if (securePort < MIN_PORT_VALUE || securePort > MAX_PORT_VALUE)
      {
        errorMsgs.add(INFO_INVALID_SECURE_PORT_VALUE_RANGE.get(MIN_PORT_VALUE, MAX_PORT_VALUE));
        qs.displayFieldInvalid(FieldName.SECURITY_OPTIONS, true);
      }
      else if (!canUseAsPort(securePort))
      {
        errorMsgs.add(getCannotBindErrorMessage(securePort));
        qs.displayFieldInvalid(FieldName.SECURITY_OPTIONS, true);
      }
      else if (port == securePort)
      {
        errorMsgs.add(INFO_EQUAL_PORTS.get());
        qs.displayFieldInvalid(FieldName.SECURITY_OPTIONS, true);
        qs.displayFieldInvalid(FieldName.SERVER_PORT, true);
      }
      else if (adminConnectorPort == securePort)
      {
        errorMsgs.add(INFO_ADMIN_CONNECTOR_VALUE_SEVERAL_TIMES.get());
        qs.displayFieldInvalid(FieldName.SECURITY_OPTIONS, true);
        qs.displayFieldInvalid(FieldName.ADMIN_CONNECTOR_PORT, true);
      }
      else
      {
        getUserData().setSecurityOptions(sec);
        qs.displayFieldInvalid(FieldName.SECURITY_OPTIONS, false);
      }
    }
    else
    {
      getUserData().setSecurityOptions(sec);
      qs.displayFieldInvalid(FieldName.SECURITY_OPTIONS, false);
    }

    // Check the Directory Manager DN
    String dmDn = qs.getFieldStringValue(FieldName.DIRECTORY_MANAGER_DN);

    if (dmDn == null || dmDn.trim().length() == 0)
    {
      errorMsgs.add(INFO_EMPTY_DIRECTORY_MANAGER_DN.get());
      qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_DN, true);
    }
    else if (!isDN(dmDn))
    {
      errorMsgs.add(INFO_NOT_A_DIRECTORY_MANAGER_DN.get());
      qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_DN, true);
    }
    else if (isConfigurationDn(dmDn))
    {
      errorMsgs.add(INFO_DIRECTORY_MANAGER_DN_IS_CONFIG_DN.get());
      qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_DN, true);
    }
    else
    {
      getUserData().setDirectoryManagerDn(dmDn);
      qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_DN, false);
    }

    // Check the provided passwords
    String pwd1 = qs.getFieldStringValue(FieldName.DIRECTORY_MANAGER_PWD);
    String pwd2 = qs.getFieldStringValue(FieldName.DIRECTORY_MANAGER_PWD_CONFIRM);
    if (pwd1 == null)
    {
      pwd1 = "";
    }

    boolean pwdValid = true;
    if (!pwd1.equals(pwd2))
    {
      errorMsgs.add(INFO_NOT_EQUAL_PWD.get());
      qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_PWD_CONFIRM, true);
      pwdValid = false;
    }
    if (pwd1.length() < MIN_DIRECTORY_MANAGER_PWD)
    {
      errorMsgs.add(INFO_PWD_TOO_SHORT.get(MIN_DIRECTORY_MANAGER_PWD));
      qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_PWD, true);
      if (pwd2 == null || pwd2.length() < MIN_DIRECTORY_MANAGER_PWD)
      {
        qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_PWD_CONFIRM, true);
      }
      pwdValid = false;
    }

    if (pwdValid)
    {
      getUserData().setDirectoryManagerPwd(pwd1);
      qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_PWD, false);
      qs.displayFieldInvalid(FieldName.DIRECTORY_MANAGER_PWD_CONFIRM, false);
    }

    // For the moment do not enable JMX
    int defaultJMXPort = UserData.getDefaultJMXPort(new int[] { port, securePort });
    if (defaultJMXPort != -1)
    {
      //getUserData().setServerJMXPort(defaultJMXPort);
      getUserData().setServerJMXPort(-1);
    }

    if (!errorMsgs.isEmpty())
    {
      throw new UserDataException(Step.SERVER_SETTINGS, getMessageFromCollection(errorMsgs, "\n"));
    }
    if (confirmationMsg != null)
    {
      throw new UserDataConfirmationException(Step.SERVER_SETTINGS, confirmationMsg);
    }
  }

  private LocalizableMessage getCannotBindErrorMessage(int port)
  {
    if (isPrivilegedPort(port))
    {
      return INFO_CANNOT_BIND_PRIVILEDGED_PORT.get(port);
    }
    return INFO_CANNOT_BIND_PORT.get(port);
  }

  /**
   * Validate the data provided by the user in the data options panel and update
   * the userData object according to that content.
   *
   * @throws UserDataException
   *           if the data provided by the user is not valid.
   */
  private void updateUserDataForReplicationOptionsPanel(QuickSetup qs) throws UserDataException
  {
    boolean hasGlobalAdministrators = false;
    int replicationPort = -1;
    boolean secureReplication = false;
    Integer port = null;
    List<LocalizableMessage> errorMsgs = new ArrayList<>();

    DataReplicationOptions.Type type = (DataReplicationOptions.Type) qs.getFieldValue(FieldName.REPLICATION_OPTIONS);
    String host = qs.getFieldStringValue(FieldName.REMOTE_SERVER_HOST);
    String dn = qs.getFieldStringValue(FieldName.REMOTE_SERVER_DN);
    String pwd = qs.getFieldStringValue(FieldName.REMOTE_SERVER_PWD);

    if (type != DataReplicationOptions.Type.STANDALONE)
    {
      // Check replication port
      replicationPort = checkReplicationPort(qs, errorMsgs);
      secureReplication = (Boolean) qs.getFieldValue(FieldName.REPLICATION_SECURE);
    }

    UserDataConfirmationException confirmEx = null;
    switch (type)
    {
    case IN_EXISTING_TOPOLOGY:
    {
      String sPort = qs.getFieldStringValue(FieldName.REMOTE_SERVER_PORT);
      checkRemoteHostPortDnAndPwd(host, sPort, dn, pwd, qs, errorMsgs);

      if (errorMsgs.isEmpty())
      {
        port = Integer.parseInt(sPort);
        // Try to connect
        boolean[] globalAdmin = { hasGlobalAdministrators };
        String[] effectiveDn = { dn };
        try
        {
          updateUserDataWithADS(host, port, dn, pwd, qs, errorMsgs, globalAdmin, effectiveDn);
        }
        catch (UserDataConfirmationException e)
        {
          confirmEx = e;
        }
        hasGlobalAdministrators = globalAdmin[0];
        dn = effectiveDn[0];
      }
      break;
    }
    case STANDALONE:
    {
      getUserData().setSuffixesToReplicateOptions(
          new SuffixesToReplicateOptions(SuffixesToReplicateOptions.Type.NO_SUFFIX_TO_REPLICATE,
              new HashSet<SuffixDescriptor>(), new HashSet<SuffixDescriptor>()));
      break;
    }
    case FIRST_IN_TOPOLOGY:
    {
      getUserData().setSuffixesToReplicateOptions(
          new SuffixesToReplicateOptions(SuffixesToReplicateOptions.Type.NEW_SUFFIX_IN_TOPOLOGY,
              new HashSet<SuffixDescriptor>(), new HashSet<SuffixDescriptor>()));
      break;
    }
    default:
      throw new IllegalStateException("Do not know what to do with type: " + type);
    }

    if (errorMsgs.isEmpty())
    {
      AuthenticationData auth = new AuthenticationData();
      auth.setHostName(host);
      if (port != null)
      {
        auth.setPort(port);
      }
      auth.setDn(dn);
      auth.setPwd(pwd);
      auth.setUseSecureConnection(true);

      getUserData().setReplicationOptions(createDataReplicationOptions(replicationPort, secureReplication, type, auth));
      getUserData().createAdministrator(
          !hasGlobalAdministrators && type == DataReplicationOptions.Type.IN_EXISTING_TOPOLOGY);
    }
    if (!errorMsgs.isEmpty())
    {
      throw new UserDataException(Step.REPLICATION_OPTIONS, getMessageFromCollection(errorMsgs, "\n"));
    }
    if (confirmEx != null)
    {
      throw confirmEx;
    }
  }

  private DataReplicationOptions createDataReplicationOptions(int replicationPort, boolean secureReplication,
      DataReplicationOptions.Type type, AuthenticationData auth)
  {
    switch (type)
    {
    case IN_EXISTING_TOPOLOGY:
      return DataReplicationOptions.createInExistingTopology(auth, replicationPort, secureReplication);
    case STANDALONE:
      return DataReplicationOptions.createStandalone();
    case FIRST_IN_TOPOLOGY:
      return DataReplicationOptions.createFirstInTopology(replicationPort, secureReplication);
    default:
      throw new IllegalStateException("Do not know what to do with type: " + type);
    }
  }

  private int checkReplicationPort(QuickSetup qs, List<LocalizableMessage> errorMsgs)
  {
    int replicationPort = -1;
    String sPort = qs.getFieldStringValue(FieldName.REPLICATION_PORT);
    try
    {
      replicationPort = Integer.parseInt(sPort);
      if (replicationPort < MIN_PORT_VALUE || replicationPort > MAX_PORT_VALUE)
      {
        errorMsgs.add(INFO_INVALID_REPLICATION_PORT_VALUE_RANGE.get(MIN_PORT_VALUE, MAX_PORT_VALUE));
        qs.displayFieldInvalid(FieldName.SERVER_PORT, true);
      }
      else if (!canUseAsPort(replicationPort))
      {
        errorMsgs.add(getCannotBindErrorMessage(replicationPort));
        qs.displayFieldInvalid(FieldName.REPLICATION_PORT, true);
      }
      else
      {
        /* Check that we did not chose this port for another protocol */
        SecurityOptions sec = getUserData().getSecurityOptions();
        if (replicationPort == getUserData().getServerPort() || replicationPort == getUserData().getServerJMXPort()
            || (replicationPort == sec.getSslPort() && sec.getEnableSSL()))
        {
          errorMsgs.add(INFO_REPLICATION_PORT_ALREADY_CHOSEN_FOR_OTHER_PROTOCOL.get());
          qs.displayFieldInvalid(FieldName.REPLICATION_PORT, true);
        }
        else
        {
          qs.displayFieldInvalid(FieldName.REPLICATION_PORT, false);
        }
      }
    }
    catch (NumberFormatException nfe)
    {
      errorMsgs.add(INFO_INVALID_REPLICATION_PORT_VALUE_RANGE.get(MIN_PORT_VALUE, MAX_PORT_VALUE));
      qs.displayFieldInvalid(FieldName.REPLICATION_PORT, true);
    }
    return replicationPort;
  }

  private void checkRemoteHostPortDnAndPwd(String host, String sPort, String dn, String pwd, QuickSetup qs,
      List<LocalizableMessage> errorMsgs)
  {
    // Check host
    if (host == null || host.length() == 0)
    {
      errorMsgs.add(INFO_EMPTY_REMOTE_HOST.get());
      qs.displayFieldInvalid(FieldName.REMOTE_SERVER_HOST, true);
    }
    else
    {
      qs.displayFieldInvalid(FieldName.REMOTE_SERVER_HOST, false);
    }

    // Check port
    try
    {
      Integer.parseInt(sPort);
      qs.displayFieldInvalid(FieldName.REMOTE_SERVER_PORT, false);
    }
    catch (Throwable t)
    {
      errorMsgs.add(INFO_INVALID_REMOTE_PORT.get());
      qs.displayFieldInvalid(FieldName.REMOTE_SERVER_PORT, true);
    }

    // Check dn
    if (dn == null || dn.length() == 0)
    {
      errorMsgs.add(INFO_EMPTY_REMOTE_DN.get());
      qs.displayFieldInvalid(FieldName.REMOTE_SERVER_DN, true);
    }
    else
    {
      qs.displayFieldInvalid(FieldName.REMOTE_SERVER_DN, false);
    }

    // Check password
    if (pwd == null || pwd.length() == 0)
    {
      errorMsgs.add(INFO_EMPTY_REMOTE_PWD.get());
      qs.displayFieldInvalid(FieldName.REMOTE_SERVER_PWD, true);
    }
    else
    {
      qs.displayFieldInvalid(FieldName.REMOTE_SERVER_PWD, false);
    }
  }

  private void updateUserDataWithADS(String host, int port, String dn, String pwd, QuickSetup qs,
      List<LocalizableMessage> errorMsgs, boolean[] hasGlobalAdministrators, String[] effectiveDn)
      throws UserDataException
  {
    host = getHostNameForLdapUrl(host);
    String ldapUrl = "ldaps://" + host + ":" + port;
    InitialLdapContext ctx = null;

    ApplicationTrustManager trustManager = getTrustManager();
    trustManager.setHost(host);
    trustManager.resetLastRefusedItems();
    try
    {
      effectiveDn[0] = dn;
      try
      {
        ctx = createLdapsContext(ldapUrl, dn, pwd, getConnectTimeout(), null, trustManager, null);
      }
      catch (Throwable t)
      {
        if (!isCertificateException(t))
        {
          // Try using a global administrator
          dn = ADSContext.getAdministratorDN(dn);
          effectiveDn[0] = dn;
          ctx = createLdapsContext(ldapUrl, dn, pwd, getConnectTimeout(), null, trustManager, null);
        }
        else
        {
          throw t;
        }
      }

      ADSContext adsContext = new ADSContext(ctx);
      if (adsContext.hasAdminData())
      {
        /* Check if there are already global administrators */
        Set<?> administrators = adsContext.readAdministratorRegistry();
        hasGlobalAdministrators[0] = !administrators.isEmpty();
        Set<TopologyCacheException> exceptions = updateUserDataWithSuffixesInADS(adsContext, trustManager);
        Set<LocalizableMessage> exceptionMsgs = new LinkedHashSet<>();
        /* Check the exceptions and see if we throw them or not. */
        for (TopologyCacheException e : exceptions)
        {
          switch (e.getType())
          {
          case NOT_GLOBAL_ADMINISTRATOR:
            LocalizableMessage errorMsg = INFO_NOT_GLOBAL_ADMINISTRATOR_PROVIDED.get();
            throw new UserDataException(Step.REPLICATION_OPTIONS, errorMsg);
          case GENERIC_CREATING_CONNECTION:
            if (isCertificateException(e.getCause()))
            {
              UserDataCertificateException.Type excType;
              ApplicationTrustManager.Cause cause = null;
              if (e.getTrustManager() != null)
              {
                cause = e.getTrustManager().getLastRefusedCause();
              }
              logger.info(LocalizableMessage.raw("Certificate exception cause: " + cause));
              if (cause == ApplicationTrustManager.Cause.NOT_TRUSTED)
              {
                excType = UserDataCertificateException.Type.NOT_TRUSTED;
              }
              else if (cause == ApplicationTrustManager.Cause.HOST_NAME_MISMATCH)
              {
                excType = UserDataCertificateException.Type.HOST_NAME_MISMATCH;
              }
              else
              {
                excType = null;
              }
              if (excType != null)
              {
                String h;
                int p;
                try
                {
                  URI uri = new URI(e.getLdapUrl());
                  h = uri.getHost();
                  p = uri.getPort();
                }
                catch (Throwable t)
                {
                  logger.warn(LocalizableMessage.raw("Error parsing ldap url of TopologyCacheException.", t));
                  h = INFO_NOT_AVAILABLE_LABEL.get().toString();
                  p = -1;
                }
                throw new UserDataCertificateException(Step.REPLICATION_OPTIONS, INFO_CERTIFICATE_EXCEPTION.get(h, p),
                    e.getCause(), h, p, e.getTrustManager().getLastRefusedChain(), e.getTrustManager()
                        .getLastRefusedAuthType(), excType);
              }
            }
            break;
          default:
            break;
          }
          exceptionMsgs.add(getMessage(e));
        }
        if (!exceptionMsgs.isEmpty())
        {
          LocalizableMessage confirmationMsg =
              INFO_ERROR_READING_REGISTERED_SERVERS_CONFIRM.get(getMessageFromCollection(exceptionMsgs, "\n"));
          throw new UserDataConfirmationException(Step.REPLICATION_OPTIONS, confirmationMsg);
        }
      }
      else
      {
        updateUserDataWithSuffixesInServer(ctx);
      }
    }
    catch (UserDataException ude)
    {
      throw ude;
    }
    catch (Throwable t)
    {
      logger.info(LocalizableMessage.raw("Error connecting to remote server.", t));
      if (isCertificateException(t))
      {
        UserDataCertificateException.Type excType;
        ApplicationTrustManager.Cause cause = trustManager.getLastRefusedCause();
        logger.info(LocalizableMessage.raw("Certificate exception cause: " + cause));
        if (cause == ApplicationTrustManager.Cause.NOT_TRUSTED)
        {
          excType = UserDataCertificateException.Type.NOT_TRUSTED;
        }
        else if (cause == ApplicationTrustManager.Cause.HOST_NAME_MISMATCH)
        {
          excType = UserDataCertificateException.Type.HOST_NAME_MISMATCH;
        }
        else
        {
          excType = null;
        }

        if (excType != null)
        {
          throw new UserDataCertificateException(Step.REPLICATION_OPTIONS, INFO_CERTIFICATE_EXCEPTION.get(host, port),
              t, host, port, trustManager.getLastRefusedChain(), trustManager.getLastRefusedAuthType(), excType);
        }
        else
        {
          qs.displayFieldInvalid(FieldName.REMOTE_SERVER_HOST, true);
          qs.displayFieldInvalid(FieldName.REMOTE_SERVER_PORT, true);
          qs.displayFieldInvalid(FieldName.REMOTE_SERVER_DN, true);
          qs.displayFieldInvalid(FieldName.REMOTE_SERVER_PWD, true);
          errorMsgs.add(INFO_CANNOT_CONNECT_TO_REMOTE_GENERIC.get(host + ":" + port, t));
        }
      }
      else if (t instanceof NamingException)
      {
        errorMsgs.add(getMessageForException((NamingException) t, host + ":" + port));
        qs.displayFieldInvalid(FieldName.REMOTE_SERVER_DN, true);
        qs.displayFieldInvalid(FieldName.REMOTE_SERVER_PWD, true);
        if (!(t instanceof NamingSecurityException))
        {
          qs.displayFieldInvalid(FieldName.REMOTE_SERVER_HOST, true);
          qs.displayFieldInvalid(FieldName.REMOTE_SERVER_PORT, true);
        }
      }
      else if (t instanceof ADSContextException)
      {
        errorMsgs.add(INFO_REMOTE_ADS_EXCEPTION.get(host + ":" + port, t));
      }
      else
      {
        throw new UserDataException(Step.REPLICATION_OPTIONS, getThrowableMsg(INFO_BUG_MSG.get(), t));
      }
    }
    finally
    {
      StaticUtils.close(ctx);
    }
  }

  /**
   * Validate the data provided by the user in the create global administrator
   * panel and update the UserInstallData object according to that content.
   *
   * @throws UserDataException
   *           if the data provided by the user is not valid.
   */
  private void updateUserDataForCreateAdministratorPanel(QuickSetup qs) throws UserDataException
  {
    List<LocalizableMessage> errorMsgs = new ArrayList<>();

    // Check the Global Administrator UID
    String uid = qs.getFieldStringValue(FieldName.GLOBAL_ADMINISTRATOR_UID);

    if (uid == null || uid.trim().length() == 0)
    {
      errorMsgs.add(INFO_EMPTY_ADMINISTRATOR_UID.get());
      qs.displayFieldInvalid(FieldName.GLOBAL_ADMINISTRATOR_UID, true);
    }
    else
    {
      getUserData().setGlobalAdministratorUID(uid);
      qs.displayFieldInvalid(FieldName.GLOBAL_ADMINISTRATOR_UID, false);
    }

    // Check the provided passwords
    String pwd1 = qs.getFieldStringValue(FieldName.GLOBAL_ADMINISTRATOR_PWD);
    String pwd2 = qs.getFieldStringValue(FieldName.GLOBAL_ADMINISTRATOR_PWD_CONFIRM);
    if (pwd1 == null)
    {
      pwd1 = "";
    }

    boolean pwdValid = true;
    if (!pwd1.equals(pwd2))
    {
      errorMsgs.add(INFO_NOT_EQUAL_PWD.get());
      qs.displayFieldInvalid(FieldName.GLOBAL_ADMINISTRATOR_PWD_CONFIRM, true);
      pwdValid = false;
    }
    if (pwd1.length() < MIN_DIRECTORY_MANAGER_PWD)
    {
      errorMsgs.add(INFO_PWD_TOO_SHORT.get(MIN_DIRECTORY_MANAGER_PWD));
      qs.displayFieldInvalid(FieldName.GLOBAL_ADMINISTRATOR_PWD, true);
      if (pwd2 == null || pwd2.length() < MIN_DIRECTORY_MANAGER_PWD)
      {
        qs.displayFieldInvalid(FieldName.GLOBAL_ADMINISTRATOR_PWD_CONFIRM, true);
      }
      pwdValid = false;
    }

    if (pwdValid)
    {
      getUserData().setGlobalAdministratorPassword(pwd1);
      qs.displayFieldInvalid(FieldName.GLOBAL_ADMINISTRATOR_PWD, false);
      qs.displayFieldInvalid(FieldName.GLOBAL_ADMINISTRATOR_PWD_CONFIRM, false);
    }

    if (!errorMsgs.isEmpty())
    {
      throw new UserDataException(Step.CREATE_GLOBAL_ADMINISTRATOR, getMessageFromCollection(errorMsgs, "\n"));
    }
  }

  /**
   * Validate the data provided by the user in the replicate suffixes options
   * panel and update the UserInstallData object according to that content.
   *
   * @throws UserDataException
   *           if the data provided by the user is not valid.
   */
  @SuppressWarnings("unchecked")
  private void updateUserDataForSuffixesOptionsPanel(QuickSetup qs) throws UserDataException
  {
    List<LocalizableMessage> errorMsgs = new ArrayList<>();
    if (qs.getFieldValue(FieldName.SUFFIXES_TO_REPLICATE_OPTIONS) ==
        SuffixesToReplicateOptions.Type.REPLICATE_WITH_EXISTING_SUFFIXES)
    {
      Set<?> s = (Set<?>) qs.getFieldValue(FieldName.SUFFIXES_TO_REPLICATE);
      if (s.isEmpty())
      {
        errorMsgs.add(INFO_NO_SUFFIXES_CHOSEN_TO_REPLICATE.get());
        qs.displayFieldInvalid(FieldName.SUFFIXES_TO_REPLICATE, true);
      }
      else
      {
        Set<SuffixDescriptor> chosen = new HashSet<>();
        for (Object o : s)
        {
          chosen.add((SuffixDescriptor) o);
        }
        qs.displayFieldInvalid(FieldName.SUFFIXES_TO_REPLICATE, false);
        Set<SuffixDescriptor> available = getUserData().getSuffixesToReplicateOptions().getAvailableSuffixes();
        Map<String, BackendTypeUIAdapter> suffixesBackendTypes =
            (Map<String, BackendTypeUIAdapter>) qs.getFieldValue(FieldName.SUFFIXES_TO_REPLICATE_BACKEND_TYPE);
        SuffixesToReplicateOptions options = new SuffixesToReplicateOptions(
            SuffixesToReplicateOptions.Type.REPLICATE_WITH_EXISTING_SUFFIXES, available, chosen, suffixesBackendTypes);
        getUserData().setSuffixesToReplicateOptions(options);
      }
      getUserData().setRemoteWithNoReplicationPort(getRemoteWithNoReplicationPort(getUserData()));
    }
    else
    {
      Set<SuffixDescriptor> available = getUserData().getSuffixesToReplicateOptions().getAvailableSuffixes();
      Set<SuffixDescriptor> chosen = getUserData().getSuffixesToReplicateOptions().getSuffixes();
      SuffixesToReplicateOptions options =
          new SuffixesToReplicateOptions(SuffixesToReplicateOptions.Type.NEW_SUFFIX_IN_TOPOLOGY, available, chosen);
      getUserData().setSuffixesToReplicateOptions(options);
    }

    if (!errorMsgs.isEmpty())
    {
      throw new UserDataException(Step.SUFFIXES_OPTIONS, getMessageFromCollection(errorMsgs, "\n"));
    }
  }

  /**
   * Validate the data provided by the user in the remote server replication
   * port panel and update the userData object according to that content.
   *
   * @throws UserDataException
   *           if the data provided by the user is not valid.
   */
  private void updateUserDataForRemoteReplicationPorts(QuickSetup qs) throws UserDataException
  {
    List<LocalizableMessage> errorMsgs = new ArrayList<>();
    Map<ServerDescriptor, AuthenticationData> servers = getUserData().getRemoteWithNoReplicationPort();
    Map<?, ?> hm = (Map<?, ?>) qs.getFieldValue(FieldName.REMOTE_REPLICATION_PORT);
    Map<?, ?> hmSecure = (Map<?, ?>) qs.getFieldValue(FieldName.REMOTE_REPLICATION_SECURE);
    for (ServerDescriptor server : servers.keySet())
    {
      String hostName = server.getHostName();
      boolean secureReplication = (Boolean) hmSecure.get(server.getId());
      String sPort = (String) hm.get(server.getId());
      try
      {
        int replicationPort = Integer.parseInt(sPort);
        if (replicationPort < MIN_PORT_VALUE || replicationPort > MAX_PORT_VALUE)
        {
          errorMsgs.add(INFO_INVALID_REMOTE_REPLICATION_PORT_VALUE_RANGE.get(getHostPort(server), MIN_PORT_VALUE,
              MAX_PORT_VALUE));
        }
        if (hostName.equalsIgnoreCase(getUserData().getHostName()))
        {
          int securePort = -1;
          if (getUserData().getSecurityOptions().getEnableSSL())
          {
            securePort = getUserData().getSecurityOptions().getSslPort();
          }
          if (replicationPort == getUserData().getServerPort() || replicationPort == getUserData().getServerJMXPort()
              || replicationPort == getUserData().getReplicationOptions().getReplicationPort()
              || replicationPort == securePort)
          {
            errorMsgs.add(INFO_REMOTE_REPLICATION_PORT_ALREADY_CHOSEN_FOR_OTHER_PROTOCOL.get(getHostPort(server)));
          }
        }
        AuthenticationData authData = new AuthenticationData();
        authData.setPort(replicationPort);
        authData.setUseSecureConnection(secureReplication);
        servers.put(server, authData);
      }
      catch (NumberFormatException nfe)
      {
        errorMsgs.add(INFO_INVALID_REMOTE_REPLICATION_PORT_VALUE_RANGE.get(hostName, MIN_PORT_VALUE, MAX_PORT_VALUE));
      }
    }

    if (!errorMsgs.isEmpty())
    {
      qs.displayFieldInvalid(FieldName.REMOTE_REPLICATION_PORT, true);
      throw new UserDataException(Step.REMOTE_REPLICATION_PORTS, getMessageFromCollection(errorMsgs, "\n"));
    }
    else
    {
      qs.displayFieldInvalid(FieldName.REMOTE_REPLICATION_PORT, false);
      getUserData().setRemoteWithNoReplicationPort(servers);
    }
  }

  /**
   * Validate the data provided by the user in the new suffix data options panel
   * and update the UserInstallData object according to that content.
   *
   * @throws UserDataException
   *           if the data provided by the user is not valid.
   */
  @SuppressWarnings("unchecked")
  private void updateUserDataForNewSuffixOptionsPanel(final QuickSetup ui) throws UserDataException
  {
    final List<LocalizableMessage> errorMsgs = new ArrayList<>();
    // Singleton list with the provided baseDN (if exists and valid)
    List<String> baseDn = new LinkedList<>();
    boolean validBaseDn = checkProvidedBaseDn(ui, baseDn, errorMsgs);
    final NewSuffixOptions dataOptions = checkImportData(ui, baseDn, validBaseDn, errorMsgs);

    if (dataOptions != null)
    {
      getUserData().setBackendType((ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg>)
          ui.getFieldValue(FieldName.BACKEND_TYPE));
      getUserData().setNewSuffixOptions(dataOptions);
    }

    if (!errorMsgs.isEmpty())
    {
      throw new UserDataException(Step.NEW_SUFFIX_OPTIONS,
          getMessageFromCollection(errorMsgs, Constants.LINE_SEPARATOR));
    }
  }

  private NewSuffixOptions checkImportData(final QuickSetup ui, final List<String> baseDn, final boolean validBaseDn,
      final List<LocalizableMessage> errorMsgs)
  {
    if (baseDn.isEmpty())
    {
      return NewSuffixOptions.createEmpty(baseDn);
    }

    final NewSuffixOptions.Type type = (NewSuffixOptions.Type) ui.getFieldValue(FieldName.DATA_OPTIONS);
    switch (type)
    {
    case IMPORT_FROM_LDIF_FILE:
      return checkImportLDIFFile(ui, baseDn, validBaseDn, errorMsgs);

    case IMPORT_AUTOMATICALLY_GENERATED_DATA:
      return checkImportGeneratedData(ui, baseDn, validBaseDn, errorMsgs);

    default:
      if (validBaseDn)
      {
        return type == NewSuffixOptions.Type.CREATE_BASE_ENTRY ? NewSuffixOptions.createBaseEntry(baseDn)
            : NewSuffixOptions.createEmpty(baseDn);
      }
    }

    return null;
  }

  private NewSuffixOptions checkImportGeneratedData(final QuickSetup ui, final List<String> baseDn,
      final boolean validBaseDn, final List<LocalizableMessage> errorMsgs)
  {
    boolean fieldIsValid = true;
    final List<LocalizableMessage> localErrorMsgs = new LinkedList<>();
    final String nEntries = ui.getFieldStringValue(FieldName.NUMBER_ENTRIES);
    if (nEntries == null || "".equals(nEntries.trim()))
    {
      localErrorMsgs.add(INFO_NO_NUMBER_ENTRIES.get());
      fieldIsValid = false;
    }
    else
    {
      boolean nEntriesValid = false;
      try
      {
        int n = Integer.parseInt(nEntries);
        nEntriesValid = n >= MIN_NUMBER_ENTRIES && n <= MAX_NUMBER_ENTRIES;
      }
      catch (NumberFormatException nfe)
      {
        /* do nothing */
      }

      if (!nEntriesValid)
      {
        localErrorMsgs.add(INFO_INVALID_NUMBER_ENTRIES_RANGE.get(MIN_NUMBER_ENTRIES, MAX_NUMBER_ENTRIES));
        fieldIsValid = false;
      }
    }

    ui.displayFieldInvalid(FieldName.NUMBER_ENTRIES, !fieldIsValid);
    if (validBaseDn && localErrorMsgs.isEmpty())
    {
      return NewSuffixOptions.createAutomaticallyGenerated(baseDn, Integer.parseInt(nEntries));
    }
    errorMsgs.addAll(localErrorMsgs);

    return null;
  }

  private NewSuffixOptions checkImportLDIFFile(final QuickSetup ui, final List<String> baseDn,
      final boolean validBaseDn, final List<LocalizableMessage> errorMsgs)
  {
    final boolean fieldIsValid = false;
    final String ldifPath = ui.getFieldStringValue(FieldName.LDIF_PATH);
    if (ldifPath == null || ldifPath.trim().isEmpty())
    {
      errorMsgs.add(INFO_NO_LDIF_PATH.get());
    }
    else if (!fileExists(ldifPath))
    {
      errorMsgs.add(INFO_LDIF_FILE_DOES_NOT_EXIST.get());
    }
    else if (validBaseDn)
    {
      return NewSuffixOptions.createImportFromLDIF(baseDn, Collections.singletonList(ldifPath), null, null);
    }
    ui.displayFieldInvalid(FieldName.LDIF_PATH, !fieldIsValid);

    return null;
  }

  private boolean checkProvidedBaseDn(final QuickSetup ui, final List<String> baseDn,
      final List<LocalizableMessage> errorMsgs)
  {
    boolean validBaseDn = true;
    String dn = ui.getFieldStringValue(FieldName.DIRECTORY_BASE_DN);
    if (dn == null || dn.trim().length() == 0)
    {
      // Do nothing, the user does not want to provide a base DN.
      dn = "";
    }
    else if (!isDN(dn))
    {
      validBaseDn = false;
      errorMsgs.add(INFO_NOT_A_BASE_DN.get());
    }
    else if (isConfigurationDn(dn))
    {
      validBaseDn = false;
      errorMsgs.add(INFO_BASE_DN_IS_CONFIGURATION_DN.get());
    }
    else
    {
      baseDn.add(dn);
    }
    ui.displayFieldInvalid(FieldName.DIRECTORY_BASE_DN, !validBaseDn);

    return validBaseDn;
  }

  /**
   * Update the userData object according to the content of the runtime options
   * panel.
   */
  private void updateUserDataForRuntimeOptionsPanel(QuickSetup qs)
  {
    getUserData().setJavaArguments(UserData.SERVER_SCRIPT_NAME,
        (JavaArguments) qs.getFieldValue(FieldName.SERVER_JAVA_ARGUMENTS));
    getUserData().setJavaArguments(UserData.IMPORT_SCRIPT_NAME,
        (JavaArguments) qs.getFieldValue(FieldName.IMPORT_JAVA_ARGUMENTS));
  }

  /** Update the userData object according to the content of the review panel. */
  private void updateUserDataForReviewPanel(QuickSetup qs)
  {
    Boolean b = (Boolean) qs.getFieldValue(FieldName.SERVER_START_INSTALLER);
    getUserData().setStartServer(b);
    b = (Boolean) qs.getFieldValue(FieldName.ENABLE_WINDOWS_SERVICE);
    getUserData().setEnableWindowsService(b);
  }

  /**
   * Returns the number of free disk space in bytes required to install Open DS
   * For the moment we just return 20 Megabytes. TODO we might want to have
   * something dynamic to calculate the required free disk space for the
   * installation.
   *
   * @return the number of free disk space required to install Open DS.
   */
  private long getRequiredInstallSpace()
  {
    return 20 * 1024 * 1024;
  }

  /** Update the UserInstallData with the contents we discover in the ADS. */
  private Set<TopologyCacheException> updateUserDataWithSuffixesInADS(ADSContext adsContext,
      ApplicationTrustManager trustManager) throws TopologyCacheException
  {
    Set<TopologyCacheException> exceptions = new HashSet<>();
    SuffixesToReplicateOptions suf = getUserData().getSuffixesToReplicateOptions();
    SuffixesToReplicateOptions.Type type;

    if (suf == null || suf.getType() == SuffixesToReplicateOptions.Type.NO_SUFFIX_TO_REPLICATE)
    {
      type = SuffixesToReplicateOptions.Type.NO_SUFFIX_TO_REPLICATE;
    }
    else
    {
      type = SuffixesToReplicateOptions.Type.NEW_SUFFIX_IN_TOPOLOGY;
    }
    lastLoadedCache = new TopologyCache(adsContext, trustManager, getConnectTimeout());
    LinkedHashSet<PreferredConnection> cnx = new LinkedHashSet<>();
    cnx.add(PreferredConnection.getPreferredConnection(adsContext.getDirContext()));
    // We cannot use getPreferredConnections since the user data has not been
    // updated yet.
    lastLoadedCache.setPreferredConnections(cnx);
    lastLoadedCache.reloadTopology();
    Set<SuffixDescriptor> suffixes = lastLoadedCache.getSuffixes();
    Set<SuffixDescriptor> moreSuffixes = null;
    if (suf != null)
    {
      moreSuffixes = suf.getSuffixes();
    }
    getUserData().setSuffixesToReplicateOptions(new SuffixesToReplicateOptions(type, suffixes, moreSuffixes));

    /*
     * Analyze if we had any exception while loading servers. For the moment
     * only throw the exception found if the user did not provide the
     * Administrator DN and this caused a problem authenticating in one server
     * or if there is a certificate problem.
     */
    Set<ServerDescriptor> servers = lastLoadedCache.getServers();
    for (ServerDescriptor server : servers)
    {
      TopologyCacheException e = server.getLastException();
      if (e != null)
      {
        exceptions.add(e);
      }
    }
    return exceptions;
  }

  /**
   * Update the UserInstallData object with the contents of the server to which
   * we are connected with the provided InitialLdapContext.
   */
  private void updateUserDataWithSuffixesInServer(InitialLdapContext ctx) throws NamingException
  {
    SuffixesToReplicateOptions suf = getUserData().getSuffixesToReplicateOptions();
    SuffixesToReplicateOptions.Type type;
    Set<SuffixDescriptor> suffixes = new HashSet<>();
    if (suf != null)
    {
      type = suf.getType();
    }
    else
    {
      type = SuffixesToReplicateOptions.Type.NEW_SUFFIX_IN_TOPOLOGY;
    }

    ServerDescriptor s = createStandalone(ctx, new TopologyCacheFilter());
    Set<ReplicaDescriptor> replicas = s.getReplicas();
    for (ReplicaDescriptor replica : replicas)
    {
      suffixes.add(replica.getSuffix());
    }
    Set<SuffixDescriptor> moreSuffixes = null;
    if (suf != null)
    {
      moreSuffixes = suf.getSuffixes();
    }
    getUserData().setSuffixesToReplicateOptions(new SuffixesToReplicateOptions(type, suffixes, moreSuffixes));
  }

  /**
   * Returns the keystore path to be used for generating a self-signed
   * certificate.
   *
   * @return the keystore path to be used for generating a self-signed
   *         certificate.
   */
  protected String getSelfSignedKeystorePath()
  {
    return getPath2("keystore");
  }

  /**
   * Returns the trustmanager path to be used for generating a self-signed
   * certificate.
   *
   * @return the trustmanager path to be used for generating a self-signed
   *         certificate.
   */
  private String getTrustManagerPath()
  {
    return getPath2("truststore");
  }

  /**
   * Returns the path of the self-signed that we export to be able to create a
   * truststore.
   *
   * @return the path of the self-signed that is exported.
   */
  private String getTemporaryCertificatePath()
  {
    return getPath2("server-cert.txt");
  }

  /**
   * Returns the path to be used to store the password of the keystore.
   *
   * @return the path to be used to store the password of the keystore.
   */
  private String getKeystorePinPath()
  {
    return getPath2("keystore.pin");
  }

  private String getPath2(String relativePath)
  {
    String parentFile = getPath(getInstancePath(), Installation.CONFIG_PATH_RELATIVE);
    return getPath(parentFile, relativePath);
  }

  /**
   * Returns the validity period to be used to generate the self-signed
   * certificate.
   *
   * @return the validity period to be used to generate the self-signed
   *         certificate.
   */
  private int getSelfSignedCertificateValidity()
  {
    return 20 * 365;
  }

  /**
   * Returns the Subject DN to be used to generate the self-signed certificate.
   *
   * @return the Subject DN to be used to generate the self-signed certificate.
   */
  private String getSelfSignedCertificateSubjectDN(KeyType keyType)
  {
    return "cn=" + Rdn.escapeValue(getUserData().getHostName()) + ",O=OpenDJ " + keyType + " Self-Signed Certificate";
  }

  /**
   * Returns the self-signed certificate password used for this session. This
   * method calls <code>createSelfSignedCertificatePwd()</code> the first time
   * this method is called.
   *
   * @return the self-signed certificate password used for this session.
   */
  protected String getSelfSignedCertificatePwd()
  {
    if (selfSignedCertPw == null)
    {
      selfSignedCertPw = SetupUtils.createSelfSignedCertificatePwd();
    }
    return new String(selfSignedCertPw);
  }

  private Map<ServerDescriptor, AuthenticationData> getRemoteWithNoReplicationPort(UserData userData)
  {
    Map<ServerDescriptor, AuthenticationData> servers = new HashMap<>();
    Set<SuffixDescriptor> suffixes = userData.getSuffixesToReplicateOptions().getSuffixes();
    for (SuffixDescriptor suffix : suffixes)
    {
      for (ReplicaDescriptor replica : suffix.getReplicas())
      {
        ServerDescriptor server = replica.getServer();
        Object v = server.getServerProperties().get(IS_REPLICATION_SERVER);
        if (!Boolean.TRUE.equals(v))
        {
          AuthenticationData authData = new AuthenticationData();
          authData.setPort(Constants.DEFAULT_REPLICATION_PORT);
          authData.setUseSecureConnection(false);
          servers.put(server, authData);
        }
      }
    }
    return servers;
  }

  private InitialLdapContext createLocalContext() throws NamingException
  {
    String ldapUrl =
        "ldaps://" + getHostNameForLdapUrl(getUserData().getHostName()) + ":" + getUserData().getAdminConnectorPort();
    String dn = getUserData().getDirectoryManagerDn();
    String pwd = getUserData().getDirectoryManagerPwd();
    return createLdapsContext(ldapUrl, dn, pwd, getConnectTimeout(), null, null, null);
  }

  /**
   * Gets an InitialLdapContext based on the information that appears on the
   * provided ServerDescriptor.
   *
   * @param server
   *          the object describing the server.
   * @param trustManager
   *          the trust manager to be used to establish the connection.
   * @param cnx
   *          the list of preferred LDAP URLs to be used to connect to the
   *          server.
   * @return the InitialLdapContext to the remote server.
   * @throws ApplicationException
   *           if something goes wrong.
   */
  private InitialLdapContext getRemoteConnection(ServerDescriptor server, ApplicationTrustManager trustManager,
      Set<PreferredConnection> cnx) throws ApplicationException
  {
    Map<ADSContext.ServerProperty, Object> adsProperties;
    AuthenticationData auth = getUserData().getReplicationOptions().getAuthenticationData();
    if (!server.isRegistered())
    {
      /*
       * Create adsProperties to be able to use the class ServerLoader to get
       * the connection. Just update the connection parameters with what the
       * user chose in the Topology Options panel (i.e. even if SSL is enabled
       * on the remote server, use standard LDAP to connect to the server if the
       * user specified the LDAP port: this avoids having an issue with the
       * certificate if it has not been accepted previously by the user).
       */
      adsProperties = new HashMap<>();
      adsProperties.put(ADSContext.ServerProperty.HOST_NAME, server.getHostName());
      if (auth.useSecureConnection())
      {
        adsProperties.put(ADSContext.ServerProperty.LDAPS_PORT, String.valueOf(auth.getPort()));
        adsProperties.put(ADSContext.ServerProperty.LDAPS_ENABLED, "true");
      }
      else
      {
        adsProperties.put(ADSContext.ServerProperty.LDAP_PORT, String.valueOf(auth.getPort()));
        adsProperties.put(ADSContext.ServerProperty.LDAP_ENABLED, "true");
      }
      server.setAdsProperties(adsProperties);
    }
    return getRemoteConnection(server, auth.getDn(), auth.getPwd(), trustManager, getConnectTimeout(), cnx);
  }

  /**
   * Initializes a suffix with the contents of a replica that has a given
   * replication id.
   *
   * @param ctx
   *          the connection to the server whose suffix we want to initialize.
   * @param replicaId
   *          the replication ID of the replica we want to use to initialize the
   *          contents of the suffix.
   * @param suffixDn
   *          the dn of the suffix.
   * @param displayProgress
   *          whether we want to display progress or not.
   * @param sourceServerDisplay
   *          the string to be used to represent the server that contains the
   *          data that will be used to initialize the suffix.
   * @throws ApplicationException
   *           if an unexpected error occurs.
   * @throws PeerNotFoundException
   *           if the replication mechanism cannot find a peer.
   */
  public void initializeSuffix(InitialLdapContext ctx, int replicaId, String suffixDn, boolean displayProgress,
      String sourceServerDisplay) throws ApplicationException, PeerNotFoundException
  {
    boolean taskCreated = false;
    int i = 1;
    boolean isOver = false;
    String dn = null;
    BasicAttributes attrs = new BasicAttributes();
    Attribute oc = new BasicAttribute("objectclass");
    oc.add("top");
    oc.add("ds-task");
    oc.add("ds-task-initialize-from-remote-replica");
    attrs.put(oc);
    attrs.put("ds-task-class-name", "org.opends.server.tasks.InitializeTask");
    attrs.put("ds-task-initialize-domain-dn", suffixDn);
    attrs.put("ds-task-initialize-replica-server-id", String.valueOf(replicaId));
    while (!taskCreated)
    {
      checkAbort();
      String id = "quicksetup-initialize" + i;
      dn = "ds-task-id=" + id + ",cn=Scheduled Tasks,cn=Tasks";
      attrs.put("ds-task-id", id);
      try
      {
        DirContext dirCtx = ctx.createSubcontext(dn, attrs);
        taskCreated = true;
        logger.info(LocalizableMessage.raw("created task entry: " + attrs));
        dirCtx.close();
      }
      catch (NameAlreadyBoundException x)
      {
        logger.warn(LocalizableMessage.raw("A task with dn: " + dn + " already existed."));
      }
      catch (NamingException ne)
      {
        logger.error(LocalizableMessage.raw("Error creating task " + attrs, ne));
        throw new ApplicationException(ReturnCode.APPLICATION_ERROR, getThrowableMsg(
            INFO_ERROR_LAUNCHING_INITIALIZATION.get(sourceServerDisplay), ne), ne);
      }
      i++;
    }
    // Wait until it is over
    SearchControls searchControls = new SearchControls();
    searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
    String filter = "objectclass=*";
    searchControls.setReturningAttributes(new String[] { "ds-task-unprocessed-entry-count",
      "ds-task-processed-entry-count", "ds-task-log-message", "ds-task-state" });
    LocalizableMessage lastDisplayedMsg = null;
    String lastLogMsg = null;
    long lastTimeMsgDisplayed = -1;
    long lastTimeMsgLogged = -1;
    long totalEntries = 0;
    while (!isOver)
    {
      if (canceled)
      {
        // TODO: we should try to cleanly abort the initialize.  As we have
        // aborted the install, the server will be stopped and the remote
        // server will receive a connect error.
        checkAbort();
      }
      StaticUtils.sleep(500);
      if (canceled)
      {
        // TODO: we should try to cleanly abort the initialize.  As we have
        // aborted the install, the server will be stopped and the remote
        // server will receive a connect error.
        checkAbort();
      }
      try
      {
        NamingEnumeration<SearchResult> res = ctx.search(dn, filter, searchControls);
        SearchResult sr = null;
        try
        {
          while (res.hasMore())
          {
            sr = res.next();
          }
        }
        finally
        {
          res.close();
        }
        // Get the number of entries that have been handled and
        // a percentage...
        LocalizableMessage msg;
        String sProcessed = getFirstValue(sr, "ds-task-processed-entry-count");
        String sUnprocessed = getFirstValue(sr, "ds-task-unprocessed-entry-count");
        long processed = -1;
        long unprocessed = -1;
        if (sProcessed != null)
        {
          processed = Integer.parseInt(sProcessed);
        }
        if (sUnprocessed != null)
        {
          unprocessed = Integer.parseInt(sUnprocessed);
        }
        totalEntries = Math.max(totalEntries, processed + unprocessed);

        if (processed != -1 && unprocessed != -1)
        {
          if (processed + unprocessed > 0)
          {
            long perc = (100 * processed) / (processed + unprocessed);
            msg = INFO_INITIALIZE_PROGRESS_WITH_PERCENTAGE.get(sProcessed, perc);
          }
          else
          {
            //msg = INFO_NO_ENTRIES_TO_INITIALIZE.get();
            msg = null;
          }
        }
        else if (processed != -1)
        {
          msg = INFO_INITIALIZE_PROGRESS_WITH_PROCESSED.get(sProcessed);
        }
        else if (unprocessed != -1)
        {
          msg = INFO_INITIALIZE_PROGRESS_WITH_UNPROCESSED.get(sUnprocessed);
        }
        else
        {
          msg = lastDisplayedMsg;
        }

        if (msg != null)
        {
          long currentTime = System.currentTimeMillis();
          /* Refresh period: to avoid having too many lines in the log */
          long minRefreshPeriod;
          if (totalEntries < 100)
          {
            minRefreshPeriod = 0;
          }
          else if (totalEntries < 1000)
          {
            minRefreshPeriod = 1000;
          }
          else if (totalEntries < 10000)
          {
            minRefreshPeriod = 5000;
          }
          else
          {
            minRefreshPeriod = 10000;
          }
          if (currentTime - minRefreshPeriod > lastTimeMsgLogged)
          {
            lastTimeMsgLogged = currentTime;
            logger.info(LocalizableMessage.raw("Progress msg: " + msg));
          }
          if (displayProgress && currentTime - minRefreshPeriod > lastTimeMsgDisplayed && !msg.equals(lastDisplayedMsg))
          {
            notifyListeners(getFormattedProgress(msg));
            lastDisplayedMsg = msg;
            notifyListeners(getLineBreak());
            lastTimeMsgDisplayed = currentTime;
          }
        }

        String logMsg = getFirstValue(sr, "ds-task-log-message");
        if (logMsg != null && !logMsg.equals(lastLogMsg))
        {
          logger.info(LocalizableMessage.raw(logMsg));
          lastLogMsg = logMsg;
        }
        InstallerHelper helper = new InstallerHelper();
        String state = getFirstValue(sr, "ds-task-state");

        if (helper.isDone(state) || helper.isStoppedByError(state))
        {
          isOver = true;
          LocalizableMessage errorMsg;
          logger.info(LocalizableMessage.raw("Last task entry: " + sr));
          if (displayProgress && msg != null && !msg.equals(lastDisplayedMsg))
          {
            notifyListeners(getFormattedProgress(msg));
            lastDisplayedMsg = msg;
            notifyListeners(getLineBreak());
          }

          if (lastLogMsg != null)
          {
            errorMsg =
                INFO_ERROR_DURING_INITIALIZATION_LOG.get(sourceServerDisplay, lastLogMsg, state, sourceServerDisplay);
          }
          else
          {
            errorMsg = INFO_ERROR_DURING_INITIALIZATION_NO_LOG.get(sourceServerDisplay, state, sourceServerDisplay);
          }

          logger.warn(LocalizableMessage.raw("Processed errorMsg: " + errorMsg));
          if (helper.isCompletedWithErrors(state))
          {
            if (displayProgress)
            {
              notifyListeners(getFormattedWarning(errorMsg));
            }
          }
          else if (!helper.isSuccessful(state) || helper.isStoppedByError(state))
          {
            ApplicationException ae = new ApplicationException(ReturnCode.APPLICATION_ERROR, errorMsg, null);
            if (lastLogMsg == null || helper.isPeersNotFoundError(lastLogMsg))
            {
              logger.warn(LocalizableMessage.raw("Throwing peer not found error.  " + "Last Log Msg: " + lastLogMsg));
              // Assume that this is a peer not found error.
              throw new PeerNotFoundException(errorMsg);
            }
            else
            {
              logger.error(LocalizableMessage.raw("Throwing ApplicationException."));
              throw ae;
            }
          }
          else if (displayProgress)
          {
            logger.info(LocalizableMessage.raw("Initialization completed successfully."));
            notifyListeners(getFormattedProgress(INFO_SUFFIX_INITIALIZED_SUCCESSFULLY.get()));
            notifyListeners(getLineBreak());
          }
        }
      }
      catch (NameNotFoundException x)
      {
        isOver = true;
        logger.info(LocalizableMessage.raw("Initialization entry not found."));
        if (displayProgress)
        {
          notifyListeners(getFormattedProgress(INFO_SUFFIX_INITIALIZED_SUCCESSFULLY.get()));
          notifyListeners(getLineBreak());
        }
      }
      catch (NamingException ne)
      {
        throw new ApplicationException(ReturnCode.APPLICATION_ERROR, getThrowableMsg(INFO_ERROR_POOLING_INITIALIZATION
            .get(sourceServerDisplay), ne), ne);
      }
    }
    resetGenerationId(ctx, suffixDn, sourceServerDisplay);
  }

  /**
   * Returns the configuration file path to be used when invoking the
   * command-lines.
   *
   * @return the configuration file path to be used when invoking the
   *         command-lines.
   */
  private String getConfigurationFile()
  {
    return getPath(getInstallation().getCurrentConfigurationFile());
  }

  /**
   * Returns the configuration class name to be used when invoking the
   * command-lines.
   *
   * @return the configuration class name to be used when invoking the
   *         command-lines.
   */
  private String getConfigurationClassName()
  {
    return DEFAULT_CONFIG_CLASS_NAME;
  }

  private String getLocalReplicationServer()
  {
    return getUserData().getHostName() + ":" + getUserData().getReplicationOptions().getReplicationPort();
  }

  private String getLocalHostPort()
  {
    return getUserData().getHostName() + ":" + getUserData().getServerPort();
  }

  private void resetGenerationId(InitialLdapContext ctx, String suffixDn, String sourceServerDisplay)
      throws ApplicationException
  {
    boolean taskCreated = false;
    int i = 1;
    boolean isOver = false;
    String dn = null;
    BasicAttributes attrs = new BasicAttributes();
    Attribute oc = new BasicAttribute("objectclass");
    oc.add("top");
    oc.add("ds-task");
    oc.add("ds-task-reset-generation-id");
    attrs.put(oc);
    attrs.put("ds-task-class-name", "org.opends.server.tasks.SetGenerationIdTask");
    attrs.put("ds-task-reset-generation-id-domain-base-dn", suffixDn);
    while (!taskCreated)
    {
      checkAbort();
      String id = "quicksetup-reset-generation-id-" + i;
      dn = "ds-task-id=" + id + ",cn=Scheduled Tasks,cn=Tasks";
      attrs.put("ds-task-id", id);
      try
      {
        DirContext dirCtx = ctx.createSubcontext(dn, attrs);
        taskCreated = true;
        logger.info(LocalizableMessage.raw("created task entry: " + attrs));
        dirCtx.close();
      }
      catch (NameAlreadyBoundException x)
      {
      }
      catch (NamingException ne)
      {
        logger.error(LocalizableMessage.raw("Error creating task " + attrs, ne));
        throw new ApplicationException(ReturnCode.APPLICATION_ERROR, getThrowableMsg(
            INFO_ERROR_LAUNCHING_INITIALIZATION.get(sourceServerDisplay), ne), ne);
      }
      i++;
    }
    // Wait until it is over
    SearchControls searchControls = new SearchControls();
    searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
    String filter = "objectclass=*";
    searchControls.setReturningAttributes(new String[] { "ds-task-log-message", "ds-task-state" });
    String lastLogMsg = null;
    while (!isOver)
    {
      StaticUtils.sleep(500);
      try
      {
        NamingEnumeration<SearchResult> res = ctx.search(dn, filter, searchControls);
        SearchResult sr = null;
        try
        {
          while (res.hasMore())
          {
            sr = res.next();
          }
        }
        finally
        {
          res.close();
        }
        String logMsg = getFirstValue(sr, "ds-task-log-message");
        if (logMsg != null && !logMsg.equals(lastLogMsg))
        {
          logger.info(LocalizableMessage.raw(logMsg));
          lastLogMsg = logMsg;
        }
        InstallerHelper helper = new InstallerHelper();
        String state = getFirstValue(sr, "ds-task-state");

        if (helper.isDone(state) || helper.isStoppedByError(state))
        {
          isOver = true;
          LocalizableMessage errorMsg = lastLogMsg != null ?
              INFO_ERROR_DURING_INITIALIZATION_LOG.get(sourceServerDisplay, lastLogMsg, state, sourceServerDisplay)
            : INFO_ERROR_DURING_INITIALIZATION_NO_LOG.get(sourceServerDisplay, state, sourceServerDisplay);

          if (helper.isCompletedWithErrors(state))
          {
            logger.warn(LocalizableMessage.raw("Completed with error: " + errorMsg));
            notifyListeners(getFormattedWarning(errorMsg));
          }
          else if (!helper.isSuccessful(state) || helper.isStoppedByError(state))
          {
            logger.warn(LocalizableMessage.raw("Error: " + errorMsg));
            throw new ApplicationException(ReturnCode.APPLICATION_ERROR, errorMsg, null);
          }
        }
      }
      catch (NameNotFoundException x)
      {
        isOver = true;
      }
      catch (NamingException ne)
      {
        throw new ApplicationException(ReturnCode.APPLICATION_ERROR,
            getThrowableMsg(INFO_ERROR_POOLING_INITIALIZATION.get(sourceServerDisplay), ne), ne);
      }
    }
  }

  /**
   * Invokes a long operation in a separate thread and checks whether the user
   * canceled the operation or not.
   *
   * @param thread
   *          the Thread that must be launched.
   * @throws ApplicationException
   *           if there was an error executing the task or if the user canceled
   *           the installer.
   */
  private void invokeLongOperation(InvokeThread thread) throws ApplicationException
  {
    try
    {
      thread.start();
      while (!thread.isOver() && thread.isAlive())
      {
        if (canceled)
        {
          // Try to abort the thread
          try
          {
            thread.abort();
          }
          catch (Throwable t)
          {
            logger.warn(LocalizableMessage.raw("Error cancelling thread: " + t, t));
          }
        }
        else if (thread.getException() != null)
        {
          throw thread.getException();
        }
        else
        {
          StaticUtils.sleep(100);
        }
      }
      if (thread.getException() != null)
      {
        throw thread.getException();
      }
      if (canceled)
      {
        checkAbort();
      }
    }
    catch (ApplicationException e)
    {
      logger.error(LocalizableMessage.raw("Error: " + e, e));
      throw e;
    }
    catch (Throwable t)
    {
      logger.error(LocalizableMessage.raw("Error: " + t, t));
      throw new ApplicationException(ReturnCode.BUG, getThrowableMsg(INFO_BUG_MSG.get(), t), t);
    }
  }

  /**
   * Returns the host port representation of the server to be used in progress
   * and error messages. It takes into account the fact the host and port
   * provided by the user in the replication options panel. NOTE: the code
   * assumes that the user data with the contents of the replication options has
   * already been updated.
   *
   * @param server
   *          the ServerDescriptor.
   * @return the host port string representation of the provided server.
   */
  protected String getHostPort(ServerDescriptor server)
  {
    String hostPort = null;

    for (PreferredConnection connection : getPreferredConnections())
    {
      String url = connection.getLDAPURL();
      if (url.equals(server.getLDAPURL()))
      {
        hostPort = server.getHostPort(false);
      }
      else if (url.equals(server.getLDAPsURL()))
      {
        hostPort = server.getHostPort(true);
      }
    }
    if (hostPort == null)
    {
      hostPort = server.getHostPort(true);
    }
    return hostPort;
  }

  @Override
  protected void applicationPrintStreamReceived(String message)
  {
    InstallerHelper helper = new InstallerHelper();
    String parsedMessage = helper.getImportProgressMessage(message);
    if (parsedMessage != null)
    {
      lastImportProgress = parsedMessage;
    }
  }

  /**
   * Returns the timeout to be used to connect in milliseconds.
   *
   * @return the timeout to be used to connect in milliseconds. Returns
   *         {@code 0} if there is no timeout.
   */
  protected int getConnectTimeout()
  {
    return getUserData().getConnectTimeout();
  }

  /**
   * Copies the template instance files into the instance directory.
   *
   * @throws ApplicationException
   *           If an IO error occurred.
   */
  private void copyTemplateInstance() throws ApplicationException
  {
    FileManager fileManager = new FileManager();
    fileManager.synchronize(getInstallation().getTemplateDirectory(), getInstallation().getInstanceDirectory());
  }
}

/** Class used to be able to cancel long operations. */
abstract class InvokeThread extends Thread implements Runnable
{
  protected boolean isOver;
  protected ApplicationException ae;

  /**
   * Returns <CODE>true</CODE> if the thread is over and <CODE>false</CODE>
   * otherwise.
   *
   * @return <CODE>true</CODE> if the thread is over and <CODE>false</CODE>
   *         otherwise.
   */
  public boolean isOver()
  {
    return isOver;
  }

  /**
   * Returns the exception that was encountered running the thread.
   *
   * @return the exception that was encountered running the thread.
   */
  public ApplicationException getException()
  {
    return ae;
  }

  /** Runnable implementation. */
  @Override
  public abstract void run();

  /** Abort this thread. */
  public abstract void abort();
}
