/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011 ForgeRock AS */ package org.opends.guitools.controlpanel.ui; import static org.opends.messages.AdminToolMessages.*; import static org.opends.messages.QuickSetupMessages.INFO_CERTIFICATE_EXCEPTION; import static org.opends.messages.QuickSetupMessages.INFO_NOT_AVAILABLE_LABEL; 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.SortedSet; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; 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.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.messages.Message; import org.opends.messages.MessageBuilder; import org.opends.quicksetup.UserDataCertificateException; import org.opends.quicksetup.ui.CertificateDialog; import org.opends.quicksetup.util.UIKeyStore; import org.opends.quicksetup.util.Utils; import org.opends.server.protocols.ldap.LDAPFilter; import org.opends.server.types.*; 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 JComboBox baseDNs; /** * The combo box containing the different filter types. */ protected JComboBox 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 = null; private boolean ignoreBaseDNEvents = false; /** * LDAP filter message. */ protected static final Message LDAP_FILTER = INFO_CTRL_PANEL_LDAP_FILTER.get(); /** * User filter message. */ protected static final Message USER_FILTER = INFO_CTRL_PANEL_USERS_FILTER.get(); /** * Group filter message. */ protected static final Message GROUP_FILTER = INFO_CTRL_PANEL_GROUPS_FILTER.get(); private final Message OTHER_BASE_DN = INFO_CTRL_PANEL_OTHER_BASE_DN.get(); private ArrayList<DN> otherBaseDns = new ArrayList<DN>(); 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 final String[] CONTAINER_CLASSES = { "organization", "organizationalUnit" }; private static final Logger LOG = Logger.getLogger(AbstractBrowseEntriesPanel.class.getName()); /** * Default constructor. * */ public AbstractBrowseEntriesPanel() { super(); createLayout(); } /** * {@inheritDoc} */ @Override public boolean requiresBorder() { return false; } /** * {@inheritDoc} */ @Override public boolean requiresScroll() { return false; } /** * {@inheritDoc} */ @Override public boolean callConfigurationChangedInBackground() { return true; } /** * {@inheritDoc} */ @Override public void setInfo(ControlPanelInfo info) { if (controller == null) { createBrowserController(info); } super.setInfo(info); treePane.setInfo(info); info.addBackendPopulatedListener(this); } /** * {@inheritDoc} */ @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(); /** * {@inheritDoc} */ @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); } } /** * {@inheritDoc} */ @Override protected void setEnabledOK(boolean enable) { okButton.setEnabled(enable); } /** * {@inheritDoc} */ @Override protected void setEnabledCancel(boolean enable) { cancelButton.setEnabled(enable); } /** * Creates the layout of the panel (but the contents are not populated here). * */ 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); Message title = INFO_CTRL_PANEL_SERVER_NOT_RUNNING_SUMMARY.get(); MessageBuilder mb = new MessageBuilder(); mb.append(INFO_CTRL_PANEL_SERVER_NOT_RUNNING_DETAILS.get()); mb.append("<br><br>"); mb.append(getStartServerHTML()); Message 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 model = new DefaultComboBoxModel(); model.addElement("dc=dn to be displayed"); baseDNs.setModel(model); baseDNs.setRenderer(new CustomComboBoxCellRenderer(baseDNs)); baseDNs.addItemListener(new ItemListener() { 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) { if (!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) { Object newElement = null; try { DN dn = DN.decode(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); } if (newElement != null) { 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(new Object[]{ 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() { 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() { 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() { 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() { 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() { 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() { public void actionPerformed(ActionEvent ev) { cancelClicked(); } }); } buttonsPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ColorAndFontConstants.defaultBorderColor)); return buttonsPanel; } /** * {@inheritDoc} */ 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() { ArrayList<Message> errors = new ArrayList<Message>(); setPrimaryValid(lFilter); String s = getBaseDN(); boolean displayAll = false; DN theDN = null; if (s != null) { displayAll = s.equals(ALL_BASE_DNS); if (!displayAll) { try { theDN = DN.decode(s); } catch (Throwable t) { errors.add(INFO_CTRL_PANEL_INVALID_DN_DETAILS.get(s, t.toString())); } } } 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().toString())); setPrimaryInvalid(lFilter); } if (errors.isEmpty()) { lLimit.setVisible(false); lNumberOfEntries.setVisible(true); controller.removeAllUnderRoot(); controller.setFilter(filterValue); controller.setAutomaticExpand(!filterValue.equals( BrowserController.ALL_OBJECTS_FILTER)); SortedSet<String> allSuffixes = new TreeSet<String>(); 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 = false; if ((theDN != null) && baseDN.getDn().equals(theDN)) { isBaseDN = true; } 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.decode(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 returnValue; String s = filter.getText(); if (s.length() == 0) { returnValue = BrowserController.ALL_OBJECTS_FILTER; } else { Object attr = filterAttribute.getSelectedItem(); if (LDAP_FILTER.equals(attr)) { s = s.trim(); if (s.length() == 0) { returnValue = BrowserController.ALL_OBJECTS_FILTER; } else { returnValue = s; } } else if (USER_FILTER.equals(attr)) { if (s.equals("*")) { returnValue = "(objectClass=person)"; } else { returnValue = "(&(objectClass=person)(|"+ "(cn="+s+")(sn="+s+")(uid="+s+")))"; } } else if (GROUP_FILTER.equals(attr)) { if (s.equals("*")) { returnValue = "(|(objectClass=groupOfUniqueNames)(objectClass=groupOfURLs))"; } else { returnValue = "(&(|(objectClass=groupOfUniqueNames)(objectClass=groupOfURLs))"+ "(cn="+s+"))"; } } else if (attr != null) { try { LDAPFilter ldapFilter = new LDAPFilter(SearchFilter.createFilterFromString( "("+attr+"="+s+")")); returnValue = ldapFilter.toString(); } catch (DirectoryException de) { // Try this alternative: AttributeType attrType = getInfo().getServerDescriptor().getSchema().getAttributeType( attr.toString().toLowerCase()); LDAPFilter ldapFilter = new LDAPFilter(SearchFilter.createEqualityFilter( attrType, AttributeValues.create(attrType, s))); returnValue = ldapFilter.toString(); } } else { returnValue = BrowserController.ALL_OBJECTS_FILTER; } } return returnValue; } /** * 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} */ 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 = s.equals(ALL_BASE_DNS); if (!displayAll) { try { theDN = DN.decode(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 ((theDN != null) && baseDN.getDn().equals(theDN)) { isBaseDN = true; } else if ((theDN != null) && 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. LOG.log(Level.WARNING, "Suffix: "+dn+ " added as a non suffix node. Exception: "+iae, iae); } } } } if (isSubordinate) { if (controller.findChildNode(rootNode, s) == -1) { controller.addNodeUnderRoot(s); } } } } } /** * {@inheritDoc} */ 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} */ public void processBrowserEvent(BrowserEvent ev) { if (ev.getType() == BrowserEvent.Type.SIZE_LIMIT_REACHED) { lLimit.setVisible(true); lNumberOfEntries.setVisible(false); } } }); controller.getTreeModel().addTreeModelListener(new TreeModelListener() { /** * {@inheritDoc} */ public void treeNodesChanged(TreeModelEvent e) { } /** * {@inheritDoc} */ public void treeNodesInserted(TreeModelEvent e) { checkRootNode(); } /** * {@inheritDoc} */ public void treeNodesRemoved(TreeModelEvent e) { checkRootNode(); } /** * {@inheritDoc} */ public void treeStructureChanged(TreeModelEvent e) { checkRootNode(); } }); } final static String[] systemIndexes = {"aci", "dn2id", "ds-sync-hist", "entryUUID", "id2children", "id2subtree"}; private static boolean displayIndex(String name) { boolean displayIndex = true; for (String systemIndex : systemIndexes) { if (systemIndex.equalsIgnoreCase(name)) { displayIndex = false; break; } } return displayIndex; } /** * Updates the contents of the combo boxes with the provided ServerDescriptor. * @param desc the server descriptor to be used to update the combo boxes. */ private void updateCombos(ServerDescriptor desc) { final SortedSet<String> newElements = new TreeSet<String>(); for (BackendDescriptor backend : desc.getBackends()) { for (IndexDescriptor index : backend.getIndexes()) { String indexName = index.getName(); if (displayIndex(indexName)) { newElements.add(indexName); } } } final DefaultComboBoxModel model = (DefaultComboBoxModel)filterAttribute.getModel(); boolean changed = newElements.size() != model.getSize() - 2; if (!changed) { int i = 0; for (String newElement : newElements) { changed = !newElement.equals(model.getElementAt(i)); if (changed) { break; } i++; } } if (changed) { SwingUtilities.invokeLater(new Runnable() { /** * {@inheritDoc} */ 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.size() > 0) { 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)); } } } }); } LinkedHashSet<Object> baseDNNewElements = new LinkedHashSet<Object>(); SortedSet<String> backendIDs = new TreeSet<String>(); HashMap<String, SortedSet<String>> hmBaseDNs = new HashMap<String, SortedSet<String>>(); boolean allAdded = false; HashMap<String, BaseDNDescriptor> hmBaseDNWithEntries = new HashMap<String, BaseDNDescriptor>(); BaseDNDescriptor baseDNWithEntries = null; for (BackendDescriptor backend : desc.getBackends()) { if (displayBackend(backend)) { String backendID = backend.getBackendID(); backendIDs.add(backendID); SortedSet<String> someBaseDNs = new TreeSet<String>(); 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 (backendID.equalsIgnoreCase("userRoot")) { for (String baseDN : someBaseDNs) { baseDNWithEntries = hmBaseDNWithEntries.get(baseDN); if (baseDNWithEntries != null) { break; } } } } } if (!allAdded) { baseDNNewElements.add(new CategorizedComboBoxElement(ALL_BASE_DNS, CategorizedComboBoxElement.Type.REGULAR)); allAdded = true; } 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) { if (allAdded) { baseDNNewElements.add(COMBO_SEPARATOR); } baseDNNewElements.add(new CategorizedComboBoxElement( Utilities.unescapeUtf8(dn.toString()), CategorizedComboBoxElement.Type.REGULAR)); } if (allAdded) { 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() { /** * {@inheritDoc} */ public void run() { // After this updateBrowseController is called. ignoreBaseDNEvents = true; baseDNs.setSelectedItem(toSelect); ignoreBaseDNEvents = false; } }); } if (getInfo().getServerDescriptor().isAuthenticated()) { firstTimeDisplayed = 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; Message errorTitle = Message.EMPTY; Message errorDetails = Message.EMPTY; ServerDescriptor.ServerStatus status = desc.getStatus(); if (status == ServerDescriptor.ServerStatus.STARTED) { if (!desc.isAuthenticated()) { MessageBuilder mb = new MessageBuilder(); mb.append( INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_TO_BROWSE_SUMMARY.get()); mb.append("<br><br>"+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() { /** * {@inheritDoc} */ 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.toString()); 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().toString()); displayErrorPane = true; } } } else if (status == ServerDescriptor.ServerStatus.NOT_CONNECTED_TO_REMOTE) { MessageBuilder mb = new MessageBuilder(); mb.append(INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get( desc.getHostname())); mb.append("<br><br>"+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(); MessageBuilder mb = new MessageBuilder(); 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 Message fErrorTitle = errorTitle; final Message fErrorDetails = errorDetails; SwingUtilities.invokeLater(new Runnable() { /** * {@inheritDoc} */ 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 = s.equals(ALL_BASE_DNS); if (!displayAll) { try { theDN = DN.decode(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) { if (!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. LOG.log(Level.WARNING, "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; Object o = baseDNs.getSelectedItem(); if (o instanceof String) { dn = (String)o; } else if (o instanceof CategorizedComboBoxElement) { dn = ((CategorizedComboBoxElement)o).getValue().toString(); } else { dn = null; } if (dn != null) { if (dn.trim().length() == 0) { dn = ALL_BASE_DNS; } // The following is never true. OTHER_BASE_DN is a Message // Comment out buggy code // else if (OTHER_BASE_DN.equals(dn)) // { // dn = null; // } } return dn; } /** * 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 (Utils.isCertificateException(ne)) { ApplicationTrustManager.Cause cause = getInfo().getTrustManager().getLastRefusedCause(); LOG.log(Level.INFO, "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) { LOG.log(Level.WARNING, "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, String.valueOf(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() { public void run() { try { handleCertificateException(udce, bindDN, bindPassword); } catch (ConfigReadException cre) { fcre[0] = cre; } catch (NamingException ne) { fne[0] = ne; } } }); } catch (Throwable t) { throw new IllegalArgumentException("Unexpected error: "+t, t); } if (fcre[0] != null) { throw fcre[0]; } if (fne[0] != null) { throw fne[0]; } } } } else { throw ne; } } 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)) { LOG.log(Level.INFO, "Accepting certificate presented by host "+host); getInfo().getTrustManager().acceptCertificate(chain, authType, host); createdUserDataCtx = createUserDataDirContext(bindDN, bindPassword); } else { if (chain == null) { LOG.log(Level.WARNING, "The chain is null for the UserDataCertificateException"); } if (authType == null) { LOG.log(Level.WARNING, "The auth type is null for the UserDataCertificateException"); } if (host == null) { LOG.log(Level.WARNING, "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) { LOG.log(Level.WARNING, "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 Message 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); } /** * {@inheritDoc} */ @Override 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(); ArrayList<DN> allSuffixes = new ArrayList<DN>(); for (BackendDescriptor backend : server.getBackends()) { for (BaseDNDescriptor baseDN : backend.getBaseDns()) { allSuffixes.add(baseDN.getDn()); } } ArrayList<DN> rootSuffixes = new ArrayList<DN>(); for (DN dn : allSuffixes) { boolean isRootSuffix = true; for (DN dn1 : allSuffixes) { if (dn1.isAncestorOf(dn) && !dn1.equals(dn)) { isRootSuffix = false; break; } } if (isRootSuffix) { rootSuffixes.add(dn); } } controller.getNumSubordinateHacker().update(allSuffixes, rootSuffixes, serverHost, serverPort); } /** * 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 * sofisticated 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 implements Runnable { private boolean recalculate; /** * Constructor. * */ public NumberOfEntriesUpdater() { } /** * 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() { 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() { 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; } } }