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        return new LDAPFilter(SearchFilter.createEqualityFilter(attrType, ByteString.valueOf(filterText))).toString();
777      }
778    }
779    else
780    {
781      return BrowserController.ALL_OBJECTS_FILTER;
782    }
783  }
784
785  /**
786   * Returns the component that will be displayed between the filtering options
787   * and the buttons panel. This component must contain the tree panel.
788   *
789   * @return the component that will be displayed between the filtering options
790   *         and the buttons panel.
791   */
792  protected abstract Component createMainPanel();
793
794  /** {@inheritDoc} */
795  @Override
796  public void backendPopulated(BackendPopulatedEvent ev)
797  {
798    if (controller.getConfigurationConnection() != null)
799    {
800      boolean displayAll = false;
801      boolean errorOccurred = false;
802      DN theDN = null;
803      String s = getBaseDN();
804      if (s != null)
805      {
806        displayAll = ALL_BASE_DNS.equals(s);
807        if (!displayAll)
808        {
809          try
810          {
811            theDN = DN.valueOf(s);
812          }
813          catch (Throwable t)
814          {
815            errorOccurred = true;
816          }
817        }
818      }
819      else
820      {
821        errorOccurred = true;
822      }
823      if (!errorOccurred)
824      {
825        treePane.getTree().setRootVisible(displayAll);
826        treePane.getTree().setShowsRootHandles(!displayAll);
827        BasicNode rootNode = (BasicNode) controller.getTree().getModel().getRoot();
828        boolean isSubordinate = false;
829        for (BackendDescriptor backend : ev.getBackends())
830        {
831          for (BaseDNDescriptor baseDN : backend.getBaseDns())
832          {
833            boolean isBaseDN = false;
834            if (baseDN.getDn().equals(theDN))
835            {
836              isBaseDN = true;
837            }
838            else if (baseDN.getDn().isAncestorOf(theDN))
839            {
840              isSubordinate = true;
841            }
842            String dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
843            if (displayAll || isBaseDN)
844            {
845              try
846              {
847                if (!controller.hasSuffix(dn))
848                {
849                  controller.addSuffix(dn, null);
850                }
851                else
852                {
853                  int index = controller.findChildNode(rootNode, dn);
854                  if (index >= 0)
855                  {
856                    TreeNode node = rootNode.getChildAt(index);
857                    if (node != null)
858                    {
859                      TreePath path = new TreePath(controller.getTreeModel().getPathToRoot(node));
860                      controller.startRefresh(controller.getNodeInfoFromPath(path));
861                    }
862                  }
863                }
864              }
865              catch (IllegalArgumentException iae)
866              {
867                // The suffix node exists but is not a suffix node. Simply log a message.
868                logger.warn(
869                    LocalizableMessage.raw("Suffix: " + dn + " added as a non suffix node. Exception: " + iae, iae));
870              }
871            }
872          }
873        }
874        if (isSubordinate && controller.findChildNode(rootNode, s) == -1)
875        {
876          controller.addNodeUnderRoot(s);
877        }
878      }
879    }
880  }
881
882  @Override
883  public void configurationChanged(ConfigurationChangeEvent ev)
884  {
885    final ServerDescriptor desc = ev.getNewDescriptor();
886
887    updateCombos(desc);
888    updateBrowserControllerAndErrorPane(desc);
889  }
890
891  /**
892   * Creates and returns the tree panel.
893   *
894   * @return the tree panel.
895   */
896  protected JComponent createTreePane()
897  {
898    treePane = new TreePanel();
899
900    lNoMatchFound = Utilities.createDefaultLabel(INFO_CTRL_PANEL_NO_MATCHES_FOUND_LABEL.get());
901    lNoMatchFound.setVisible(false);
902
903    // Calculate default size
904    JTree tree = treePane.getTree();
905    DefaultMutableTreeNode root = new DefaultMutableTreeNode("myserver.mydomain.com:389");
906    DefaultTreeModel model = new DefaultTreeModel(root);
907    tree.setModel(model);
908    tree.setShowsRootHandles(false);
909    tree.expandPath(new TreePath(root));
910    JPanel p = new JPanel(new GridBagLayout());
911    p.setBackground(ColorAndFontConstants.background);
912    GridBagConstraints gbc = new GridBagConstraints();
913    gbc.gridx = 0;
914    gbc.gridy = 0;
915    gbc.gridwidth = 1;
916    gbc.anchor = GridBagConstraints.NORTHWEST;
917    gbc.fill = GridBagConstraints.BOTH;
918    gbc.weightx = 1.0;
919    gbc.weighty = 1.0;
920    Utilities.setBorder(treePane, new EmptyBorder(10, 0, 10, 0));
921    p.add(treePane, gbc);
922    gbc.fill = GridBagConstraints.HORIZONTAL;
923    Utilities.setBorder(lNoMatchFound, new EmptyBorder(15, 15, 15, 15));
924    p.add(lNoMatchFound, gbc);
925
926    if (getInfo() != null && controller == null)
927    {
928      createBrowserController(getInfo());
929    }
930    numberEntriesUpdater = new NumberOfEntriesUpdater();
931    numberEntriesUpdater.start();
932
933    return p;
934  }
935
936  /**
937   * Creates the browser controller object.
938   *
939   * @param info
940   *          the ControlPanelInfo to be used to create the browser controller.
941   */
942  protected void createBrowserController(ControlPanelInfo info)
943  {
944    controller = new BrowserController(treePane.getTree(), info.getConnectionPool(), info.getIconPool());
945    controller.setContainerClasses(CONTAINER_CLASSES);
946    controller.setShowContainerOnly(false);
947    controller.setMaxChildren(MAX_NUMBER_ENTRIES);
948    controller.addBrowserEventListener(new BrowserEventListener()
949    {
950      /** {@inheritDoc} */
951      @Override
952      public void processBrowserEvent(BrowserEvent ev)
953      {
954        if (ev.getType() == BrowserEvent.Type.SIZE_LIMIT_REACHED)
955        {
956          lLimit.setVisible(true);
957          lNumberOfEntries.setVisible(false);
958        }
959      }
960    });
961    controller.getTreeModel().addTreeModelListener(new TreeModelListener()
962    {
963      @Override
964      public void treeNodesChanged(TreeModelEvent e)
965      {
966      }
967
968      @Override
969      public void treeNodesInserted(TreeModelEvent e)
970      {
971        checkRootNode();
972      }
973
974      @Override
975      public void treeNodesRemoved(TreeModelEvent e)
976      {
977        checkRootNode();
978      }
979
980      @Override
981      public void treeStructureChanged(TreeModelEvent e)
982      {
983        checkRootNode();
984      }
985    });
986  }
987
988
989  private static boolean displayIndex(String name)
990  {
991    for (String systemIndex : SYSTEM_INDEXES)
992    {
993      if (systemIndex.equalsIgnoreCase(name))
994      {
995        return false;
996      }
997    }
998    return true;
999  }
1000
1001  /**
1002   * Updates the contents of the combo boxes with the provided ServerDescriptor.
1003   *
1004   * @param desc
1005   *          the server descriptor to be used to update the combo boxes.
1006   */
1007  @SuppressWarnings("rawtypes")
1008  private void updateCombos(ServerDescriptor desc)
1009  {
1010    final SortedSet<String> newElements = new TreeSet<>();
1011    for (BackendDescriptor backend : desc.getBackends())
1012    {
1013      for (IndexDescriptor index : backend.getIndexes())
1014      {
1015        String indexName = index.getName();
1016        if (displayIndex(indexName))
1017        {
1018          newElements.add(indexName);
1019        }
1020      }
1021    }
1022
1023    @SuppressWarnings("unchecked")
1024    final DefaultComboBoxModel<CharSequence> model = (DefaultComboBoxModel<CharSequence>) filterAttribute.getModel();
1025    if (hasChanged(newElements, model))
1026    {
1027      SwingUtilities.invokeLater(new Runnable()
1028      {
1029        @Override
1030        public void run()
1031        {
1032          Object selected = filterAttribute.getSelectedItem();
1033          model.removeAllElements();
1034          model.addElement(USER_FILTER);
1035          model.addElement(GROUP_FILTER);
1036          model.addElement(COMBO_SEPARATOR);
1037          for (String newElement : newElements)
1038          {
1039            model.addElement(newElement);
1040          }
1041          // If there are not backends, we get no indexes to set.
1042          if (!newElements.isEmpty())
1043          {
1044            model.addElement(COMBO_SEPARATOR);
1045          }
1046          model.addElement(LDAP_FILTER);
1047          if (selected != null)
1048          {
1049            if (model.getIndexOf(selected) != -1)
1050            {
1051              model.setSelectedItem(selected);
1052            }
1053            else
1054            {
1055              model.setSelectedItem(model.getElementAt(0));
1056            }
1057          }
1058        }
1059      });
1060    }
1061
1062    Set<Object> baseDNNewElements = new LinkedHashSet<>();
1063    SortedSet<String> backendIDs = new TreeSet<>();
1064    Map<String, SortedSet<String>> hmBaseDNs = new HashMap<>();
1065
1066    Map<String, BaseDNDescriptor> hmBaseDNWithEntries = new HashMap<>();
1067
1068    BaseDNDescriptor baseDNWithEntries = null;
1069    for (BackendDescriptor backend : desc.getBackends())
1070    {
1071      if (displayBackend(backend))
1072      {
1073        String backendID = backend.getBackendID();
1074        backendIDs.add(backendID);
1075        SortedSet<String> someBaseDNs = new TreeSet<>();
1076        for (BaseDNDescriptor baseDN : backend.getBaseDns())
1077        {
1078          try
1079          {
1080            someBaseDNs.add(Utilities.unescapeUtf8(baseDN.getDn().toString()));
1081          }
1082          catch (Throwable t)
1083          {
1084            throw new RuntimeException("Unexpected error: " + t, t);
1085          }
1086          if (baseDN.getEntries() > 0)
1087          {
1088            hmBaseDNWithEntries.put(Utilities.unescapeUtf8(baseDN.getDn().toString()), baseDN);
1089          }
1090        }
1091        hmBaseDNs.put(backendID, someBaseDNs);
1092        if ("userRoot".equalsIgnoreCase(backendID))
1093        {
1094          for (String baseDN : someBaseDNs)
1095          {
1096            baseDNWithEntries = hmBaseDNWithEntries.get(baseDN);
1097            if (baseDNWithEntries != null)
1098            {
1099              break;
1100            }
1101          }
1102        }
1103      }
1104    }
1105
1106    baseDNNewElements.add(new CategorizedComboBoxElement(ALL_BASE_DNS, CategorizedComboBoxElement.Type.REGULAR));
1107    for (String backendID : backendIDs)
1108    {
1109      baseDNNewElements.add(new CategorizedComboBoxElement(backendID, CategorizedComboBoxElement.Type.CATEGORY));
1110      SortedSet<String> someBaseDNs = hmBaseDNs.get(backendID);
1111      for (String baseDN : someBaseDNs)
1112      {
1113        baseDNNewElements.add(new CategorizedComboBoxElement(baseDN, CategorizedComboBoxElement.Type.REGULAR));
1114        if (baseDNWithEntries == null)
1115        {
1116          baseDNWithEntries = hmBaseDNWithEntries.get(baseDN);
1117        }
1118      }
1119    }
1120    for (DN dn : otherBaseDns)
1121    {
1122      baseDNNewElements.add(COMBO_SEPARATOR);
1123      baseDNNewElements.add(new CategorizedComboBoxElement(
1124          Utilities.unescapeUtf8(dn.toString()), CategorizedComboBoxElement.Type.REGULAR));
1125    }
1126    baseDNNewElements.add(COMBO_SEPARATOR);
1127    baseDNNewElements.add(OTHER_BASE_DN);
1128
1129    if (firstTimeDisplayed && baseDNWithEntries != null)
1130    {
1131      ignoreBaseDNEvents = true;
1132    }
1133    updateComboBoxModel(baseDNNewElements, (DefaultComboBoxModel) baseDNs.getModel());
1134    // Select the element in the combo box.
1135    if (firstTimeDisplayed && baseDNWithEntries != null)
1136    {
1137      final Object toSelect = new CategorizedComboBoxElement(
1138          Utilities.unescapeUtf8(baseDNWithEntries.getDn().toString()), CategorizedComboBoxElement.Type.REGULAR);
1139      SwingUtilities.invokeLater(new Runnable()
1140      {
1141        @Override
1142        public void run()
1143        {
1144          // After this updateBrowseController is called.
1145          ignoreBaseDNEvents = true;
1146          baseDNs.setSelectedItem(toSelect);
1147          ignoreBaseDNEvents = false;
1148        }
1149      });
1150    }
1151    if (getInfo().getServerDescriptor().isAuthenticated())
1152    {
1153      firstTimeDisplayed = false;
1154    }
1155  }
1156
1157  private boolean hasChanged(final SortedSet<String> newElements, final DefaultComboBoxModel<CharSequence> model)
1158  {
1159    if (newElements.size() != model.getSize() - 2)
1160    {
1161      return true;
1162    }
1163
1164    int i = 0;
1165    for (String newElement : newElements)
1166    {
1167      if (!newElement.equals(model.getElementAt(i)))
1168      {
1169        return true;
1170      }
1171      i++;
1172    }
1173    return false;
1174  }
1175
1176  /**
1177   * Updates the contents of the error pane and the browser controller with the
1178   * provided ServerDescriptor. It checks that the server is running and that we
1179   * are authenticated, that the connection to the server has not changed, etc.
1180   *
1181   * @param desc
1182   *          the server descriptor to be used to update the error pane and browser controller.
1183   */
1184  private void updateBrowserControllerAndErrorPane(ServerDescriptor desc)
1185  {
1186    boolean displayNodes = false;
1187    boolean displayErrorPane = false;
1188    LocalizableMessage errorTitle = LocalizableMessage.EMPTY;
1189    LocalizableMessage errorDetails = LocalizableMessage.EMPTY;
1190    ServerDescriptor.ServerStatus status = desc.getStatus();
1191    if (status == ServerDescriptor.ServerStatus.STARTED)
1192    {
1193      if (!desc.isAuthenticated())
1194      {
1195        LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
1196        mb.append(INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_TO_BROWSE_SUMMARY.get());
1197        mb.append("<br><br>").append(getAuthenticateHTML());
1198        errorDetails = mb.toMessage();
1199        errorTitle = INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_SUMMARY.get();
1200
1201        displayErrorPane = true;
1202      }
1203      else
1204      {
1205        try
1206        {
1207          InitialLdapContext ctx = getInfo().getDirContext();
1208          InitialLdapContext ctx1 = controller.getConfigurationConnection();
1209          boolean setConnection = ctx != ctx1;
1210          updateNumSubordinateHacker(desc);
1211          if (setConnection)
1212          {
1213            if (getInfo().getUserDataDirContext() == null)
1214            {
1215              InitialLdapContext ctxUserData =
1216                  createUserDataDirContext(ConnectionUtils.getBindDN(ctx), ConnectionUtils.getBindPassword(ctx));
1217              getInfo().setUserDataDirContext(ctxUserData);
1218            }
1219            final NamingException[] fNe = { null };
1220            Runnable runnable = new Runnable()
1221            {
1222              @Override
1223              public void run()
1224              {
1225                try
1226                {
1227                  controller.setConnections(
1228                      getInfo().getServerDescriptor(), getInfo().getDirContext(), getInfo().getUserDataDirContext());
1229                  applyButtonClicked();
1230                }
1231                catch (NamingException ne)
1232                {
1233                  fNe[0] = ne;
1234                }
1235              }
1236            };
1237            if (!SwingUtilities.isEventDispatchThread())
1238            {
1239              try
1240              {
1241                SwingUtilities.invokeAndWait(runnable);
1242              }
1243              catch (Throwable t) {}
1244            }
1245            else
1246            {
1247              runnable.run();
1248            }
1249
1250            if (fNe[0] != null)
1251            {
1252              throw fNe[0];
1253            }
1254          }
1255          displayNodes = true;
1256        }
1257        catch (NamingException ne)
1258        {
1259          errorTitle = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_DETAILS.get();
1260          errorDetails = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_SUMMARY.get(ne);
1261          displayErrorPane = true;
1262        }
1263        catch (ConfigReadException cre)
1264        {
1265          errorTitle = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_DETAILS.get();
1266          errorDetails = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_SUMMARY.get(cre.getMessageObject());
1267          displayErrorPane = true;
1268        }
1269      }
1270    }
1271    else if (status == ServerDescriptor.ServerStatus.NOT_CONNECTED_TO_REMOTE)
1272    {
1273      LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
1274      mb.append(INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get(desc.getHostname()));
1275      mb.append("<br><br>").append(getAuthenticateHTML());
1276      errorDetails = mb.toMessage();
1277      errorTitle = INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_SUMMARY.get();
1278      displayErrorPane = true;
1279    }
1280    else
1281    {
1282      errorTitle = INFO_CTRL_PANEL_SERVER_NOT_RUNNING_SUMMARY.get();
1283      LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
1284      mb.append(INFO_CTRL_PANEL_AUTHENTICATION_SERVER_MUST_RUN_TO_BROWSE_SUMMARY.get());
1285      mb.append("<br><br>");
1286      mb.append(getStartServerHTML());
1287      errorDetails = mb.toMessage();
1288      displayErrorPane = true;
1289    }
1290
1291    final boolean fDisplayNodes = displayNodes;
1292    final boolean fDisplayErrorPane = displayErrorPane;
1293    final LocalizableMessage fErrorTitle = errorTitle;
1294    final LocalizableMessage fErrorDetails = errorDetails;
1295    SwingUtilities.invokeLater(new Runnable()
1296    {
1297      @Override
1298      public void run()
1299      {
1300        applyButton.setEnabled(!fDisplayErrorPane);
1301        errorPane.setVisible(fDisplayErrorPane);
1302        if (fDisplayErrorPane)
1303        {
1304          updateErrorPane(errorPane, fErrorTitle,
1305              ColorAndFontConstants.errorTitleFont, fErrorDetails, ColorAndFontConstants.defaultFont);
1306        }
1307        else if (fDisplayNodes)
1308        {
1309          // Update the browser controller with the potential new suffixes.
1310          String s = getBaseDN();
1311          DN theDN = null;
1312          boolean displayAll = false;
1313          if (s != null)
1314          {
1315            displayAll = ALL_BASE_DNS.equals(s);
1316            if (!displayAll)
1317            {
1318              try
1319              {
1320                theDN = DN.valueOf(s);
1321              }
1322              catch (Throwable t)
1323              {
1324                s = null;
1325              }
1326            }
1327          }
1328          treePane.getTree().setRootVisible(displayAll);
1329          treePane.getTree().setShowsRootHandles(!displayAll);
1330          if (s != null)
1331          {
1332            boolean added = false;
1333            for (BackendDescriptor backend : getInfo().getServerDescriptor().getBackends())
1334            {
1335              for (BaseDNDescriptor baseDN : backend.getBaseDns())
1336              {
1337                boolean isBaseDN = false;
1338                String dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
1339                if (theDN != null && baseDN.getDn().equals(theDN))
1340                {
1341                  isBaseDN = true;
1342                }
1343                if (baseDN.getEntries() > 0)
1344                {
1345                  try
1346                  {
1347                    if ((displayAll || isBaseDN) && !controller.hasSuffix(dn))
1348                    {
1349                      controller.addSuffix(dn, null);
1350                      added = true;
1351                    }
1352                  }
1353                  catch (IllegalArgumentException iae)
1354                  {
1355                    // The suffix node exists but is not a suffix node. Simply log a message.
1356                    logger.warn(LocalizableMessage.raw(
1357                        "Suffix: " + dn + " added as a non suffix node. Exception: " + iae, iae));
1358                  }
1359                }
1360              }
1361              if (!added && !displayAll)
1362              {
1363                BasicNode rootNode = (BasicNode) controller.getTree().getModel().getRoot();
1364                if (controller.findChildNode(rootNode, s) == -1)
1365                {
1366                  controller.addNodeUnderRoot(s);
1367                }
1368              }
1369            }
1370          }
1371        }
1372
1373        if (!fDisplayNodes)
1374        {
1375          controller.removeAllUnderRoot();
1376          treePane.getTree().setRootVisible(false);
1377        }
1378      }
1379    });
1380  }
1381
1382  /**
1383   * Returns the base DN specified by the user.
1384   *
1385   * @return the base DN specified by the user.
1386   */
1387  private String getBaseDN()
1388  {
1389    String dn = getBaseDN0();
1390    if (dn != null && dn.trim().length() == 0)
1391    {
1392      dn = ALL_BASE_DNS;
1393    }
1394    return dn;
1395  }
1396
1397  private String getBaseDN0()
1398  {
1399    Object o = baseDNs.getSelectedItem();
1400    if (o instanceof String)
1401    {
1402      return (String) o;
1403    }
1404    else if (o instanceof CategorizedComboBoxElement)
1405    {
1406      return ((CategorizedComboBoxElement) o).getValue().toString();
1407    }
1408    else
1409    {
1410      return null;
1411    }
1412  }
1413
1414  /**
1415   * Creates the context to be used to retrieve user data for some given
1416   * credentials.
1417   *
1418   * @param bindDN
1419   *          the bind DN.
1420   * @param bindPassword
1421   *          the bind password.
1422   * @return the context to be used to retrieve user data for some given
1423   *         credentials.
1424   * @throws NamingException
1425   *           if an error occurs connecting to the server.
1426   * @throws ConfigReadException
1427   *           if an error occurs reading the configuration.
1428   */
1429  private InitialLdapContext createUserDataDirContext(final String bindDN, final String bindPassword)
1430      throws NamingException, ConfigReadException
1431  {
1432    createdUserDataCtx = null;
1433    try
1434    {
1435      createdUserDataCtx = Utilities.getUserDataDirContext(getInfo(), bindDN, bindPassword);
1436    }
1437    catch (NamingException ne)
1438    {
1439      if (!isCertificateException(ne))
1440      {
1441        throw ne;
1442      }
1443
1444      ApplicationTrustManager.Cause cause = getInfo().getTrustManager().getLastRefusedCause();
1445
1446      logger.info(LocalizableMessage.raw("Certificate exception cause: " + cause));
1447      UserDataCertificateException.Type excType = null;
1448      if (cause == ApplicationTrustManager.Cause.NOT_TRUSTED)
1449      {
1450        excType = UserDataCertificateException.Type.NOT_TRUSTED;
1451      }
1452      else if (cause == ApplicationTrustManager.Cause.HOST_NAME_MISMATCH)
1453      {
1454        excType = UserDataCertificateException.Type.HOST_NAME_MISMATCH;
1455      }
1456
1457      if (excType != null)
1458      {
1459        String h;
1460        int p;
1461        try
1462        {
1463          URI uri = new URI(getInfo().getAdminConnectorURL());
1464          h = uri.getHost();
1465          p = uri.getPort();
1466        }
1467        catch (Throwable t)
1468        {
1469          logger.warn(LocalizableMessage.raw("Error parsing ldap url of ldap url.", t));
1470          h = INFO_NOT_AVAILABLE_LABEL.get().toString();
1471          p = -1;
1472        }
1473        final UserDataCertificateException udce = new UserDataCertificateException(
1474            null, INFO_CERTIFICATE_EXCEPTION.get(h, p), ne, h, p, getInfo().getTrustManager().getLastRefusedChain(),
1475            getInfo().getTrustManager().getLastRefusedAuthType(), excType);
1476
1477        if (SwingUtilities.isEventDispatchThread())
1478        {
1479          handleCertificateException(udce, bindDN, bindPassword);
1480        }
1481        else
1482        {
1483          final ConfigReadException[] fcre = { null };
1484          final NamingException[] fne = { null };
1485          try
1486          {
1487            SwingUtilities.invokeAndWait(new Runnable()
1488            {
1489              @Override
1490              public void run()
1491              {
1492                try
1493                {
1494                  handleCertificateException(udce, bindDN, bindPassword);
1495                }
1496                catch (ConfigReadException cre)
1497                {
1498                  fcre[0] = cre;
1499                }
1500                catch (NamingException ne)
1501                {
1502                  fne[0] = ne;
1503                }
1504              }
1505            });
1506          }
1507          catch (Exception e)
1508          {
1509            throw new IllegalArgumentException("Unexpected error: " + e, e);
1510          }
1511          if (fcre[0] != null)
1512          {
1513            throw fcre[0];
1514          }
1515          if (fne[0] != null)
1516          {
1517            throw fne[0];
1518          }
1519        }
1520      }
1521    }
1522    return createdUserDataCtx;
1523  }
1524
1525  /**
1526   * Displays a dialog asking the user to accept a certificate if the user
1527   * accepts it, we update the trust manager and simulate a click on "OK" to
1528   * re-check the authentication. This method assumes that we are being called
1529   * from the event thread.
1530   *
1531   * @param bindDN
1532   *          the bind DN.
1533   * @param bindPassword
1534   *          the bind password.
1535   */
1536  private void handleCertificateException(UserDataCertificateException ce, String bindDN, String bindPassword)
1537      throws NamingException, ConfigReadException
1538  {
1539    CertificateDialog dlg = new CertificateDialog(null, ce);
1540    dlg.pack();
1541    Utilities.centerGoldenMean(dlg, Utilities.getParentDialog(this));
1542    dlg.setVisible(true);
1543    if (dlg.getUserAnswer() != CertificateDialog.ReturnType.NOT_ACCEPTED)
1544    {
1545      X509Certificate[] chain = ce.getChain();
1546      String authType = ce.getAuthType();
1547      String host = ce.getHost();
1548
1549      if (chain != null && authType != null && host != null)
1550      {
1551        logger.info(LocalizableMessage.raw("Accepting certificate presented by host " + host));
1552        getInfo().getTrustManager().acceptCertificate(chain, authType, host);
1553        createdUserDataCtx = createUserDataDirContext(bindDN, bindPassword);
1554      }
1555      else
1556      {
1557        if (chain == null)
1558        {
1559          logger.warn(LocalizableMessage.raw("The chain is null for the UserDataCertificateException"));
1560        }
1561        if (authType == null)
1562        {
1563          logger.warn(LocalizableMessage.raw("The auth type is null for the UserDataCertificateException"));
1564        }
1565        if (host == null)
1566        {
1567          logger.warn(LocalizableMessage.raw("The host is null for the UserDataCertificateException"));
1568        }
1569      }
1570    }
1571    if (dlg.getUserAnswer() == CertificateDialog.ReturnType.ACCEPTED_PERMANENTLY)
1572    {
1573      X509Certificate[] chain = ce.getChain();
1574      if (chain != null)
1575      {
1576        try
1577        {
1578          UIKeyStore.acceptCertificate(chain);
1579        }
1580        catch (Throwable t)
1581        {
1582          logger.warn(LocalizableMessage.raw("Error accepting certificate: " + t, t));
1583        }
1584      }
1585    }
1586  }
1587
1588  /**
1589   * This class is used simply to avoid an inset on the left for the 'All Base
1590   * DNs' item. Since this item is a CategorizedComboBoxElement of type
1591   * CategorizedComboBoxElement.Type.REGULAR, it has by default an inset on the
1592   * left. The class simply handles this particular case to not to have that
1593   * inset for the 'All Base DNs' item.
1594   */
1595  class CustomComboBoxCellRenderer extends CustomListCellRenderer
1596  {
1597    private LocalizableMessage ALL_BASE_DNS_STRING = INFO_CTRL_PANEL_ALL_BASE_DNS.get();
1598
1599    /**
1600     * The constructor.
1601     *
1602     * @param combo
1603     *          the combo box to be rendered.
1604     */
1605    CustomComboBoxCellRenderer(JComboBox<?> combo)
1606    {
1607      super(combo);
1608    }
1609
1610    @Override
1611    @SuppressWarnings("rawtypes")
1612    public Component getListCellRendererComponent(
1613        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
1614    {
1615      Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
1616      if (value instanceof CategorizedComboBoxElement)
1617      {
1618        CategorizedComboBoxElement element = (CategorizedComboBoxElement) value;
1619        String name = getStringValue(element);
1620        if (ALL_BASE_DNS.equals(name))
1621        {
1622          ((JLabel) comp).setText(ALL_BASE_DNS_STRING.toString());
1623        }
1624      }
1625      comp.setFont(defaultFont);
1626      return comp;
1627    }
1628  }
1629
1630  /**
1631   * Checks that the root node has some children. It it has no children the
1632   * message 'No Match Found' is displayed instead of the tree panel.
1633   */
1634  private void checkRootNode()
1635  {
1636    DefaultMutableTreeNode root = (DefaultMutableTreeNode) controller.getTreeModel().getRoot();
1637    boolean visible = root.getChildCount() > 0;
1638    if (visible != treePane.isVisible())
1639    {
1640      treePane.setVisible(visible);
1641      lNoMatchFound.setVisible(!visible);
1642      lNumberOfEntries.setVisible(visible);
1643    }
1644    numberEntriesUpdater.recalculate();
1645  }
1646
1647  /**
1648   * Updates the NumsubordinateHacker of the browser controller with the
1649   * provided server descriptor.
1650   *
1651   * @param server
1652   *          the server descriptor.
1653   */
1654  private void updateNumSubordinateHacker(ServerDescriptor server)
1655  {
1656    String serverHost = server.getHostname();
1657    int serverPort = server.getAdminConnector().getPort();
1658
1659    List<DN> allSuffixes = new ArrayList<>();
1660    for (BackendDescriptor backend : server.getBackends())
1661    {
1662      for (BaseDNDescriptor baseDN : backend.getBaseDns())
1663      {
1664        allSuffixes.add(baseDN.getDn());
1665      }
1666    }
1667
1668    List<DN> rootSuffixes = new ArrayList<>();
1669    for (DN dn : allSuffixes)
1670    {
1671      if (isRootSuffix(allSuffixes, dn))
1672      {
1673        rootSuffixes.add(dn);
1674      }
1675    }
1676    controller.getNumSubordinateHacker().update(allSuffixes, rootSuffixes, serverHost, serverPort);
1677  }
1678
1679  private boolean isRootSuffix(List<DN> allSuffixes, DN dn)
1680  {
1681    for (DN suffix : allSuffixes)
1682    {
1683      if (suffix.isAncestorOf(dn) && !suffix.equals(dn))
1684      {
1685        return false;
1686      }
1687    }
1688    return true;
1689  }
1690
1691  /**
1692   * This is a class that simply checks the number of entries that the browser
1693   * contains and updates a counter with the new number of entries. It is
1694   * basically a thread that sleeps and checks whether some calculation must be
1695   * made: when we know that something is updated in the browser the method
1696   * recalculate() is called. We could use a more sophisticated code (like use a
1697   * wait() call that would get notified when recalculate() is called) but this
1698   * is not required and it might have an impact on the reactivity of the UI if
1699   * recalculate gets called too often. We can afford to wait 400 miliseconds
1700   * before updating the number of entries and with this approach there is
1701   * hardly no impact on the reactivity of the UI.
1702   */
1703  protected class NumberOfEntriesUpdater extends Thread
1704  {
1705    private boolean recalculate;
1706
1707    /** Notifies that the number of entries in the browser has changed. */
1708    public void recalculate()
1709    {
1710      recalculate = true;
1711    }
1712
1713    /** Executes the updater. */
1714    @Override
1715    public void run()
1716    {
1717      while (true)
1718      {
1719        try
1720        {
1721          Thread.sleep(400);
1722        }
1723        catch (Throwable t)
1724        {
1725        }
1726        if (recalculate)
1727        {
1728          recalculate = false;
1729          SwingUtilities.invokeLater(new Runnable()
1730          {
1731            @Override
1732            public void run()
1733            {
1734              int nEntries = 0;
1735              // This recursive algorithm is fast enough to use it on the
1736              // event thread.  Running it here we avoid issues with concurrent
1737              // access to the node children
1738              if (controller.getTree().isRootVisible())
1739              {
1740                nEntries++;
1741              }
1742              DefaultMutableTreeNode root = (DefaultMutableTreeNode) controller.getTreeModel().getRoot();
1743
1744              nEntries += getChildren(root);
1745              lNumberOfEntries.setText(INFO_CTRL_BROWSER_NUMBER_OF_ENTRIES.get(nEntries).toString());
1746            }
1747          });
1748        }
1749        if (controller != null)
1750        {
1751          final boolean mustDisplayRefreshIcon = controller.getQueueSize() > 0;
1752          if (mustDisplayRefreshIcon != filter.isRefreshIconDisplayed())
1753          {
1754            SwingUtilities.invokeLater(new Runnable()
1755            {
1756              @Override
1757              public void run()
1758              {
1759                filter.displayRefreshIcon(mustDisplayRefreshIcon);
1760              }
1761            });
1762          }
1763        }
1764      }
1765    }
1766
1767    /**
1768     * Returns the number of children for a given node.
1769     *
1770     * @param node
1771     *          the node.
1772     * @return the number of children for the node.
1773     */
1774    private int getChildren(DefaultMutableTreeNode node)
1775    {
1776      int nEntries = 0;
1777
1778      if (!node.isLeaf())
1779      {
1780        Enumeration<?> en = node.children();
1781        while (en.hasMoreElements())
1782        {
1783          nEntries++;
1784          nEntries += getChildren((DefaultMutableTreeNode) en.nextElement());
1785        }
1786      }
1787      return nEntries;
1788    }
1789  }
1790}