/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.guitools.controlpanel.ui;
import static org.opends.messages.AdminToolMessages.*;
import static org.opends.messages.QuickSetupMessages.*;
import static com.forgerock.opendj.cli.Utils.*;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.admin.ads.util.ApplicationTrustManager;
import org.opends.admin.ads.util.ConnectionUtils;
import org.opends.guitools.controlpanel.browser.BrowserController;
import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
import org.opends.guitools.controlpanel.datamodel.CategorizedComboBoxElement;
import org.opends.guitools.controlpanel.datamodel.ConfigReadException;
import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
import org.opends.guitools.controlpanel.datamodel.IndexDescriptor;
import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
import org.opends.guitools.controlpanel.event.BackendPopulatedEvent;
import org.opends.guitools.controlpanel.event.BackendPopulatedListener;
import org.opends.guitools.controlpanel.event.BrowserEvent;
import org.opends.guitools.controlpanel.event.BrowserEventListener;
import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
import org.opends.guitools.controlpanel.ui.components.FilterTextField;
import org.opends.guitools.controlpanel.ui.components.TreePanel;
import org.opends.guitools.controlpanel.ui.nodes.BasicNode;
import org.opends.guitools.controlpanel.ui.renderer.CustomListCellRenderer;
import org.opends.guitools.controlpanel.util.Utilities;
import org.opends.quicksetup.UserDataCertificateException;
import org.opends.quicksetup.ui.CertificateDialog;
import org.opends.quicksetup.util.UIKeyStore;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.LDAPException;
import org.opends.server.types.SearchFilter;
import org.opends.server.util.ServerConstants;
/**
* The abstract class used to refactor some code. The classes that extend this
* class are the 'Browse Entries' panel and the panel of the dialog we display
* when the user can choose a set of entries (for instance when the user adds a
* member to a group in the 'New Group' dialog).
*/
public abstract class AbstractBrowseEntriesPanel extends StatusGenericPanel implements BackendPopulatedListener
{
private static final long serialVersionUID = -6063927039968115236L;
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** LDAP filter message. */
protected static final LocalizableMessage LDAP_FILTER = INFO_CTRL_PANEL_LDAP_FILTER.get();
/** User filter message. */
protected static final LocalizableMessage USER_FILTER = INFO_CTRL_PANEL_USERS_FILTER.get();
/** Group filter message. */
protected static final LocalizableMessage GROUP_FILTER = INFO_CTRL_PANEL_GROUPS_FILTER.get();
private static final LocalizableMessage OTHER_BASE_DN = INFO_CTRL_PANEL_OTHER_BASE_DN.get();
private static final String ALL_BASE_DNS = "All Base DNs";
private static final int MAX_NUMBER_ENTRIES = 5000;
private static final int MAX_NUMBER_OTHER_BASE_DNS = 10;
private static final String[] CONTAINER_CLASSES = { "organization", "organizationalUnit" };
private static final String[] SYSTEM_INDEXES =
{ "aci", "dn2id", "ds-sync-hist", "entryUUID", "id2children", "id2subtree" };
private JComboBox<String> baseDNs;
/** The combo box containing the different filter types. */
protected JComboBox<CharSequence> filterAttribute;
/** The text field of the filter. */
protected FilterTextField filter;
private JButton applyButton;
private JButton okButton;
private JButton cancelButton;
private JButton closeButton;
private JLabel lBaseDN;
private JLabel lFilter;
private JLabel lLimit;
private JLabel lNumberOfEntries;
private JLabel lNoMatchFound;
private InitialLdapContext createdUserDataCtx;
/** The tree pane contained in this panel. */
protected TreePanel treePane;
/** The browser controller used to update the LDAP entry tree. */
protected BrowserController controller;
private NumberOfEntriesUpdater numberEntriesUpdater;
private BaseDNPanel otherBaseDNPanel;
private GenericDialog otherBaseDNDlg;
private boolean firstTimeDisplayed = true;
private Object lastSelectedBaseDN;
private boolean ignoreBaseDNEvents;
private List<DN> otherBaseDns = new ArrayList<>();
/**
* Default constructor.
*/
public AbstractBrowseEntriesPanel()
{
super();
createLayout();
}
@Override
public boolean requiresBorder()
{
return false;
}
@Override
public boolean requiresScroll()
{
return false;
}
@Override
public boolean callConfigurationChangedInBackground()
{
return true;
}
@Override
public void setInfo(ControlPanelInfo info)
{
if (controller == null)
{
createBrowserController(info);
}
super.setInfo(info);
treePane.setInfo(info);
info.addBackendPopulatedListener(this);
}
@Override
public final GenericDialog.ButtonType getButtonType()
{
return GenericDialog.ButtonType.NO_BUTTON;
}
/**
* Since these panel has a special layout, we cannot use the layout of the
* GenericDialog and we return ButtonType.NO_BUTTON in the method
* getButtonType. We use this method to be able to add some progress
* information to the left of the buttons.
*
* @return the button type of the panel.
*/
protected abstract GenericDialog.ButtonType getBrowseButtonType();
@Override
public void toBeDisplayed(boolean visible)
{
super.toBeDisplayed(visible);
Window w = Utilities.getParentDialog(this);
if (w instanceof GenericDialog)
{
((GenericDialog) w).getRootPane().setDefaultButton(null);
}
else if (w instanceof GenericFrame)
{
((GenericFrame) w).getRootPane().setDefaultButton(null);
}
}
@Override
protected void setEnabledOK(boolean enable)
{
okButton.setEnabled(enable);
}
@Override
protected void setEnabledCancel(boolean enable)
{
cancelButton.setEnabled(enable);
}
/** Creates the layout of the panel (but the contents are not populated here). */
@SuppressWarnings("unchecked")
private void createLayout()
{
setBackground(ColorAndFontConstants.greyBackground);
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 7;
addErrorPane(gbc);
LocalizableMessage title = INFO_CTRL_PANEL_SERVER_NOT_RUNNING_SUMMARY.get();
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(INFO_CTRL_PANEL_SERVER_NOT_RUNNING_DETAILS.get());
mb.append("<br><br>");
mb.append(getStartServerHTML());
LocalizableMessage details = mb.toMessage();
updateErrorPane(errorPane, title, ColorAndFontConstants.errorTitleFont, details, ColorAndFontConstants.defaultFont);
errorPane.setVisible(true);
errorPane.setFocusable(true);
gbc.insets = new Insets(10, 10, 0, 10);
gbc.gridy++;
gbc.gridwidth = 1;
gbc.weightx = 0;
gbc.fill = GridBagConstraints.NONE;
lBaseDN = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_BASE_DN_LABEL.get());
gbc.gridx = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets.right = 0;
add(lBaseDN, gbc);
gbc.insets.left = 5;
baseDNs = Utilities.createComboBox();
DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();
model.addElement("dc=dn to be displayed");
baseDNs.setModel(model);
baseDNs.setRenderer(new CustomComboBoxCellRenderer(baseDNs));
baseDNs.addItemListener(new ItemListener()
{
@SuppressWarnings("rawtypes")
@Override
public void itemStateChanged(ItemEvent ev)
{
if (ignoreBaseDNEvents || ev.getStateChange() != ItemEvent.SELECTED)
{
return;
}
Object o = baseDNs.getSelectedItem();
if (isCategory(o))
{
if (lastSelectedBaseDN == null)
{
// Look for the first element that is not a category
for (int i = 0; i < baseDNs.getModel().getSize(); i++)
{
Object item = baseDNs.getModel().getElementAt(i);
if (item instanceof CategorizedComboBoxElement && !isCategory(item))
{
lastSelectedBaseDN = item;
break;
}
}
if (lastSelectedBaseDN != null)
{
baseDNs.setSelectedItem(lastSelectedBaseDN);
}
}
else
{
ignoreBaseDNEvents = true;
baseDNs.setSelectedItem(lastSelectedBaseDN);
ignoreBaseDNEvents = false;
}
}
else if (COMBO_SEPARATOR.equals(o))
{
ignoreBaseDNEvents = true;
baseDNs.setSelectedItem(lastSelectedBaseDN);
ignoreBaseDNEvents = false;
}
else if (!OTHER_BASE_DN.equals(o))
{
lastSelectedBaseDN = o;
if (lastSelectedBaseDN != null)
{
applyButtonClicked();
}
}
else
{
if (otherBaseDNDlg == null)
{
otherBaseDNPanel = new BaseDNPanel();
otherBaseDNDlg = new GenericDialog(Utilities.getFrame(AbstractBrowseEntriesPanel.this), otherBaseDNPanel);
otherBaseDNDlg.setModal(true);
Utilities.centerGoldenMean(otherBaseDNDlg, Utilities.getParentDialog(AbstractBrowseEntriesPanel.this));
}
otherBaseDNDlg.setVisible(true);
String newBaseDn = otherBaseDNPanel.getBaseDn();
DefaultComboBoxModel model = (DefaultComboBoxModel) baseDNs.getModel();
if (newBaseDn != null)
{
CategorizedComboBoxElement newElement = null;
try
{
DN dn = DN.valueOf(newBaseDn);
newElement =
new CategorizedComboBoxElement(Utilities.unescapeUtf8(dn.toString()),
CategorizedComboBoxElement.Type.REGULAR);
if (!otherBaseDns.contains(dn))
{
otherBaseDns.add(0, dn);
if (otherBaseDns.size() > MAX_NUMBER_OTHER_BASE_DNS)
{
ignoreBaseDNEvents = true;
for (int i = otherBaseDns.size() - 1; i >= MAX_NUMBER_OTHER_BASE_DNS; i--)
{
DN dnToRemove = otherBaseDns.get(i);
otherBaseDns.remove(i);
Object elementToRemove =
new CategorizedComboBoxElement(Utilities.unescapeUtf8(dnToRemove.toString()),
CategorizedComboBoxElement.Type.REGULAR);
model.removeElement(elementToRemove);
}
ignoreBaseDNEvents = false;
}
}
if (model.getIndexOf(newElement) == -1)
{
int index = model.getIndexOf(COMBO_SEPARATOR);
model.insertElementAt(newElement, index + 1);
if (otherBaseDns.size() == 1)
{
model.insertElementAt(COMBO_SEPARATOR, index + 2);
}
}
}
catch (Throwable t)
{
throw new RuntimeException("Unexpected error decoding dn " + newBaseDn, t);
}
model.setSelectedItem(newElement);
}
else if (lastSelectedBaseDN != null)
{
ignoreBaseDNEvents = true;
model.setSelectedItem(lastSelectedBaseDN);
ignoreBaseDNEvents = false;
}
}
}
});
gbc.gridx++;
add(baseDNs, gbc);
gbc.gridx++;
gbc.fill = GridBagConstraints.VERTICAL;
gbc.insets.left = 10;
add(new JSeparator(SwingConstants.VERTICAL), gbc);
gbc.fill = GridBagConstraints.HORIZONTAL;
lFilter = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_FILTER_LABEL.get());
gbc.gridx++;
add(lFilter, gbc);
filterAttribute = Utilities.createComboBox();
filterAttribute.setModel(new DefaultComboBoxModel<CharSequence>(new CharSequence[] {
USER_FILTER, GROUP_FILTER, COMBO_SEPARATOR, "attributetobedisplayed", COMBO_SEPARATOR, LDAP_FILTER }));
filterAttribute.setRenderer(new CustomListCellRenderer(filterAttribute));
filterAttribute.addItemListener(new IgnoreItemListener(filterAttribute));
gbc.gridx++;
gbc.insets.left = 5;
add(filterAttribute, gbc);
filter = new FilterTextField();
filter.setToolTipText(INFO_CTRL_PANEL_SUBSTRING_SEARCH_INLINE_HELP.get().toString());
filter.addKeyListener(new KeyAdapter()
{
@Override
public void keyReleased(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ENTER && applyButton.isEnabled())
{
filter.displayRefreshIcon(true);
applyButtonClicked();
}
}
});
filter.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ev)
{
filter.displayRefreshIcon(true);
applyButtonClicked();
}
});
gbc.weightx = 1.0;
gbc.gridx++;
add(filter, gbc);
gbc.insets.top = 10;
applyButton = Utilities.createButton(INFO_CTRL_PANEL_APPLY_BUTTON_LABEL.get());
gbc.insets.right = 10;
gbc.gridx++;
gbc.weightx = 0.0;
add(applyButton, gbc);
applyButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ev)
{
applyButtonClicked();
}
});
gbc.insets = new Insets(10, 0, 0, 0);
gbc.gridx = 0;
gbc.gridy++;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridwidth = 7;
add(createMainPanel(), gbc);
// The button panel
gbc.gridy++;
gbc.weighty = 0.0;
gbc.insets = new Insets(0, 0, 0, 0);
add(createButtonsPanel(), gbc);
}
/**
* Returns the panel that contains the buttons of type OK, CANCEL, etc.
*
* @return the panel that contains the buttons of type OK, CANCEL, etc.
*/
private JPanel createButtonsPanel()
{
JPanel buttonsPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = 1;
gbc.gridy = 0;
lLimit = Utilities.createDefaultLabel();
Utilities.setWarningLabel(lLimit, INFO_CTRL_PANEL_MAXIMUM_CHILDREN_DISPLAYED.get(MAX_NUMBER_ENTRIES));
gbc.weighty = 0.0;
gbc.gridy++;
lLimit.setVisible(false);
lNumberOfEntries = Utilities.createDefaultLabel();
gbc.insets = new Insets(10, 10, 10, 10);
buttonsPanel.add(lNumberOfEntries, gbc);
buttonsPanel.add(lLimit, gbc);
gbc.weightx = 1.0;
gbc.gridx++;
buttonsPanel.add(Box.createHorizontalGlue(), gbc);
buttonsPanel.setOpaque(true);
buttonsPanel.setBackground(ColorAndFontConstants.greyBackground);
gbc.gridx++;
gbc.weightx = 0.0;
if (getBrowseButtonType() == GenericDialog.ButtonType.CLOSE)
{
closeButton = Utilities.createButton(INFO_CTRL_PANEL_CLOSE_BUTTON_LABEL.get());
closeButton.setOpaque(false);
buttonsPanel.add(closeButton, gbc);
closeButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ev)
{
closeClicked();
}
});
}
else if (getBrowseButtonType() == GenericDialog.ButtonType.OK)
{
okButton = Utilities.createButton(INFO_CTRL_PANEL_OK_BUTTON_LABEL.get());
okButton.setOpaque(false);
buttonsPanel.add(okButton, gbc);
okButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ev)
{
okClicked();
}
});
}
if (getBrowseButtonType() == GenericDialog.ButtonType.OK_CANCEL)
{
okButton = Utilities.createButton(INFO_CTRL_PANEL_OK_BUTTON_LABEL.get());
okButton.setOpaque(false);
gbc.insets.right = 0;
buttonsPanel.add(okButton, gbc);
okButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ev)
{
okClicked();
}
});
cancelButton = Utilities.createButton(INFO_CTRL_PANEL_CANCEL_BUTTON_LABEL.get());
cancelButton.setOpaque(false);
gbc.insets.right = 10;
gbc.insets.left = 5;
gbc.gridx++;
buttonsPanel.add(cancelButton, gbc);
cancelButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ev)
{
cancelClicked();
}
});
}
buttonsPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ColorAndFontConstants.defaultBorderColor));
return buttonsPanel;
}
/** {@inheritDoc} */
@Override
public Component getPreferredFocusComponent()
{
return baseDNs;
}
/** {@inheritDoc} */
@Override
public void cancelClicked()
{
setPrimaryValid(lBaseDN);
setSecondaryValid(lFilter);
super.cancelClicked();
}
/**
* The method that is called when the user clicks on Apply. Basically it will
* update the BrowserController with the new base DN and filter specified by
* the user. The method assumes that is being called from the event thread.
*/
protected void applyButtonClicked()
{
List<LocalizableMessage> errors = new ArrayList<>();
setPrimaryValid(lFilter);
String s = getBaseDN();
boolean displayAll = false;
DN theDN = null;
if (s != null)
{
displayAll = ALL_BASE_DNS.equals(s);
if (!displayAll)
{
try
{
theDN = DN.valueOf(s);
}
catch (Throwable t)
{
errors.add(INFO_CTRL_PANEL_INVALID_DN_DETAILS.get(s, t));
}
}
}
else
{
errors.add(INFO_CTRL_PANEL_NO_BASE_DN_SELECTED.get());
}
String filterValue = getFilter();
try
{
LDAPFilter.decode(filterValue);
}
catch (LDAPException le)
{
errors.add(INFO_CTRL_PANEL_INVALID_FILTER_DETAILS.get(le.getMessageObject()));
setPrimaryInvalid(lFilter);
}
if (errors.isEmpty())
{
lLimit.setVisible(false);
lNumberOfEntries.setVisible(true);
controller.removeAllUnderRoot();
controller.setFilter(filterValue);
controller.setAutomaticExpand(!BrowserController.ALL_OBJECTS_FILTER.equals(filterValue));
SortedSet<String> allSuffixes = new TreeSet<>();
if (controller.getConfigurationConnection() != null)
{
treePane.getTree().setRootVisible(displayAll);
treePane.getTree().setShowsRootHandles(!displayAll);
boolean added = false;
for (BackendDescriptor backend : getInfo().getServerDescriptor().getBackends())
{
for (BaseDNDescriptor baseDN : backend.getBaseDns())
{
boolean isBaseDN = baseDN.getDn().equals(theDN);
String dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
if (displayAll)
{
allSuffixes.add(dn);
}
else if (isBaseDN)
{
controller.addSuffix(dn, null);
added = true;
}
}
}
if (displayAll)
{
allSuffixes.add(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
for (String dn : allSuffixes)
{
controller.addSuffix(dn, null);
}
}
else if (!added && !displayAll)
{
if (isChangeLog(theDN))
{
// Consider it a suffix
controller.addSuffix(s, null);
}
else
{
BasicNode rootNode = (BasicNode) controller.getTree().getModel().getRoot();
if (controller.findChildNode(rootNode, s) == -1)
{
controller.addNodeUnderRoot(s);
}
}
}
}
else
{
controller.getTree().setRootVisible(false);
controller.removeAllUnderRoot();
}
}
else
{
displayErrorDialog(errors);
}
}
private boolean isChangeLog(DN theDN)
{
try
{
return theDN.equals(DN.valueOf(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT));
}
catch (Throwable t)
{
// Bug
t.printStackTrace();
return false;
}
}
/**
* Returns the LDAP filter built based in the parameters provided by the user.
*
* @return the LDAP filter built based in the parameters provided by the user.
*/
private String getFilter()
{
String filterText = filter.getText();
if (filterText.length() == 0)
{
return BrowserController.ALL_OBJECTS_FILTER;
}
Object attr = filterAttribute.getSelectedItem();
if (LDAP_FILTER.equals(attr))
{
filterText = filterText.trim();
if (filterText.length() == 0)
{
return BrowserController.ALL_OBJECTS_FILTER;
}
return filterText;
}
else if (USER_FILTER.equals(attr))
{
if ("*".equals(filterText))
{
return "(objectClass=person)";
}
return "(&(objectClass=person)(|" + "(cn=" + filterText + ")(sn=" + filterText + ")(uid=" + filterText + ")))";
}
else if (GROUP_FILTER.equals(attr))
{
if ("*".equals(filterText))
{
return "(|(objectClass=groupOfUniqueNames)(objectClass=groupOfURLs))";
}
return "(&(|(objectClass=groupOfUniqueNames)(objectClass=groupOfURLs))" + "(cn=" + filterText + "))";
}
else if (attr != null)
{
try
{
return new LDAPFilter(SearchFilter.createFilterFromString("(" + attr + "=" + filterText + ")")).toString();
}
catch (DirectoryException de)
{
// Try this alternative:
AttributeType attrType =
getInfo().getServerDescriptor().getSchema().getAttributeType(attr.toString().toLowerCase());
ByteString filterBytes = ByteString.valueOfUtf8(filterText);
return new LDAPFilter(SearchFilter.createEqualityFilter(attrType, filterBytes)).toString();
}
}
else
{
return BrowserController.ALL_OBJECTS_FILTER;
}
}
/**
* Returns the component that will be displayed between the filtering options
* and the buttons panel. This component must contain the tree panel.
*
* @return the component that will be displayed between the filtering options
* and the buttons panel.
*/
protected abstract Component createMainPanel();
/** {@inheritDoc} */
@Override
public void backendPopulated(BackendPopulatedEvent ev)
{
if (controller.getConfigurationConnection() != null)
{
boolean displayAll = false;
boolean errorOccurred = false;
DN theDN = null;
String s = getBaseDN();
if (s != null)
{
displayAll = ALL_BASE_DNS.equals(s);
if (!displayAll)
{
try
{
theDN = DN.valueOf(s);
}
catch (Throwable t)
{
errorOccurred = true;
}
}
}
else
{
errorOccurred = true;
}
if (!errorOccurred)
{
treePane.getTree().setRootVisible(displayAll);
treePane.getTree().setShowsRootHandles(!displayAll);
BasicNode rootNode = (BasicNode) controller.getTree().getModel().getRoot();
boolean isSubordinate = false;
for (BackendDescriptor backend : ev.getBackends())
{
for (BaseDNDescriptor baseDN : backend.getBaseDns())
{
boolean isBaseDN = false;
if (baseDN.getDn().equals(theDN))
{
isBaseDN = true;
}
else if (baseDN.getDn().isAncestorOf(theDN))
{
isSubordinate = true;
}
String dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
if (displayAll || isBaseDN)
{
try
{
if (!controller.hasSuffix(dn))
{
controller.addSuffix(dn, null);
}
else
{
int index = controller.findChildNode(rootNode, dn);
if (index >= 0)
{
TreeNode node = rootNode.getChildAt(index);
if (node != null)
{
TreePath path = new TreePath(controller.getTreeModel().getPathToRoot(node));
controller.startRefresh(controller.getNodeInfoFromPath(path));
}
}
}
}
catch (IllegalArgumentException iae)
{
// The suffix node exists but is not a suffix node. Simply log a message.
logger.warn(
LocalizableMessage.raw("Suffix: " + dn + " added as a non suffix node. Exception: " + iae, iae));
}
}
}
}
if (isSubordinate && controller.findChildNode(rootNode, s) == -1)
{
controller.addNodeUnderRoot(s);
}
}
}
}
@Override
public void configurationChanged(ConfigurationChangeEvent ev)
{
final ServerDescriptor desc = ev.getNewDescriptor();
updateCombos(desc);
updateBrowserControllerAndErrorPane(desc);
}
/**
* Creates and returns the tree panel.
*
* @return the tree panel.
*/
protected JComponent createTreePane()
{
treePane = new TreePanel();
lNoMatchFound = Utilities.createDefaultLabel(INFO_CTRL_PANEL_NO_MATCHES_FOUND_LABEL.get());
lNoMatchFound.setVisible(false);
// Calculate default size
JTree tree = treePane.getTree();
DefaultMutableTreeNode root = new DefaultMutableTreeNode("myserver.mydomain.com:389");
DefaultTreeModel model = new DefaultTreeModel(root);
tree.setModel(model);
tree.setShowsRootHandles(false);
tree.expandPath(new TreePath(root));
JPanel p = new JPanel(new GridBagLayout());
p.setBackground(ColorAndFontConstants.background);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 1;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
Utilities.setBorder(treePane, new EmptyBorder(10, 0, 10, 0));
p.add(treePane, gbc);
gbc.fill = GridBagConstraints.HORIZONTAL;
Utilities.setBorder(lNoMatchFound, new EmptyBorder(15, 15, 15, 15));
p.add(lNoMatchFound, gbc);
if (getInfo() != null && controller == null)
{
createBrowserController(getInfo());
}
numberEntriesUpdater = new NumberOfEntriesUpdater();
numberEntriesUpdater.start();
return p;
}
/**
* Creates the browser controller object.
*
* @param info
* the ControlPanelInfo to be used to create the browser controller.
*/
protected void createBrowserController(ControlPanelInfo info)
{
controller = new BrowserController(treePane.getTree(), info.getConnectionPool(), info.getIconPool());
controller.setContainerClasses(CONTAINER_CLASSES);
controller.setShowContainerOnly(false);
controller.setMaxChildren(MAX_NUMBER_ENTRIES);
controller.addBrowserEventListener(new BrowserEventListener()
{
/** {@inheritDoc} */
@Override
public void processBrowserEvent(BrowserEvent ev)
{
if (ev.getType() == BrowserEvent.Type.SIZE_LIMIT_REACHED)
{
lLimit.setVisible(true);
lNumberOfEntries.setVisible(false);
}
}
});
controller.getTreeModel().addTreeModelListener(new TreeModelListener()
{
@Override
public void treeNodesChanged(TreeModelEvent e)
{
}
@Override
public void treeNodesInserted(TreeModelEvent e)
{
checkRootNode();
}
@Override
public void treeNodesRemoved(TreeModelEvent e)
{
checkRootNode();
}
@Override
public void treeStructureChanged(TreeModelEvent e)
{
checkRootNode();
}
});
}
private static boolean displayIndex(String name)
{
for (String systemIndex : SYSTEM_INDEXES)
{
if (systemIndex.equalsIgnoreCase(name))
{
return false;
}
}
return true;
}
/**
* Updates the contents of the combo boxes with the provided ServerDescriptor.
*
* @param desc
* the server descriptor to be used to update the combo boxes.
*/
@SuppressWarnings("rawtypes")
private void updateCombos(ServerDescriptor desc)
{
final SortedSet<String> newElements = new TreeSet<>();
for (BackendDescriptor backend : desc.getBackends())
{
for (IndexDescriptor index : backend.getIndexes())
{
String indexName = index.getName();
if (displayIndex(indexName))
{
newElements.add(indexName);
}
}
}
@SuppressWarnings("unchecked")
final DefaultComboBoxModel<CharSequence> model = (DefaultComboBoxModel<CharSequence>) filterAttribute.getModel();
if (hasChanged(newElements, model))
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
Object selected = filterAttribute.getSelectedItem();
model.removeAllElements();
model.addElement(USER_FILTER);
model.addElement(GROUP_FILTER);
model.addElement(COMBO_SEPARATOR);
for (String newElement : newElements)
{
model.addElement(newElement);
}
// If there are not backends, we get no indexes to set.
if (!newElements.isEmpty())
{
model.addElement(COMBO_SEPARATOR);
}
model.addElement(LDAP_FILTER);
if (selected != null)
{
if (model.getIndexOf(selected) != -1)
{
model.setSelectedItem(selected);
}
else
{
model.setSelectedItem(model.getElementAt(0));
}
}
}
});
}
Set<Object> baseDNNewElements = new LinkedHashSet<>();
SortedSet<String> backendIDs = new TreeSet<>();
Map<String, SortedSet<String>> hmBaseDNs = new HashMap<>();
Map<String, BaseDNDescriptor> hmBaseDNWithEntries = new HashMap<>();
BaseDNDescriptor baseDNWithEntries = null;
for (BackendDescriptor backend : desc.getBackends())
{
if (displayBackend(backend))
{
String backendID = backend.getBackendID();
backendIDs.add(backendID);
SortedSet<String> someBaseDNs = new TreeSet<>();
for (BaseDNDescriptor baseDN : backend.getBaseDns())
{
try
{
someBaseDNs.add(Utilities.unescapeUtf8(baseDN.getDn().toString()));
}
catch (Throwable t)
{
throw new RuntimeException("Unexpected error: " + t, t);
}
if (baseDN.getEntries() > 0)
{
hmBaseDNWithEntries.put(Utilities.unescapeUtf8(baseDN.getDn().toString()), baseDN);
}
}
hmBaseDNs.put(backendID, someBaseDNs);
if ("userRoot".equalsIgnoreCase(backendID))
{
for (String baseDN : someBaseDNs)
{
baseDNWithEntries = hmBaseDNWithEntries.get(baseDN);
if (baseDNWithEntries != null)
{
break;
}
}
}
}
}
baseDNNewElements.add(new CategorizedComboBoxElement(ALL_BASE_DNS, CategorizedComboBoxElement.Type.REGULAR));
for (String backendID : backendIDs)
{
baseDNNewElements.add(new CategorizedComboBoxElement(backendID, CategorizedComboBoxElement.Type.CATEGORY));
SortedSet<String> someBaseDNs = hmBaseDNs.get(backendID);
for (String baseDN : someBaseDNs)
{
baseDNNewElements.add(new CategorizedComboBoxElement(baseDN, CategorizedComboBoxElement.Type.REGULAR));
if (baseDNWithEntries == null)
{
baseDNWithEntries = hmBaseDNWithEntries.get(baseDN);
}
}
}
for (DN dn : otherBaseDns)
{
baseDNNewElements.add(COMBO_SEPARATOR);
baseDNNewElements.add(new CategorizedComboBoxElement(
Utilities.unescapeUtf8(dn.toString()), CategorizedComboBoxElement.Type.REGULAR));
}
baseDNNewElements.add(COMBO_SEPARATOR);
baseDNNewElements.add(OTHER_BASE_DN);
if (firstTimeDisplayed && baseDNWithEntries != null)
{
ignoreBaseDNEvents = true;
}
updateComboBoxModel(baseDNNewElements, (DefaultComboBoxModel) baseDNs.getModel());
// Select the element in the combo box.
if (firstTimeDisplayed && baseDNWithEntries != null)
{
final Object toSelect = new CategorizedComboBoxElement(
Utilities.unescapeUtf8(baseDNWithEntries.getDn().toString()), CategorizedComboBoxElement.Type.REGULAR);
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
// After this updateBrowseController is called.
ignoreBaseDNEvents = true;
baseDNs.setSelectedItem(toSelect);
ignoreBaseDNEvents = false;
}
});
}
if (getInfo().getServerDescriptor().isAuthenticated())
{
firstTimeDisplayed = false;
}
}
private boolean hasChanged(final SortedSet<String> newElements, final DefaultComboBoxModel<CharSequence> model)
{
if (newElements.size() != model.getSize() - 2)
{
return true;
}
int i = 0;
for (String newElement : newElements)
{
if (!newElement.equals(model.getElementAt(i)))
{
return true;
}
i++;
}
return false;
}
/**
* Updates the contents of the error pane and the browser controller with the
* provided ServerDescriptor. It checks that the server is running and that we
* are authenticated, that the connection to the server has not changed, etc.
*
* @param desc
* the server descriptor to be used to update the error pane and browser controller.
*/
private void updateBrowserControllerAndErrorPane(ServerDescriptor desc)
{
boolean displayNodes = false;
boolean displayErrorPane = false;
LocalizableMessage errorTitle = LocalizableMessage.EMPTY;
LocalizableMessage errorDetails = LocalizableMessage.EMPTY;
ServerDescriptor.ServerStatus status = desc.getStatus();
if (status == ServerDescriptor.ServerStatus.STARTED)
{
if (!desc.isAuthenticated())
{
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_TO_BROWSE_SUMMARY.get());
mb.append("<br><br>").append(getAuthenticateHTML());
errorDetails = mb.toMessage();
errorTitle = INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_SUMMARY.get();
displayErrorPane = true;
}
else
{
try
{
InitialLdapContext ctx = getInfo().getDirContext();
InitialLdapContext ctx1 = controller.getConfigurationConnection();
boolean setConnection = ctx != ctx1;
updateNumSubordinateHacker(desc);
if (setConnection)
{
if (getInfo().getUserDataDirContext() == null)
{
InitialLdapContext ctxUserData =
createUserDataDirContext(ConnectionUtils.getBindDN(ctx), ConnectionUtils.getBindPassword(ctx));
getInfo().setUserDataDirContext(ctxUserData);
}
final NamingException[] fNe = { null };
Runnable runnable = new Runnable()
{
@Override
public void run()
{
try
{
controller.setConnections(
getInfo().getServerDescriptor(), getInfo().getDirContext(), getInfo().getUserDataDirContext());
applyButtonClicked();
}
catch (NamingException ne)
{
fNe[0] = ne;
}
}
};
if (!SwingUtilities.isEventDispatchThread())
{
try
{
SwingUtilities.invokeAndWait(runnable);
}
catch (Throwable t) {}
}
else
{
runnable.run();
}
if (fNe[0] != null)
{
throw fNe[0];
}
}
displayNodes = true;
}
catch (NamingException ne)
{
errorTitle = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_DETAILS.get();
errorDetails = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_SUMMARY.get(ne);
displayErrorPane = true;
}
catch (ConfigReadException cre)
{
errorTitle = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_DETAILS.get();
errorDetails = INFO_CTRL_PANEL_ERROR_CONNECT_BROWSE_SUMMARY.get(cre.getMessageObject());
displayErrorPane = true;
}
}
}
else if (status == ServerDescriptor.ServerStatus.NOT_CONNECTED_TO_REMOTE)
{
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get(desc.getHostname()));
mb.append("<br><br>").append(getAuthenticateHTML());
errorDetails = mb.toMessage();
errorTitle = INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_SUMMARY.get();
displayErrorPane = true;
}
else
{
errorTitle = INFO_CTRL_PANEL_SERVER_NOT_RUNNING_SUMMARY.get();
LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
mb.append(INFO_CTRL_PANEL_AUTHENTICATION_SERVER_MUST_RUN_TO_BROWSE_SUMMARY.get());
mb.append("<br><br>");
mb.append(getStartServerHTML());
errorDetails = mb.toMessage();
displayErrorPane = true;
}
final boolean fDisplayNodes = displayNodes;
final boolean fDisplayErrorPane = displayErrorPane;
final LocalizableMessage fErrorTitle = errorTitle;
final LocalizableMessage fErrorDetails = errorDetails;
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
applyButton.setEnabled(!fDisplayErrorPane);
errorPane.setVisible(fDisplayErrorPane);
if (fDisplayErrorPane)
{
updateErrorPane(errorPane, fErrorTitle,
ColorAndFontConstants.errorTitleFont, fErrorDetails, ColorAndFontConstants.defaultFont);
}
else if (fDisplayNodes)
{
// Update the browser controller with the potential new suffixes.
String s = getBaseDN();
DN theDN = null;
boolean displayAll = false;
if (s != null)
{
displayAll = ALL_BASE_DNS.equals(s);
if (!displayAll)
{
try
{
theDN = DN.valueOf(s);
}
catch (Throwable t)
{
s = null;
}
}
}
treePane.getTree().setRootVisible(displayAll);
treePane.getTree().setShowsRootHandles(!displayAll);
if (s != null)
{
boolean added = false;
for (BackendDescriptor backend : getInfo().getServerDescriptor().getBackends())
{
for (BaseDNDescriptor baseDN : backend.getBaseDns())
{
boolean isBaseDN = false;
String dn = Utilities.unescapeUtf8(baseDN.getDn().toString());
if (theDN != null && baseDN.getDn().equals(theDN))
{
isBaseDN = true;
}
if (baseDN.getEntries() > 0)
{
try
{
if ((displayAll || isBaseDN) && !controller.hasSuffix(dn))
{
controller.addSuffix(dn, null);
added = true;
}
}
catch (IllegalArgumentException iae)
{
// The suffix node exists but is not a suffix node. Simply log a message.
logger.warn(LocalizableMessage.raw(
"Suffix: " + dn + " added as a non suffix node. Exception: " + iae, iae));
}
}
}
if (!added && !displayAll)
{
BasicNode rootNode = (BasicNode) controller.getTree().getModel().getRoot();
if (controller.findChildNode(rootNode, s) == -1)
{
controller.addNodeUnderRoot(s);
}
}
}
}
}
if (!fDisplayNodes)
{
controller.removeAllUnderRoot();
treePane.getTree().setRootVisible(false);
}
}
});
}
/**
* Returns the base DN specified by the user.
*
* @return the base DN specified by the user.
*/
private String getBaseDN()
{
String dn = getBaseDN0();
if (dn != null && dn.trim().length() == 0)
{
dn = ALL_BASE_DNS;
}
return dn;
}
private String getBaseDN0()
{
Object o = baseDNs.getSelectedItem();
if (o instanceof String)
{
return (String) o;
}
else if (o instanceof CategorizedComboBoxElement)
{
return ((CategorizedComboBoxElement) o).getValue().toString();
}
else
{
return null;
}
}
/**
* Creates the context to be used to retrieve user data for some given
* credentials.
*
* @param bindDN
* the bind DN.
* @param bindPassword
* the bind password.
* @return the context to be used to retrieve user data for some given
* credentials.
* @throws NamingException
* if an error occurs connecting to the server.
* @throws ConfigReadException
* if an error occurs reading the configuration.
*/
private InitialLdapContext createUserDataDirContext(final String bindDN, final String bindPassword)
throws NamingException, ConfigReadException
{
createdUserDataCtx = null;
try
{
createdUserDataCtx = Utilities.getUserDataDirContext(getInfo(), bindDN, bindPassword);
}
catch (NamingException ne)
{
if (!isCertificateException(ne))
{
throw ne;
}
ApplicationTrustManager.Cause cause = getInfo().getTrustManager().getLastRefusedCause();
logger.info(LocalizableMessage.raw("Certificate exception cause: " + cause));
UserDataCertificateException.Type excType = null;
if (cause == ApplicationTrustManager.Cause.NOT_TRUSTED)
{
excType = UserDataCertificateException.Type.NOT_TRUSTED;
}
else if (cause == ApplicationTrustManager.Cause.HOST_NAME_MISMATCH)
{
excType = UserDataCertificateException.Type.HOST_NAME_MISMATCH;
}
if (excType != null)
{
String h;
int p;
try
{
URI uri = new URI(getInfo().getAdminConnectorURL());
h = uri.getHost();
p = uri.getPort();
}
catch (Throwable t)
{
logger.warn(LocalizableMessage.raw("Error parsing ldap url of ldap url.", t));
h = INFO_NOT_AVAILABLE_LABEL.get().toString();
p = -1;
}
final UserDataCertificateException udce = new UserDataCertificateException(
null, INFO_CERTIFICATE_EXCEPTION.get(h, p), ne, h, p, getInfo().getTrustManager().getLastRefusedChain(),
getInfo().getTrustManager().getLastRefusedAuthType(), excType);
if (SwingUtilities.isEventDispatchThread())
{
handleCertificateException(udce, bindDN, bindPassword);
}
else
{
final ConfigReadException[] fcre = { null };
final NamingException[] fne = { null };
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
@Override
public void run()
{
try
{
handleCertificateException(udce, bindDN, bindPassword);
}
catch (ConfigReadException cre)
{
fcre[0] = cre;
}
catch (NamingException ne)
{
fne[0] = ne;
}
}
});
}
catch (Exception e)
{
throw new IllegalArgumentException("Unexpected error: " + e, e);
}
if (fcre[0] != null)
{
throw fcre[0];
}
if (fne[0] != null)
{
throw fne[0];
}
}
}
}
return createdUserDataCtx;
}
/**
* Displays a dialog asking the user to accept a certificate if the user
* accepts it, we update the trust manager and simulate a click on "OK" to
* re-check the authentication. This method assumes that we are being called
* from the event thread.
*
* @param bindDN
* the bind DN.
* @param bindPassword
* the bind password.
*/
private void handleCertificateException(UserDataCertificateException ce, String bindDN, String bindPassword)
throws NamingException, ConfigReadException
{
CertificateDialog dlg = new CertificateDialog(null, ce);
dlg.pack();
Utilities.centerGoldenMean(dlg, Utilities.getParentDialog(this));
dlg.setVisible(true);
if (dlg.getUserAnswer() != CertificateDialog.ReturnType.NOT_ACCEPTED)
{
X509Certificate[] chain = ce.getChain();
String authType = ce.getAuthType();
String host = ce.getHost();
if (chain != null && authType != null && host != null)
{
logger.info(LocalizableMessage.raw("Accepting certificate presented by host " + host));
getInfo().getTrustManager().acceptCertificate(chain, authType, host);
createdUserDataCtx = createUserDataDirContext(bindDN, bindPassword);
}
else
{
if (chain == null)
{
logger.warn(LocalizableMessage.raw("The chain is null for the UserDataCertificateException"));
}
if (authType == null)
{
logger.warn(LocalizableMessage.raw("The auth type is null for the UserDataCertificateException"));
}
if (host == null)
{
logger.warn(LocalizableMessage.raw("The host is null for the UserDataCertificateException"));
}
}
}
if (dlg.getUserAnswer() == CertificateDialog.ReturnType.ACCEPTED_PERMANENTLY)
{
X509Certificate[] chain = ce.getChain();
if (chain != null)
{
try
{
UIKeyStore.acceptCertificate(chain);
}
catch (Throwable t)
{
logger.warn(LocalizableMessage.raw("Error accepting certificate: " + t, t));
}
}
}
}
/**
* This class is used simply to avoid an inset on the left for the 'All Base
* DNs' item. Since this item is a CategorizedComboBoxElement of type
* CategorizedComboBoxElement.Type.REGULAR, it has by default an inset on the
* left. The class simply handles this particular case to not to have that
* inset for the 'All Base DNs' item.
*/
class CustomComboBoxCellRenderer extends CustomListCellRenderer
{
private LocalizableMessage ALL_BASE_DNS_STRING = INFO_CTRL_PANEL_ALL_BASE_DNS.get();
/**
* The constructor.
*
* @param combo
* the combo box to be rendered.
*/
CustomComboBoxCellRenderer(JComboBox<?> combo)
{
super(combo);
}
@Override
@SuppressWarnings("rawtypes")
public Component getListCellRendererComponent(
JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
{
Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof CategorizedComboBoxElement)
{
CategorizedComboBoxElement element = (CategorizedComboBoxElement) value;
String name = getStringValue(element);
if (ALL_BASE_DNS.equals(name))
{
((JLabel) comp).setText(ALL_BASE_DNS_STRING.toString());
}
}
comp.setFont(defaultFont);
return comp;
}
}
/**
* Checks that the root node has some children. It it has no children the
* message 'No Match Found' is displayed instead of the tree panel.
*/
private void checkRootNode()
{
DefaultMutableTreeNode root = (DefaultMutableTreeNode) controller.getTreeModel().getRoot();
boolean visible = root.getChildCount() > 0;
if (visible != treePane.isVisible())
{
treePane.setVisible(visible);
lNoMatchFound.setVisible(!visible);
lNumberOfEntries.setVisible(visible);
}
numberEntriesUpdater.recalculate();
}
/**
* Updates the NumsubordinateHacker of the browser controller with the
* provided server descriptor.
*
* @param server
* the server descriptor.
*/
private void updateNumSubordinateHacker(ServerDescriptor server)
{
String serverHost = server.getHostname();
int serverPort = server.getAdminConnector().getPort();
List<DN> allSuffixes = new ArrayList<>();
for (BackendDescriptor backend : server.getBackends())
{
for (BaseDNDescriptor baseDN : backend.getBaseDns())
{
allSuffixes.add(baseDN.getDn());
}
}
List<DN> rootSuffixes = new ArrayList<>();
for (DN dn : allSuffixes)
{
if (isRootSuffix(allSuffixes, dn))
{
rootSuffixes.add(dn);
}
}
controller.getNumSubordinateHacker().update(allSuffixes, rootSuffixes, serverHost, serverPort);
}
private boolean isRootSuffix(List<DN> allSuffixes, DN dn)
{
for (DN suffix : allSuffixes)
{
if (suffix.isAncestorOf(dn) && !suffix.equals(dn))
{
return false;
}
}
return true;
}
/**
* This is a class that simply checks the number of entries that the browser
* contains and updates a counter with the new number of entries. It is
* basically a thread that sleeps and checks whether some calculation must be
* made: when we know that something is updated in the browser the method
* recalculate() is called. We could use a more sophisticated code (like use a
* wait() call that would get notified when recalculate() is called) but this
* is not required and it might have an impact on the reactivity of the UI if
* recalculate gets called too often. We can afford to wait 400 miliseconds
* before updating the number of entries and with this approach there is
* hardly no impact on the reactivity of the UI.
*/
protected class NumberOfEntriesUpdater extends Thread
{
private boolean recalculate;
/** Notifies that the number of entries in the browser has changed. */
public void recalculate()
{
recalculate = true;
}
/** Executes the updater. */
@Override
public void run()
{
while (true)
{
try
{
Thread.sleep(400);
}
catch (Throwable t)
{
}
if (recalculate)
{
recalculate = false;
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
int nEntries = 0;
// This recursive algorithm is fast enough to use it on the
// event thread. Running it here we avoid issues with concurrent
// access to the node children
if (controller.getTree().isRootVisible())
{
nEntries++;
}
DefaultMutableTreeNode root = (DefaultMutableTreeNode) controller.getTreeModel().getRoot();
nEntries += getChildren(root);
lNumberOfEntries.setText(INFO_CTRL_BROWSER_NUMBER_OF_ENTRIES.get(nEntries).toString());
}
});
}
if (controller != null)
{
final boolean mustDisplayRefreshIcon = controller.getQueueSize() > 0;
if (mustDisplayRefreshIcon != filter.isRefreshIconDisplayed())
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
filter.displayRefreshIcon(mustDisplayRefreshIcon);
}
});
}
}
}
}
/**
* Returns the number of children for a given node.
*
* @param node
* the node.
* @return the number of children for the node.
*/
private int getChildren(DefaultMutableTreeNode node)
{
int nEntries = 0;
if (!node.isLeaf())
{
Enumeration<?> en = node.children();
while (en.hasMoreElements())
{
nEntries++;
nEntries += getChildren((DefaultMutableTreeNode) en.nextElement());
}
}
return nEntries;
}
}
}