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