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