001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.guitools.controlpanel.ui;
028
029import static org.opends.messages.AdminToolMessages.*;
030import static org.opends.messages.QuickSetupMessages.*;
031
032import static com.forgerock.opendj.cli.Utils.*;
033
034import java.awt.Component;
035import java.awt.GridBagConstraints;
036import java.awt.GridBagLayout;
037import java.awt.Insets;
038import java.awt.Window;
039import java.awt.event.ActionEvent;
040import java.awt.event.ActionListener;
041import java.awt.event.ItemEvent;
042import java.awt.event.ItemListener;
043import java.awt.event.KeyAdapter;
044import java.awt.event.KeyEvent;
045import java.net.URI;
046import java.security.cert.X509Certificate;
047import java.util.ArrayList;
048import java.util.Enumeration;
049import java.util.HashMap;
050import java.util.LinkedHashSet;
051import java.util.List;
052import java.util.Map;
053import java.util.Set;
054import java.util.SortedSet;
055import java.util.TreeSet;
056
057import javax.naming.NamingException;
058import javax.naming.ldap.InitialLdapContext;
059import javax.swing.BorderFactory;
060import javax.swing.Box;
061import javax.swing.DefaultComboBoxModel;
062import javax.swing.JButton;
063import javax.swing.JComboBox;
064import javax.swing.JComponent;
065import javax.swing.JLabel;
066import javax.swing.JList;
067import javax.swing.JPanel;
068import javax.swing.JSeparator;
069import javax.swing.JTree;
070import javax.swing.SwingConstants;
071import javax.swing.SwingUtilities;
072import javax.swing.border.EmptyBorder;
073import javax.swing.event.TreeModelEvent;
074import javax.swing.event.TreeModelListener;
075import javax.swing.tree.DefaultMutableTreeNode;
076import javax.swing.tree.DefaultTreeModel;
077import javax.swing.tree.TreeNode;
078import javax.swing.tree.TreePath;
079
080import org.forgerock.i18n.LocalizableMessage;
081import org.forgerock.i18n.LocalizableMessageBuilder;
082import org.forgerock.i18n.slf4j.LocalizedLogger;
083import org.forgerock.opendj.ldap.ByteString;
084import org.opends.admin.ads.util.ApplicationTrustManager;
085import org.opends.admin.ads.util.ConnectionUtils;
086import org.opends.guitools.controlpanel.browser.BrowserController;
087import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
088import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
089import org.opends.guitools.controlpanel.datamodel.CategorizedComboBoxElement;
090import org.opends.guitools.controlpanel.datamodel.ConfigReadException;
091import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
092import org.opends.guitools.controlpanel.datamodel.IndexDescriptor;
093import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
094import org.opends.guitools.controlpanel.event.BackendPopulatedEvent;
095import org.opends.guitools.controlpanel.event.BackendPopulatedListener;
096import org.opends.guitools.controlpanel.event.BrowserEvent;
097import org.opends.guitools.controlpanel.event.BrowserEventListener;
098import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
099import org.opends.guitools.controlpanel.ui.components.FilterTextField;
100import org.opends.guitools.controlpanel.ui.components.TreePanel;
101import org.opends.guitools.controlpanel.ui.nodes.BasicNode;
102import org.opends.guitools.controlpanel.ui.renderer.CustomListCellRenderer;
103import org.opends.guitools.controlpanel.util.Utilities;
104import org.opends.quicksetup.UserDataCertificateException;
105import org.opends.quicksetup.ui.CertificateDialog;
106import org.opends.quicksetup.util.UIKeyStore;
107import org.opends.server.protocols.ldap.LDAPFilter;
108import org.opends.server.types.AttributeType;
109import org.opends.server.types.DN;
110import org.opends.server.types.DirectoryException;
111import org.opends.server.types.LDAPException;
112import org.opends.server.types.SearchFilter;
113import org.opends.server.util.ServerConstants;
114
115/**
116 * The abstract class used to refactor some code. The classes that extend this
117 * class are the 'Browse Entries' panel and the panel of the dialog we display
118 * when the user can choose a set of entries (for instance when the user adds a
119 * member to a group in the 'New Group' dialog).
120 */
121public abstract class AbstractBrowseEntriesPanel extends StatusGenericPanel implements BackendPopulatedListener
122{
123  private static final long serialVersionUID = -6063927039968115236L;
124  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
125
126  /** LDAP filter message. */
127  protected static final LocalizableMessage LDAP_FILTER = INFO_CTRL_PANEL_LDAP_FILTER.get();
128  /** User filter message. */
129  protected static final LocalizableMessage USER_FILTER = INFO_CTRL_PANEL_USERS_FILTER.get();
130  /** Group filter message. */
131  protected static final LocalizableMessage GROUP_FILTER = INFO_CTRL_PANEL_GROUPS_FILTER.get();
132  private static final LocalizableMessage OTHER_BASE_DN = INFO_CTRL_PANEL_OTHER_BASE_DN.get();
133
134  private static final String ALL_BASE_DNS = "All Base DNs";
135  private static final int MAX_NUMBER_ENTRIES = 5000;
136  private static final int MAX_NUMBER_OTHER_BASE_DNS = 10;
137  private static final String[] CONTAINER_CLASSES = { "organization", "organizationalUnit" };
138  private static final String[] SYSTEM_INDEXES =
139    { "aci", "dn2id", "ds-sync-hist", "entryUUID", "id2children", "id2subtree" };
140
141
142  private JComboBox<String> baseDNs;
143
144  /** The combo box containing the different filter types. */
145  protected JComboBox<CharSequence> filterAttribute;
146  /** The text field of the filter. */
147  protected FilterTextField filter;
148
149  private JButton applyButton;
150  private JButton okButton;
151  private JButton cancelButton;
152  private JButton closeButton;
153
154  private JLabel lBaseDN;
155  private JLabel lFilter;
156  private JLabel lLimit;
157  private JLabel lNumberOfEntries;
158  private JLabel lNoMatchFound;
159
160  private InitialLdapContext createdUserDataCtx;
161  /** The tree pane contained in this panel. */
162  protected TreePanel treePane;
163  /** The browser controller used to update the LDAP entry tree. */
164  protected BrowserController controller;
165  private NumberOfEntriesUpdater numberEntriesUpdater;
166  private BaseDNPanel otherBaseDNPanel;
167  private GenericDialog otherBaseDNDlg;
168  private boolean firstTimeDisplayed = true;
169  private Object lastSelectedBaseDN;
170  private boolean ignoreBaseDNEvents;
171
172  private List<DN> otherBaseDns = new ArrayList<>();
173
174  /**
175   * Default constructor.
176   */
177  public AbstractBrowseEntriesPanel()
178  {
179    super();
180    createLayout();
181  }
182
183  @Override
184  public boolean requiresBorder()
185  {
186    return false;
187  }
188
189  @Override
190  public boolean requiresScroll()
191  {
192    return false;
193  }
194
195  @Override
196  public boolean callConfigurationChangedInBackground()
197  {
198    return true;
199  }
200
201  @Override
202  public void setInfo(ControlPanelInfo info)
203  {
204    if (controller == null)
205    {
206      createBrowserController(info);
207    }
208    super.setInfo(info);
209    treePane.setInfo(info);
210    info.addBackendPopulatedListener(this);
211  }
212
213  @Override
214  public final GenericDialog.ButtonType getButtonType()
215  {
216    return GenericDialog.ButtonType.NO_BUTTON;
217  }
218
219  /**
220   * Since these panel has a special layout, we cannot use the layout of the
221   * GenericDialog and we return ButtonType.NO_BUTTON in the method
222   * getButtonType. We use this method to be able to add some progress
223   * information to the left of the buttons.
224   *
225   * @return the button type of the panel.
226   */
227  protected abstract GenericDialog.ButtonType getBrowseButtonType();
228
229  @Override
230  public void toBeDisplayed(boolean visible)
231  {
232    super.toBeDisplayed(visible);
233    Window w = Utilities.getParentDialog(this);
234    if (w instanceof GenericDialog)
235    {
236      ((GenericDialog) w).getRootPane().setDefaultButton(null);
237    }
238    else if (w instanceof GenericFrame)
239    {
240      ((GenericFrame) w).getRootPane().setDefaultButton(null);
241    }
242  }
243
244  @Override
245  protected void setEnabledOK(boolean enable)
246  {
247    okButton.setEnabled(enable);
248  }
249
250  @Override
251  protected void setEnabledCancel(boolean enable)
252  {
253    cancelButton.setEnabled(enable);
254  }
255
256  /** Creates the layout of the panel (but the contents are not populated here). */
257  @SuppressWarnings("unchecked")
258  private void createLayout()
259  {
260    setBackground(ColorAndFontConstants.greyBackground);
261    GridBagConstraints gbc = new GridBagConstraints();
262    gbc.anchor = GridBagConstraints.WEST;
263    gbc.gridx = 0;
264    gbc.gridy = 0;
265    gbc.gridwidth = 7;
266    addErrorPane(gbc);
267    LocalizableMessage title = INFO_CTRL_PANEL_SERVER_NOT_RUNNING_SUMMARY.get();
268    LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
269    mb.append(INFO_CTRL_PANEL_SERVER_NOT_RUNNING_DETAILS.get());
270    mb.append("<br><br>");
271    mb.append(getStartServerHTML());
272    LocalizableMessage details = mb.toMessage();
273    updateErrorPane(errorPane, title, ColorAndFontConstants.errorTitleFont, details, ColorAndFontConstants.defaultFont);
274    errorPane.setVisible(true);
275    errorPane.setFocusable(true);
276
277    gbc.insets = new Insets(10, 10, 0, 10);
278    gbc.gridy++;
279    gbc.gridwidth = 1;
280    gbc.weightx = 0;
281    gbc.fill = GridBagConstraints.NONE;
282    lBaseDN = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_BASE_DN_LABEL.get());
283    gbc.gridx = 0;
284    gbc.fill = GridBagConstraints.HORIZONTAL;
285    gbc.insets.right = 0;
286    add(lBaseDN, gbc);
287    gbc.insets.left = 5;
288    baseDNs = Utilities.createComboBox();
289
290    DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();
291    model.addElement("dc=dn to be displayed");
292    baseDNs.setModel(model);
293    baseDNs.setRenderer(new CustomComboBoxCellRenderer(baseDNs));
294    baseDNs.addItemListener(new ItemListener()
295    {
296      @SuppressWarnings("rawtypes")
297      @Override
298      public void itemStateChanged(ItemEvent ev)
299      {
300        if (ignoreBaseDNEvents || ev.getStateChange() != ItemEvent.SELECTED)
301        {
302          return;
303        }
304        Object o = baseDNs.getSelectedItem();
305        if (isCategory(o))
306        {
307          if (lastSelectedBaseDN == null)
308          {
309            // Look for the first element that is not a category
310            for (int i = 0; i < baseDNs.getModel().getSize(); i++)
311            {
312              Object item = baseDNs.getModel().getElementAt(i);
313              if (item instanceof CategorizedComboBoxElement && !isCategory(item))
314              {
315                lastSelectedBaseDN = item;
316                break;
317              }
318            }
319            if (lastSelectedBaseDN != null)
320            {
321              baseDNs.setSelectedItem(lastSelectedBaseDN);
322            }
323          }
324          else
325          {
326            ignoreBaseDNEvents = true;
327            baseDNs.setSelectedItem(lastSelectedBaseDN);
328            ignoreBaseDNEvents = false;
329          }
330        }
331        else if (COMBO_SEPARATOR.equals(o))
332        {
333          ignoreBaseDNEvents = true;
334          baseDNs.setSelectedItem(lastSelectedBaseDN);
335          ignoreBaseDNEvents = false;
336        }
337        else if (!OTHER_BASE_DN.equals(o))
338        {
339          lastSelectedBaseDN = o;
340          if (lastSelectedBaseDN != null)
341          {
342            applyButtonClicked();
343          }
344        }
345        else
346        {
347          if (otherBaseDNDlg == null)
348          {
349            otherBaseDNPanel = new BaseDNPanel();
350            otherBaseDNDlg = new GenericDialog(Utilities.getFrame(AbstractBrowseEntriesPanel.this), otherBaseDNPanel);
351            otherBaseDNDlg.setModal(true);
352            Utilities.centerGoldenMean(otherBaseDNDlg, Utilities.getParentDialog(AbstractBrowseEntriesPanel.this));
353          }
354          otherBaseDNDlg.setVisible(true);
355          String newBaseDn = otherBaseDNPanel.getBaseDn();
356          DefaultComboBoxModel model = (DefaultComboBoxModel) baseDNs.getModel();
357          if (newBaseDn != null)
358          {
359            CategorizedComboBoxElement newElement = null;
360
361            try
362            {
363              DN dn = DN.valueOf(newBaseDn);
364              newElement =
365                  new CategorizedComboBoxElement(Utilities.unescapeUtf8(dn.toString()),
366                      CategorizedComboBoxElement.Type.REGULAR);
367              if (!otherBaseDns.contains(dn))
368              {
369                otherBaseDns.add(0, dn);
370
371                if (otherBaseDns.size() > MAX_NUMBER_OTHER_BASE_DNS)
372                {
373                  ignoreBaseDNEvents = true;
374                  for (int i = otherBaseDns.size() - 1; i >= MAX_NUMBER_OTHER_BASE_DNS; i--)
375                  {
376                    DN dnToRemove = otherBaseDns.get(i);
377                    otherBaseDns.remove(i);
378                    Object elementToRemove =
379                        new CategorizedComboBoxElement(Utilities.unescapeUtf8(dnToRemove.toString()),
380                            CategorizedComboBoxElement.Type.REGULAR);
381                    model.removeElement(elementToRemove);
382                  }
383                  ignoreBaseDNEvents = false;
384                }
385              }
386              if (model.getIndexOf(newElement) == -1)
387              {
388                int index = model.getIndexOf(COMBO_SEPARATOR);
389                model.insertElementAt(newElement, index + 1);
390                if (otherBaseDns.size() == 1)
391                {
392                  model.insertElementAt(COMBO_SEPARATOR, index + 2);
393                }
394              }
395            }
396            catch (Throwable t)
397            {
398              throw new RuntimeException("Unexpected error decoding dn " + newBaseDn, t);
399            }
400
401            model.setSelectedItem(newElement);
402          }
403          else if (lastSelectedBaseDN != null)
404          {
405            ignoreBaseDNEvents = true;
406            model.setSelectedItem(lastSelectedBaseDN);
407            ignoreBaseDNEvents = false;
408          }
409        }
410      }
411    });
412    gbc.gridx++;
413    add(baseDNs, gbc);
414
415    gbc.gridx++;
416    gbc.fill = GridBagConstraints.VERTICAL;
417    gbc.insets.left = 10;
418    add(new JSeparator(SwingConstants.VERTICAL), gbc);
419    gbc.fill = GridBagConstraints.HORIZONTAL;
420    lFilter = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_FILTER_LABEL.get());
421    gbc.gridx++;
422    add(lFilter, gbc);
423
424    filterAttribute = Utilities.createComboBox();
425    filterAttribute.setModel(new DefaultComboBoxModel<CharSequence>(new CharSequence[] {
426      USER_FILTER, GROUP_FILTER, COMBO_SEPARATOR, "attributetobedisplayed", COMBO_SEPARATOR, LDAP_FILTER }));
427    filterAttribute.setRenderer(new CustomListCellRenderer(filterAttribute));
428    filterAttribute.addItemListener(new IgnoreItemListener(filterAttribute));
429    gbc.gridx++;
430    gbc.insets.left = 5;
431    add(filterAttribute, gbc);
432
433    filter = new FilterTextField();
434    filter.setToolTipText(INFO_CTRL_PANEL_SUBSTRING_SEARCH_INLINE_HELP.get().toString());
435    filter.addKeyListener(new KeyAdapter()
436    {
437      @Override
438      public void keyReleased(KeyEvent e)
439      {
440        if (e.getKeyCode() == KeyEvent.VK_ENTER && applyButton.isEnabled())
441        {
442          filter.displayRefreshIcon(true);
443          applyButtonClicked();
444        }
445      }
446    });
447    filter.addActionListener(new ActionListener()
448    {
449      @Override
450      public void actionPerformed(ActionEvent ev)
451      {
452        filter.displayRefreshIcon(true);
453        applyButtonClicked();
454      }
455    });
456
457    gbc.weightx = 1.0;
458    gbc.gridx++;
459    add(filter, gbc);
460
461    gbc.insets.top = 10;
462    applyButton = Utilities.createButton(INFO_CTRL_PANEL_APPLY_BUTTON_LABEL.get());
463    gbc.insets.right = 10;
464    gbc.gridx++;
465    gbc.weightx = 0.0;
466    add(applyButton, gbc);
467    applyButton.addActionListener(new ActionListener()
468    {
469      @Override
470      public void actionPerformed(ActionEvent ev)
471      {
472        applyButtonClicked();
473      }
474    });
475    gbc.insets = new Insets(10, 0, 0, 0);
476    gbc.gridx = 0;
477    gbc.gridy++;
478    gbc.weightx = 1.0;
479    gbc.weighty = 1.0;
480    gbc.fill = GridBagConstraints.BOTH;
481    gbc.gridwidth = 7;
482    add(createMainPanel(), gbc);
483
484    //  The button panel
485    gbc.gridy++;
486    gbc.weighty = 0.0;
487    gbc.insets = new Insets(0, 0, 0, 0);
488    add(createButtonsPanel(), gbc);
489  }
490
491  /**
492   * Returns the panel that contains the buttons of type OK, CANCEL, etc.
493   *
494   * @return the panel that contains the buttons of type OK, CANCEL, etc.
495   */
496  private JPanel createButtonsPanel()
497  {
498    JPanel buttonsPanel = new JPanel(new GridBagLayout());
499    GridBagConstraints gbc = new GridBagConstraints();
500    gbc.gridx = 0;
501    gbc.gridy = 0;
502    gbc.anchor = GridBagConstraints.WEST;
503    gbc.fill = GridBagConstraints.HORIZONTAL;
504    gbc.gridwidth = 1;
505    gbc.gridy = 0;
506    lLimit = Utilities.createDefaultLabel();
507    Utilities.setWarningLabel(lLimit, INFO_CTRL_PANEL_MAXIMUM_CHILDREN_DISPLAYED.get(MAX_NUMBER_ENTRIES));
508    gbc.weighty = 0.0;
509    gbc.gridy++;
510    lLimit.setVisible(false);
511    lNumberOfEntries = Utilities.createDefaultLabel();
512    gbc.insets = new Insets(10, 10, 10, 10);
513    buttonsPanel.add(lNumberOfEntries, gbc);
514    buttonsPanel.add(lLimit, gbc);
515    gbc.weightx = 1.0;
516    gbc.gridx++;
517    buttonsPanel.add(Box.createHorizontalGlue(), gbc);
518    buttonsPanel.setOpaque(true);
519    buttonsPanel.setBackground(ColorAndFontConstants.greyBackground);
520    gbc.gridx++;
521    gbc.weightx = 0.0;
522    if (getBrowseButtonType() == GenericDialog.ButtonType.CLOSE)
523    {
524      closeButton = Utilities.createButton(INFO_CTRL_PANEL_CLOSE_BUTTON_LABEL.get());
525      closeButton.setOpaque(false);
526      buttonsPanel.add(closeButton, gbc);
527      closeButton.addActionListener(new ActionListener()
528      {
529        @Override
530        public void actionPerformed(ActionEvent ev)
531        {
532          closeClicked();
533        }
534      });
535    }
536    else if (getBrowseButtonType() == GenericDialog.ButtonType.OK)
537    {
538      okButton = Utilities.createButton(INFO_CTRL_PANEL_OK_BUTTON_LABEL.get());
539      okButton.setOpaque(false);
540      buttonsPanel.add(okButton, gbc);
541      okButton.addActionListener(new ActionListener()
542      {
543        @Override
544        public void actionPerformed(ActionEvent ev)
545        {
546          okClicked();
547        }
548      });
549    }
550    if (getBrowseButtonType() == GenericDialog.ButtonType.OK_CANCEL)
551    {
552      okButton = Utilities.createButton(INFO_CTRL_PANEL_OK_BUTTON_LABEL.get());
553      okButton.setOpaque(false);
554      gbc.insets.right = 0;
555      buttonsPanel.add(okButton, gbc);
556      okButton.addActionListener(new ActionListener()
557      {
558        @Override
559        public void actionPerformed(ActionEvent ev)
560        {
561          okClicked();
562        }
563      });
564      cancelButton = Utilities.createButton(INFO_CTRL_PANEL_CANCEL_BUTTON_LABEL.get());
565      cancelButton.setOpaque(false);
566      gbc.insets.right = 10;
567      gbc.insets.left = 5;
568      gbc.gridx++;
569      buttonsPanel.add(cancelButton, gbc);
570      cancelButton.addActionListener(new ActionListener()
571      {
572        @Override
573        public void actionPerformed(ActionEvent ev)
574        {
575          cancelClicked();
576        }
577      });
578    }
579
580    buttonsPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ColorAndFontConstants.defaultBorderColor));
581
582    return buttonsPanel;
583  }
584
585  /** {@inheritDoc} */
586  @Override
587  public Component getPreferredFocusComponent()
588  {
589    return baseDNs;
590  }
591
592  /** {@inheritDoc} */
593  @Override
594  public void cancelClicked()
595  {
596    setPrimaryValid(lBaseDN);
597    setSecondaryValid(lFilter);
598    super.cancelClicked();
599  }
600
601  /**
602   * The method that is called when the user clicks on Apply. Basically it will
603   * update the BrowserController with the new base DN and filter specified by
604   * the user. The method assumes that is being called from the event thread.
605   */
606  protected void applyButtonClicked()
607  {
608    List<LocalizableMessage> errors = new ArrayList<>();
609    setPrimaryValid(lFilter);
610    String s = getBaseDN();
611    boolean displayAll = false;
612    DN theDN = null;
613    if (s != null)
614    {
615      displayAll = ALL_BASE_DNS.equals(s);
616      if (!displayAll)
617      {
618        try
619        {
620          theDN = DN.valueOf(s);
621        }
622        catch (Throwable t)
623        {
624          errors.add(INFO_CTRL_PANEL_INVALID_DN_DETAILS.get(s, t));
625        }
626      }
627    }
628    else
629    {
630      errors.add(INFO_CTRL_PANEL_NO_BASE_DN_SELECTED.get());
631    }
632    String filterValue = getFilter();
633    try
634    {
635      LDAPFilter.decode(filterValue);
636    }
637    catch (LDAPException le)
638    {
639      errors.add(INFO_CTRL_PANEL_INVALID_FILTER_DETAILS.get(le.getMessageObject()));
640      setPrimaryInvalid(lFilter);
641    }
642    if (errors.isEmpty())
643    {
644      lLimit.setVisible(false);
645      lNumberOfEntries.setVisible(true);
646      controller.removeAllUnderRoot();
647      controller.setFilter(filterValue);
648      controller.setAutomaticExpand(!BrowserController.ALL_OBJECTS_FILTER.equals(filterValue));
649      SortedSet<String> allSuffixes = new TreeSet<>();
650      if (controller.getConfigurationConnection() != null)
651      {
652        treePane.getTree().setRootVisible(displayAll);
653        treePane.getTree().setShowsRootHandles(!displayAll);
654        boolean added = false;
655        for (BackendDescriptor backend : getInfo().getServerDescriptor().getBackends())
656        {
657          for (BaseDNDescriptor baseDN : backend.getBaseDns())
658          {
659            boolean isBaseDN = baseDN.getDn().equals(theDN);
660            String dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
661            if (displayAll)
662            {
663              allSuffixes.add(dn);
664            }
665            else if (isBaseDN)
666            {
667              controller.addSuffix(dn, null);
668              added = true;
669            }
670          }
671        }
672        if (displayAll)
673        {
674          allSuffixes.add(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
675          for (String dn : allSuffixes)
676          {
677            controller.addSuffix(dn, null);
678          }
679        }
680        else if (!added && !displayAll)
681        {
682          if (isChangeLog(theDN))
683          {
684            // Consider it a suffix
685            controller.addSuffix(s, null);
686          }
687          else
688          {
689            BasicNode rootNode = (BasicNode) controller.getTree().getModel().getRoot();
690            if (controller.findChildNode(rootNode, s) == -1)
691            {
692              controller.addNodeUnderRoot(s);
693            }
694          }
695        }
696      }
697      else
698      {
699        controller.getTree().setRootVisible(false);
700        controller.removeAllUnderRoot();
701      }
702    }
703    else
704    {
705      displayErrorDialog(errors);
706    }
707  }
708
709  private boolean isChangeLog(DN theDN)
710  {
711    try
712    {
713      return theDN.equals(DN.valueOf(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT));
714    }
715    catch (Throwable t)
716    {
717      // Bug
718      t.printStackTrace();
719      return false;
720    }
721  }
722
723  /**
724   * Returns the LDAP filter built based in the parameters provided by the user.
725   *
726   * @return the LDAP filter built based in the parameters provided by the user.
727   */
728  private String getFilter()
729  {
730    String filterText = filter.getText();
731    if (filterText.length() == 0)
732    {
733      return BrowserController.ALL_OBJECTS_FILTER;
734    }
735
736    Object attr = filterAttribute.getSelectedItem();
737    if (LDAP_FILTER.equals(attr))
738    {
739      filterText = filterText.trim();
740      if (filterText.length() == 0)
741      {
742        return BrowserController.ALL_OBJECTS_FILTER;
743      }
744
745      return filterText;
746    }
747    else if (USER_FILTER.equals(attr))
748    {
749      if ("*".equals(filterText))
750      {
751        return "(objectClass=person)";
752      }
753
754      return "(&(objectClass=person)(|" + "(cn=" + filterText + ")(sn=" + filterText + ")(uid=" + filterText + ")))";
755    }
756    else if (GROUP_FILTER.equals(attr))
757    {
758      if ("*".equals(filterText))
759      {
760        return "(|(objectClass=groupOfUniqueNames)(objectClass=groupOfURLs))";
761      }
762
763      return "(&(|(objectClass=groupOfUniqueNames)(objectClass=groupOfURLs))" + "(cn=" + filterText + "))";
764    }
765    else if (attr != null)
766    {
767      try
768      {
769        return new LDAPFilter(SearchFilter.createFilterFromString("(" + attr + "=" + filterText + ")")).toString();
770      }
771      catch (DirectoryException de)
772      {
773        // Try this alternative:
774        AttributeType attrType =
775            getInfo().getServerDescriptor().getSchema().getAttributeType(attr.toString().toLowerCase());
776        ByteString filterBytes = ByteString.valueOfUtf8(filterText);
777        return new LDAPFilter(SearchFilter.createEqualityFilter(attrType, filterBytes)).toString();
778      }
779    }
780    else
781    {
782      return BrowserController.ALL_OBJECTS_FILTER;
783    }
784  }
785
786  /**
787   * Returns the component that will be displayed between the filtering options
788   * and the buttons panel. This component must contain the tree panel.
789   *
790   * @return the component that will be displayed between the filtering options
791   *         and the buttons panel.
792   */
793  protected abstract Component createMainPanel();
794
795  /** {@inheritDoc} */
796  @Override
797  public void backendPopulated(BackendPopulatedEvent ev)
798  {
799    if (controller.getConfigurationConnection() != null)
800    {
801      boolean displayAll = false;
802      boolean errorOccurred = false;
803      DN theDN = null;
804      String s = getBaseDN();
805      if (s != null)
806      {
807        displayAll = ALL_BASE_DNS.equals(s);
808        if (!displayAll)
809        {
810          try
811          {
812            theDN = DN.valueOf(s);
813          }
814          catch (Throwable t)
815          {
816            errorOccurred = true;
817          }
818        }
819      }
820      else
821      {
822        errorOccurred = true;
823      }
824      if (!errorOccurred)
825      {
826        treePane.getTree().setRootVisible(displayAll);
827        treePane.getTree().setShowsRootHandles(!displayAll);
828        BasicNode rootNode = (BasicNode) controller.getTree().getModel().getRoot();
829        boolean isSubordinate = false;
830        for (BackendDescriptor backend : ev.getBackends())
831        {
832          for (BaseDNDescriptor baseDN : backend.getBaseDns())
833          {
834            boolean isBaseDN = false;
835            if (baseDN.getDn().equals(theDN))
836            {
837              isBaseDN = true;
838            }
839            else if (baseDN.getDn().isAncestorOf(theDN))
840            {
841              isSubordinate = true;
842            }
843            String dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
844            if (displayAll || isBaseDN)
845            {
846              try
847              {
848                if (!controller.hasSuffix(dn))
849                {
850                  controller.addSuffix(dn, null);
851                }
852                else
853                {
854                  int index = controller.findChildNode(rootNode, dn);
855                  if (index >= 0)
856                  {
857                    TreeNode node = rootNode.getChildAt(index);
858                    if (node != null)
859                    {
860                      TreePath path = new TreePath(controller.getTreeModel().getPathToRoot(node));
861                      controller.startRefresh(controller.getNodeInfoFromPath(path));
862                    }
863                  }
864                }
865              }
866              catch (IllegalArgumentException iae)
867              {
868                // The suffix node exists but is not a suffix node. Simply log a message.
869                logger.warn(
870                    LocalizableMessage.raw("Suffix: " + dn + " added as a non suffix node. Exception: " + iae, iae));
871              }
872            }
873          }
874        }
875        if (isSubordinate && controller.findChildNode(rootNode, s) == -1)
876        {
877          controller.addNodeUnderRoot(s);
878        }
879      }
880    }
881  }
882
883  @Override
884  public void configurationChanged(ConfigurationChangeEvent ev)
885  {
886    final ServerDescriptor desc = ev.getNewDescriptor();
887
888    updateCombos(desc);
889    updateBrowserControllerAndErrorPane(desc);
890  }
891
892  /**
893   * Creates and returns the tree panel.
894   *
895   * @return the tree panel.
896   */
897  protected JComponent createTreePane()
898  {
899    treePane = new TreePanel();
900
901    lNoMatchFound = Utilities.createDefaultLabel(INFO_CTRL_PANEL_NO_MATCHES_FOUND_LABEL.get());
902    lNoMatchFound.setVisible(false);
903
904    // Calculate default size
905    JTree tree = treePane.getTree();
906    DefaultMutableTreeNode root = new DefaultMutableTreeNode("myserver.mydomain.com:389");
907    DefaultTreeModel model = new DefaultTreeModel(root);
908    tree.setModel(model);
909    tree.setShowsRootHandles(false);
910    tree.expandPath(new TreePath(root));
911    JPanel p = new JPanel(new GridBagLayout());
912    p.setBackground(ColorAndFontConstants.background);
913    GridBagConstraints gbc = new GridBagConstraints();
914    gbc.gridx = 0;
915    gbc.gridy = 0;
916    gbc.gridwidth = 1;
917    gbc.anchor = GridBagConstraints.NORTHWEST;
918    gbc.fill = GridBagConstraints.BOTH;
919    gbc.weightx = 1.0;
920    gbc.weighty = 1.0;
921    Utilities.setBorder(treePane, new EmptyBorder(10, 0, 10, 0));
922    p.add(treePane, gbc);
923    gbc.fill = GridBagConstraints.HORIZONTAL;
924    Utilities.setBorder(lNoMatchFound, new EmptyBorder(15, 15, 15, 15));
925    p.add(lNoMatchFound, gbc);
926
927    if (getInfo() != null && controller == null)
928    {
929      createBrowserController(getInfo());
930    }
931    numberEntriesUpdater = new NumberOfEntriesUpdater();
932    numberEntriesUpdater.start();
933
934    return p;
935  }
936
937  /**
938   * Creates the browser controller object.
939   *
940   * @param info
941   *          the ControlPanelInfo to be used to create the browser controller.
942   */
943  protected void createBrowserController(ControlPanelInfo info)
944  {
945    controller = new BrowserController(treePane.getTree(), info.getConnectionPool(), info.getIconPool());
946    controller.setContainerClasses(CONTAINER_CLASSES);
947    controller.setShowContainerOnly(false);
948    controller.setMaxChildren(MAX_NUMBER_ENTRIES);
949    controller.addBrowserEventListener(new BrowserEventListener()
950    {
951      /** {@inheritDoc} */
952      @Override
953      public void processBrowserEvent(BrowserEvent ev)
954      {
955        if (ev.getType() == BrowserEvent.Type.SIZE_LIMIT_REACHED)
956        {
957          lLimit.setVisible(true);
958          lNumberOfEntries.setVisible(false);
959        }
960      }
961    });
962    controller.getTreeModel().addTreeModelListener(new TreeModelListener()
963    {
964      @Override
965      public void treeNodesChanged(TreeModelEvent e)
966      {
967      }
968
969      @Override
970      public void treeNodesInserted(TreeModelEvent e)
971      {
972        checkRootNode();
973      }
974
975      @Override
976      public void treeNodesRemoved(TreeModelEvent e)
977      {
978        checkRootNode();
979      }
980
981      @Override
982      public void treeStructureChanged(TreeModelEvent e)
983      {
984        checkRootNode();
985      }
986    });
987  }
988
989
990  private static boolean displayIndex(String name)
991  {
992    for (String systemIndex : SYSTEM_INDEXES)
993    {
994      if (systemIndex.equalsIgnoreCase(name))
995      {
996        return false;
997      }
998    }
999    return true;
1000  }
1001
1002  /**
1003   * Updates the contents of the combo boxes with the provided ServerDescriptor.
1004   *
1005   * @param desc
1006   *          the server descriptor to be used to update the combo boxes.
1007   */
1008  @SuppressWarnings("rawtypes")
1009  private void updateCombos(ServerDescriptor desc)
1010  {
1011    final SortedSet<String> newElements = new TreeSet<>();
1012    for (BackendDescriptor backend : desc.getBackends())
1013    {
1014      for (IndexDescriptor index : backend.getIndexes())
1015      {
1016        String indexName = index.getName();
1017        if (displayIndex(indexName))
1018        {
1019          newElements.add(indexName);
1020        }
1021      }
1022    }
1023
1024    @SuppressWarnings("unchecked")
1025    final DefaultComboBoxModel<CharSequence> model = (DefaultComboBoxModel<CharSequence>) filterAttribute.getModel();
1026    if (hasChanged(newElements, model))
1027    {
1028      SwingUtilities.invokeLater(new Runnable()
1029      {
1030        @Override
1031        public void run()
1032        {
1033          Object selected = filterAttribute.getSelectedItem();
1034          model.removeAllElements();
1035          model.addElement(USER_FILTER);
1036          model.addElement(GROUP_FILTER);
1037          model.addElement(COMBO_SEPARATOR);
1038          for (String newElement : newElements)
1039          {
1040            model.addElement(newElement);
1041          }
1042          // If there are not backends, we get no indexes to set.
1043          if (!newElements.isEmpty())
1044          {
1045            model.addElement(COMBO_SEPARATOR);
1046          }
1047          model.addElement(LDAP_FILTER);
1048          if (selected != null)
1049          {
1050            if (model.getIndexOf(selected) != -1)
1051            {
1052              model.setSelectedItem(selected);
1053            }
1054            else
1055            {
1056              model.setSelectedItem(model.getElementAt(0));
1057            }
1058          }
1059        }
1060      });
1061    }
1062
1063    Set<Object> baseDNNewElements = new LinkedHashSet<>();
1064    SortedSet<String> backendIDs = new TreeSet<>();
1065    Map<String, SortedSet<String>> hmBaseDNs = new HashMap<>();
1066
1067    Map<String, BaseDNDescriptor> hmBaseDNWithEntries = new HashMap<>();
1068
1069    BaseDNDescriptor baseDNWithEntries = null;
1070    for (BackendDescriptor backend : desc.getBackends())
1071    {
1072      if (displayBackend(backend))
1073      {
1074        String backendID = backend.getBackendID();
1075        backendIDs.add(backendID);
1076        SortedSet<String> someBaseDNs = new TreeSet<>();
1077        for (BaseDNDescriptor baseDN : backend.getBaseDns())
1078        {
1079          try
1080          {
1081            someBaseDNs.add(Utilities.unescapeUtf8(baseDN.getDn().toString()));
1082          }
1083          catch (Throwable t)
1084          {
1085            throw new RuntimeException("Unexpected error: " + t, t);
1086          }
1087          if (baseDN.getEntries() > 0)
1088          {
1089            hmBaseDNWithEntries.put(Utilities.unescapeUtf8(baseDN.getDn().toString()), baseDN);
1090          }
1091        }
1092        hmBaseDNs.put(backendID, someBaseDNs);
1093        if ("userRoot".equalsIgnoreCase(backendID))
1094        {
1095          for (String baseDN : someBaseDNs)
1096          {
1097            baseDNWithEntries = hmBaseDNWithEntries.get(baseDN);
1098            if (baseDNWithEntries != null)
1099            {
1100              break;
1101            }
1102          }
1103        }
1104      }
1105    }
1106
1107    baseDNNewElements.add(new CategorizedComboBoxElement(ALL_BASE_DNS, CategorizedComboBoxElement.Type.REGULAR));
1108    for (String backendID : backendIDs)
1109    {
1110      baseDNNewElements.add(new CategorizedComboBoxElement(backendID, CategorizedComboBoxElement.Type.CATEGORY));
1111      SortedSet<String> someBaseDNs = hmBaseDNs.get(backendID);
1112      for (String baseDN : someBaseDNs)
1113      {
1114        baseDNNewElements.add(new CategorizedComboBoxElement(baseDN, CategorizedComboBoxElement.Type.REGULAR));
1115        if (baseDNWithEntries == null)
1116        {
1117          baseDNWithEntries = hmBaseDNWithEntries.get(baseDN);
1118        }
1119      }
1120    }
1121    for (DN dn : otherBaseDns)
1122    {
1123      baseDNNewElements.add(COMBO_SEPARATOR);
1124      baseDNNewElements.add(new CategorizedComboBoxElement(
1125          Utilities.unescapeUtf8(dn.toString()), CategorizedComboBoxElement.Type.REGULAR));
1126    }
1127    baseDNNewElements.add(COMBO_SEPARATOR);
1128    baseDNNewElements.add(OTHER_BASE_DN);
1129
1130    if (firstTimeDisplayed && baseDNWithEntries != null)
1131    {
1132      ignoreBaseDNEvents = true;
1133    }
1134    updateComboBoxModel(baseDNNewElements, (DefaultComboBoxModel) baseDNs.getModel());
1135    // Select the element in the combo box.
1136    if (firstTimeDisplayed && baseDNWithEntries != null)
1137    {
1138      final Object toSelect = new CategorizedComboBoxElement(
1139          Utilities.unescapeUtf8(baseDNWithEntries.getDn().toString()), CategorizedComboBoxElement.Type.REGULAR);
1140      SwingUtilities.invokeLater(new Runnable()
1141      {
1142        @Override
1143        public void run()
1144        {
1145          // After this updateBrowseController is called.
1146          ignoreBaseDNEvents = true;
1147          baseDNs.setSelectedItem(toSelect);
1148          ignoreBaseDNEvents = false;
1149        }
1150      });
1151    }
1152    if (getInfo().getServerDescriptor().isAuthenticated())
1153    {
1154      firstTimeDisplayed = false;
1155    }
1156  }
1157
1158  private boolean hasChanged(final SortedSet<String> newElements, final DefaultComboBoxModel<CharSequence> model)
1159  {
1160    if (newElements.size() != model.getSize() - 2)
1161    {
1162      return true;
1163    }
1164
1165    int i = 0;
1166    for (String newElement : newElements)
1167    {
1168      if (!newElement.equals(model.getElementAt(i)))
1169      {
1170        return true;
1171      }
1172      i++;
1173    }
1174    return false;
1175  }
1176
1177  /**
1178   * Updates the contents of the error pane and the browser controller with the
1179   * provided ServerDescriptor. It checks that the server is running and that we
1180   * are authenticated, that the connection to the server has not changed, etc.
1181   *
1182   * @param desc
1183   *          the server descriptor to be used to update the error pane and browser controller.
1184   */
1185  private void updateBrowserControllerAndErrorPane(ServerDescriptor desc)
1186  {
1187    boolean displayNodes = false;
1188    boolean displayErrorPane = false;
1189    LocalizableMessage errorTitle = LocalizableMessage.EMPTY;
1190    LocalizableMessage errorDetails = LocalizableMessage.EMPTY;
1191    ServerDescriptor.ServerStatus status = desc.getStatus();
1192    if (status == ServerDescriptor.ServerStatus.STARTED)
1193    {
1194      if (!desc.isAuthenticated())
1195      {
1196        LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
1197        mb.append(INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_TO_BROWSE_SUMMARY.get());
1198        mb.append("<br><br>").append(getAuthenticateHTML());
1199        errorDetails = mb.toMessage();
1200        errorTitle = INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_SUMMARY.get();
1201
1202        displayErrorPane = true;
1203      }
1204      else
1205      {
1206        try
1207        {
1208          InitialLdapContext ctx = getInfo().getDirContext();
1209          InitialLdapContext ctx1 = controller.getConfigurationConnection();
1210          boolean setConnection = ctx != ctx1;
1211          updateNumSubordinateHacker(desc);
1212          if (setConnection)
1213          {
1214            if (getInfo().getUserDataDirContext() == null)
1215            {
1216              InitialLdapContext ctxUserData =
1217                  createUserDataDirContext(ConnectionUtils.getBindDN(ctx), ConnectionUtils.getBindPassword(ctx));
1218              getInfo().setUserDataDirContext(ctxUserData);
1219            }
1220            final NamingException[] fNe = { null };
1221            Runnable runnable = new Runnable()
1222            {
1223              @Override
1224              public void run()
1225              {
1226                try
1227                {
1228                  controller.setConnections(
1229                      getInfo().getServerDescriptor(), getInfo().getDirContext(), getInfo().getUserDataDirContext());
1230                  applyButtonClicked();
1231                }
1232                catch (NamingException ne)
1233                {
1234                  fNe[0] = ne;
1235                }
1236              }
1237            };
1238            if (!SwingUtilities.isEventDispatchThread())
1239            {
1240              try
1241              {
1242                SwingUtilities.invokeAndWait(runnable);
1243              }
1244              catch (Throwable t) {}
1245            }
1246            else
1247            {
1248              runnable.run();
1249            }
1250
1251            if (fNe[0] != null)
1252            {
1253              throw fNe[0];
1254            }
1255          }
1256          displayNodes = true;
1257        }
1258        catch (NamingException ne)
1259        {
1260          errorTitle = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_DETAILS.get();
1261          errorDetails = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_SUMMARY.get(ne);
1262          displayErrorPane = true;
1263        }
1264        catch (ConfigReadException cre)
1265        {
1266          errorTitle = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_DETAILS.get();
1267          errorDetails = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_SUMMARY.get(cre.getMessageObject());
1268          displayErrorPane = true;
1269        }
1270      }
1271    }
1272    else if (status == ServerDescriptor.ServerStatus.NOT_CONNECTED_TO_REMOTE)
1273    {
1274      LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
1275      mb.append(INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get(desc.getHostname()));
1276      mb.append("<br><br>").append(getAuthenticateHTML());
1277      errorDetails = mb.toMessage();
1278      errorTitle = INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_SUMMARY.get();
1279      displayErrorPane = true;
1280    }
1281    else
1282    {
1283      errorTitle = INFO_CTRL_PANEL_SERVER_NOT_RUNNING_SUMMARY.get();
1284      LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
1285      mb.append(INFO_CTRL_PANEL_AUTHENTICATION_SERVER_MUST_RUN_TO_BROWSE_SUMMARY.get());
1286      mb.append("<br><br>");
1287      mb.append(getStartServerHTML());
1288      errorDetails = mb.toMessage();
1289      displayErrorPane = true;
1290    }
1291
1292    final boolean fDisplayNodes = displayNodes;
1293    final boolean fDisplayErrorPane = displayErrorPane;
1294    final LocalizableMessage fErrorTitle = errorTitle;
1295    final LocalizableMessage fErrorDetails = errorDetails;
1296    SwingUtilities.invokeLater(new Runnable()
1297    {
1298      @Override
1299      public void run()
1300      {
1301        applyButton.setEnabled(!fDisplayErrorPane);
1302        errorPane.setVisible(fDisplayErrorPane);
1303        if (fDisplayErrorPane)
1304        {
1305          updateErrorPane(errorPane, fErrorTitle,
1306              ColorAndFontConstants.errorTitleFont, fErrorDetails, ColorAndFontConstants.defaultFont);
1307        }
1308        else if (fDisplayNodes)
1309        {
1310          // Update the browser controller with the potential new suffixes.
1311          String s = getBaseDN();
1312          DN theDN = null;
1313          boolean displayAll = false;
1314          if (s != null)
1315          {
1316            displayAll = ALL_BASE_DNS.equals(s);
1317            if (!displayAll)
1318            {
1319              try
1320              {
1321                theDN = DN.valueOf(s);
1322              }
1323              catch (Throwable t)
1324              {
1325                s = null;
1326              }
1327            }
1328          }
1329          treePane.getTree().setRootVisible(displayAll);
1330          treePane.getTree().setShowsRootHandles(!displayAll);
1331          if (s != null)
1332          {
1333            boolean added = false;
1334            for (BackendDescriptor backend : getInfo().getServerDescriptor().getBackends())
1335            {
1336              for (BaseDNDescriptor baseDN : backend.getBaseDns())
1337              {
1338                boolean isBaseDN = false;
1339                String dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
1340                if (theDN != null && baseDN.getDn().equals(theDN))
1341                {
1342                  isBaseDN = true;
1343                }
1344                if (baseDN.getEntries() > 0)
1345                {
1346                  try
1347                  {
1348                    if ((displayAll || isBaseDN) && !controller.hasSuffix(dn))
1349                    {
1350                      controller.addSuffix(dn, null);
1351                      added = true;
1352                    }
1353                  }
1354                  catch (IllegalArgumentException iae)
1355                  {
1356                    // The suffix node exists but is not a suffix node. Simply log a message.
1357                    logger.warn(LocalizableMessage.raw(
1358                        "Suffix: " + dn + " added as a non suffix node. Exception: " + iae, iae));
1359                  }
1360                }
1361              }
1362              if (!added && !displayAll)
1363              {
1364                BasicNode rootNode = (BasicNode) controller.getTree().getModel().getRoot();
1365                if (controller.findChildNode(rootNode, s) == -1)
1366                {
1367                  controller.addNodeUnderRoot(s);
1368                }
1369              }
1370            }
1371          }
1372        }
1373
1374        if (!fDisplayNodes)
1375        {
1376          controller.removeAllUnderRoot();
1377          treePane.getTree().setRootVisible(false);
1378        }
1379      }
1380    });
1381  }
1382
1383  /**
1384   * Returns the base DN specified by the user.
1385   *
1386   * @return the base DN specified by the user.
1387   */
1388  private String getBaseDN()
1389  {
1390    String dn = getBaseDN0();
1391    if (dn != null && dn.trim().length() == 0)
1392    {
1393      dn = ALL_BASE_DNS;
1394    }
1395    return dn;
1396  }
1397
1398  private String getBaseDN0()
1399  {
1400    Object o = baseDNs.getSelectedItem();
1401    if (o instanceof String)
1402    {
1403      return (String) o;
1404    }
1405    else if (o instanceof CategorizedComboBoxElement)
1406    {
1407      return ((CategorizedComboBoxElement) o).getValue().toString();
1408    }
1409    else
1410    {
1411      return null;
1412    }
1413  }
1414
1415  /**
1416   * Creates the context to be used to retrieve user data for some given
1417   * credentials.
1418   *
1419   * @param bindDN
1420   *          the bind DN.
1421   * @param bindPassword
1422   *          the bind password.
1423   * @return the context to be used to retrieve user data for some given
1424   *         credentials.
1425   * @throws NamingException
1426   *           if an error occurs connecting to the server.
1427   * @throws ConfigReadException
1428   *           if an error occurs reading the configuration.
1429   */
1430  private InitialLdapContext createUserDataDirContext(final String bindDN, final String bindPassword)
1431      throws NamingException, ConfigReadException
1432  {
1433    createdUserDataCtx = null;
1434    try
1435    {
1436      createdUserDataCtx = Utilities.getUserDataDirContext(getInfo(), bindDN, bindPassword);
1437    }
1438    catch (NamingException ne)
1439    {
1440      if (!isCertificateException(ne))
1441      {
1442        throw ne;
1443      }
1444
1445      ApplicationTrustManager.Cause cause = getInfo().getTrustManager().getLastRefusedCause();
1446
1447      logger.info(LocalizableMessage.raw("Certificate exception cause: " + cause));
1448      UserDataCertificateException.Type excType = null;
1449      if (cause == ApplicationTrustManager.Cause.NOT_TRUSTED)
1450      {
1451        excType = UserDataCertificateException.Type.NOT_TRUSTED;
1452      }
1453      else if (cause == ApplicationTrustManager.Cause.HOST_NAME_MISMATCH)
1454      {
1455        excType = UserDataCertificateException.Type.HOST_NAME_MISMATCH;
1456      }
1457
1458      if (excType != null)
1459      {
1460        String h;
1461        int p;
1462        try
1463        {
1464          URI uri = new URI(getInfo().getAdminConnectorURL());
1465          h = uri.getHost();
1466          p = uri.getPort();
1467        }
1468        catch (Throwable t)
1469        {
1470          logger.warn(LocalizableMessage.raw("Error parsing ldap url of ldap url.", t));
1471          h = INFO_NOT_AVAILABLE_LABEL.get().toString();
1472          p = -1;
1473        }
1474        final UserDataCertificateException udce = new UserDataCertificateException(
1475            null, INFO_CERTIFICATE_EXCEPTION.get(h, p), ne, h, p, getInfo().getTrustManager().getLastRefusedChain(),
1476            getInfo().getTrustManager().getLastRefusedAuthType(), excType);
1477
1478        if (SwingUtilities.isEventDispatchThread())
1479        {
1480          handleCertificateException(udce, bindDN, bindPassword);
1481        }
1482        else
1483        {
1484          final ConfigReadException[] fcre = { null };
1485          final NamingException[] fne = { null };
1486          try
1487          {
1488            SwingUtilities.invokeAndWait(new Runnable()
1489            {
1490              @Override
1491              public void run()
1492              {
1493                try
1494                {
1495                  handleCertificateException(udce, bindDN, bindPassword);
1496                }
1497                catch (ConfigReadException cre)
1498                {
1499                  fcre[0] = cre;
1500                }
1501                catch (NamingException ne)
1502                {
1503                  fne[0] = ne;
1504                }
1505              }
1506            });
1507          }
1508          catch (Exception e)
1509          {
1510            throw new IllegalArgumentException("Unexpected error: " + e, e);
1511          }
1512          if (fcre[0] != null)
1513          {
1514            throw fcre[0];
1515          }
1516          if (fne[0] != null)
1517          {
1518            throw fne[0];
1519          }
1520        }
1521      }
1522    }
1523    return createdUserDataCtx;
1524  }
1525
1526  /**
1527   * Displays a dialog asking the user to accept a certificate if the user
1528   * accepts it, we update the trust manager and simulate a click on "OK" to
1529   * re-check the authentication. This method assumes that we are being called
1530   * from the event thread.
1531   *
1532   * @param bindDN
1533   *          the bind DN.
1534   * @param bindPassword
1535   *          the bind password.
1536   */
1537  private void handleCertificateException(UserDataCertificateException ce, String bindDN, String bindPassword)
1538      throws NamingException, ConfigReadException
1539  {
1540    CertificateDialog dlg = new CertificateDialog(null, ce);
1541    dlg.pack();
1542    Utilities.centerGoldenMean(dlg, Utilities.getParentDialog(this));
1543    dlg.setVisible(true);
1544    if (dlg.getUserAnswer() != CertificateDialog.ReturnType.NOT_ACCEPTED)
1545    {
1546      X509Certificate[] chain = ce.getChain();
1547      String authType = ce.getAuthType();
1548      String host = ce.getHost();
1549
1550      if (chain != null && authType != null && host != null)
1551      {
1552        logger.info(LocalizableMessage.raw("Accepting certificate presented by host " + host));
1553        getInfo().getTrustManager().acceptCertificate(chain, authType, host);
1554        createdUserDataCtx = createUserDataDirContext(bindDN, bindPassword);
1555      }
1556      else
1557      {
1558        if (chain == null)
1559        {
1560          logger.warn(LocalizableMessage.raw("The chain is null for the UserDataCertificateException"));
1561        }
1562        if (authType == null)
1563        {
1564          logger.warn(LocalizableMessage.raw("The auth type is null for the UserDataCertificateException"));
1565        }
1566        if (host == null)
1567        {
1568          logger.warn(LocalizableMessage.raw("The host is null for the UserDataCertificateException"));
1569        }
1570      }
1571    }
1572    if (dlg.getUserAnswer() == CertificateDialog.ReturnType.ACCEPTED_PERMANENTLY)
1573    {
1574      X509Certificate[] chain = ce.getChain();
1575      if (chain != null)
1576      {
1577        try
1578        {
1579          UIKeyStore.acceptCertificate(chain);
1580        }
1581        catch (Throwable t)
1582        {
1583          logger.warn(LocalizableMessage.raw("Error accepting certificate: " + t, t));
1584        }
1585      }
1586    }
1587  }
1588
1589  /**
1590   * This class is used simply to avoid an inset on the left for the 'All Base
1591   * DNs' item. Since this item is a CategorizedComboBoxElement of type
1592   * CategorizedComboBoxElement.Type.REGULAR, it has by default an inset on the
1593   * left. The class simply handles this particular case to not to have that
1594   * inset for the 'All Base DNs' item.
1595   */
1596  class CustomComboBoxCellRenderer extends CustomListCellRenderer
1597  {
1598    private LocalizableMessage ALL_BASE_DNS_STRING = INFO_CTRL_PANEL_ALL_BASE_DNS.get();
1599
1600    /**
1601     * The constructor.
1602     *
1603     * @param combo
1604     *          the combo box to be rendered.
1605     */
1606    CustomComboBoxCellRenderer(JComboBox<?> combo)
1607    {
1608      super(combo);
1609    }
1610
1611    @Override
1612    @SuppressWarnings("rawtypes")
1613    public Component getListCellRendererComponent(
1614        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
1615    {
1616      Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
1617      if (value instanceof CategorizedComboBoxElement)
1618      {
1619        CategorizedComboBoxElement element = (CategorizedComboBoxElement) value;
1620        String name = getStringValue(element);
1621        if (ALL_BASE_DNS.equals(name))
1622        {
1623          ((JLabel) comp).setText(ALL_BASE_DNS_STRING.toString());
1624        }
1625      }
1626      comp.setFont(defaultFont);
1627      return comp;
1628    }
1629  }
1630
1631  /**
1632   * Checks that the root node has some children. It it has no children the
1633   * message 'No Match Found' is displayed instead of the tree panel.
1634   */
1635  private void checkRootNode()
1636  {
1637    DefaultMutableTreeNode root = (DefaultMutableTreeNode) controller.getTreeModel().getRoot();
1638    boolean visible = root.getChildCount() > 0;
1639    if (visible != treePane.isVisible())
1640    {
1641      treePane.setVisible(visible);
1642      lNoMatchFound.setVisible(!visible);
1643      lNumberOfEntries.setVisible(visible);
1644    }
1645    numberEntriesUpdater.recalculate();
1646  }
1647
1648  /**
1649   * Updates the NumsubordinateHacker of the browser controller with the
1650   * provided server descriptor.
1651   *
1652   * @param server
1653   *          the server descriptor.
1654   */
1655  private void updateNumSubordinateHacker(ServerDescriptor server)
1656  {
1657    String serverHost = server.getHostname();
1658    int serverPort = server.getAdminConnector().getPort();
1659
1660    List<DN> allSuffixes = new ArrayList<>();
1661    for (BackendDescriptor backend : server.getBackends())
1662    {
1663      for (BaseDNDescriptor baseDN : backend.getBaseDns())
1664      {
1665        allSuffixes.add(baseDN.getDn());
1666      }
1667    }
1668
1669    List<DN> rootSuffixes = new ArrayList<>();
1670    for (DN dn : allSuffixes)
1671    {
1672      if (isRootSuffix(allSuffixes, dn))
1673      {
1674        rootSuffixes.add(dn);
1675      }
1676    }
1677    controller.getNumSubordinateHacker().update(allSuffixes, rootSuffixes, serverHost, serverPort);
1678  }
1679
1680  private boolean isRootSuffix(List<DN> allSuffixes, DN dn)
1681  {
1682    for (DN suffix : allSuffixes)
1683    {
1684      if (suffix.isAncestorOf(dn) && !suffix.equals(dn))
1685      {
1686        return false;
1687      }
1688    }
1689    return true;
1690  }
1691
1692  /**
1693   * This is a class that simply checks the number of entries that the browser
1694   * contains and updates a counter with the new number of entries. It is
1695   * basically a thread that sleeps and checks whether some calculation must be
1696   * made: when we know that something is updated in the browser the method
1697   * recalculate() is called. We could use a more sophisticated code (like use a
1698   * wait() call that would get notified when recalculate() is called) but this
1699   * is not required and it might have an impact on the reactivity of the UI if
1700   * recalculate gets called too often. We can afford to wait 400 miliseconds
1701   * before updating the number of entries and with this approach there is
1702   * hardly no impact on the reactivity of the UI.
1703   */
1704  protected class NumberOfEntriesUpdater extends Thread
1705  {
1706    private boolean recalculate;
1707
1708    /** Notifies that the number of entries in the browser has changed. */
1709    public void recalculate()
1710    {
1711      recalculate = true;
1712    }
1713
1714    /** Executes the updater. */
1715    @Override
1716    public void run()
1717    {
1718      while (true)
1719      {
1720        try
1721        {
1722          Thread.sleep(400);
1723        }
1724        catch (Throwable t)
1725        {
1726        }
1727        if (recalculate)
1728        {
1729          recalculate = false;
1730          SwingUtilities.invokeLater(new Runnable()
1731          {
1732            @Override
1733            public void run()
1734            {
1735              int nEntries = 0;
1736              // This recursive algorithm is fast enough to use it on the
1737              // event thread.  Running it here we avoid issues with concurrent
1738              // access to the node children
1739              if (controller.getTree().isRootVisible())
1740              {
1741                nEntries++;
1742              }
1743              DefaultMutableTreeNode root = (DefaultMutableTreeNode) controller.getTreeModel().getRoot();
1744
1745              nEntries += getChildren(root);
1746              lNumberOfEntries.setText(INFO_CTRL_BROWSER_NUMBER_OF_ENTRIES.get(nEntries).toString());
1747            }
1748          });
1749        }
1750        if (controller != null)
1751        {
1752          final boolean mustDisplayRefreshIcon = controller.getQueueSize() > 0;
1753          if (mustDisplayRefreshIcon != filter.isRefreshIconDisplayed())
1754          {
1755            SwingUtilities.invokeLater(new Runnable()
1756            {
1757              @Override
1758              public void run()
1759              {
1760                filter.displayRefreshIcon(mustDisplayRefreshIcon);
1761              }
1762            });
1763          }
1764        }
1765      }
1766    }
1767
1768    /**
1769     * Returns the number of children for a given node.
1770     *
1771     * @param node
1772     *          the node.
1773     * @return the number of children for the node.
1774     */
1775    private int getChildren(DefaultMutableTreeNode node)
1776    {
1777      int nEntries = 0;
1778
1779      if (!node.isLeaf())
1780      {
1781        Enumeration<?> en = node.children();
1782        while (en.hasMoreElements())
1783        {
1784          nEntries++;
1785          nEntries += getChildren((DefaultMutableTreeNode) en.nextElement());
1786        }
1787      }
1788      return nEntries;
1789    }
1790  }
1791}