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 2014-2015 ForgeRock AS. 016 */ 017package org.opends.guitools.controlpanel.browser; 018 019import java.awt.Font; 020import java.io.IOException; 021import java.lang.reflect.InvocationTargetException; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Enumeration; 025import java.util.List; 026import java.util.Set; 027import java.util.SortedSet; 028import java.util.TreeSet; 029import java.util.logging.Level; 030import java.util.logging.Logger; 031 032import javax.naming.NameNotFoundException; 033import javax.naming.NamingException; 034import javax.naming.directory.SearchControls; 035import javax.naming.directory.SearchResult; 036import javax.naming.ldap.Control; 037import javax.naming.ldap.InitialLdapContext; 038import javax.naming.ldap.ManageReferralControl; 039import javax.naming.ldap.SortControl; 040import javax.naming.ldap.SortKey; 041import javax.swing.Icon; 042import javax.swing.JTree; 043import javax.swing.SwingUtilities; 044import javax.swing.event.TreeExpansionEvent; 045import javax.swing.event.TreeExpansionListener; 046import javax.swing.tree.DefaultTreeModel; 047import javax.swing.tree.TreeNode; 048import javax.swing.tree.TreePath; 049 050import org.opends.admin.ads.ADSContext; 051import org.opends.admin.ads.util.ConnectionUtils; 052import org.opends.guitools.controlpanel.datamodel.CustomSearchResult; 053import org.opends.guitools.controlpanel.datamodel.ServerDescriptor; 054import org.opends.guitools.controlpanel.event.BrowserEvent; 055import org.opends.guitools.controlpanel.event.BrowserEventListener; 056import org.opends.guitools.controlpanel.event.ReferralAuthenticationListener; 057import org.opends.guitools.controlpanel.ui.nodes.BasicNode; 058import org.opends.guitools.controlpanel.ui.nodes.BrowserNodeInfo; 059import org.opends.guitools.controlpanel.ui.nodes.RootNode; 060import org.opends.guitools.controlpanel.ui.nodes.SuffixNode; 061import org.opends.guitools.controlpanel.ui.renderer.BrowserCellRenderer; 062import org.opends.guitools.controlpanel.util.NumSubordinateHacker; 063import org.opends.guitools.controlpanel.util.Utilities; 064import org.opends.server.config.ConfigConstants; 065import org.opends.server.types.LDAPURL; 066 067import static org.opends.server.util.ServerConstants.*; 068 069/** 070 * This is the main class of the LDAP entry browser. It is in charge of 071 * updating a tree that is passed as parameter. Every instance of 072 * BrowserController is associated with a unique JTree. 073 * The different visualization options are passed to BrowserController using 074 * some setter and getter methods (the user can specify for instance whether 075 * the entries must be sorted or not). 076 */ 077public class BrowserController 078implements TreeExpansionListener, ReferralAuthenticationListener 079{ 080 /** 081 * The mask used to display the number of ACIs or not. 082 */ 083 private static final int DISPLAY_ACI_COUNT = 0x01; 084 085 /** 086 * The list of attributes that are used to sort the entries (if the sorting 087 * option is used). 088 */ 089 private static final String[] SORT_ATTRIBUTES = 090 { "cn", "givenname", "o", "ou", "sn", "uid" }; 091 092 /** 093 * This is a key value. It is used to specify that the attribute that should 094 * be used to display the entry is the RDN attribute. 095 */ 096 private static final String RDN_ATTRIBUTE = "rdn attribute"; 097 098 /** 099 * The filter used to retrieve all the entries. 100 */ 101 public static final String ALL_OBJECTS_FILTER = 102 "(|(objectClass=*)(objectClass=ldapsubentry))"; 103 104 private static final String NUMSUBORDINATES_ATTR = "numsubordinates"; 105 private static final String HASSUBORDINATES_ATTR = "hassubordinates"; 106 private static final String ACI_ATTR = "aci"; 107 108 private final JTree tree; 109 private final DefaultTreeModel treeModel; 110 private final RootNode rootNode; 111 private int displayFlags; 112 private String displayAttribute; 113 private final boolean showAttributeName; 114 private InitialLdapContext ctxConfiguration; 115 private InitialLdapContext ctxUserData; 116 private boolean followReferrals; 117 private boolean sorted; 118 private boolean showContainerOnly; 119 private boolean automaticExpand; 120 private boolean automaticallyExpandedNode; 121 private String[] containerClasses; 122 private NumSubordinateHacker numSubordinateHacker; 123 private int queueTotalSize; 124 private int maxChildren; 125 private final Collection<BrowserEventListener> listeners = new ArrayList<>(); 126 private final LDAPConnectionPool connectionPool; 127 private final IconPool iconPool; 128 129 private final NodeSearcherQueue refreshQueue; 130 131 private String filter; 132 133 private static final Logger LOG = 134 Logger.getLogger(BrowserController.class.getName()); 135 136 /** 137 * Constructor of the BrowserController. 138 * @param tree the tree that must be updated. 139 * @param cpool the connection pool object that will provide the connections 140 * to be used. 141 * @param ipool the icon pool to be used to retrieve the icons that will be 142 * used to render the nodes in the tree. 143 */ 144 public BrowserController(JTree tree, LDAPConnectionPool cpool, 145 IconPool ipool) 146 { 147 this.tree = tree; 148 iconPool = ipool; 149 rootNode = new RootNode(); 150 rootNode.setIcon(iconPool.getIconForRootNode()); 151 treeModel = new DefaultTreeModel(rootNode); 152 tree.setModel(treeModel); 153 tree.addTreeExpansionListener(this); 154 tree.setCellRenderer(new BrowserCellRenderer()); 155 displayFlags = DISPLAY_ACI_COUNT; 156 showAttributeName = false; 157 displayAttribute = RDN_ATTRIBUTE; 158 followReferrals = false; 159 sorted = false; 160 showContainerOnly = true; 161 containerClasses = new String[0]; 162 queueTotalSize = 0; 163 connectionPool = cpool; 164 connectionPool.addReferralAuthenticationListener(this); 165 166 refreshQueue = new NodeSearcherQueue("New red", 2); 167 168 // NUMSUBORDINATE HACK 169 // Create an empty hacker to avoid null value test. 170 // However this value will be overridden by full hacker. 171 numSubordinateHacker = new NumSubordinateHacker(); 172 } 173 174 175 /** 176 * Set the connection for accessing the directory. Since we must use 177 * different controls when searching the configuration and the user data, 178 * two connections must be provided (this is done to avoid synchronization 179 * issues). We also pass the server descriptor corresponding to the 180 * connections to have a proper rendering of the root node. 181 * @param server the server descriptor. 182 * @param ctxConfiguration the connection to be used to retrieve the data in 183 * the configuration base DNs. 184 * @param ctxUserData the connection to be used to retrieve the data in the 185 * user base DNs. 186 * @throws NamingException if an error occurs. 187 */ 188 public void setConnections( 189 ServerDescriptor server, 190 InitialLdapContext ctxConfiguration, 191 InitialLdapContext ctxUserData) throws NamingException { 192 String rootNodeName; 193 if (ctxConfiguration != null) 194 { 195 this.ctxConfiguration = ctxConfiguration; 196 this.ctxUserData = ctxUserData; 197 198 this.ctxConfiguration.setRequestControls( 199 getConfigurationRequestControls()); 200 this.ctxUserData.setRequestControls(getRequestControls()); 201 rootNodeName = server.getHostname() + ":" + 202 ConnectionUtils.getPort(ctxConfiguration); 203 } 204 else { 205 rootNodeName = ""; 206 } 207 rootNode.setDisplayName(rootNodeName); 208 startRefresh(null); 209 } 210 211 212 /** 213 * Return the connection for accessing the directory configuration. 214 * @return the connection for accessing the directory configuration. 215 */ 216 public InitialLdapContext getConfigurationConnection() { 217 return ctxConfiguration; 218 } 219 220 /** 221 * Return the connection for accessing the directory user data. 222 * @return the connection for accessing the directory user data. 223 */ 224 public InitialLdapContext getUserDataConnection() { 225 return ctxUserData; 226 } 227 228 229 /** 230 * Return the JTree controlled by this controller. 231 * @return the JTree controlled by this controller. 232 */ 233 public JTree getTree() { 234 return tree; 235 } 236 237 238 /** 239 * Return the connection pool used by this controller. 240 * If a client class adds authentication to the connection 241 * pool, it must inform the controller by calling notifyAuthDataChanged(). 242 * @return the connection pool used by this controller. 243 */ 244 public LDAPConnectionPool getConnectionPool() { 245 return connectionPool; 246 } 247 248 /** 249 * Return the icon pool used by this controller. 250 * @return the icon pool used by this controller. 251 */ 252 public IconPool getIconPool() { 253 return iconPool; 254 } 255 256 /** 257 * Tells whether the given suffix is in the tree or not. 258 * @param suffixDn the DN of the suffix to be analyzed. 259 * @return <CODE>true</CODE> if the provided String is the DN of a suffix 260 * and <CODE>false</CODE> otherwise. 261 * @throws IllegalArgumentException if a node with the given dn exists but 262 * is not a suffix node. 263 */ 264 public boolean hasSuffix(String suffixDn) throws IllegalArgumentException 265 { 266 return findSuffixNode(suffixDn, rootNode) != null; 267 } 268 269 /** 270 * Add an LDAP suffix to this controller. 271 * A new node is added in the JTree and a refresh is started. 272 * @param suffixDn the DN of the suffix. 273 * @param parentSuffixDn the DN of the parent suffix (or <CODE>null</CODE> if 274 * there is no parent DN). 275 * @return the TreePath of the new node. 276 * @throws IllegalArgumentException if a node with the given dn exists. 277 */ 278 public TreePath addSuffix(String suffixDn, String parentSuffixDn) 279 throws IllegalArgumentException 280 { 281 SuffixNode parentNode; 282 if (parentSuffixDn != null) { 283 parentNode = findSuffixNode(parentSuffixDn, rootNode); 284 if (parentNode == null) { 285 throw new IllegalArgumentException("Invalid suffix dn " + 286 parentSuffixDn); 287 } 288 } 289 else { 290 parentNode = rootNode; 291 } 292 int index = findChildNode(parentNode, suffixDn); 293 if (index >= 0) { // A node has alreay this dn -> bug 294 throw new IllegalArgumentException("Duplicate suffix dn " + suffixDn); 295 } 296 index = -(index + 1); 297 SuffixNode newNode = new SuffixNode(suffixDn); 298 treeModel.insertNodeInto(newNode, parentNode, index); 299 startRefreshNode(newNode, null, true); 300 301 return new TreePath(treeModel.getPathToRoot(newNode)); 302 } 303 304 /** 305 * Add an LDAP suffix to this controller. 306 * A new node is added in the JTree and a refresh is started. 307 * @param nodeDn the DN of the node to be added. 308 * @return the TreePath of the new node. 309 */ 310 public TreePath addNodeUnderRoot(String nodeDn) { 311 SuffixNode parentNode = rootNode; 312 int index = findChildNode(parentNode, nodeDn); 313 if (index >= 0) { // A node has already this dn -> bug 314 throw new IllegalArgumentException("Duplicate node dn " + nodeDn); 315 } 316 index = -(index + 1); 317 BasicNode newNode = new BasicNode(nodeDn); 318 treeModel.insertNodeInto(newNode, parentNode, index); 319 startRefreshNode(newNode, null, true); 320 321 return new TreePath(treeModel.getPathToRoot(newNode)); 322 } 323 324 325 /** 326 * Remove all the suffixes. 327 * The controller removes all the nodes from the JTree except the root. 328 * @return the TreePath of the root node. 329 */ 330 public TreePath removeAllUnderRoot() { 331 stopRefresh(); 332 removeAllChildNodes(rootNode, false /* Delete suffixes */); 333 return new TreePath(treeModel.getPathToRoot(rootNode)); 334 } 335 336 337 /** 338 * Return the display flags. 339 * @return the display flags. 340 */ 341 public int getDisplayFlags() { 342 return displayFlags; 343 } 344 345 346 /** 347 * Set the display flags and call startRefresh(). 348 * @param flags the display flags to be set. 349 */ 350 public void setDisplayFlags(int flags) { 351 displayFlags = flags; 352 startRefresh(null); 353 } 354 355 /** 356 * Set the display attribute (the attribute that will be used to retrieve 357 * the string that will appear in the tree when rendering the node). 358 * This routine collapses the JTree and invokes startRefresh(). 359 * @param displayAttribute the display attribute to be used. 360 */ 361 public void setDisplayAttribute(String displayAttribute) { 362 this.displayAttribute = displayAttribute; 363 stopRefresh(); 364 removeAllChildNodes(rootNode, true /* Keep suffixes */); 365 startRefresh(null); 366 } 367 368 /** 369 * Returns the attribute used to display the entry. 370 * RDN_ATTRIBUTE is the rdn is used. 371 * @return the attribute used to display the entry. 372 */ 373 public String getDisplayAttribute() { 374 return displayAttribute; 375 } 376 377 /** 378 * Says whether we are showing the attribute name or not. 379 * @return <CODE>true</CODE> if we are showing the attribute name and 380 * <CODE>false</CODE> otherwise. 381 */ 382 public boolean isAttributeNameShown() { 383 return showAttributeName; 384 } 385 386 /** 387 * Sets the maximum number of children to display for a node. 388 * 0 if there is no limit 389 * @param maxChildren the maximum number of children to display for a node. 390 */ 391 public void setMaxChildren(int maxChildren) { 392 this.maxChildren = maxChildren; 393 } 394 395 /** 396 * Return the maximum number of children to display. 397 * @return the maximum number of children to display. 398 */ 399 public int getMaxChildren() { 400 return maxChildren; 401 } 402 403 /** 404 * Return true if this controller follows referrals. 405 * @return <CODE>true</CODE> if this controller follows referrals and 406 * <CODE>false</CODE> otherwise. 407 */ 408 public boolean getFollowReferrals() { 409 return followReferrals; 410 } 411 412 413 /** 414 * Enable/display the following of referrals. 415 * This routine starts a refresh on each referral node. 416 * @param followReferrals whether to follow referrals or not. 417 * @throws NamingException if there is an error updating the request controls 418 * of the internal connections. 419 */ 420 public void setFollowReferrals(boolean followReferrals) throws NamingException 421 { 422 this.followReferrals = followReferrals; 423 stopRefresh(); 424 removeAllChildNodes(rootNode, true /* Keep suffixes */); 425 ctxConfiguration.setRequestControls(getConfigurationRequestControls()); 426 ctxUserData.setRequestControls(getRequestControls()); 427 connectionPool.setRequestControls(getRequestControls()); 428 startRefresh(null); 429 } 430 431 432 /** 433 * Return true if entries are displayed sorted. 434 * @return <CODE>true</CODE> if entries are displayed sorted and 435 * <CODE>false</CODE> otherwise. 436 */ 437 public boolean isSorted() { 438 return sorted; 439 } 440 441 442 /** 443 * Enable/disable entry sort. 444 * This routine collapses the JTree and invokes startRefresh(). 445 * @param sorted whether to sort the entries or not. 446 * @throws NamingException if there is an error updating the request controls 447 * of the internal connections. 448 */ 449 public void setSorted(boolean sorted) throws NamingException { 450 stopRefresh(); 451 removeAllChildNodes(rootNode, true /* Keep suffixes */); 452 this.sorted = sorted; 453 ctxConfiguration.setRequestControls(getConfigurationRequestControls()); 454 ctxUserData.setRequestControls(getRequestControls()); 455 connectionPool.setRequestControls(getRequestControls()); 456 startRefresh(null); 457 } 458 459 460 /** 461 * Return true if only container entries are displayed. 462 * An entry is a container if: 463 * - it has some children 464 * - or its class is one of the container classes 465 * specified with setContainerClasses(). 466 * @return <CODE>true</CODE> if only container entries are displayed and 467 * <CODE>false</CODE> otherwise. 468 */ 469 public boolean isShowContainerOnly() { 470 return showContainerOnly; 471 } 472 473 474 /** 475 * Enable or disable container display and call startRefresh(). 476 * @param showContainerOnly whether to display only containers or all the 477 * entries. 478 */ 479 public void setShowContainerOnly(boolean showContainerOnly) { 480 this.showContainerOnly = showContainerOnly; 481 startRefresh(null); 482 } 483 484 485 /** 486 * Find the BrowserNodeInfo associated to a TreePath and returns 487 * the describing IBrowserNodeInfo. 488 * @param path the TreePath associated with the node we are searching. 489 * @return the BrowserNodeInfo associated to the TreePath. 490 */ 491 public BrowserNodeInfo getNodeInfoFromPath(TreePath path) { 492 BasicNode node = (BasicNode)path.getLastPathComponent(); 493 return new BrowserNodeInfoImpl(node); 494 } 495 496 497 /** 498 * Return the array of container classes for this controller. 499 * Warning: the returned array is not cloned. 500 * @return the array of container classes for this controller. 501 */ 502 public String[] getContainerClasses() { 503 return containerClasses; 504 } 505 506 507 /** 508 * Set the list of container classes and calls startRefresh(). 509 * Warning: the array is not cloned. 510 * @param containerClasses the lis of container classes. 511 */ 512 public void setContainerClasses(String[] containerClasses) { 513 this.containerClasses = containerClasses; 514 startRefresh(null); 515 } 516 517 518 /** 519 * NUMSUBORDINATE HACK 520 * Make the hacker public so that RefreshTask can use it. 521 * @return the NumSubordinateHacker object used by the controller. 522 */ 523 public NumSubordinateHacker getNumSubordinateHacker() { 524 return numSubordinateHacker; 525 } 526 527 528 /** 529 * NUMSUBORDINATE HACK 530 * Set the hacker. Note this method does not trigger any 531 * refresh. The caller is supposed to do it afterward. 532 * @param h the NumSubordinateHacker. 533 */ 534 public void setNumSubordinateHacker(NumSubordinateHacker h) { 535 if (h == null) { 536 throw new IllegalArgumentException("hacker cannot be null"); 537 } 538 numSubordinateHacker = h; 539 } 540 541 /** 542 * Add a BrowserEventListener to this controller. 543 * @param l the listener to be added. 544 */ 545 public void addBrowserEventListener(BrowserEventListener l) { 546 listeners.add(l); 547 } 548 549 /** 550 * Notify this controller that an entry has been added. 551 * The controller adds a new node in the JTree and starts refreshing this new 552 * node. 553 * This routine returns the tree path about the new entry. 554 * @param parentInfo the parent node of the entry added. 555 * @param newEntryDn the dn of the entry to be added. 556 * @return the tree path associated with the new entry. 557 */ 558 public TreePath notifyEntryAdded(BrowserNodeInfo parentInfo, 559 String newEntryDn) { 560 BasicNode parentNode = parentInfo.getNode(); 561 BasicNode childNode = new BasicNode(newEntryDn); 562 int childIndex; 563 if (sorted) { 564 childIndex = findChildNode(parentNode, newEntryDn); 565 if (childIndex >= 0) { 566 throw new IllegalArgumentException("Duplicate DN " + newEntryDn); 567 } 568 childIndex = -(childIndex + 1); 569 } 570 else { 571 childIndex = parentNode.getChildCount(); 572 } 573 parentNode.setLeaf(false); 574 treeModel.insertNodeInto(childNode, parentNode, childIndex); 575 startRefreshNode(childNode, null, false); 576 return new TreePath(treeModel.getPathToRoot(childNode)); 577 } 578 579 580 /** 581 * Notify this controller that a entry has been deleted. 582 * The controller removes the corresponding node from the JTree and returns 583 * the TreePath of the parent node. 584 * @param nodeInfo the node to be deleted. 585 * @return the tree path associated with the parent of the deleted node. 586 */ 587 public TreePath notifyEntryDeleted(BrowserNodeInfo nodeInfo) { 588 BasicNode node = nodeInfo.getNode(); 589 if (node == rootNode) { 590 throw new IllegalArgumentException("Root node cannot be removed"); 591 } 592 593 /* If the parent is null... the node is no longer in the tree */ 594 final TreeNode parentNode = node.getParent(); 595 if (parentNode != null) { 596 removeOneNode(node); 597 return new TreePath(treeModel.getPathToRoot(parentNode)); 598 } 599 return null; 600 } 601 602 603 /** 604 * Notify this controller that an entry has changed. 605 * The controller starts refreshing the corresponding node. 606 * Child nodes are not refreshed. 607 * @param nodeInfo the node that changed. 608 */ 609 public void notifyEntryChanged(BrowserNodeInfo nodeInfo) { 610 BasicNode node = nodeInfo.getNode(); 611 startRefreshNode(node, null, false); 612 } 613 614 /** 615 * Notify this controller that authentication data have changed in the 616 * connection pool. 617 */ 618 @Override 619 public void notifyAuthDataChanged() { 620 notifyAuthDataChanged(null); 621 } 622 623 /** 624 * Notify this controller that authentication data have changed in the 625 * connection pool for the specified url. 626 * The controller starts refreshing the node which represent entries from the 627 * url. 628 * @param url the URL of the connection that changed. 629 */ 630 private void notifyAuthDataChanged(LDAPURL url) { 631 // TODO: temporary implementation 632 // we should refresh only nodes : 633 // - whose URL matches 'url' 634 // - whose errorType == ERROR_SOLVING_REFERRAL and 635 // errorArg == url 636 startRefreshReferralNodes(rootNode); 637 } 638 639 640 /** 641 * Start a refresh from the specified node. 642 * If some refresh are on-going on descendant nodes, they are stopped. 643 * If nodeInfo is null, refresh is started from the root. 644 * @param nodeInfo the node to be refreshed. 645 */ 646 public void startRefresh(BrowserNodeInfo nodeInfo) { 647 BasicNode node; 648 if (nodeInfo == null) { 649 node = rootNode; 650 } 651 else { 652 node = nodeInfo.getNode(); 653 } 654 stopRefreshNode(node); 655 startRefreshNode(node, null, true); 656 } 657 658 /** 659 * Stop the current refreshing. 660 * Nodes being expanded are collapsed. 661 */ 662 private void stopRefresh() { 663 stopRefreshNode(rootNode); 664 // TODO: refresh must be stopped in a clean state. 665 } 666 667 /** 668 * Start refreshing the whole tree from the specified node. 669 * We queue a refresh which: 670 * - updates the base node 671 * - is recursive 672 * @param node the parent node that will be refreshed. 673 * @param localEntry the local entry corresponding to the node. 674 * @param recursive whether the refresh must be executed recursively or not. 675 */ 676 private void startRefreshNode(BasicNode node, SearchResult localEntry, 677 boolean recursive) { 678 if (node == rootNode) { 679 // For the root node, readBaseEntry is meaningless. 680 if (recursive) { 681 // The root cannot be queued directly. 682 // We need to queue each child individually. 683 Enumeration<?> e = rootNode.children(); 684 while (e.hasMoreElements()) { 685 BasicNode child = (BasicNode)e.nextElement(); 686 startRefreshNode(child, null, true); 687 } 688 } 689 } 690 else { 691 refreshQueue.queue(new NodeRefresher(node, this, localEntry, recursive)); 692 // The task does not *see* suffixes. 693 // So we need to propagate the refresh on 694 // the sub-suffixes if any. 695 if (recursive && node instanceof SuffixNode) { 696 Enumeration<?> e = node.children(); 697 while (e.hasMoreElements()) { 698 BasicNode child = (BasicNode)e.nextElement(); 699 if (child instanceof SuffixNode) { 700 startRefreshNode(child, null, true); 701 } 702 } 703 } 704 } 705 } 706 707 708 709 710 /** 711 * Stop refreshing below this node. 712 * TODO: this method is very costly when applied to something else than the 713 * root node. 714 * @param node the node where the refresh must stop. 715 */ 716 private void stopRefreshNode(BasicNode node) { 717 if (node == rootNode) { 718 refreshQueue.cancelAll(); 719 } 720 else { 721 Enumeration<?> e = node.children(); 722 while (e.hasMoreElements()) { 723 BasicNode child = (BasicNode)e.nextElement(); 724 stopRefreshNode(child); 725 } 726 refreshQueue.cancelForNode(node); 727 } 728 } 729 730 731 732 /** 733 * Call startRefreshNode() on each referral node accessible from parentNode. 734 * @param parentNode the parent node. 735 */ 736 private void startRefreshReferralNodes(BasicNode parentNode) { 737 Enumeration<?> e = parentNode.children(); 738 while (e.hasMoreElements()) { 739 BasicNode child = (BasicNode)e.nextElement(); 740 if (child.getReferral() != null || child.getRemoteUrl() != null) { 741 startRefreshNode(child, null, true); 742 } 743 else { 744 startRefreshReferralNodes(child); 745 } 746 } 747 } 748 749 750 751 /** 752 * Remove all the children below parentNode *without changing the leaf state*. 753 * If specified, it keeps the SuffixNode and recurses on them. Inform the tree 754 * model. 755 * @param parentNode the parent node. 756 * @param keepSuffixes whether the suffixes should be kept or not. 757 */ 758 private void removeAllChildNodes(BasicNode parentNode, boolean keepSuffixes) { 759 for (int i = parentNode.getChildCount() - 1; i >= 0; i--) { 760 BasicNode child = (BasicNode)parentNode.getChildAt(i); 761 if (child instanceof SuffixNode && keepSuffixes) { 762 removeAllChildNodes(child, true); 763 child.setRefreshNeededOnExpansion(true); 764 } 765 else { 766 child.removeFromParent(); 767 } 768 } 769 treeModel.nodeStructureChanged(parentNode); 770 } 771 772 /** 773 * For BrowserController private use. When a node is expanded, refresh it 774 * if it needs it (to search the children for instance). 775 * @param event the tree expansion event. 776 */ 777 @Override 778 public void treeExpanded(TreeExpansionEvent event) { 779 if (!automaticallyExpandedNode) 780 { 781 automaticExpand = false; 782 } 783 BasicNode basicNode = (BasicNode)event.getPath().getLastPathComponent(); 784 if (basicNode.isRefreshNeededOnExpansion()) { 785 basicNode.setRefreshNeededOnExpansion(false); 786 // Starts a recursive refresh which does not read the base entry 787 startRefreshNode(basicNode, null, true); 788 } 789 } 790 791 792 /** 793 * For BrowserController private use. When a node is collapsed the refresh 794 * tasks on it are canceled. 795 * @param event the tree collapse event. 796 */ 797 @Override 798 public void treeCollapsed(TreeExpansionEvent event) { 799 Object node = event.getPath().getLastPathComponent(); 800 if (!(node instanceof RootNode)) { 801 BasicNode basicNode = (BasicNode)node; 802 stopRefreshNode(basicNode); 803 synchronized (refreshQueue) 804 { 805 boolean isWorking = refreshQueue.isWorking(basicNode); 806 refreshQueue.cancelForNode(basicNode); 807 if (isWorking) 808 { 809 basicNode.setRefreshNeededOnExpansion(true); 810 } 811 } 812 } 813 } 814 815 /** 816 * Sets which is the inspected node. This method simply marks the selected 817 * node in the tree so that it can have a different rendering. This is 818 * useful for instance when the right panel has a list of entries to which 819 * the menu action apply, to make a difference between the selected node in 820 * the tree (to which the action in the main menu will not apply) and the 821 * selected nodes in the right pane. 822 * @param node the selected node. 823 */ 824 public void setInspectedNode(BrowserNodeInfo node) { 825 BrowserCellRenderer renderer = (BrowserCellRenderer)tree.getCellRenderer(); 826 if (node == null) { 827 renderer.setInspectedNode(null); 828 } else { 829 renderer.setInspectedNode(node.getNode()); 830 } 831 } 832 833 834 /** 835 * Routines for the task classes 836 * ============================= 837 * 838 * Note that these routines only read controller variables. 839 * They do not alter any variable: so they can be safely 840 * called by task threads without synchronize clauses. 841 */ 842 843 844 /** 845 * The tree model created by the controller and assigned 846 * to the JTree. 847 * @return the tree model. 848 */ 849 public DefaultTreeModel getTreeModel() { 850 return treeModel; 851 } 852 853 /** 854 * Sets the filter that must be used by the browser controller to retrieve 855 * entries. 856 * @param filter the LDAP filter. 857 */ 858 public void setFilter(String filter) 859 { 860 this.filter = filter; 861 } 862 863 /** 864 * Returns the filter that is being used to search the entries. 865 * @return the filter that is being used to search the entries. 866 */ 867 public String getFilter() 868 { 869 return filter; 870 } 871 872 /** 873 * Returns the filter used to make a object base search. 874 * @return the filter used to make a object base search. 875 */ 876 String getObjectSearchFilter() 877 { 878 return ALL_OBJECTS_FILTER; 879 } 880 881 882 /** 883 * Return the LDAP search filter to use for searching child entries. 884 * If showContainerOnly is true, the filter will select only the 885 * container entries. If not, the filter will select all the children. 886 * @return the LDAP search filter to use for searching child entries. 887 */ 888 String getChildSearchFilter() { 889 String result; 890 if (showContainerOnly) { 891 if (followReferrals) { 892 /* In the case we are following referrals, we have to consider referrals 893 as nodes. 894 Suppose the following scenario: a referral points to a remote entry 895 that has children (node), BUT the referral entry in the local server 896 has no children. It won't be included in the filter and it won't 897 appear in the tree. But what we are displaying is the remote entry, 898 the result is that we have a NODE that does not appear in the tree and 899 so the user cannot browse it. 900 901 This has some side effects: 902 If we cannot follow the referral, a leaf will appear on the tree (as it 903 if were a node). 904 If the referral points to a leaf entry, a leaf will appear on the tree 905 (as if it were a node). 906 907 This is minor compared to the impossibility of browsing a subtree with 908 the NODE/LEAF layout. 909 */ 910 result = "(|(&(hasSubordinates=true)"+filter+")(objectClass=referral)"; 911 } else { 912 result = "(|(&(hasSubordinates=true)"+filter+")"; 913 } 914 for (String containerClass : containerClasses) 915 { 916 result += "(objectClass=" + containerClass + ")"; 917 } 918 result += ")"; 919 } 920 else { 921 result = filter; 922 } 923 924 return result; 925 } 926 927 928 929 930 /** 931 * Return the LDAP connection to reading the base entry of a node. 932 * @param node the node for which we want the LDAP connection. 933 * @throws NamingException if there is an error retrieving the connection. 934 * @return the LDAP connection to reading the base entry of a node. 935 */ 936 InitialLdapContext findConnectionForLocalEntry(BasicNode node) 937 throws NamingException { 938 return findConnectionForLocalEntry(node, isConfigurationNode(node)); 939 } 940 941 /** 942 * Return the LDAP connection to reading the base entry of a node. 943 * @param node the node for which we want toe LDAP connection. 944 * @param isConfigurationNode whether the node is a configuration node or not. 945 * @throws NamingException if there is an error retrieving the connection. 946 * @return the LDAP connection to reading the base entry of a node. 947 */ 948 private InitialLdapContext findConnectionForLocalEntry(BasicNode node, 949 boolean isConfigurationNode) throws NamingException 950 { 951 if (node == rootNode) { 952 return ctxConfiguration; 953 } 954 955 final BasicNode parent = (BasicNode) node.getParent(); 956 if (parent != null && parent != rootNode) 957 { 958 return findConnectionForDisplayedEntry(parent, isConfigurationNode); 959 } 960 return isConfigurationNode ? ctxConfiguration : ctxUserData; 961 } 962 963 /** 964 * Returns whether a given node is a configuration node or not. 965 * @param node the node to analyze. 966 * @return <CODE>true</CODE> if the node is a configuration node and 967 * <CODE>false</CODE> otherwise. 968 */ 969 public boolean isConfigurationNode(BasicNode node) 970 { 971 if (node instanceof RootNode) 972 { 973 return true; 974 } 975 if (node instanceof SuffixNode) 976 { 977 String dn = node.getDN(); 978 return Utilities.areDnsEqual(dn, ADSContext.getAdministrationSuffixDN()) || 979 Utilities.areDnsEqual(dn, ConfigConstants.DN_DEFAULT_SCHEMA_ROOT) || 980 Utilities.areDnsEqual(dn, ConfigConstants.DN_TASK_ROOT) || 981 Utilities.areDnsEqual(dn, ConfigConstants.DN_CONFIG_ROOT) || 982 Utilities.areDnsEqual(dn, ConfigConstants.DN_MONITOR_ROOT) || 983 Utilities.areDnsEqual(dn, ConfigConstants.DN_TRUST_STORE_ROOT) || 984 Utilities.areDnsEqual(dn, ConfigConstants.DN_BACKUP_ROOT) || 985 Utilities.areDnsEqual(dn, DN_EXTERNAL_CHANGELOG_ROOT); 986 } 987 else 988 { 989 BasicNode parentNode = (BasicNode)node.getParent(); 990 return isConfigurationNode(parentNode); 991 } 992 } 993 994 /** 995 * Return the LDAP connection to search the displayed entry (which can be the 996 * local or remote entry). 997 * @param node the node for which we want toe LDAP connection. 998 * @return the LDAP connection to search the displayed entry. 999 * @throws NamingException if there is an error retrieving the connection. 1000 */ 1001 public InitialLdapContext findConnectionForDisplayedEntry(BasicNode node) 1002 throws NamingException { 1003 return findConnectionForDisplayedEntry(node, isConfigurationNode(node)); 1004 } 1005 1006 1007 /** 1008 * Return the LDAP connection to search the displayed entry (which can be the 1009 * local or remote entry). 1010 * @param node the node for which we want toe LDAP connection. 1011 * @param isConfigurationNode whether the node is a configuration node or not. 1012 * @return the LDAP connection to search the displayed entry. 1013 * @throws NamingException if there is an error retrieving the connection. 1014 */ 1015 private InitialLdapContext findConnectionForDisplayedEntry(BasicNode node, 1016 boolean isConfigurationNode) throws NamingException { 1017 if (followReferrals && node.getRemoteUrl() != null) 1018 { 1019 return connectionPool.getConnection(node.getRemoteUrl()); 1020 } 1021 return findConnectionForLocalEntry(node, isConfigurationNode); 1022 } 1023 1024 1025 1026 /** 1027 * Release a connection returned by selectConnectionForChildEntries() or 1028 * selectConnectionForBaseEntry(). 1029 * @param ctx the connection to be released. 1030 */ 1031 void releaseLDAPConnection(InitialLdapContext ctx) { 1032 if (ctx != this.ctxConfiguration && ctx != this.ctxUserData) 1033 { 1034 // Thus it comes from the connection pool 1035 connectionPool.releaseConnection(ctx); 1036 } 1037 } 1038 1039 1040 /** 1041 * Returns the local entry URL for a given node. 1042 * @param node the node. 1043 * @return the local entry URL for a given node. 1044 */ 1045 LDAPURL findUrlForLocalEntry(BasicNode node) { 1046 if (node == rootNode) { 1047 return LDAPConnectionPool.makeLDAPUrl(ctxConfiguration, ""); 1048 } 1049 final BasicNode parent = (BasicNode) node.getParent(); 1050 if (parent != null) 1051 { 1052 final LDAPURL parentUrl = findUrlForDisplayedEntry(parent); 1053 return LDAPConnectionPool.makeLDAPUrl(parentUrl, node.getDN()); 1054 } 1055 return LDAPConnectionPool.makeLDAPUrl(ctxConfiguration, node.getDN()); 1056 } 1057 1058 1059 /** 1060 * Returns the displayed entry URL for a given node. 1061 * @param node the node. 1062 * @return the displayed entry URL for a given node. 1063 */ 1064 private LDAPURL findUrlForDisplayedEntry(BasicNode node) 1065 { 1066 if (followReferrals && node.getRemoteUrl() != null) { 1067 return node.getRemoteUrl(); 1068 } 1069 return findUrlForLocalEntry(node); 1070 } 1071 1072 1073 /** 1074 * Returns the DN to use for searching children of a given node. 1075 * In most cases, it's node.getDN(). However if node has referral data 1076 * and _followReferrals is true, the result is calculated from the 1077 * referral resolution. 1078 * 1079 * @param node the node. 1080 * @return the DN to use for searching children of a given node. 1081 */ 1082 String findBaseDNForChildEntries(BasicNode node) { 1083 if (followReferrals && node.getRemoteUrl() != null) { 1084 return node.getRemoteUrl().getRawBaseDN(); 1085 } 1086 return node.getDN(); 1087 } 1088 1089 1090 1091 /** 1092 * Tells whether a node is displaying a remote entry. 1093 * @param node the node. 1094 * @return <CODE>true</CODE> if the node displays a remote entry and 1095 * <CODE>false</CODE> otherwise. 1096 */ 1097 private boolean isDisplayedEntryRemote(BasicNode node) { 1098 if (followReferrals) { 1099 if (node == rootNode) { 1100 return false; 1101 } 1102 if (node.getRemoteUrl() != null) { 1103 return true; 1104 } 1105 final BasicNode parent = (BasicNode)node.getParent(); 1106 if (parent != null) { 1107 return isDisplayedEntryRemote(parent); 1108 } 1109 } 1110 return false; 1111 } 1112 1113 1114 /** 1115 * Returns the list of attributes for the red search. 1116 * @return the list of attributes for the red search. 1117 */ 1118 String[] getAttrsForRedSearch() { 1119 ArrayList<String> v = new ArrayList<>(); 1120 1121 v.add(OBJECTCLASS_ATTRIBUTE_TYPE_NAME); 1122 v.add(NUMSUBORDINATES_ATTR); 1123 v.add(HASSUBORDINATES_ATTR); 1124 v.add(ATTR_REFERRAL_URL); 1125 if ((displayFlags & DISPLAY_ACI_COUNT) != 0) { 1126 v.add(ACI_ATTR); 1127 } 1128 if (!RDN_ATTRIBUTE.equals(displayAttribute)) { 1129 v.add(displayAttribute); 1130 } 1131 1132 return v.toArray(new String[v.size()]); 1133 } 1134 1135 /** 1136 * Returns the list of attributes for the black search. 1137 * @return the list of attributes for the black search. 1138 */ 1139 String[] getAttrsForBlackSearch() { 1140 if (!RDN_ATTRIBUTE.equals(displayAttribute)) { 1141 return new String[] { 1142 OBJECTCLASS_ATTRIBUTE_TYPE_NAME, 1143 NUMSUBORDINATES_ATTR, 1144 HASSUBORDINATES_ATTR, 1145 ATTR_REFERRAL_URL, 1146 ACI_ATTR, 1147 displayAttribute}; 1148 } else { 1149 return new String[] { 1150 OBJECTCLASS_ATTRIBUTE_TYPE_NAME, 1151 NUMSUBORDINATES_ATTR, 1152 HASSUBORDINATES_ATTR, 1153 ATTR_REFERRAL_URL, 1154 ACI_ATTR 1155 }; 1156 } 1157 } 1158 1159 /** 1160 * Returns the basic search controls. 1161 * @return the basic search controls. 1162 */ 1163 SearchControls getBasicSearchControls() { 1164 SearchControls searchControls = new SearchControls(); 1165 searchControls.setCountLimit(maxChildren); 1166 return searchControls; 1167 } 1168 1169 /** 1170 * Returns the request controls to search user data. 1171 * @return the request controls to search user data. 1172 */ 1173 private Control[] getRequestControls() 1174 { 1175 Control ctls[]; 1176 if (followReferrals) 1177 { 1178 ctls = new Control[sorted ? 2 : 1]; 1179 } 1180 else 1181 { 1182 ctls = new Control[sorted ? 1 : 0]; 1183 } 1184 if (sorted) 1185 { 1186 SortKey[] keys = new SortKey[SORT_ATTRIBUTES.length]; 1187 for (int i=0; i<keys.length; i++) { 1188 keys[i] = new SortKey(SORT_ATTRIBUTES[i]); 1189 } 1190 try 1191 { 1192 ctls[0] = new SortControl(keys, false); 1193 } 1194 catch (IOException ioe) 1195 { 1196 // Bug 1197 throw new RuntimeException("Unexpected encoding exception: "+ioe, 1198 ioe); 1199 } 1200 } 1201 if (followReferrals) 1202 { 1203 ctls[ctls.length - 1] = new ManageReferralControl(false); 1204 } 1205 return ctls; 1206 } 1207 1208 /** 1209 * Returns the request controls to search configuration data. 1210 * @return the request controls to search configuration data. 1211 */ 1212 private Control[] getConfigurationRequestControls() 1213 { 1214 return getRequestControls(); 1215 } 1216 1217 1218 /** 1219 * Callbacks invoked by task classes 1220 * ================================= 1221 * 1222 * The routines below are invoked by the task classes; they 1223 * update the nodes and the tree model. 1224 * 1225 * To ensure the consistency of the tree model, these routines 1226 * are not invoked directly by the task classes: they are 1227 * invoked using SwingUtilities.invokeAndWait() (each of the 1228 * methods XXX() below has a matching wrapper invokeXXX()). 1229 * 1230 */ 1231 1232 /** 1233 * Invoked when the refresh task has finished the red operation. 1234 * It has read the attributes of the base entry ; the result of the 1235 * operation is: 1236 * - an LDAPEntry if successful 1237 * - an Exception if failed 1238 * @param task the task that progressed. 1239 * @param oldState the previous state of the task. 1240 * @param newState the new state of the task. 1241 * @throws NamingException if there is an error reading entries. 1242 */ 1243 private void refreshTaskDidProgress(NodeRefresher task, 1244 NodeRefresher.State oldState, 1245 NodeRefresher.State newState) throws NamingException { 1246 BasicNode node = task.getNode(); 1247 boolean nodeChanged = false; 1248 1249 //task.dump(); 1250 1251 // Manage events 1252 if (oldState == NodeRefresher.State.QUEUED) { 1253 checkUpdateEvent(true); 1254 } 1255 if (task.isInFinalState()) { 1256 checkUpdateEvent(false); 1257 } 1258 1259 if (newState == NodeRefresher.State.FAILED) { 1260 // In case of NameNotFoundException, we simply remove the node from the 1261 // tree. 1262 // Except when it's due a to referral resolution: we keep the node 1263 // in order the user can fix the referral. 1264 if (isNameNotFoundException(task.getException()) 1265 && oldState != NodeRefresher.State.SOLVING_REFERRAL) { 1266 removeOneNode(node); 1267 } 1268 else { 1269 if (oldState == NodeRefresher.State.SOLVING_REFERRAL) 1270 { 1271 node.setRemoteUrl(task.getRemoteUrl()); 1272 if (task.getRemoteEntry() != null) 1273 { 1274 /* This is the case when there are multiple hops in the referral 1275 and so we have a remote referral entry but not the entry that it 1276 points to */ 1277 updateNodeRendering(node, task.getRemoteEntry()); 1278 } 1279 /* It is a referral and we try to follow referrals. 1280 We remove its children (that are supposed to be 1281 entries on the remote server). 1282 If this referral entry has children locally (even if this goes 1283 against the recommendation of the standards) these children will 1284 NOT be displayed. */ 1285 1286 node.setLeaf(true); 1287 removeAllChildNodes(node, true /* Keep suffixes */); 1288 } 1289 node.setError(new BasicNodeError(oldState, task.getException(), 1290 task.getExceptionArg())); 1291 nodeChanged = updateNodeRendering(node, task.getDisplayedEntry()); 1292 } 1293 } 1294 else if (newState == NodeRefresher.State.CANCELLED || 1295 newState == NodeRefresher.State.INTERRUPTED) { 1296 1297 // Let's collapse task.getNode() 1298 tree.collapsePath(new TreePath(treeModel.getPathToRoot(node))); 1299 1300 // TODO: should we reflect this situation visually ? 1301 } 1302 else { 1303 1304 if (oldState != NodeRefresher.State.SEARCHING_CHILDREN 1305 && newState == NodeRefresher.State.SEARCHING_CHILDREN) { 1306 // The children search is going to start 1307 if (canDoDifferentialUpdate(task)) { 1308 Enumeration<?> e = node.children(); 1309 while (e.hasMoreElements()) { 1310 BasicNode child = (BasicNode)e.nextElement(); 1311 child.setObsolete(true); 1312 } 1313 } 1314 else { 1315 removeAllChildNodes(node, true /* Keep suffixes */); 1316 } 1317 } 1318 1319 if (oldState == NodeRefresher.State.READING_LOCAL_ENTRY) { 1320 /* The task is going to try to solve the referral if there's one. 1321 If succeeds we will update the remote url. Set it to null for 1322 the case when there was a referral and it has been deleted */ 1323 node.setRemoteUrl((String)null); 1324 SearchResult localEntry = task.getLocalEntry(); 1325 nodeChanged = updateNodeRendering(node, localEntry); 1326 } 1327 else if (oldState == NodeRefresher.State.SOLVING_REFERRAL) { 1328 node.setRemoteUrl(task.getRemoteUrl()); 1329 updateNodeRendering(node, task.getRemoteEntry()); 1330 nodeChanged = true; 1331 } 1332 else if (oldState == NodeRefresher.State.DETECTING_CHILDREN) { 1333 if (node.isLeaf() != task.isLeafNode()) { 1334 node.setLeaf(task.isLeafNode()); 1335 updateNodeRendering(node, task.getDisplayedEntry()); 1336 nodeChanged = true; 1337 if (node.isLeaf()) { 1338 /* We didn't detect any child: remove the previously existing 1339 * ones */ 1340 removeAllChildNodes(node, false /* Remove suffixes */); 1341 } 1342 } 1343 } 1344 else if (oldState == NodeRefresher.State.SEARCHING_CHILDREN) { 1345 1346 updateChildNodes(task); 1347 if (newState == NodeRefresher.State.FINISHED) { 1348 // The children search is finished 1349 if (canDoDifferentialUpdate(task)) { 1350 // Remove obsolete child nodes 1351 // Note: we scan in the reverse order to preserve indexes 1352 for (int i = node.getChildCount()-1; i >= 0; i--) { 1353 BasicNode child = (BasicNode)node.getChildAt(i); 1354 if (child.isObsolete()) { 1355 removeOneNode(child); 1356 } 1357 } 1358 } 1359 // The node may have become a leaf. 1360 if (node.getChildCount() == 0) { 1361 node.setLeaf(true); 1362 updateNodeRendering(node, task.getDisplayedEntry()); 1363 nodeChanged = true; 1364 } 1365 } 1366 if (node.isSizeLimitReached()) 1367 { 1368 fireEvent(BrowserEvent.Type.SIZE_LIMIT_REACHED); 1369 } 1370 } 1371 1372 if (newState == NodeRefresher.State.FINISHED && node.getError() != null) { 1373 node.setError(null); 1374 nodeChanged = updateNodeRendering(node, task.getDisplayedEntry()); 1375 } 1376 } 1377 1378 1379 if (nodeChanged) { 1380 treeModel.nodeChanged(task.getNode()); 1381 } 1382 1383 if (node.isLeaf() && node.getChildCount() >= 1) { 1384 throw new RuntimeException("Inconsistent node: " + node.getDN()); 1385 } 1386 } 1387 1388 1389 /** 1390 * Commodity method that calls the method refreshTaskDidProgress in the event 1391 * thread. 1392 * @param task the task that progressed. 1393 * @param oldState the previous state of the task. 1394 * @param newState the new state of the task. 1395 * @throws InterruptedException if an errors occurs invoking the method. 1396 */ 1397 void invokeRefreshTaskDidProgress(final NodeRefresher task, 1398 final NodeRefresher.State oldState, 1399 final NodeRefresher.State newState) 1400 throws InterruptedException { 1401 Runnable r = new Runnable() { 1402 @Override 1403 public void run() { 1404 try { 1405 refreshTaskDidProgress(task, oldState, newState); 1406 } 1407 catch(Throwable t) 1408 { 1409 LOG.log(Level.SEVERE, "Error calling refreshTaskDidProgress: "+t, t); 1410 } 1411 } 1412 }; 1413 swingInvoke(r); 1414 } 1415 1416 1417 1418 /** 1419 * Core routines shared by the callbacks above 1420 * =========================================== 1421 */ 1422 1423 /** 1424 * Updates the child nodes for a given task. 1425 * @param task the task. 1426 * @throws NamingException if an error occurs. 1427 */ 1428 private void updateChildNodes(NodeRefresher task) throws NamingException { 1429 BasicNode parent = task.getNode(); 1430 ArrayList<Integer> insertIndex = new ArrayList<>(); 1431 ArrayList<Integer> changedIndex = new ArrayList<>(); 1432 boolean differential = canDoDifferentialUpdate(task); 1433 1434 // NUMSUBORDINATE HACK 1435 // To avoid testing each child to the hacker, 1436 // we verify here if the parent node is parent of 1437 // any entry listed in the hacker. 1438 // In most case, the doNotTrust flag will false and 1439 // no overhead will be caused in the child loop. 1440 LDAPURL parentUrl = findUrlForDisplayedEntry(parent); 1441 boolean doNotTrust = numSubordinateHacker.containsChildrenOf(parentUrl); 1442 1443 // Walk through the entries 1444 for (SearchResult entry : task.getChildEntries()) 1445 { 1446 BasicNode child; 1447 1448 // Search a child node matching the DN of the entry 1449 int index; 1450 if (differential) { 1451// System.out.println("Differential mode -> starting to search"); 1452 index = findChildNode(parent, entry.getName()); 1453// System.out.println("Differential mode -> ending to search"); 1454 } 1455 else { 1456 index = - (parent.getChildCount() + 1); 1457 } 1458 1459 // If no node matches, we create a new node 1460 if (index < 0) { 1461 // -(index + 1) is the location where to insert the new node 1462 index = -(index + 1); 1463 child = new BasicNode(entry.getName()); 1464 parent.insert(child, index); 1465 updateNodeRendering(child, entry); 1466 insertIndex.add(index); 1467// System.out.println("Inserted " + child.getDN() + " at " + index); 1468 } 1469 else { // Else we update the existing one 1470 child = (BasicNode)parent.getChildAt(index); 1471 if (updateNodeRendering(child, entry)) { 1472 changedIndex.add(index); 1473 } 1474 // The node is no longer obsolete 1475 child.setObsolete(false); 1476 } 1477 1478 // NUMSUBORDINATE HACK 1479 // Let's see if child has subordinates or not. 1480 // Thanks to slapd, we cannot always trust the numSubOrdinates attribute. 1481 // If the child entry's DN is found in the hacker's list, then we ignore 1482 // the numSubordinate attribute... :(( 1483 boolean hasNoSubOrdinates; 1484 if (!child.hasSubOrdinates() && doNotTrust) { 1485 hasNoSubOrdinates = !numSubordinateHacker.contains( 1486 findUrlForDisplayedEntry(child)); 1487 } 1488 else { 1489 hasNoSubOrdinates = !child.hasSubOrdinates(); 1490 } 1491 1492 1493 1494 // Propagate the refresh 1495 // Note: logically we should unconditionally call: 1496 // startRefreshNode(child, false, true); 1497 // 1498 // However doing that saturates refreshQueue 1499 // with many nodes. And, by design, RefreshTask 1500 // won't do anything on a node if: 1501 // - this node has no subordinates 1502 // - *and* this node has no referral data 1503 // So we test these conditions here and 1504 // skip the call to startRefreshNode() if 1505 // possible. 1506 // 1507 // The exception to this is the case where the 1508 // node had children (in the tree). In this case 1509 // we force the refresh. See bug 5015115 1510 // 1511 if (!hasNoSubOrdinates 1512 || child.getReferral() != null 1513 || child.getChildCount() > 0) { 1514 startRefreshNode(child, entry, true); 1515 } 1516 } 1517 1518 1519 // Inform the tree model that we have created some new nodes 1520 if (insertIndex.size() >= 1) { 1521 treeModel.nodesWereInserted(parent, intArrayFromCollection(insertIndex)); 1522 } 1523 if (changedIndex.size() >= 1) { 1524 treeModel.nodesChanged(parent, intArrayFromCollection(changedIndex)); 1525 } 1526 } 1527 1528 1529 1530 /** 1531 * Tells whether a differential update can be made in the provided task. 1532 * @param task the task. 1533 * @return <CODE>true</CODE> if a differential update can be made and 1534 * <CODE>false</CODE> otherwise. 1535 */ 1536 private boolean canDoDifferentialUpdate(NodeRefresher task) { 1537 return task.getNode().getChildCount() >= 1 1538 && task.getNode().getNumSubOrdinates() <= 100; 1539 } 1540 1541 1542 /** 1543 * Recompute the rendering props of a node (text, style, icon) depending on. 1544 * - the state of this node 1545 * - the LDAPEntry displayed by this node 1546 * @param node the node to be rendered. 1547 * @param entry the search result for the entry that the node represents. 1548 */ 1549 private boolean updateNodeRendering(BasicNode node, SearchResult entry) 1550 throws NamingException { 1551 if (entry != null) { 1552 node.setNumSubOrdinates(getNumSubOrdinates(entry)); 1553 node.setHasSubOrdinates( 1554 node.getNumSubOrdinates() > 0 || getHasSubOrdinates(entry)); 1555 node.setReferral(getReferral(entry)); 1556 Set<String> ocValues = ConnectionUtils.getValues(entry, 1557 OBJECTCLASS_ATTRIBUTE_TYPE_NAME); 1558 if (ocValues != null) { 1559 node.setObjectClassValues(ocValues.toArray(new String[ocValues.size()])); 1560 } 1561 } 1562 1563 int aciCount = getAciCount(entry); 1564 Icon newIcon = getNewIcon(node, entry); 1565 1566 // Construct the icon text according the dn, the aci count... 1567 StringBuilder sb2 = new StringBuilder(); 1568 if (aciCount >= 1) { 1569 sb2.append(aciCount); 1570 sb2.append(" aci"); 1571 if (aciCount != 1) { 1572 sb2.append("s"); 1573 } 1574 } 1575 1576 StringBuilder sb1 = new StringBuilder(); 1577 if (node instanceof SuffixNode) { 1578 if (entry != null) { 1579 sb1.append(entry.getName()); 1580 } 1581 } else { 1582 boolean useRdn = true; 1583 if (!RDN_ATTRIBUTE.equals(displayAttribute) && entry != null) { 1584 String value = ConnectionUtils.getFirstValue(entry,displayAttribute); 1585 if (value != null) { 1586 if (showAttributeName) { 1587 value = displayAttribute+"="+value; 1588 } 1589 sb1.append(value); 1590 useRdn = false; 1591 } 1592 } 1593 1594 if (useRdn) { 1595 String rdn; 1596 if (followReferrals && node.getRemoteUrl() != null) { 1597 if (showAttributeName) { 1598 rdn = node.getRemoteRDNWithAttributeName(); 1599 } else { 1600 rdn = node.getRemoteRDN(); 1601 } 1602 } 1603 else { 1604 if (showAttributeName) { 1605 rdn = node.getRDNWithAttributeName(); 1606 } else { 1607 rdn = node.getRDN(); 1608 } 1609 } 1610 sb1.append(rdn); 1611 } 1612 } 1613 if (sb2.length() >= 1) { 1614 sb1.append(" ("); 1615 sb1.append(sb2); 1616 sb1.append(")"); 1617 } 1618 String newDisplayName = sb1.toString(); 1619 1620 // Select the font style according referral 1621 int newStyle = 0; 1622 if (isDisplayedEntryRemote(node)) { 1623 newStyle |= Font.ITALIC; 1624 } 1625 1626 // Determine if the rendering needs to be updated 1627 boolean changed = 1628 node.getIcon() != newIcon 1629 || !node.getDisplayName().equals(newDisplayName) 1630 || node.getFontStyle() != newStyle; 1631 if (changed) { 1632 node.setIcon(newIcon); 1633 node.setDisplayName(newDisplayName); 1634 node.setFontStyle(newStyle); 1635 } 1636 return changed; 1637 } 1638 1639 private int getAciCount(SearchResult entry) throws NamingException 1640 { 1641 if ((displayFlags & DISPLAY_ACI_COUNT) != 0 && entry != null) { 1642 Set<String> aciValues = ConnectionUtils.getValues(entry, "aci"); 1643 if (aciValues != null) { 1644 return aciValues.size(); 1645 } 1646 } 1647 return 0; 1648 } 1649 1650 1651 private Icon getNewIcon(BasicNode node, SearchResult entry) 1652 throws NamingException 1653 { 1654 // Select the icon according the objectClass,... 1655 int modifiers = 0; 1656 if (node.isLeaf() && !node.hasSubOrdinates()) { 1657 modifiers |= IconPool.MODIFIER_LEAF; 1658 } 1659 if (node.getReferral() != null) { 1660 modifiers |= IconPool.MODIFIER_REFERRAL; 1661 } 1662 if (node.getError() != null) { 1663 final Exception ex = node.getError().getException(); 1664 if (ex != null) 1665 { 1666 LOG.log(Level.SEVERE, "node has error: " + ex, ex); 1667 } 1668 modifiers |= IconPool.MODIFIER_ERROR; 1669 } 1670 1671 SortedSet<String> objectClasses = new TreeSet<>(); 1672 if (entry != null) { 1673 Set<String> ocs = ConnectionUtils.getValues(entry, "objectClass"); 1674 if (ocs != null) 1675 { 1676 objectClasses.addAll(ocs); 1677 } 1678 } 1679 1680 if (node instanceof SuffixNode) 1681 { 1682 return iconPool.getSuffixIcon(); 1683 } 1684 return iconPool.getIcon(objectClasses, modifiers); 1685 } 1686 1687 /** 1688 * Find a child node matching a given DN. 1689 * 1690 * result >= 0 result is the index of the node matching childDn. 1691 * result < 0 -(result + 1) is the index at which the new node must be 1692 * inserted. 1693 * @param parent the parent node of the node that is being searched. 1694 * @param childDn the DN of the entry that is being searched. 1695 * @return the index of the node matching childDn. 1696 */ 1697 public int findChildNode(BasicNode parent, String childDn) { 1698 int childCount = parent.getChildCount(); 1699 int i = 0; 1700 while (i < childCount 1701 && !childDn.equals(((BasicNode)parent.getChildAt(i)).getDN())) { 1702 i++; 1703 } 1704 if (i >= childCount) { // Not found 1705 i = -(childCount + 1); 1706 } 1707 return i; 1708 } 1709 1710 /** 1711 * Remove a single node from the tree model. 1712 * It takes care to cancel all the tasks associated to this node. 1713 * @param node the node to be removed. 1714 */ 1715 private void removeOneNode(BasicNode node) { 1716 stopRefreshNode(node); 1717 treeModel.removeNodeFromParent(node); 1718 } 1719 1720 1721 /** 1722 * BrowserEvent management 1723 * ======================= 1724 * 1725 * This method computes the total size of the queues, 1726 * compares this value with the last computed and 1727 * decides if an update event should be fired or not. 1728 * 1729 * It's invoked by task classes through SwingUtilities.invokeLater() 1730 * (see the wrapper below). That means the event handling routine 1731 * (processBrowserEvent) is executed in the event thread. 1732 * @param taskIsStarting whether the task is starting or not. 1733 */ 1734 private void checkUpdateEvent(boolean taskIsStarting) { 1735 int newSize = refreshQueue.size(); 1736 if (!taskIsStarting) { 1737 newSize = newSize - 1; 1738 } 1739 if (newSize != queueTotalSize) { 1740 if (queueTotalSize == 0 && newSize >= 1) { 1741 fireEvent(BrowserEvent.Type.UPDATE_START); 1742 } 1743 else if (queueTotalSize >= 1 && newSize == 0) { 1744 fireEvent(BrowserEvent.Type.UPDATE_END); 1745 } 1746 queueTotalSize = newSize; 1747 } 1748 } 1749 1750 /** 1751 * Returns the size of the queue containing the different tasks. It can be 1752 * used to know if there are search operations ongoing. 1753 * @return the number of RefreshTask operations ongoing (or waiting to start). 1754 */ 1755 public int getQueueSize() 1756 { 1757 return refreshQueue.size(); 1758 } 1759 1760 1761 /** 1762 * Fires a BrowserEvent. 1763 * @param type the type of the event. 1764 */ 1765 private void fireEvent(BrowserEvent.Type type) { 1766 BrowserEvent event = new BrowserEvent(this, type); 1767 for (BrowserEventListener listener : listeners) 1768 { 1769 listener.processBrowserEvent(event); 1770 } 1771 } 1772 1773 1774 /** 1775 * Miscellaneous private routines 1776 * ============================== 1777 */ 1778 1779 1780 /** 1781 * Find a SuffixNode in the tree model. 1782 * @param suffixDn the dn of the suffix node. 1783 * @param suffixNode the node from which we start searching. 1784 * @return the SuffixNode associated with the provided DN. <CODE>null</CODE> 1785 * if nothing is found. 1786 * @throws IllegalArgumentException if a node with the given dn exists but 1787 * is not a suffix node. 1788 */ 1789 private SuffixNode findSuffixNode(String suffixDn, SuffixNode suffixNode) 1790 throws IllegalArgumentException 1791 { 1792 if (Utilities.areDnsEqual(suffixNode.getDN(), suffixDn)) { 1793 return suffixNode; 1794 } 1795 1796 int childCount = suffixNode.getChildCount(); 1797 if (childCount == 0) 1798 { 1799 return null; 1800 } 1801 BasicNode child; 1802 int i = 0; 1803 boolean found = false; 1804 do 1805 { 1806 child = (BasicNode) suffixNode.getChildAt(i); 1807 if (Utilities.areDnsEqual(child.getDN(), suffixDn)) 1808 { 1809 found = true; 1810 } 1811 i++; 1812 } 1813 while (i < childCount && !found); 1814 1815 if (!found) 1816 { 1817 return null; 1818 } 1819 if (child instanceof SuffixNode) 1820 { 1821 return (SuffixNode) child; 1822 } 1823 1824 // A node matches suffixDn however it's not a suffix node. 1825 // There's a bug in the caller. 1826 throw new IllegalArgumentException(suffixDn + " is not a suffix node"); 1827 } 1828 1829 1830 1831 /** 1832 * Return <CODE>true</CODE> if x is a non <code>null</code> 1833 * NameNotFoundException. 1834 * @return <CODE>true</CODE> if x is a non <code>null</code> 1835 * NameNotFoundException. 1836 */ 1837 private boolean isNameNotFoundException(Object x) { 1838 return x instanceof NameNotFoundException; 1839 } 1840 1841 1842 1843 /** 1844 * Get the value of the numSubordinates attribute. 1845 * If numSubordinates is not present, returns 0. 1846 * @param entry the entry to analyze. 1847 * @throws NamingException if an error occurs. 1848 * @return the value of the numSubordinates attribute. 0 if the attribute 1849 * could not be found. 1850 */ 1851 private static int getNumSubOrdinates(SearchResult entry) throws NamingException 1852 { 1853 return toInt(ConnectionUtils.getFirstValue(entry, NUMSUBORDINATES_ATTR)); 1854 } 1855 1856 /** 1857 * Returns whether the entry has subordinates or not. It uses an algorithm 1858 * based in hasSubordinates and numSubordinates attributes. 1859 * @param entry the entry to analyze. 1860 * @throws NamingException if an error occurs. 1861 * @return {@code true} if the entry has subordinates according to the values 1862 * of hasSubordinates and numSubordinates, returns {@code false} if none of 1863 * the attributes could be found. 1864 */ 1865 public static boolean getHasSubOrdinates(SearchResult entry) 1866 throws NamingException 1867 { 1868 String v = ConnectionUtils.getFirstValue(entry, HASSUBORDINATES_ATTR); 1869 if (v != null) { 1870 return "true".equalsIgnoreCase(v); 1871 } 1872 return getNumSubOrdinates(entry) > 0; 1873 } 1874 1875 /** 1876 * Get the value of the numSubordinates attribute. 1877 * If numSubordinates is not present, returns 0. 1878 * @param entry the entry to analyze. 1879 * @return the value of the numSubordinates attribute. 0 if the attribute 1880 * could not be found. 1881 */ 1882 private static int getNumSubOrdinates(CustomSearchResult entry) 1883 { 1884 List<Object> vs = entry.getAttributeValues(NUMSUBORDINATES_ATTR); 1885 String v = null; 1886 if (vs != null && !vs.isEmpty()) 1887 { 1888 v = vs.get(0).toString(); 1889 } 1890 return toInt(v); 1891 } 1892 1893 1894 private static int toInt(String v) 1895 { 1896 if (v == null) 1897 { 1898 return 0; 1899 } 1900 try 1901 { 1902 return Integer.parseInt(v); 1903 } 1904 catch (NumberFormatException x) 1905 { 1906 return 0; 1907 } 1908 } 1909 1910 /** 1911 * Returns whether the entry has subordinates or not. It uses an algorithm 1912 * based in hasSubordinates and numSubordinates attributes. 1913 * @param entry the entry to analyze. 1914 * @return {@code true} if the entry has subordinates according to the values 1915 * of hasSubordinates and numSubordinates, returns {@code false} if none of 1916 * the attributes could be found. 1917 */ 1918 public static boolean getHasSubOrdinates(CustomSearchResult entry) 1919 { 1920 List<Object> vs = entry.getAttributeValues(HASSUBORDINATES_ATTR); 1921 String v = null; 1922 if (vs != null && !vs.isEmpty()) 1923 { 1924 v = vs.get(0).toString(); 1925 } 1926 if (v != null) 1927 { 1928 return "true".equalsIgnoreCase(v); 1929 } 1930 return getNumSubOrdinates(entry) > 0; 1931 } 1932 1933 1934 /** 1935 * Returns the value of the 'ref' attribute. 1936 * <CODE>null</CODE> if the attribute is not present. 1937 * @param entry the entry to analyze. 1938 * @throws NamingException if an error occurs. 1939 * @return the value of the ref attribute. <CODE>null</CODE> if the attribute 1940 * could not be found. 1941 */ 1942 public static String[] getReferral(SearchResult entry) throws NamingException 1943 { 1944 String[] result = null; 1945 Set<String> values = ConnectionUtils.getValues(entry, 1946 OBJECTCLASS_ATTRIBUTE_TYPE_NAME); 1947 if (values != null) 1948 { 1949 for (String value : values) 1950 { 1951 boolean isReferral = "referral".equalsIgnoreCase(value); 1952 if (isReferral) 1953 { 1954 Set<String> refValues = ConnectionUtils.getValues(entry, 1955 ATTR_REFERRAL_URL); 1956 if (refValues != null) 1957 { 1958 result = new String[refValues.size()]; 1959 refValues.toArray(result); 1960 } 1961 break; 1962 } 1963 } 1964 } 1965 return result; 1966 } 1967 1968 1969 /** 1970 * Returns true if the node is expanded. 1971 * @param node the node to analyze. 1972 * @return <CODE>true</CODE> if the node is expanded and <CODE>false</CODE> 1973 * otherwise. 1974 */ 1975 public boolean nodeIsExpanded(BasicNode node) { 1976 TreePath tp = new TreePath(treeModel.getPathToRoot(node)); 1977 return tree.isExpanded(tp); 1978 } 1979 1980 /** 1981 * Expands node. Must be run from the event thread. This is called 1982 * when the node is automatically expanded. 1983 * @param node the node to expand. 1984 */ 1985 public void expandNode(BasicNode node) { 1986 automaticallyExpandedNode = true; 1987 TreePath tp = new TreePath(treeModel.getPathToRoot(node)); 1988 tree.expandPath(tp); 1989 tree.fireTreeExpanded(tp); 1990 automaticallyExpandedNode = false; 1991 } 1992 1993 1994 1995 /** 1996 * Collection utilities 1997 */ 1998 /** 1999 * Returns an array of integer from a Collection of Integer objects. 2000 * @param v the Collection of Integer objects. 2001 * @return an array of int from a Collection of Integer objects. 2002 */ 2003 private static int[] intArrayFromCollection(Collection<Integer> v) { 2004 int[] result = new int[v.size()]; 2005 int i = 0; 2006 for (Integer value : v) 2007 { 2008 result[i] = value; 2009 i++; 2010 } 2011 return result; 2012 } 2013 2014 2015 /** 2016 * For debugging purpose: allows to switch easily 2017 * between invokeLater() and invokeAndWait() for 2018 * experimentation... 2019 * @param r the runnable to be invoked. 2020 * @throws InterruptedException if there is an error invoking SwingUtilities. 2021 */ 2022 private static void swingInvoke(Runnable r) throws InterruptedException { 2023 try { 2024 SwingUtilities.invokeAndWait(r); 2025 } 2026 catch(InterruptedException x) { 2027 throw x; 2028 } 2029 catch(InvocationTargetException x) { 2030 // Probably a very big trouble... 2031 x.printStackTrace(); 2032 } 2033 } 2034 2035 2036 /** 2037 * The default implementation of the BrowserNodeInfo interface. 2038 */ 2039 private class BrowserNodeInfoImpl implements BrowserNodeInfo 2040 { 2041 private BasicNode node; 2042 private LDAPURL url; 2043 private boolean isRemote; 2044 private boolean isSuffix; 2045 private boolean isRootNode; 2046 private String[] referral; 2047 private int numSubOrdinates; 2048 private boolean hasSubOrdinates; 2049 private int errorType; 2050 private Exception errorException; 2051 private Object errorArg; 2052 private String[] objectClassValues; 2053 private String toString; 2054 2055 /** 2056 * The constructor of this object. 2057 * @param node the node in the tree that is used. 2058 */ 2059 public BrowserNodeInfoImpl(BasicNode node) { 2060 this.node = node; 2061 url = findUrlForDisplayedEntry(node); 2062 2063 isRootNode = node instanceof RootNode; 2064 isRemote = isDisplayedEntryRemote(node); 2065 isSuffix = node instanceof SuffixNode; 2066 referral = node.getReferral(); 2067 numSubOrdinates = node.getNumSubOrdinates(); 2068 hasSubOrdinates = node.hasSubOrdinates(); 2069 objectClassValues = node.getObjectClassValues(); 2070 if (node.getError() != null) { 2071 BasicNodeError error = node.getError(); 2072 switch(error.getState()) { 2073 case READING_LOCAL_ENTRY: 2074 errorType = ERROR_READING_ENTRY; 2075 break; 2076 case SOLVING_REFERRAL: 2077 errorType = ERROR_SOLVING_REFERRAL; 2078 break; 2079 case DETECTING_CHILDREN: 2080 case SEARCHING_CHILDREN: 2081 errorType = ERROR_SEARCHING_CHILDREN; 2082 break; 2083 2084 } 2085 errorException = error.getException(); 2086 errorArg = error.getArg(); 2087 } 2088 StringBuilder sb = new StringBuilder(); 2089 sb.append(getURL()); 2090 if (getReferral() != null) { 2091 sb.append(" -> "); 2092 sb.append(getReferral()); 2093 } 2094 toString = sb.toString(); 2095 } 2096 2097 /** 2098 * Returns the node associated with this object. 2099 * @return the node associated with this object. 2100 */ 2101 @Override 2102 public BasicNode getNode() { 2103 return node; 2104 } 2105 2106 /** 2107 * Returns the LDAP URL associated with this object. 2108 * @return the LDAP URL associated with this object. 2109 */ 2110 @Override 2111 public LDAPURL getURL() { 2112 return url; 2113 } 2114 2115 /** 2116 * Tells whether this is a root node or not. 2117 * @return <CODE>true</CODE> if this is a root node and <CODE>false</CODE> 2118 * otherwise. 2119 */ 2120 @Override 2121 public boolean isRootNode() { 2122 return isRootNode; 2123 } 2124 2125 /** 2126 * Tells whether this is a suffix node or not. 2127 * @return <CODE>true</CODE> if this is a suffix node and <CODE>false</CODE> 2128 * otherwise. 2129 */ 2130 @Override 2131 public boolean isSuffix() { 2132 return isSuffix; 2133 } 2134 2135 /** 2136 * Tells whether this is a remote node or not. 2137 * @return <CODE>true</CODE> if this is a remote node and <CODE>false</CODE> 2138 * otherwise. 2139 */ 2140 @Override 2141 public boolean isRemote() { 2142 return isRemote; 2143 } 2144 2145 /** 2146 * Returns the list of referral associated with this node. 2147 * @return the list of referral associated with this node. 2148 */ 2149 @Override 2150 public String[] getReferral() { 2151 return referral; 2152 } 2153 2154 /** 2155 * Returns the number of subordinates of the entry associated with this 2156 * node. 2157 * @return the number of subordinates of the entry associated with this 2158 * node. 2159 */ 2160 @Override 2161 public int getNumSubOrdinates() { 2162 return numSubOrdinates; 2163 } 2164 2165 /** 2166 * Returns whether the entry has subordinates or not. 2167 * @return {@code true} if the entry has subordinates and {@code false} 2168 * otherwise. 2169 */ 2170 @Override 2171 public boolean hasSubOrdinates() { 2172 return hasSubOrdinates; 2173 } 2174 2175 /** 2176 * Returns the error type associated we got when refreshing the node. 2177 * <CODE>null</CODE> if no error was found. 2178 * @return the error type associated we got when refreshing the node. 2179 * <CODE>null</CODE> if no error was found. 2180 */ 2181 @Override 2182 public int getErrorType() { 2183 return errorType; 2184 } 2185 2186 /** 2187 * Returns the exception associated we got when refreshing the node. 2188 * <CODE>null</CODE> if no exception was found. 2189 * @return the exception associated we got when refreshing the node. 2190 * <CODE>null</CODE> if no exception was found. 2191 */ 2192 @Override 2193 public Exception getErrorException() { 2194 return errorException; 2195 } 2196 2197 /** 2198 * Returns the error argument associated we got when refreshing the node. 2199 * <CODE>null</CODE> if no error argument was found. 2200 * @return the error argument associated we got when refreshing the node. 2201 * <CODE>null</CODE> if no error argument was found. 2202 */ 2203 @Override 2204 public Object getErrorArg() { 2205 return errorArg; 2206 } 2207 2208 /** 2209 * Return the tree path associated with the node in the tree. 2210 * @return the tree path associated with the node in the tree. 2211 */ 2212 @Override 2213 public TreePath getTreePath() { 2214 return new TreePath(treeModel.getPathToRoot(node)); 2215 } 2216 2217 /** 2218 * Returns the object class values of the entry associated with the node. 2219 * @return the object class values of the entry associated with the node. 2220 */ 2221 @Override 2222 public String[] getObjectClassValues() { 2223 return objectClassValues; 2224 } 2225 2226 /** 2227 * Returns a String representation of the object. 2228 * @return a String representation of the object. 2229 */ 2230 @Override 2231 public String toString() { 2232 return toString; 2233 } 2234 2235 /** 2236 * Compares the provide node with this object. 2237 * @param node the node. 2238 * @return <CODE>true</CODE> if the node info represents the same node as 2239 * this and <CODE>false</CODE> otherwise. 2240 */ 2241 @Override 2242 public boolean representsSameNode(BrowserNodeInfo node) { 2243 return node != null && node.getNode() == node; 2244 } 2245 } 2246 2247 2248 /** 2249 * Returns whether we are in automatic expand mode. This mode is used when 2250 * the user specifies a filter and all the nodes are automatically expanded. 2251 * @return <CODE>true</CODE> if we are in automatic expand mode and 2252 * <CODE>false</CODE> otherwise. 2253 */ 2254 public boolean isAutomaticExpand() 2255 { 2256 return automaticExpand; 2257 } 2258 2259 2260 /** 2261 * Sets the automatic expand mode. 2262 * @param automaticExpand whether to expand automatically the nodes or not. 2263 */ 2264 public void setAutomaticExpand(boolean automaticExpand) 2265 { 2266 this.automaticExpand = automaticExpand; 2267 } 2268}