/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2008-2010 Sun Microsystems, Inc.
 */

package org.opends.guitools.controlpanel.ui;

import static org.opends.messages.AdminToolMessages.*;

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.NamingEnumeration;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.swing.Box;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;

import org.opends.admin.ads.util.ConnectionUtils;
import org.opends.guitools.controlpanel.browser.BrowserController;
import org.opends.guitools.controlpanel.browser.IconPool;
import org.opends.guitools.controlpanel.datamodel.AbstractIndexDescriptor;
import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
import org.opends.guitools.controlpanel.datamodel.CategorizedComboBoxElement;
import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
import org.opends.guitools.controlpanel.datamodel.MonitoringAttributes;
import org.opends.guitools.controlpanel.datamodel.ScheduleType;
import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
import org.opends.guitools.controlpanel.event.*;
import org.opends.guitools.controlpanel.task.RebuildIndexTask;
import org.opends.guitools.controlpanel.task.RestartServerTask;
import org.opends.guitools.controlpanel.task.StartServerTask;
import org.opends.guitools.controlpanel.task.StopServerTask;
import org.opends.guitools.controlpanel.task.Task;
import org.opends.guitools.controlpanel.ui.components.AddRemovePanel;
import org.opends.guitools.controlpanel.util.BackgroundTask;
import org.opends.guitools.controlpanel.util.LowerCaseComparator;
import org.opends.guitools.controlpanel.util.Utilities;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.messages.MessageDescriptor;
import org.opends.quicksetup.ui.CustomHTMLEditorKit;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.ObjectClassType;
import org.opends.server.types.OpenDsException;
import org.opends.server.util.ServerConstants;

/**
 * An abstract class that contains a number of methods that are shared by all
 * the inheriting classes.  In general a StatusGenericPanel is contained in a
 * GenericDialog and specifies the kind of buttons that this dialog has.  The
 * StatusGenericPanel is also notified when the dialog is displayed (through
 * the toBeDisplayed method)
 *
 */

public abstract class StatusGenericPanel extends JPanel
implements ConfigChangeListener
{
  private static final long serialVersionUID = -9123358652232556732L;

  /**
   * The string to be used as combo separator.
   */
  public static final String COMBO_SEPARATOR = "----------";

  /**
   * The not applicable message.
   */
  protected final static Message NOT_APPLICABLE =
    INFO_NOT_APPLICABLE_LABEL.get();

  private Message AUTHENTICATE = INFO_AUTHENTICATE_BUTTON_LABEL.get();
  private Message START = INFO_START_BUTTON_LABEL.get();

  private ControlPanelInfo info;

  private boolean enableClose = true;
  private boolean enableCancel = true;
  private boolean enableOK = true;

  private boolean disposeOnClose = false;

  private JPanel cardPanel;
  private JPanel mainPanel;
  private JEditorPane message;

  private CardLayout cardLayout;

  private static final String MAIN_PANEL = "mainPanel";
  private static final String MESSAGE_PANEL = "messagePanel";

  private static final Logger LOG =
    Logger.getLogger(StatusGenericPanel.class.getName());

  /**
   * The error pane.
   */
  protected JEditorPane errorPane;

  /**
   * The last displayed message in the error pane.
   */
  protected String lastDisplayedError = null;

  private ArrayList<ConfigurationElementCreatedListener> confListeners =
    new ArrayList<ConfigurationElementCreatedListener>();

  private boolean sizeSet = false;
  private boolean focusSet = false;

  private static DateFormat taskDateFormat =
    new SimpleDateFormat("yyyyMMddHHmmss");

  /**
   * Returns the title that will be used as title of the dialog.
   * @return the title that will be used as title of the dialog.
   */
  public abstract Message getTitle();

  /**
   * Returns the buttons that the dialog where this panel is contained should
   * display.
   * @return the buttons that the dialog where this panel is contained should
   * display.
   */
  public GenericDialog.ButtonType getButtonType()
  {
    return GenericDialog.ButtonType.OK_CANCEL;
  }

  /**
   * Returns the component that should get the focus when the dialog that
   * contains this panel is displayed.
   * @return the component that should get the focus.
   */
  public abstract Component getPreferredFocusComponent();

  /**
   * Returns <CODE>true</CODE> if this panel requires some bordering (in general
   * an EmptyBorder with some insets) and <CODE>false</CODE> otherwise.
   * @return <CODE>true</CODE> if this panel requires some bordering (in general
   * an EmptyBorder with some insets) and <CODE>false</CODE> otherwise.
   */
  public boolean requiresBorder()
  {
    return true;
  }

  /**
   * Returns the menu bar that the panel might have.  Returns
   * <CODE>null</CODE> if the panel has no menu bar associated.
   * @return the menu bar that the panel might have.
   */
  public JMenuBar getMenuBar()
  {
    return null;
  }


  /**
   * This method is called to indicate that the configuration changes should
   * be called in the background.  In the case of panels which require some
   * time to be updated with the new configuration this method returns
   * <CODE>true</CODE> and the operation will be performed in the background
   * while a message of type 'Loading...' is displayed on the panel.
   * @return <CODE>true</CODE> if changes should be loaded in the background and
   * <CODE>false</CODE> otherwise.
   */
  public boolean callConfigurationChangedInBackground()
  {
    return false;
  }

  /**
   * The panel is notified that the dialog is going to be visible or invisible.
   * @param visible whether is going to be visible or not.
   */
  public void toBeDisplayed(boolean visible)
  {
  }

  /**
   * Tells whether this panel should be contained in a scroll pane or not.
   * @return <CODE>true</CODE> if this panel should be contained in a scroll
   * pane and <CODE>false</CODE> otherwise.
   */
  public boolean requiresScroll()
  {
    return true;
  }

  /**
   * Constructor.
   *
   */
  protected StatusGenericPanel()
  {
    super(new GridBagLayout());
    setBackground(ColorAndFontConstants.background);

    cardLayout = new CardLayout();
    cardPanel = new JPanel(cardLayout);
    cardPanel.setOpaque(false);

    mainPanel = new JPanel(new GridBagLayout());
    mainPanel.setOpaque(false);

    message = Utilities.makeHtmlPane("", ColorAndFontConstants.progressFont);

    GridBagConstraints gbc = new GridBagConstraints();
    gbc.gridx = 0;
    gbc.gridy = 0;
    gbc.fill = GridBagConstraints.BOTH;
    gbc.weightx = 1.0;
    gbc.weighty = 1.0;
    super.add(cardPanel, gbc);

    cardPanel.add(mainPanel, MAIN_PANEL);

    JPanel messagePanel = new JPanel(new GridBagLayout());
    messagePanel.setOpaque(false);
    gbc.fill = GridBagConstraints.NONE;
    gbc.anchor = GridBagConstraints.CENTER;
    messagePanel.add(message, gbc);
    cardPanel.add(messagePanel, MESSAGE_PANEL);

    cardLayout.show(cardPanel, MAIN_PANEL);
  }

  /**
   * The components are not added directly to the panel but to the main panel.
   * This is done to be able to display a message that takes the whole panel
   * (of type 'Loading...') when we are doing long operations.
   * @param comp the Component to be added.
   * @param constraints the constraints.
   */
  public void add(Component comp, Object constraints)
  {
    mainPanel.add(comp, constraints);
  }

  /**
   * Adds a bottom glue to the main panel with the provided constraints.
   * @param gbc the constraints.
   */
  protected void addBottomGlue(GridBagConstraints gbc)
  {
    GridBagConstraints gbc2 = (GridBagConstraints)gbc.clone();
    gbc2.insets = new Insets(0, 0, 0, 0);
    gbc2.gridy ++;
    gbc2.gridwidth = GridBagConstraints.REMAINDER;
    gbc2.weighty = 1.0;
    gbc2.fill = GridBagConstraints.VERTICAL;
    add(Box.createVerticalGlue(), gbc2);
    gbc.gridy ++;
  }

  /**
   * Returns a label with text 'Required Field' and an icon (used as legend in
   * some panels).
   * @return a label with text 'Required Field' and an icon (used as legend in
   * some panels).
   */
  protected JLabel createRequiredLabel()
  {
    JLabel requiredLabel = Utilities.createInlineHelpLabel(
        INFO_CTRL_PANEL_INDICATES_REQUIRED_FIELD_LABEL.get());
    requiredLabel.setIcon(
        Utilities.createImageIcon(IconPool.IMAGE_PATH+"/required.gif"));

    return requiredLabel;
  }

  /**
   * Creates and adds an error pane.  Is up to the caller to set the proper
   * gridheight, gridwidth, gridx and gridy on the provided GridBagConstraints.
   * @param baseGbc the GridBagConstraints to be used.
   */
  protected void addErrorPane(GridBagConstraints baseGbc)
  {
    addErrorPane(this, baseGbc);
  }

  /**
   * Adds an error pane to the provided container.
   * Is up to the caller to set the proper gridheight, gridwidth, gridx and
   * gridy on the provided GridBagConstraints.
   * @param baseGbc the GridBagConstraints to be used.
   * @param p the container.
   */
  protected void addErrorPane(Container p, GridBagConstraints baseGbc)
  {
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.gridx = baseGbc.gridx;
    gbc.gridy = baseGbc.gridy;
    gbc.gridwidth = baseGbc.gridwidth;
    gbc.gridheight = baseGbc.gridheight;
    gbc.weightx = 1.0;
    gbc.fill = GridBagConstraints.BOTH;
    if (requiresBorder())
    {
      gbc.insets = new Insets(0, 0, 10, 0);
    }
    else
    {
      gbc.insets = new Insets(20, 20, 0, 20);
    }
    createErrorPane();
    p.add(errorPane, gbc);
  }

  /**
   * Creates the error pane.
   */
  protected void createErrorPane()
  {
    errorPane = Utilities.makeHtmlPane("", ColorAndFontConstants.progressFont);
    errorPane.setOpaque(false);
    errorPane.setEditable(false);
    errorPane.setVisible(false);
    CustomHTMLEditorKit htmlEditor = new CustomHTMLEditorKit();
    htmlEditor.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent ev)
      {
        if (AUTHENTICATE.toString().equals(ev.getActionCommand()))
        {
          authenticate();
        }
        else if (START.toString().equals(ev.getActionCommand()))
        {
          startServer();
        }
      }
    });
    errorPane.setEditorKit(htmlEditor);
  }

  /**
   * Commodity method used to add lines, where each line contains a label, a
   * component and an inline help label.
   * @param labels the labels.
   * @param comps the components.
   * @param inlineHelp the inline help labels.
   * @param panel the panel where we will add the lines.
   * @param gbc the grid bag constraints.
   */
  protected void add(JLabel[] labels, Component[] comps, JLabel[] inlineHelp,
      Container panel, GridBagConstraints gbc)
  {
    int i = 0;
    for (Component comp : comps)
    {
      gbc.insets.left = 0;
      gbc.weightx = 0.0;
      gbc.gridx = 0;
      if (labels[i] != null)
      {
        panel.add(labels[i], gbc);
      }
      gbc.insets.left = 10;
      gbc.weightx = 1.0;
      gbc.gridx = 1;
      panel.add(comp, gbc);
      if (inlineHelp[i] != null)
      {
        gbc.insets.top = 3;
        gbc.gridy ++;
        panel.add(inlineHelp[i], gbc);
      }
      gbc.insets.top = 10;
      gbc.gridy ++;
      i++;
    }
  }

  /**
   * Enables the OK button in the parent dialog.
   * @param enable whether to enable or disable the button.
   */
  protected void setEnabledOK(boolean enable)
  {
    Window w = Utilities.getParentDialog(this);
    if (w instanceof GenericDialog)
    {
      ((GenericDialog)w).setEnabledOK(enable);
    }
    else if (w instanceof GenericFrame)
    {
      ((GenericFrame)w).setEnabledOK(enable);
    }
    enableOK = enable;
  }

  /**
   * Enables the Cancel button in the parent dialog.
   * @param enable whether to enable or disable the button.
   */
  protected void setEnabledCancel(boolean enable)
  {
    Window w = Utilities.getParentDialog(this);
    if (w instanceof GenericDialog)
    {
      ((GenericDialog)w).setEnabledCancel(enable);
    }
    else if (w instanceof GenericFrame)
    {
      ((GenericFrame)w).setEnabledCancel(enable);
    }
    enableCancel = enable;
  }

  /**
   * Updates the font type and color of the component to be invalid and
   * primary.
   * @param comp the component to update.
   */
  protected void setPrimaryInvalid(JComponent comp)
  {
    comp.setFont(ColorAndFontConstants.primaryInvalidFont);
    comp.setForeground(ColorAndFontConstants.invalidFontColor);
  }

  /**
   * Updates the font type and color of the component to be valid and
   * primary.
   * @param comp the component to update.
   */
  protected void setPrimaryValid(JComponent comp)
  {
    comp.setForeground(ColorAndFontConstants.validFontColor);
    comp.setFont(ColorAndFontConstants.primaryFont);
  }

  /**
   * Updates the font type and color of the component to be invalid and
   * secondary.
   * @param comp the component to update.
   */
  protected void setSecondaryInvalid(JComponent comp)
  {
    comp.setForeground(ColorAndFontConstants.invalidFontColor);
    comp.setFont(ColorAndFontConstants.invalidFont);
  }

  /**
   * Updates the font type and color of the component to be valid and
   * secondary.
   * @param comp the component to update.
   */
  protected void setSecondaryValid(JComponent comp)
  {
    comp.setForeground(ColorAndFontConstants.validFontColor);
    comp.setFont(ColorAndFontConstants.defaultFont);
  }

  /**
   * Packs the parent dialog.
   *
   */
  protected void packParentDialog()
  {
    Window dlg = Utilities.getParentDialog(this);
    if (dlg != null)
    {
      invalidate();
      dlg.invalidate();
      dlg.pack();
      if (!SwingUtilities.isEventDispatchThread())
      {
        Thread.dumpStack();
      }
    }
  }

  /**
   * Notification that the ok button has been clicked, the panel is in charge
   * of doing whatever is required (close the dialog, launch a task, etc.).
   *
   */
  abstract public void okClicked();

  /**
   * Adds a configuration element created listener.
   * @param listener the listener.
   */
  public void addConfigurationElementCreatedListener(
      ConfigurationElementCreatedListener listener)
  {
    getConfigurationElementCreatedListeners().add(listener);
  }

  /**
   * Removes a configuration element created listener.
   * @param listener the listener.
   */
  public void removeConfigurationElementCreatedListener(
      ConfigurationElementCreatedListener listener)
  {
    getConfigurationElementCreatedListeners().remove(listener);
  }

  /**
   * Notifies the configuration element created listener that a new object has
   * been created.
   * @param configObject the created object.
   */
  protected void notifyConfigurationElementCreated(Object configObject)
  {
    for (ConfigurationElementCreatedListener listener :
      getConfigurationElementCreatedListeners())
    {
      listener.elementCreated(
          new ConfigurationElementCreatedEvent(this, configObject));
    }
  }

  /**
   * Returns the list of configuration listeners.
   * @return the list of configuration listeners.
   */
  protected List<ConfigurationElementCreatedListener>
  getConfigurationElementCreatedListeners()
  {
    return confListeners;
  }

  /**
   * Notification that cancel was clicked, the panel is in charge
   * of doing whatever is required (close the dialog, etc.).
   *
   */
  public void cancelClicked()
  {
    // Default implementation
    Utilities.getParentDialog(this).setVisible(false);
    if (isDisposeOnClose())
    {
      Utilities.getParentDialog(this).dispose();
    }
  }

  /**
   * Whether the dialog should be disposed when the user closes it.
   * @return <CODE>true</CODE> if the dialog should be disposed when the user
   * closes it or <CODE>true</CODE> otherwise.
   */
  public boolean isDisposeOnClose()
  {
    return disposeOnClose;
  }

  /**
   * Sets whether the dialog should be disposed when the user closes it or not.
   * @param disposeOnClose <CODE>true</CODE> if the dialog should be disposed
   * when the user closes it or <CODE>true</CODE> otherwise.
   */
  public void setDisposeOnClose(boolean disposeOnClose)
  {
    this.disposeOnClose = disposeOnClose;
  }

  /**
   * Notification that close was clicked, the panel is in charge
   * of doing whatever is required (close the dialog, etc.).
   *
   */
  public void closeClicked()
  {
    // Default implementation
    Utilities.getParentDialog(this).setVisible(false);
    if (isDisposeOnClose())
    {
      Utilities.getParentDialog(this).dispose();
    }
  }

  /**
   * Displays a dialog with the provided list of error messages.
   * @param errors the error messages.
   */
  protected void displayErrorDialog(Collection<Message> errors)
  {
    Utilities.displayErrorDialog(Utilities.getParentDialog(this), errors);
  }

  /**
   * Displays a confirmation message.
   * @param title the title/summary of the message.
   * @param msg the description of the confirmation.
   * @return <CODE>true</CODE> if the user confirms and <CODE>false</CODE>
   * otherwise.
   */
  protected boolean displayConfirmationDialog(Message title, Message msg)
  {
    return Utilities.displayConfirmationDialog(Utilities.getParentDialog(this),
        title, msg);
  }


  /**
   * If the index must be rebuilt, asks the user for confirmation.  If the user
   * confirms launches a task that will rebuild the indexes.  The progress will
   * be displayed in the provided progress dialog.
   * @param index the index.
   * @param progressDialog the progress dialog.
   */
  protected void rebuildIndexIfNecessary(AbstractIndexDescriptor index,
      ProgressDialog progressDialog)
  {
    progressDialog.setTaskIsOver(false);
    boolean rebuildIndexes;
    String backendName = index.getBackend().getBackendID();
    if (!isServerRunning())
    {
      rebuildIndexes = Utilities.displayConfirmationDialog(progressDialog,
          INFO_CTRL_PANEL_INDEX_REBUILD_REQUIRED_SUMMARY.get(),
          INFO_CTRL_PANEL_INDEX_REBUILD_REQUIRED_OFFLINE_DETAILS.get(
              index.getName(), backendName));
    }
    else
    {
      if (isLocal())
      {
        rebuildIndexes = Utilities.displayConfirmationDialog(progressDialog,
            INFO_CTRL_PANEL_INDEX_REBUILD_REQUIRED_SUMMARY.get(),
            INFO_CTRL_PANEL_INDEX_REBUILD_REQUIRED_ONLINE_DETAILS.get(
                index.getName(), backendName, backendName));
      }
      else
      {
        Utilities.displayWarningDialog(progressDialog,
            INFO_CTRL_PANEL_INDEX_REBUILD_REQUIRED_SUMMARY.get(),
            INFO_CTRL_PANEL_INDEX_REBUILD_REQUIRED_REMOTE_DETAILS.get(
                index.getName(), backendName));
        rebuildIndexes = false;
      }
    }
    if (rebuildIndexes)
    {
      SortedSet<AbstractIndexDescriptor> indexes =
        new TreeSet<AbstractIndexDescriptor>();
      indexes.add(index);
      SortedSet<String> baseDNs = new TreeSet<String>();
      for (BaseDNDescriptor b : index.getBackend().getBaseDns())
      {
        String baseDN = Utilities.unescapeUtf8(b.getDn().toString());
        baseDNs.add(baseDN);
      }

      RebuildIndexTask newTask = new RebuildIndexTask(getInfo(),
          progressDialog, baseDNs, indexes);
      ArrayList<Message> errors = new ArrayList<Message>();
      for (Task task : getInfo().getTasks())
      {
        task.canLaunch(newTask, errors);
      }
      if (errors.size() == 0)
      {
        progressDialog.appendProgressHtml("<br><br>");
        launchOperation(newTask,
            INFO_CTRL_PANEL_REBUILDING_INDEXES_SUMMARY.get(backendName),
            INFO_CTRL_PANEL_REBUILDING_INDEXES_SUCCESSFUL_SUMMARY.get(),
            INFO_CTRL_PANEL_REBUILDING_INDEXES_SUCCESSFUL_DETAILS.get(),
            ERR_CTRL_PANEL_REBUILDING_INDEXES_ERROR_SUMMARY.get(),
            null,
            ERR_CTRL_PANEL_REBUILDING_INDEXES_ERROR_DETAILS,
            progressDialog, false);
        if (progressDialog.isModal())
        {
          progressDialog.toFront();
        }
        progressDialog.setVisible(true);
        if (!progressDialog.isModal())
        {
          progressDialog.toFront();
        }
      }
      if (errors.size() > 0)
      {
        displayErrorDialog(errors);
      }
    }
    else
    {
      progressDialog.setTaskIsOver(true);
      if (progressDialog.isVisible())
      {
        progressDialog.toFront();
      }
    }
  }


  /**
   * A class used to avoid the possibility a certain type of objects in a combo
   * box.  This is used for instance in the combo box that contains base DNs
   * where the base DNs are separated in backends, so the combo box displays
   * both the backends (~ categories) and base DNs (~ values) and we do not
   * allow to select the backends (~ categories).
   *
   */
  protected class IgnoreItemListener implements ItemListener
  {
    private Object selectedItem;
    private JComboBox combo;

    /**
     * Constructor.
     * @param combo the combo box.
     */
    public IgnoreItemListener(JComboBox combo)
    {
      this.combo = combo;
      selectedItem = combo.getSelectedItem();
      if (isCategory(selectedItem))
      {
        selectedItem = null;
      }
    }

    /**
     * {@inheritDoc}
     */
    public void itemStateChanged(ItemEvent ev)
    {
      Object o = combo.getSelectedItem();
      if (isCategory(o))
      {
        if (selectedItem == null)
        {
          // Look for the first element that is not a category
          for (int i=0; i<combo.getModel().getSize(); i++)
          {
            Object item = combo.getModel().getElementAt(i);
            if (item instanceof CategorizedComboBoxElement)
            {
              if (!isCategory(item))
              {
                selectedItem = item;
                break;
              }
            }
          }
        }
        if (selectedItem != null)
        {
          combo.setSelectedItem(selectedItem);
        }
      }
      else if (COMBO_SEPARATOR.equals(o))
      {
        combo.setSelectedItem(selectedItem);
      }
      else
      {
        selectedItem = o;
      }
    }
  }

  /**
   * Returns the HTML required to render an Authenticate button in HTML.
   * @return the HTML required to render an Authenticate button in HTML.
   */
  protected String getAuthenticateHTML()
  {
    return "<INPUT type=\"submit\" value=\""+AUTHENTICATE+"\"></INPUT>";
  }

  /**
   * Returns the HTML required to render an Start button in HTML.
   * @return the HTML required to render an Start button in HTML.
   */
  protected String getStartServerHTML()
  {
    return "<INPUT type=\"submit\" value=\""+START+"\"></INPUT>";
  }

  /**
   * Updates the error panel and enables/disables the OK button depending on
   * the status of the server.
   * @param desc the Server Descriptor.
   * @param details the message to be displayed if authentication has not been
   * provided and the server is running.
   */
  protected void updateErrorPaneAndOKButtonIfAuthRequired(ServerDescriptor desc,
      Message details)
  {
    if (authenticationRequired(desc))
    {
      MessageBuilder mb = new MessageBuilder();
      mb.append(details);
      mb.append("<br><br>"+getAuthenticateHTML());
      Message title = INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_SUMMARY.get();
      updateErrorPane(errorPane, title, ColorAndFontConstants.errorTitleFont,
          mb.toMessage(), ColorAndFontConstants.defaultFont);
      SwingUtilities.invokeLater(new Runnable()
      {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
          errorPane.setVisible(true);
          packParentDialog();
          setEnabledOK(false);
        }
      });
    }
    else
    {
      SwingUtilities.invokeLater(new Runnable()
      {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
          errorPane.setVisible(false);
          checkOKButtonEnable();
        }
      });
    }
  }

  /**
   * Returns <CODE>true</CODE> if the server is running and the user did not
   * provide authentication and <CODE>false</CODE> otherwise.
   * @param desc the server descriptor.
   * @return <CODE>true</CODE> if the server is running and the user did not
   * provide authentication and <CODE>false</CODE> otherwise.
   */
  protected boolean authenticationRequired(ServerDescriptor desc)
  {
    boolean returnValue;
    ServerDescriptor.ServerStatus status = desc.getStatus();
    if (((status == ServerDescriptor.ServerStatus.STARTED) &&
        !desc.isAuthenticated()) ||
        (status == ServerDescriptor.ServerStatus.NOT_CONNECTED_TO_REMOTE))
    {
      returnValue = true;
    }
    else
    {
      returnValue = false;
    }
    return returnValue;
  }

  /**
   * Updates the error panel depending on the status of the server.
   * @param desc the Server Descriptor.
   * @param details the message to be displayed if authentication has not been
   * provided and the server is running.
   */
  protected void updateErrorPaneIfAuthRequired(ServerDescriptor desc,
      Message details)
  {
    if (authenticationRequired(desc))
    {
      Message title = INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_SUMMARY.get();
      MessageBuilder mb = new MessageBuilder();
      mb.append(details);
      mb.append("<br><br>"+getAuthenticateHTML());
      updateErrorPane(errorPane, title, ColorAndFontConstants.errorTitleFont,
          mb.toMessage(), ColorAndFontConstants.defaultFont);
      SwingUtilities.invokeLater(new Runnable()
      {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
          errorPane.setVisible(true);
          packParentDialog();
        }
      });
    }
    else
    {
      SwingUtilities.invokeLater(new Runnable()
      {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
          errorPane.setVisible(false);
        }
      });
    }
  }

  /**
   * Updates the error panel depending on the status of the server.  This
   * method will display an error message in the error pane if the server is not
   * running and another message if the server is running but authentication
   * has not been provided.
   * @param desc the Server Descriptor.
   * @param detailsServerNotRunning the message to be displayed if the server is
   * not running.
   * @param authRequired the message to be displayed if authentication has not
   * been provided and the server is running.
   */
  protected void updateErrorPaneIfServerRunningAndAuthRequired(
      ServerDescriptor desc, Message detailsServerNotRunning,
      Message authRequired)
  {
    ServerDescriptor.ServerStatus status = desc.getStatus();
    if ((status != ServerDescriptor.ServerStatus.STARTED) &&
        (status != ServerDescriptor.ServerStatus.NOT_CONNECTED_TO_REMOTE))
    {
      Message title = INFO_CTRL_PANEL_SERVER_NOT_RUNNING_SUMMARY.get();
      MessageBuilder mb = new MessageBuilder();
      mb.append(detailsServerNotRunning);
      mb.append("<br><br>"+getStartServerHTML());
      updateErrorPane(errorPane, title, ColorAndFontConstants.errorTitleFont,
          mb.toMessage(),
          ColorAndFontConstants.defaultFont);
      SwingUtilities.invokeLater(new Runnable()
      {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
          errorPane.setVisible(true);
          packParentDialog();
        }
      });
    }
    else if (authenticationRequired(desc))
    {
      Message title = INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_SUMMARY.get();
      MessageBuilder mb = new MessageBuilder();
      mb.append(authRequired);
      mb.append("<br><br>"+getAuthenticateHTML());
      updateErrorPane(errorPane, title, ColorAndFontConstants.errorTitleFont,
          mb.toMessage(), ColorAndFontConstants.defaultFont);
      SwingUtilities.invokeLater(new Runnable()
      {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
          errorPane.setVisible(true);
          packParentDialog();
        }
      });
    }
    else
    {
      SwingUtilities.invokeLater(new Runnable()
      {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
          errorPane.setVisible(false);
        }
      });
    }
  }

  /**
   * Updates the enabling/disabling of the OK button.  The code assumes that
   * the error pane has already been updated.
   *
   */
  protected void checkOKButtonEnable()
  {
    setEnabledOK(!errorPane.isVisible());
  }

  /**
   * Returns <CODE>true</CODE> if the provided object is a category object in
   * a combo box.
   * @param o the item in the combo box.
   * @return <CODE>true</CODE> if the provided object is a category object in
   * a combo box.
   */
  protected boolean isCategory(Object o)
  {
    boolean isCategory = false;
    if (o instanceof CategorizedComboBoxElement)
    {
      CategorizedComboBoxElement desc = (CategorizedComboBoxElement)o;
      isCategory = desc.getType() == CategorizedComboBoxElement.Type.CATEGORY;
    }
    return isCategory;
  }

  /**
   * Returns the control panel info object.
   * @return the control panel info object.
   */
  public ControlPanelInfo getInfo()
  {
    return info;
  }

  /**
   * Sets the control panel info object.
   * @param info the control panel info object.
   */
  public void setInfo(ControlPanelInfo info)
  {
    if (!info.equals(this.info))
    {
      if (this.info != null)
      {
        this.info.removeConfigChangeListener(this);
      }
      this.info = info;
      this.info.addConfigChangeListener(this);
      if (SwingUtilities.isEventDispatchThread() &&
          callConfigurationChangedInBackground())
      {
        final Color savedBackground = getBackground();
        setBackground(ColorAndFontConstants.background);
        if (!sizeSet)
        {
          setPreferredSize(mainPanel.getPreferredSize());
          sizeSet = true;
        }
        // Do it outside the event thread if the panel requires it.
        BackgroundTask<Void> worker = new BackgroundTask<Void>()
        {
          public Void processBackgroundTask() throws Throwable
          {
            try
            {
              Thread.sleep(1000);
            }
            catch (Throwable t)
            {
            }
            configurationChanged(new ConfigurationChangeEvent(
                StatusGenericPanel.this.info,
                StatusGenericPanel.this.info.getServerDescriptor()));
            return null;
          }


          public void backgroundTaskCompleted(Void returnValue,
              Throwable t)
          {
            setBackground(savedBackground);
            displayMainPanel();
            if (!focusSet)
            {
              focusSet = true;
              Component comp = getPreferredFocusComponent();
              if (comp != null)
              {
                comp.requestFocusInWindow();
              }
            }
          }
        };
        displayMessage(INFO_CTRL_PANEL_LOADING_PANEL_SUMMARY.get());
        worker.startBackgroundTask();
      }
      else if (info.getServerDescriptor() != null)
      {
        configurationChanged(new ConfigurationChangeEvent(
          this.info, this.info.getServerDescriptor()));
      }
    }
  }

  /**
   * Displays the main panel.
   *
   */
  protected void displayMainPanel()
  {
    cardLayout.show(cardPanel, MAIN_PANEL);
  }

  /**
   * Returns whether the main panel is visible or not.
   * @return whether the main panel is visible or not.
   */
  protected boolean isMainPanelVisible()
  {
    return mainPanel.isVisible();
  }

  /**
   * Displays a message and hides the main panel.
   * @param msg the message to be displayed.
   */
  protected void displayMessage(Message msg)
  {
    message.setText(Utilities.applyFont(msg.toString(),
        ColorAndFontConstants.defaultFont));
    cardLayout.show(cardPanel, MESSAGE_PANEL);
    message.requestFocusInWindow();
  }

  /**
   * Displays an error message and hides the main panel.
   * @param title the title of the message to be displayed.
   * @param msg the message to be displayed.
   */
  protected void displayErrorMessage(Message title, Message msg)
  {
    updateErrorPane(message, title, ColorAndFontConstants.errorTitleFont,
        msg, ColorAndFontConstants.defaultFont);
    cardLayout.show(cardPanel, MESSAGE_PANEL);
    message.requestFocusInWindow();
  }

  /**
   * Returns whether the message is visible or not.
   * @return whether the message is visible or not.
   */
  protected boolean isMessageVisible()
  {
    return message.isVisible();
  }

  /**
   * Updates the contents of an editor pane using the error format.
   * @param pane the editor pane to be updated.
   * @param title the title.
   * @param titleFont the font to be used for the title.
   * @param details the details message.
   * @param detailsFont the font to be used for the details.
   */
  protected void updateErrorPane(JEditorPane pane, Message title,
      Font titleFont, Message details, Font detailsFont)
  {
    updatePane(pane, title, titleFont, details, detailsFont, PanelType.ERROR);
  }

  /**
   * Updates the contents of an editor pane using the warning format.
   * @param pane the editor pane to be updated.
   * @param title the title.
   * @param titleFont the font to be used for the title.
   * @param details the details message.
   * @param detailsFont the font to be used for the details.
   */
  protected void updateWarningPane(JEditorPane pane, Message title,
      Font titleFont, Message details, Font detailsFont)
  {
    updatePane(pane, title, titleFont, details, detailsFont, PanelType.WARNING);
  }

  /**
   * Updates the contents of an editor pane using the confirmation format.
   * @param pane the editor pane to be updated.
   * @param title the title.
   * @param titleFont the font to be used for the title.
   * @param details the details message.
   * @param detailsFont the font to be used for the details.
   */
  protected void updateConfirmationPane(JEditorPane pane, Message title,
      Font titleFont, Message details, Font detailsFont)
  {
    updatePane(pane, title, titleFont, details, detailsFont,
        PanelType.CONFIRMATION);
  }

  /**
   * The different types of error panels that are handled.
   *
   */
  protected enum PanelType
  {
    /**
     * The message in the panel is an error.
     */
    ERROR,
    /**
     * The message in the panel is a confirmation.
     */
    CONFIRMATION,
    /**
     * The message in the panel is an information message.
     */
    INFORMATION,
    /**
     * The message in the panel is a warning message.
     */
    WARNING
  };

  /**
   * Updates the contents of an editor pane using the provided format.
   * @param pane the editor pane to be updated.
   * @param title the title.
   * @param titleFont the font to be used for the title.
   * @param details the details message.
   * @param detailsFont the font to be used for the details.
   * @param type the type of panel.
   */
  private void updatePane(final JEditorPane pane, Message title,
      Font titleFont, Message details, Font detailsFont, PanelType type)
  {
    String text;
    switch (type)
    {
    case ERROR:
      text = Utilities.getFormattedError(title, titleFont, details,
          detailsFont);
      break;
    case CONFIRMATION:
      text = Utilities.getFormattedConfirmation(title, titleFont, details,
          detailsFont);
      break;
    case WARNING:
      text = Utilities.getFormattedWarning(title, titleFont, details,
          detailsFont);
      break;
    default:
      text = Utilities.getFormattedSuccess(title, titleFont, details,
          detailsFont);
      break;
    }
    if (!text.equals(lastDisplayedError))
    {
      Message wrappedTitle = Utilities.wrapHTML(title, 80);
      Message wrappedDetails = Utilities.wrapHTML(details, 90);

      JEditorPane wrappedPane = Utilities.makeHtmlPane(null, pane.getFont());
      String wrappedText;
      switch (type)
      {
      case ERROR:
        wrappedText = Utilities.getFormattedError(wrappedTitle, titleFont,
            wrappedDetails, detailsFont);
        break;
      default:
        wrappedText = Utilities.getFormattedSuccess(wrappedTitle, titleFont,
            wrappedDetails, detailsFont);
        break;
      }
      wrappedPane.setText(wrappedText);
      Dimension d = wrappedPane.getPreferredSize();

      pane.setText(text);
      pane.setPreferredSize(d);

      lastDisplayedError = text;
    }
    final Window window =
      Utilities.getParentDialog(StatusGenericPanel.this);
    if (window != null)
    {
      SwingUtilities.invokeLater(new Runnable()
      {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
          pane.invalidate();
          window.validate();
        }
      });
    }
  }

  /**
   * Commodity method used to update the elements of a combo box that contains
   * the different user backends.  If no backends are found the combo box will
   * be made invisible and a label will be made visible.  This method does not
   * update the label's text nor creates any layout.
   * @param combo the combo to be updated.
   * @param lNoBackendsFound the label that must be shown if no user backends
   * are found.
   * @param desc the server descriptor that contains the configuration.
   */
  protected void updateSimpleBackendComboBoxModel(final JComboBox combo,
      final JLabel lNoBackendsFound, ServerDescriptor desc)
  {
    final SortedSet<String> newElements =
      new TreeSet<String>(new LowerCaseComparator());
    for (BackendDescriptor backend : desc.getBackends())
    {
      if (!backend.isConfigBackend())
      {
        newElements.add(backend.getBackendID());
      }
    }
    DefaultComboBoxModel model = (DefaultComboBoxModel)combo.getModel();
    updateComboBoxModel(newElements, model);
    SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        combo.setVisible(newElements.size() > 0);
        lNoBackendsFound.setVisible(newElements.size() == 0);
      }
    });
  }

  /**
   * Method that says if a backend must be displayed.  Only non-config backends
   * are displayed.
   * @param backend the backend.
   * @return <CODE>true</CODE> if the backend must be displayed and
   * <CODE>false</CODE> otherwise.
   */
  protected boolean displayBackend(BackendDescriptor backend)
  {
    return !backend.isConfigBackend();
  }

  /**
   * Commodity method to update a combo box model with the backends of a server.
   * @param model the combo box model to be updated.
   * @param desc the server descriptor containing the configuration.
   */
  protected void updateBaseDNComboBoxModel(DefaultComboBoxModel model,
      ServerDescriptor desc)
  {
    LinkedHashSet<CategorizedComboBoxElement> newElements =
      new LinkedHashSet<CategorizedComboBoxElement>();
    SortedSet<String> backendIDs =
      new TreeSet<String>(new LowerCaseComparator());
    HashMap<String, SortedSet<String>> hmBaseDNs =
      new HashMap<String, SortedSet<String>>();

    for (BackendDescriptor backend : desc.getBackends())
    {
      if (displayBackend(backend))
      {
        String backendID = backend.getBackendID();
        backendIDs.add(backendID);
        SortedSet<String> baseDNs =
          new TreeSet<String>(new LowerCaseComparator());
        for (BaseDNDescriptor baseDN : backend.getBaseDns())
        {
          try
          {
            baseDNs.add(Utilities.unescapeUtf8(baseDN.getDn().toString()));
          }
          catch (Throwable t)
          {
            throw new RuntimeException("Unexpected error: "+t, t);
          }
        }
        hmBaseDNs.put(backendID, baseDNs);
      }
    }

    for (String backendID : backendIDs)
    {
      newElements.add(new CategorizedComboBoxElement(backendID,
          CategorizedComboBoxElement.Type.CATEGORY));
      SortedSet<String> baseDNs = hmBaseDNs.get(backendID);
      for (String baseDN : baseDNs)
      {
        newElements.add(new CategorizedComboBoxElement(baseDN,
            CategorizedComboBoxElement.Type.REGULAR));
      }
    }
    updateComboBoxModel(newElements, model);
  }

  /**
   * Updates a combo box model with a number of items.
   * @param newElements the new items for the combo box model.
   * @param model the combo box model to be updated.
   */
  protected void updateComboBoxModel(Collection<?> newElements,
      DefaultComboBoxModel model)
  {
    updateComboBoxModel(newElements, model, null);
  }

  /**
   * Updates a combo box model with a number of items.
   * The method assumes that is called outside the event thread.
   * @param newElements the new items for the combo box model.
   * @param model the combo box model to be updated.
   * @param comparator the object that will be used to compare the objects in
   * the model.  If <CODE>null</CODE>, the equals method will be used.
   */
  protected void updateComboBoxModel(final Collection<?> newElements,
      final DefaultComboBoxModel model,
      final Comparator<Object> comparator)
  {
    SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        Utilities.updateComboBoxModel(newElements, model, comparator);
      }
    });
  }

  /**
   * Updates a map, so that the keys are the base DN where the indexes are
   * defined and the values are a sorted set of indexes.
   * @param desc the server descriptor containing the index configuration.
   * @param hmIndexes the map to be updated.
   */
  protected void updateIndexMap(ServerDescriptor desc,
      HashMap<String, SortedSet<AbstractIndexDescriptor>> hmIndexes)
  {
    synchronized (hmIndexes)
    {
      HashSet<String> dns = new HashSet<String>();
      for (BackendDescriptor backend : desc.getBackends())
      {
        if (backend.getType() == BackendDescriptor.Type.LOCAL_DB)
        {
          for (BaseDNDescriptor baseDN : backend.getBaseDns())
          {
            String dn;
            try
            {
              dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
            }
            catch (Throwable t)
            {
              throw new RuntimeException("Unexpected error: "+t, t);
            }
            dns.add(dn);
            SortedSet<AbstractIndexDescriptor> indexes =
              new TreeSet<AbstractIndexDescriptor>();
            indexes.addAll(backend.getIndexes());
            indexes.addAll(backend.getVLVIndexes());
            SortedSet<AbstractIndexDescriptor> currentIndexes =
              hmIndexes.get(dn);
            if (currentIndexes != null)
            {
              if (!currentIndexes.equals(indexes))
              {
                hmIndexes.put(dn, indexes);
              }
            }
            else
            {
              hmIndexes.put(dn, indexes);
            }
          }
        }
      }
      for (String dn : new HashSet<String>(hmIndexes.keySet()))
      {
        if (!dns.contains(dn))
        {
          hmIndexes.remove(dn);
        }
      }
    }
  }



  /**
   * Updates and addremove panel with the contents of the provided item.  The
   * selected item represents a base DN.
   * @param hmIndexes the map that contains the indexes definitions as values
   * and the base DNs as keys.
   * @param selectedItem the selected item.
   * @param addRemove the add remove panel to be updated.
   */
  protected void comboBoxSelected(
      HashMap<String, SortedSet<AbstractIndexDescriptor>> hmIndexes,
      CategorizedComboBoxElement selectedItem,
      AddRemovePanel<AbstractIndexDescriptor> addRemove)
  {
    synchronized (hmIndexes)
    {
      String selectedDn = null;
      if (selectedItem != null)
      {
        selectedDn = (String)selectedItem.getValue();
      }
      if (selectedDn != null)
      {
        SortedSet<AbstractIndexDescriptor> indexes =
          hmIndexes.get(selectedDn);
        if (indexes != null)
        {
          boolean availableChanged = false;
          boolean selectedChanged = false;
          SortedSet<AbstractIndexDescriptor> availableIndexes =
            addRemove.getAvailableListModel().getData();
          SortedSet<AbstractIndexDescriptor> selectedIndexes =
            addRemove.getSelectedListModel().getData();
          availableChanged = availableIndexes.retainAll(indexes);
          selectedChanged = selectedIndexes.retainAll(indexes);

          for (AbstractIndexDescriptor index : indexes)
          {
            if (!availableIndexes.contains(index) &&
                !selectedIndexes.contains(index))
            {
              availableIndexes.add(index);
              availableChanged = true;
            }
          }
          if (availableChanged)
          {
            addRemove.getAvailableListModel().clear();
            addRemove.getAvailableListModel().addAll(availableIndexes);
            addRemove.getAvailableListModel().fireContentsChanged(
                addRemove.getAvailableListModel(), 0,
                addRemove.getAvailableListModel().getSize());
          }
          if (selectedChanged)
          {
            addRemove.getSelectedListModel().clear();
            addRemove.getSelectedListModel().addAll(selectedIndexes);
            addRemove.getSelectedListModel().fireContentsChanged(
                addRemove.getSelectedListModel(), 0,
                addRemove.getSelectedListModel().getSize());
          }
        }
      }
    }
  }

  /**
   * Returns <CODE>true</CODE> if the cancel button is enabled and
   * <CODE>false</CODE> otherwise.
   * @return <CODE>true</CODE> if the cancel button is enabled and
   * <CODE>false</CODE> otherwise.
   */
  public boolean isEnableCancel()
  {
    return enableCancel;
  }

  /**
   * Returns <CODE>true</CODE> if the close button is enabled and
   * <CODE>false</CODE> otherwise.
   * @return <CODE>true</CODE> if the close button is enabled and
   * <CODE>false</CODE> otherwise.
   */
  public boolean isEnableClose()
  {
    return enableClose;
  }

  /**
   * Returns <CODE>true</CODE> if the ok button is enabled and
   * <CODE>false</CODE> otherwise.
   * @return <CODE>true</CODE> if the ok button is enabled and
   * <CODE>false</CODE> otherwise.
   */
  public boolean isEnableOK()
  {
    return enableOK;
  }

  /**
   * Returns <CODE>true</CODE> if the server is running  and
   * <CODE>false</CODE> otherwise.
   * @return <CODE>true</CODE> if the server is running  and
   * <CODE>false</CODE> otherwise.
   */
  protected boolean isServerRunning()
  {
    return getInfo().getServerDescriptor().getStatus() ==
      ServerDescriptor.ServerStatus.STARTED;
  }

  /**
   * Returns <CODE>true</CODE> if the managed server is the local installation
   * (where the control panel is installed) <CODE>false</CODE> otherwise.
   * @return <CODE>true</CODE> if the managed server is the local installation
   * (where the control panel is installed) <CODE>false</CODE> otherwise.
   */
  protected boolean isLocal()
  {
    return getInfo().getServerDescriptor().isLocal();
  }

  /**
   * Launch an task.
   * @param task the task to be launched.
   * @param initialSummary the initial summary to be displayed in the progress
   * dialog.
   * @param successSummary the success summary to be displayed in the progress
   * dialog if the task is successful.
   * @param successDetail the success details to be displayed in the progress
   * dialog if the task is successful.
   * @param errorSummary the error summary to be displayed in the progress
   * dialog if the task ended with error.
   * @param errorDetail error details to be displayed in the progress
   * dialog if the task ended with error.
   * @param errorDetailCode error detail message to be displayed in the progress
   * dialog if the task ended with error and we have an exit error code (for
   * instance if the error occurred when launching a script we will have an
   * error code).
   * @param dialog the progress dialog.
   */
  protected void launchOperation(final Task task, Message initialSummary,
      final Message successSummary, final Message successDetail,
      final Message errorSummary,
      final Message errorDetail,
      final MessageDescriptor.Arg1<Number> errorDetailCode,
      final ProgressDialog dialog)
  {
    launchOperation(task, initialSummary, successSummary, successDetail,
        errorSummary, errorDetail, errorDetailCode, dialog, true);
  }

  /**
   * Launch an task.
   * @param task the task to be launched.
   * @param initialSummary the initial summary to be displayed in the progress
   * dialog.
   * @param successSummary the success summary to be displayed in the progress
   * dialog if the task is successful.
   * @param successDetail the success details to be displayed in the progress
   * dialog if the task is successful.
   * @param errorSummary the error summary to be displayed in the progress
   * dialog if the task ended with error.
   * @param errorDetail error details to be displayed in the progress
   * dialog if the task ended with error.
   * @param errorDetailCode error detail message to be displayed in the progress
   * dialog if the task ended with error and we have an exit error code (for
   * instance if the error occurred when launching a script we will have an
   * error code).
   * @param dialog the progress dialog.
   * @param resetLogs whether the contents of the progress dialog should be
   * reset or not.
   */
  protected void launchOperation(final Task task, Message initialSummary,
      final Message successSummary, final Message successDetail,
      final Message errorSummary,
      final Message errorDetail,
      final MessageDescriptor.Arg1<Number> errorDetailCode,
      final ProgressDialog dialog, boolean resetLogs)
  {
    launchOperation(task, initialSummary, successSummary, successDetail,
        errorSummary, errorDetail, errorDetailCode, dialog, resetLogs,
        getInfo());
  }

  /**
   * Launch an task.
   * @param task the task to be launched.
   * @param initialSummary the initial summary to be displayed in the progress
   * dialog.
   * @param successSummary the success summary to be displayed in the progress
   * dialog if the task is successful.
   * @param successDetail the success details to be displayed in the progress
   * dialog if the task is successful.
   * @param errorSummary the error summary to be displayed in the progress
   * dialog if the task ended with error.
   * @param errorDetail error details to be displayed in the progress
   * dialog if the task ended with error.
   * @param errorDetailCode error detail message to be displayed in the progress
   * dialog if the task ended with error and we have an exit error code (for
   * instance if the error occurred when launching a script we will have an
   * error code).
   * @param dialog the progress dialog.
   * @param resetLogs whether the contents of the progress dialog should be
   * reset or not.
   * @param info the ControlPanelInfo.
   */
  public static void launchOperation(final Task task, Message initialSummary,
      final Message successSummary, final Message successDetail,
      final Message errorSummary,
      final Message errorDetail,
      final MessageDescriptor.Arg1<Number> errorDetailCode,
      final ProgressDialog dialog, boolean resetLogs,
      final ControlPanelInfo info)
  {
    dialog.setTaskIsOver(false);
    dialog.getProgressBar().setIndeterminate(true);
    dialog.addPrintStreamListeners(task.getOutPrintStream(),
        task.getErrorPrintStream());
    if (resetLogs)
    {
      dialog.resetProgressLogs();
    }
    String cmdLine = task.getCommandLineToDisplay();
    if (cmdLine != null)
    {
      dialog.appendProgressHtml(Utilities.applyFont(
          INFO_CTRL_PANEL_EQUIVALENT_COMMAND_LINE.get()+"<br><b>"+cmdLine+
          "</b><br><br>",
          ColorAndFontConstants.progressFont));
    }
    dialog.setEnabledClose(false);
    dialog.setSummary(Message.raw(
        Utilities.applyFont(initialSummary.toString(),
            ColorAndFontConstants.defaultFont)));
    dialog.getProgressBar().setVisible(true);
    BackgroundTask<Task> worker = new BackgroundTask<Task>()
    {
      /**
       * {@inheritDoc}
       */
      public Task processBackgroundTask() throws Throwable
      {
        task.runTask();
        if (task.regenerateDescriptor())
        {
          info.regenerateDescriptor();
        }
        return task;
      }

      /**
       * {@inheritDoc}
       */
      public void backgroundTaskCompleted(Task returnValue, Throwable t)
      {
        String summaryMsg;
        if (task.getState() == Task.State.FINISHED_SUCCESSFULLY)
        {
          summaryMsg = Utilities.getFormattedSuccess(successSummary,
              ColorAndFontConstants.errorTitleFont,
              successDetail, ColorAndFontConstants.defaultFont);
        }
        else
        {
          if (t == null)
          {
            t = task.getLastException();
          }

          if (t != null)
          {
            LOG.log(Level.WARNING, "Error occurred running task: "+t, t);
            if ((task.getReturnCode() != null) &&
                (errorDetailCode != null))
            {
              String sThrowable;
              if (t instanceof OpenDsException)
              {
                sThrowable = ((OpenDsException)t).getMessageObject().toString();
              }
              else
              {
                if (t.getMessage() != null)
                {
                  sThrowable = t.getMessage();
                }
                else
                {
                  sThrowable = t.toString();
                }
              }
              MessageBuilder mb = new MessageBuilder();
              mb.append(errorDetailCode.get(task.getReturnCode()));
              mb.append(
                  "  "+INFO_CTRL_PANEL_DETAILS_THROWABLE.get(sThrowable));
              summaryMsg = Utilities.getFormattedError(errorSummary,
                  ColorAndFontConstants.errorTitleFont,
                  mb.toMessage(), ColorAndFontConstants.defaultFont);
            }
            else if (errorDetail != null)
            {
              MessageBuilder mb = new MessageBuilder();
              mb.append(errorDetail);
              mb.append(
                  "  "+INFO_CTRL_PANEL_DETAILS_THROWABLE.get(t.toString()));
              summaryMsg = Utilities.getFormattedError(errorSummary,
                  ColorAndFontConstants.errorTitleFont,
                  mb.toMessage(), ColorAndFontConstants.defaultFont);
            }
            else
            {
              summaryMsg = null;
            }
          }
          else if ((task.getReturnCode() != null) &&
              (errorDetailCode != null))
          {
            summaryMsg = Utilities.getFormattedError(errorSummary,
                ColorAndFontConstants.errorTitleFont,
                errorDetailCode.get(task.getReturnCode()),
                ColorAndFontConstants.defaultFont);
          }
          else if (errorDetail != null)
          {
            summaryMsg = Utilities.getFormattedError(errorSummary,
                ColorAndFontConstants.errorTitleFont,
                errorDetail, ColorAndFontConstants.defaultFont);
          }
          else
          {
            summaryMsg = null;
          }
        }
        if (summaryMsg != null)
        {
          dialog.setSummary(Message.raw(summaryMsg));
        }
        dialog.setEnabledClose(true);
        dialog.getProgressBar().setVisible(false);
        if (task.getState() == Task.State.FINISHED_SUCCESSFULLY)
        {
          dialog.setTaskIsOver(true);
        }
        task.postOperation();
      }
    };
    info.registerTask(task);
    worker.startBackgroundTask();
  }

  /**
   * Checks that the provided string value is a valid integer and if it is not
   * updates a list of error messages with an error.
   * @param errors the list of error messages to be updated.
   * @param stringValue the string value to analyze.
   * @param minValue the minimum integer value accepted.
   * @param maxValue the maximum integer value accepted.
   * @param errMsg the error message to use to update the error list if the
   * provided value is not valid.
   */
  protected void checkIntValue(Collection<Message> errors, String stringValue,
      int minValue, int maxValue, Message errMsg)
  {
    try
    {
      int n = Integer.parseInt(stringValue);
      if ((n > maxValue) || (n < minValue))
      {
        throw new RuntimeException("Invalid value");
      }
    }
    catch (Throwable t)
    {
      errors.add(errMsg);
    }
  }

  /**
   * Starts the server.  This method will launch a task and open a progress
   * dialog that will start the server.  This method must be called from the
   * event thread.
   *
   */
  protected void startServer()
  {
    LinkedHashSet<Message> errors = new LinkedHashSet<Message>();
    ProgressDialog progressDialog = new ProgressDialog(
        Utilities.createFrame(),
        Utilities.getParentDialog(this),
        INFO_CTRL_PANEL_START_SERVER_PROGRESS_DLG_TITLE.get(), getInfo());
    StartServerTask newTask = new StartServerTask(getInfo(), progressDialog);
    for (Task task : getInfo().getTasks())
    {
      task.canLaunch(newTask, errors);
    }
    if (errors.size() == 0)
    {
      launchOperation(newTask,
          INFO_CTRL_PANEL_STARTING_SERVER_SUMMARY.get(),
          INFO_CTRL_PANEL_STARTING_SERVER_SUCCESSFUL_SUMMARY.get(),
          INFO_CTRL_PANEL_STARTING_SERVER_SUCCESSFUL_DETAILS.get(),
          ERR_CTRL_PANEL_STARTING_SERVER_ERROR_SUMMARY.get(),
          null,
          ERR_CTRL_PANEL_STARTING_SERVER_ERROR_DETAILS,
          progressDialog);
      progressDialog.setVisible(true);
    }
    else
    {
      displayErrorDialog(errors);
    }
  }

  /**
   * Stops the server.  This method will launch a task and open a progress
   * dialog that will stop the server.  This method must be called from the
   * event thread.
   *
   */
  protected void stopServer()
  {
    LinkedHashSet<Message> errors = new LinkedHashSet<Message>();
    ProgressDialog progressDialog = new ProgressDialog(
        Utilities.createFrame(),
        Utilities.getParentDialog(this),
        INFO_CTRL_PANEL_STOP_SERVER_PROGRESS_DLG_TITLE.get(), getInfo());
    StopServerTask newTask = new StopServerTask(getInfo(), progressDialog);
    for (Task task : getInfo().getTasks())
    {
      task.canLaunch(newTask, errors);
    }
    boolean confirmed = true;
    if (errors.size() == 0)
    {
      confirmed = displayConfirmationDialog(
          INFO_CTRL_PANEL_CONFIRMATION_REQUIRED_SUMMARY.get(),
          INFO_CTRL_PANEL_CONFIRM_STOP_SERVER_DETAILS.get());
    }
    if ((errors.size() == 0) && confirmed)
    {
      launchOperation(newTask,
          INFO_CTRL_PANEL_STOPPING_SERVER_SUMMARY.get(),
          INFO_CTRL_PANEL_STOPPING_SERVER_SUCCESSFUL_SUMMARY.get(),
          INFO_CTRL_PANEL_STOPPING_SERVER_SUCCESSFUL_DETAILS.get(),
          ERR_CTRL_PANEL_STOPPING_SERVER_ERROR_SUMMARY.get(),
          null,
          ERR_CTRL_PANEL_STOPPING_SERVER_ERROR_DETAILS,
          progressDialog);
      progressDialog.setVisible(true);
    }
    if (errors.size() > 0)
    {
      displayErrorDialog(errors);
    }
  }

  /**
   * Restarts the server.  This method will launch a task and open a progress
   * dialog that will restart the server.  This method must be called from the
   * event thread.
   *
   */
  protected void restartServer()
  {
    LinkedHashSet<Message> errors = new LinkedHashSet<Message>();
    ProgressDialog progressDialog = new ProgressDialog(
        Utilities.createFrame(),
        Utilities.getParentDialog(this),
        INFO_CTRL_PANEL_RESTART_SERVER_PROGRESS_DLG_TITLE.get(), getInfo());
    RestartServerTask newTask = new RestartServerTask(getInfo(),
        progressDialog);
    for (Task task : getInfo().getTasks())
    {
      task.canLaunch(newTask, errors);
    }
    boolean confirmed = true;
    if (errors.size() == 0)
    {
      confirmed = displayConfirmationDialog(
          INFO_CTRL_PANEL_CONFIRMATION_REQUIRED_SUMMARY.get(),
          INFO_CTRL_PANEL_CONFIRM_RESTART_SERVER_DETAILS.get());
    }
    if ((errors.size() == 0) && confirmed)
    {
      launchOperation(newTask,
          INFO_CTRL_PANEL_STOPPING_SERVER_SUMMARY.get(),
          INFO_CTRL_PANEL_RESTARTING_SERVER_SUCCESSFUL_SUMMARY.get(),
          INFO_CTRL_PANEL_RESTARTING_SERVER_SUCCESSFUL_DETAILS.get(),
          ERR_CTRL_PANEL_RESTARTING_SERVER_ERROR_SUMMARY.get(),
          null,
          ERR_CTRL_PANEL_RESTARTING_SERVER_ERROR_DETAILS,
          progressDialog);
      progressDialog.setVisible(true);
    }
    if (errors.size() > 0)
    {
      displayErrorDialog(errors);
    }
  }

  /**
   * Displays a dialog asking for authentication. This method must be called
   * from the event thread.
   *
   */
  protected void authenticate()
  {
    if (!getLoginDialog().isVisible())
    {
      getLoginDialog().setVisible(true);
    }
    getLoginDialog().toFront();
  }

  /**
   * Returns the login dialog that is displayed when the method authenticate
   * is called.
   * @return the login dialog that is displayed when the method authenticate
   * is called.
   */
  protected GenericDialog getLoginDialog()
  {
    if (isLocal())
    {
      GenericDialog loginDialog =
        ControlCenterMainPane.getLocalServerLoginDialog(getInfo());
      Utilities.centerGoldenMean(loginDialog, Utilities.getFrame(this));
      loginDialog.setModal(true);
      return loginDialog;
    }
    else
    {
      GenericDialog localOrRemoteDialog =
        ControlCenterMainPane.getLocalOrRemoteDialog(getInfo());
      Utilities.centerGoldenMean(localOrRemoteDialog, Utilities.getFrame(this));
      localOrRemoteDialog.setModal(true);
      return localOrRemoteDialog;
    }
  }

  /**
   * Tells whether an entry exists or not.  Actually it tells if we could find
   * a given entry or not.
   * @param dn the DN of the entry to look for.
   * @return <CODE>true</CODE> if the entry with the provided DN could be found
   * and <CODE>false</CODE> otherwise.
   */
  protected boolean entryExists(String dn)
  {
    boolean entryExists = false;
    try
    {
      SearchControls ctls = new SearchControls();
      ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
      ctls.setReturningAttributes(
          new String[] {
              "1.1"
          });
      String filter = BrowserController.ALL_OBJECTS_FILTER;
      NamingEnumeration<SearchResult> result =
        getInfo().getDirContext().search(Utilities.getJNDIName(dn),
            filter, ctls);

      try
      {
        while (result.hasMore())
        {
          SearchResult sr = result.next();
          entryExists = sr != null;
        }
      }
      finally
      {
        result.close();
      }
    }
    catch (Throwable t)
    {
    }
    return entryExists;
  }

  /**
   * Tells whether a given entry exists and contains the specified object class.
   * @param dn the DN of the entry.
   * @param objectClass the object class.
   * @return <CODE>true</CODE> if the entry exists and contains the specified
   * object class and <CODE>false</CODE> otherwise.
   */
  protected boolean hasObjectClass(String dn, String objectClass)
  {
    boolean hasObjectClass = false;
    try
    {
      SearchControls ctls = new SearchControls();
      ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
      ctls.setReturningAttributes(
          new String[] {
              ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME
          });
      String filter = BrowserController.ALL_OBJECTS_FILTER;
      NamingEnumeration<SearchResult> result =
        getInfo().getDirContext().search(Utilities.getJNDIName(dn),
            filter, ctls);

      try
      {
        while (result.hasMore())
        {
          SearchResult sr = result.next();
          Set<String> values = ConnectionUtils.getValues(sr,
              ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME);
          if (values != null)
          {
            for (String s : values)
            {
              if (s.equalsIgnoreCase(objectClass))
              {
                hasObjectClass = true;
                break;
              }
            }
          }
        }
      }
      finally
      {
        result.close();
      }
    }
    catch (Throwable t)
    {
    }
    return hasObjectClass;
  }

  /**
   * Returns the border to be used in the right panel of the dialog with a tree
   * on the left (for instance the schema browser, entry browser and index
   * browser).
   * @return the border to be used in the right panel.
   */
  protected Border getRightPanelBorder()
  {
    return ColorAndFontConstants.textAreaBorder;
  }

  /**
   * Returns the monitoring value in a String form to be displayed to the user.
   * @param attr the attribute to analyze.
   * @param monitoringEntry the monitoring entry.
   * @return the monitoring value in a String form to be displayed to the user.
   */
  public static String getMonitoringValue(MonitoringAttributes attr,
      CustomSearchResult monitoringEntry)
  {
    return Utilities.getMonitoringValue(attr, monitoringEntry);
  }

  /**
   * Updates the monitoring information writing it to a list of labels.
   * @param monitoringAttrs the monitoring operations whose information we want
   * to update.
   * @param monitoringLabels the monitoring labels to be updated.
   * @param monitoringEntry the monitoring entry containing the information to
   * be displayed.
   */
  protected void updateMonitoringInfo(
      List<MonitoringAttributes> monitoringAttrs,
      List<JLabel> monitoringLabels, CustomSearchResult monitoringEntry)
  {
    for (int i=0 ; i<monitoringAttrs.size(); i++)
    {
      String value =
        getMonitoringValue(monitoringAttrs.get(i), monitoringEntry);
      JLabel l = monitoringLabels.get(i);
      l.setText(value);
    }
  }

  /**
   * Returns the first value for a given attribute in the provided entry.
   * @param sr the entry.  It may be <CODE>null</CODE>.
   * @param attrName the attribute name.
   * @return the first value for a given attribute in the provided entry.
   */
  protected Object getFirstMonitoringValue(CustomSearchResult sr,
      String attrName)
  {
    return Utilities.getFirstMonitoringValue(sr, attrName);
  }

  /**
   * Returns the label to be used in panels (with ':') based on the definition
   * of the monitoring attribute.
   * @param attr the monitoring attribute.
   * @return the label to be used in panels (with ':') based on the definition
   * of the monitoring attribute.
   */
  protected static Message getLabel(MonitoringAttributes attr)
  {
    return INFO_CTRL_PANEL_OPERATION_NAME_AS_LABEL.get(
        attr.getMessage().toString());
  }

  /**
   * Returns the command-line arguments associated with the provided schedule.
   * @param schedule the schedule.
   * @return the command-line arguments associated with the provided schedule.
   */
  protected List<String> getScheduleArgs(ScheduleType schedule)
  {
    List<String> args = new ArrayList<String>(2);
    switch (schedule.getType())
    {
    case LAUNCH_LATER:
      args.add("--start");
      args.add(getStartTimeForTask(schedule.getLaunchLaterDate()));
      break;
    case LAUNCH_PERIODICALLY:
      args.add("--recurringTask");
      args.add(schedule.getCronValue());
      break;
    }
    return args;
  }

  /**
   * Checks whether the server is running or not and depending on the schedule
   * updates the list of errors with the errors found.
   * @param schedule the schedule.
   * @param errors the list of errors.
   * @param label the label to be marked as invalid if errors where encountered.
   */
  protected void addScheduleErrors(ScheduleType schedule,
      Collection<Message> errors, JLabel label)
  {
    if (!isServerRunning())
    {
      ScheduleType.Type type = schedule.getType();
      if (type == ScheduleType.Type.LAUNCH_LATER)
      {
        errors.add(ERR_CTRL_PANEL_LAUNCH_LATER_REQUIRES_SERVER_RUNNING.get());
        setPrimaryInvalid(label);
      }
      else if (type == ScheduleType.Type.LAUNCH_PERIODICALLY)
      {
        errors.add(
            ERR_CTRL_PANEL_LAUNCH_SCHEDULE_REQUIRES_SERVER_RUNNING.get());
        setPrimaryInvalid(label);
      }
    }
  }

  private String getStartTimeForTask(Date date)
  {
    return taskDateFormat.format(date);
  }



  /**
   * Checks whether the provided superior object classes are compatible with
   * the provided object class type.  If not, the method updates the provided
   * list of error messages with a message describing the incompatibility.
   * @param objectClassSuperiors the superior object classes.
   * @param objectClassType the object class type.
   * @param errors the list of error messages.
   */
  protected void checkCompatibleSuperiors(Set<ObjectClass> objectClassSuperiors,
      ObjectClassType objectClassType, List<Message> errors)
  {
    SortedSet<String> notCompatibleClasses =
      new TreeSet<String>(new LowerCaseComparator());
    for (ObjectClass oc : objectClassSuperiors)
    {
      if (oc.getObjectClassType() == ObjectClassType.ABSTRACT)
      {
        // Nothing to do.
      }
      else if (oc.getObjectClassType() != objectClassType)
      {
        notCompatibleClasses.add(oc.getNameOrOID());
      }
    }
    if (!notCompatibleClasses.isEmpty())
    {
      String arg = Utilities.getStringFromCollection(notCompatibleClasses,
          ", ");
      if (objectClassType == ObjectClassType.STRUCTURAL)
      {
        errors.add(
            ERR_CTRL_PANEL_INCOMPATIBLE_SUPERIORS_WITH_STRUCTURAL.get(arg));
      }
      else if (objectClassType == ObjectClassType.AUXILIARY)
      {
        errors.add(
            ERR_CTRL_PANEL_INCOMPATIBLE_SUPERIORS_WITH_AUXILIARY.get(arg));
      }
      else if (objectClassType == ObjectClassType.ABSTRACT)
      {
        errors.add(
            ERR_CTRL_PANEL_INCOMPATIBLE_SUPERIORS_WITH_ABSTRACT.get(arg));
      }
    }
  }
}
