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 2012-2015 ForgeRock AS 026 */ 027package org.opends.guitools.controlpanel.browser; 028 029import static org.opends.messages.AdminToolMessages.*; 030 031import java.util.ArrayList; 032import java.util.Set; 033 034import javax.naming.InterruptedNamingException; 035import javax.naming.NameNotFoundException; 036import javax.naming.NamingEnumeration; 037import javax.naming.NamingException; 038import javax.naming.SizeLimitExceededException; 039import javax.naming.directory.SearchControls; 040import javax.naming.directory.SearchResult; 041import javax.naming.ldap.InitialLdapContext; 042import javax.naming.ldap.LdapName; 043import javax.swing.SwingUtilities; 044import javax.swing.tree.TreeNode; 045 046import org.forgerock.opendj.ldap.SearchScope; 047import org.opends.admin.ads.util.ConnectionUtils; 048import org.opends.guitools.controlpanel.ui.nodes.BasicNode; 049import org.opends.messages.AdminToolMessages; 050import org.opends.server.schema.SchemaConstants; 051import org.opends.server.types.DN; 052import org.opends.server.types.DirectoryException; 053import org.opends.server.types.LDAPURL; 054import org.opends.server.types.OpenDsException; 055import org.opends.server.types.RDN; 056 057/** 058 * The class that is in charge of doing the LDAP searches required to update a 059 * node: search the local entry, detect if it has children, retrieve the 060 * attributes required to render the node, etc. 061 */ 062public class NodeRefresher extends AbstractNodeTask { 063 /** The enumeration containing all the states the refresher can have. */ 064 public enum State 065 { 066 /** The refresher is queued, but not started. */ 067 QUEUED, 068 /** The refresher is reading the local entry. */ 069 READING_LOCAL_ENTRY, 070 /** The refresher is solving a referral. */ 071 SOLVING_REFERRAL, 072 /** The refresher is detecting whether the entry has children or not. */ 073 DETECTING_CHILDREN, 074 /** The refresher is searching for the children of the entry. */ 075 SEARCHING_CHILDREN, 076 /** The refresher is finished. */ 077 FINISHED, 078 /** The refresher is cancelled. */ 079 CANCELLED, 080 /** The refresher has been interrupted. */ 081 INTERRUPTED, 082 /** The refresher has failed. */ 083 FAILED 084 } 085 086 BrowserController controller; 087 State state; 088 boolean recursive; 089 090 SearchResult localEntry; 091 SearchResult remoteEntry; 092 LDAPURL remoteUrl; 093 boolean isLeafNode; 094 final ArrayList<SearchResult> childEntries = new ArrayList<>(); 095 final boolean differential; 096 Exception exception; 097 Object exceptionArg; 098 099 /** 100 * The constructor of the refresher object. 101 * @param node the node on the tree to be updated. 102 * @param ctlr the BrowserController. 103 * @param localEntry the local entry corresponding to the node. 104 * @param recursive whether this task is recursive or not (children must be searched). 105 */ 106 NodeRefresher(BasicNode node, BrowserController ctlr, SearchResult localEntry, boolean recursive) { 107 super(node); 108 controller = ctlr; 109 state = State.QUEUED; 110 this.recursive = recursive; 111 112 this.localEntry = localEntry; 113 differential = false; 114 } 115 116 /** 117 * Returns the local entry the refresher is handling. 118 * @return the local entry the refresher is handling. 119 */ 120 public SearchResult getLocalEntry() { 121 return localEntry; 122 } 123 124 /** 125 * Returns the remote entry for the node. It will be <CODE>null</CODE> if 126 * the entry is not a referral. 127 * @return the remote entry for the node. 128 */ 129 public SearchResult getRemoteEntry() { 130 return remoteEntry; 131 } 132 133 /** 134 * Returns the URL of the remote entry. It will be <CODE>null</CODE> if 135 * the entry is not a referral. 136 * @return the URL of the remote entry. 137 */ 138 public LDAPURL getRemoteUrl() { 139 return remoteUrl; 140 } 141 142 /** 143 * Tells whether the node is a leaf or not. 144 * @return <CODE>true</CODE> if the node is a leaf and <CODE>false</CODE> 145 * otherwise. 146 */ 147 public boolean isLeafNode() { 148 return isLeafNode; 149 } 150 151 /** 152 * Returns the child entries of the node. 153 * @return the child entries of the node. 154 */ 155 public ArrayList<SearchResult> getChildEntries() { 156 return childEntries; 157 } 158 159 /** 160 * Returns whether this refresher object is working on differential mode or 161 * not. 162 * @return <CODE>true</CODE> if the refresher is working on differential 163 * mode and <CODE>false</CODE> otherwise. 164 */ 165 public boolean isDifferential() { 166 return differential; 167 } 168 169 /** 170 * Returns the exception that occurred during the processing. It returns 171 * <CODE>null</CODE> if no exception occurred. 172 * @return the exception that occurred during the processing. 173 */ 174 public Exception getException() { 175 return exception; 176 } 177 178 /** 179 * Returns the argument of the exception that occurred during the processing. 180 * It returns <CODE>null</CODE> if no exception occurred or if the exception 181 * has no arguments. 182 * @return the argument exception that occurred during the processing. 183 */ 184 public Object getExceptionArg() { 185 return exceptionArg; 186 } 187 188 /** 189 * Returns the displayed entry in the browser. This depends on the 190 * visualization options in the BrowserController. 191 * @return the remote entry if the entry is a referral and the 192 * BrowserController is following referrals and the local entry otherwise. 193 */ 194 public SearchResult getDisplayedEntry() { 195 SearchResult result; 196 if (controller.getFollowReferrals() && remoteEntry != null) 197 { 198 result = remoteEntry; 199 } 200 else { 201 result = localEntry; 202 } 203 return result; 204 } 205 206 /** 207 * Returns the LDAP URL of the displayed entry in the browser. This depends 208 * on the visualization options in the BrowserController. 209 * @return the remote entry LDAP URL if the entry is a referral and the 210 * BrowserController is following referrals and the local entry LDAP URL 211 * otherwise. 212 */ 213 public LDAPURL getDisplayedUrl() { 214 LDAPURL result; 215 if (controller.getFollowReferrals() && remoteUrl != null) 216 { 217 result = remoteUrl; 218 } 219 else { 220 result = controller.findUrlForLocalEntry(getNode()); 221 } 222 return result; 223 } 224 225 /** 226 * Returns whether the refresh is over or not. 227 * @return <CODE>true</CODE> if the refresh is over and <CODE>false</CODE> 228 * otherwise. 229 */ 230 public boolean isInFinalState() { 231 return state == State.FINISHED || state == State.CANCELLED || state == State.FAILED || state == State.INTERRUPTED; 232 } 233 234 /** The method that actually does the refresh. */ 235 @Override 236 public void run() { 237 final BasicNode node = getNode(); 238 239 try { 240 boolean checkExpand = false; 241 if (localEntry == null) { 242 changeStateTo(State.READING_LOCAL_ENTRY); 243 runReadLocalEntry(); 244 } 245 if (!isInFinalState()) { 246 if (controller.getFollowReferrals() && isReferralEntry(localEntry)) { 247 changeStateTo(State.SOLVING_REFERRAL); 248 runSolveReferral(); 249 } 250 if (node.isLeaf()) { 251 changeStateTo(State.DETECTING_CHILDREN); 252 runDetectChildren(); 253 } 254 if (controller.nodeIsExpanded(node) && recursive) { 255 changeStateTo(State.SEARCHING_CHILDREN); 256 runSearchChildren(); 257 /* If the node is not expanded, we have to refresh its children when we expand it */ 258 } else if (recursive && (!node.isLeaf() || !isLeafNode)) { 259 node.setRefreshNeededOnExpansion(true); 260 checkExpand = true; 261 } 262 changeStateTo(State.FINISHED); 263 if (checkExpand && mustAutomaticallyExpand(node)) 264 { 265 SwingUtilities.invokeLater(new Runnable() 266 { 267 @Override 268 public void run() 269 { 270 controller.expandNode(node); 271 } 272 }); 273 } 274 } 275 } 276 catch (NamingException ne) 277 { 278 exception = ne; 279 exceptionArg = null; 280 } 281 catch(SearchAbandonException x) { 282 exception = x.getException(); 283 exceptionArg = x.getArg(); 284 try { 285 changeStateTo(x.getState()); 286 } 287 catch(SearchAbandonException xx) { 288 // We've done all what we can... 289 } 290 } 291 } 292 293 /** 294 * Tells whether a custom filter is being used (specified by the user in the 295 * browser dialog) or not. 296 * @return <CODE>true</CODE> if a custom filter is being used and 297 * <CODE>false</CODE> otherwise. 298 */ 299 private boolean useCustomFilter() 300 { 301 boolean result=false; 302 if (controller.getFilter()!=null) 303 { 304 result = 305 !BrowserController.ALL_OBJECTS_FILTER.equals(controller.getFilter()); 306 } 307 return result; 308 } 309 310 /** 311 * Performs the search in the case the user specified a custom filter. 312 * @param node the parent node we perform the search from. 313 * @param ctx the connection to be used. 314 * @throws NamingException if a problem occurred. 315 */ 316 private void searchForCustomFilter(BasicNode node, InitialLdapContext ctx) 317 throws NamingException 318 { 319 SearchControls ctls = controller.getBasicSearchControls(); 320 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 321 ctls.setReturningAttributes(new String[] { SchemaConstants.NO_ATTRIBUTES }); 322 ctls.setCountLimit(1); 323 NamingEnumeration<SearchResult> s = ctx.search(new LdapName(node.getDN()), 324 controller.getFilter(), 325 ctls); 326 try 327 { 328 if (!s.hasMore()) 329 { 330 throw new NameNotFoundException("Entry "+node.getDN()+ 331 " does not verify filter "+controller.getFilter()); 332 } 333 while (s.hasMore()) 334 { 335 s.next(); 336 } 337 } 338 catch (SizeLimitExceededException slme) 339 { 340 // We are just searching for an entry, but if there is more than one 341 // this exception will be thrown. We call sr.hasMore after the 342 // first entry has been retrieved to avoid sending a systematic 343 // abandon when closing the s NamingEnumeration. 344 // See CR 6976906. 345 } 346 finally 347 { 348 s.close(); 349 } 350 } 351 352 /** 353 * Performs the search in the case the user specified a custom filter. 354 * @param dn the parent DN we perform the search from. 355 * @param ctx the connection to be used. 356 * @throws NamingException if a problem occurred. 357 */ 358 private void searchForCustomFilter(String dn, InitialLdapContext ctx) 359 throws NamingException 360 { 361 SearchControls ctls = controller.getBasicSearchControls(); 362 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 363 ctls.setReturningAttributes(new String[]{}); 364 ctls.setCountLimit(1); 365 NamingEnumeration<SearchResult> s = ctx.search(new LdapName(dn), 366 controller.getFilter(), 367 ctls); 368 try 369 { 370 if (!s.hasMore()) 371 { 372 throw new NameNotFoundException("Entry "+dn+ 373 " does not verify filter "+controller.getFilter()); 374 } 375 while (s.hasMore()) 376 { 377 s.next(); 378 } 379 } 380 catch (SizeLimitExceededException slme) 381 { 382 // We are just searching for an entry, but if there is more than one 383 // this exception will be thrown. We call sr.hasMore after the 384 // first entry has been retrieved to avoid sending a systematic 385 // abandon when closing the s NamingEnumeration. 386 // See CR 6976906. 387 } 388 finally 389 { 390 s.close(); 391 } 392 } 393 394 /** Read the local entry associated to the current node. */ 395 private void runReadLocalEntry() throws SearchAbandonException { 396 BasicNode node = getNode(); 397 InitialLdapContext ctx = null; 398 try { 399 ctx = controller.findConnectionForLocalEntry(node); 400 401 if (ctx != null) { 402 if (useCustomFilter()) 403 { 404 // Check that the entry verifies the filter 405 searchForCustomFilter(node, ctx); 406 } 407 408 SearchControls ctls = controller.getBasicSearchControls(); 409 ctls.setReturningAttributes(controller.getAttrsForRedSearch()); 410 ctls.setSearchScope(SearchControls.OBJECT_SCOPE); 411 412 NamingEnumeration<SearchResult> s = 413 ctx.search(new LdapName(node.getDN()), 414 controller.getObjectSearchFilter(), 415 ctls); 416 try 417 { 418 while (s.hasMore()) 419 { 420 localEntry = s.next(); 421 localEntry.setName(node.getDN()); 422 } 423 } 424 finally 425 { 426 s.close(); 427 } 428 if (localEntry == null) { 429 /* Not enough rights to read the entry or the entry simply does not exist */ 430 throw new NameNotFoundException("Can't find entry: "+node.getDN()); 431 } 432 throwAbandonIfNeeded(null); 433 } else { 434 changeStateTo(State.FINISHED); 435 } 436 } 437 catch(NamingException x) { 438 throwAbandonIfNeeded(x); 439 } 440 finally { 441 if (ctx != null) { 442 controller.releaseLDAPConnection(ctx); 443 } 444 } 445 } 446 447 /** 448 * Solve the referral associated to the current node. 449 * This routine assumes that node.getReferral() is non null 450 * and that BrowserController.getFollowReferrals() == true. 451 * It also protect the browser against looping referrals by 452 * limiting the number of hops. 453 * @throws SearchAbandonException if the hop count limit for referrals has 454 * been exceeded. 455 * @throws NamingException if an error occurred searching the entry. 456 */ 457 private void runSolveReferral() 458 throws SearchAbandonException, NamingException { 459 int hopCount = 0; 460 String[] referral = getNode().getReferral(); 461 while (referral != null && hopCount < 10) 462 { 463 readRemoteEntry(referral); 464 referral = BrowserController.getReferral(remoteEntry); 465 hopCount++; 466 } 467 if (referral != null) 468 { 469 throwAbandonIfNeeded(new ReferralLimitExceededException( 470 AdminToolMessages.ERR_REFERRAL_LIMIT_EXCEEDED.get(hopCount))); 471 } 472 } 473 474 /** 475 * Searches for the remote entry. 476 * @param referral the referral list to be used to search the remote entry. 477 * @throws SearchAbandonException if an error occurs. 478 */ 479 private void readRemoteEntry(String[] referral) 480 throws SearchAbandonException { 481 LDAPConnectionPool connectionPool = controller.getConnectionPool(); 482 LDAPURL url = null; 483 SearchResult entry = null; 484 String remoteDn = null; 485 Exception lastException = null; 486 Object lastExceptionArg = null; 487 488 int i = 0; 489 while (i < referral.length && entry == null) 490 { 491 InitialLdapContext ctx = null; 492 try { 493 url = LDAPURL.decode(referral[i], false); 494 if (url.getHost() == null) 495 { 496 // Use the local server connection. 497 ctx = controller.getUserDataConnection(); 498 url.setHost(ConnectionUtils.getHostName(ctx)); 499 url.setPort(ConnectionUtils.getPort(ctx)); 500 url.setScheme(ConnectionUtils.isSSL(ctx)?"ldaps":"ldap"); 501 } 502 ctx = connectionPool.getConnection(url); 503 remoteDn = url.getRawBaseDN(); 504 if (remoteDn == null || "".equals(remoteDn)) 505 { 506 /* The referral has not a target DN specified: we 507 have to use the DN of the entry that contains the 508 referral... */ 509 if (remoteEntry != null) { 510 remoteDn = remoteEntry.getName(); 511 } else { 512 remoteDn = localEntry.getName(); 513 } 514 /* We have to recreate the url including the target DN we are using */ 515 url = new LDAPURL(url.getScheme(), url.getHost(), url.getPort(), 516 remoteDn, url.getAttributes(), url.getScope(), url.getRawFilter(), 517 url.getExtensions()); 518 } 519 if (useCustomFilter() && url.getScope() == SearchScope.BASE_OBJECT) 520 { 521 // Check that the entry verifies the filter 522 searchForCustomFilter(remoteDn, ctx); 523 } 524 525 int scope = getJNDIScope(url); 526 String filter = getJNDIFilter(url); 527 528 SearchControls ctls = controller.getBasicSearchControls(); 529 ctls.setReturningAttributes(controller.getAttrsForBlackSearch()); 530 ctls.setSearchScope(scope); 531 ctls.setCountLimit(1); 532 NamingEnumeration<SearchResult> sr = ctx.search(remoteDn, 533 filter, 534 ctls); 535 try 536 { 537 boolean found = false; 538 while (sr.hasMore()) 539 { 540 entry = sr.next(); 541 String name; 542 if (entry.getName().length() == 0) 543 { 544 name = remoteDn; 545 } 546 else 547 { 548 name = unquoteRelativeName(entry.getName())+","+remoteDn; 549 } 550 entry.setName(name); 551 found = true; 552 } 553 if (!found) 554 { 555 throw new NameNotFoundException(); 556 } 557 } 558 catch (SizeLimitExceededException sle) 559 { 560 // We are just searching for an entry, but if there is more than one 561 // this exception will be thrown. We call sr.hasMore after the 562 // first entry has been retrieved to avoid sending a systematic 563 // abandon when closing the sr NamingEnumeration. 564 // See CR 6976906. 565 } 566 finally 567 { 568 sr.close(); 569 } 570 throwAbandonIfNeeded(null); 571 } 572 catch (InterruptedNamingException x) { 573 throwAbandonIfNeeded(x); 574 } 575 catch (NamingException | DirectoryException x) { 576 lastException = x; 577 lastExceptionArg = referral[i]; 578 } 579 finally { 580 if (ctx != null) { 581 connectionPool.releaseConnection(ctx); 582 } 583 } 584 i = i + 1; 585 } 586 if (entry == null) { 587 throw new SearchAbandonException( 588 State.FAILED, lastException, lastExceptionArg); 589 } 590 else 591 { 592 if (url.getScope() != SearchScope.BASE_OBJECT) 593 { 594 // The URL is to be transformed: the code assumes that the URL points 595 // to the remote entry. 596 url = new LDAPURL(url.getScheme(), url.getHost(), 597 url.getPort(), entry.getName(), url.getAttributes(), 598 SearchScope.BASE_OBJECT, null, url.getExtensions()); 599 } 600 checkLoopInReferral(url, referral[i-1]); 601 remoteUrl = url; 602 remoteEntry = entry; 603 } 604 } 605 606 /** 607 * Tells whether the provided node must be automatically expanded or not. 608 * This is used when the user provides a custom filter, in this case we 609 * expand automatically the tree. 610 * @param node the node to analyze. 611 * @return <CODE>true</CODE> if the node must be expanded and 612 * <CODE>false</CODE> otherwise. 613 */ 614 private boolean mustAutomaticallyExpand(BasicNode node) 615 { 616 boolean mustAutomaticallyExpand = false; 617 if (controller.isAutomaticExpand()) 618 { 619 // Limit the number of expansion levels to 3 620 int nLevels = 0; 621 TreeNode parent = node; 622 while (parent != null) 623 { 624 nLevels ++; 625 parent = parent.getParent(); 626 } 627 mustAutomaticallyExpand = nLevels <= 4; 628 } 629 return mustAutomaticallyExpand; 630 } 631 632 /** 633 * Detects whether the entries has children or not. 634 * @throws SearchAbandonException if the search was abandoned. 635 * @throws NamingException if an error during the search occurred. 636 */ 637 private void runDetectChildren() 638 throws SearchAbandonException, NamingException { 639 if (controller.isShowContainerOnly() || !isNumSubOrdinatesUsable()) { 640 runDetectChildrenManually(); 641 } 642 else { 643 SearchResult entry = getDisplayedEntry(); 644 isLeafNode = !BrowserController.getHasSubOrdinates(entry); 645 } 646 } 647 648 /** 649 * Detects whether the entry has children by performing a search using the 650 * entry as base DN. 651 * @throws SearchAbandonException if there is an error. 652 */ 653 private void runDetectChildrenManually() throws SearchAbandonException { 654 BasicNode parentNode = getNode(); 655 InitialLdapContext ctx = null; 656 NamingEnumeration<SearchResult> searchResults = null; 657 658 try { 659 // We set the search constraints so that only one entry is returned. 660 // It's enough to know if the entry has children or not. 661 SearchControls ctls = controller.getBasicSearchControls(); 662 ctls.setCountLimit(1); 663 ctls.setReturningAttributes( 664 new String[] { SchemaConstants.NO_ATTRIBUTES }); 665 if (useCustomFilter()) 666 { 667 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 668 } 669 else 670 { 671 ctls.setSearchScope(SearchControls.OBJECT_SCOPE); 672 } 673 // Send an LDAP search 674 ctx = controller.findConnectionForDisplayedEntry(parentNode); 675 searchResults = ctx.search( 676 new LdapName(controller.findBaseDNForChildEntries(parentNode)), 677 controller.getChildSearchFilter(), 678 ctls); 679 680 throwAbandonIfNeeded(null); 681 isLeafNode = true; 682 // Check if parentNode has children 683 while (searchResults.hasMoreElements()) { 684 isLeafNode = false; 685 } 686 } 687 catch (SizeLimitExceededException e) 688 { 689 // We are just searching for an entry, but if there is more than one 690 // this exception will be thrown. We call sr.hasMore after the 691 // first entry has been retrieved to avoid sending a systematic 692 // abandon when closing the searchResults NamingEnumeration. 693 // See CR 6976906. 694 } 695 catch (NamingException x) { 696 throwAbandonIfNeeded(x); 697 } 698 finally { 699 if (ctx != null) { 700 controller.releaseLDAPConnection(ctx); 701 } 702 if (searchResults != null) 703 { 704 try 705 { 706 searchResults.close(); 707 } 708 catch (NamingException x) 709 { 710 throwAbandonIfNeeded(x); 711 } 712 } 713 } 714 } 715 716 /** 717 * NUMSUBORDINATE HACK 718 * numsubordinates is not usable if the displayed entry 719 * is listed in in the hacker. 720 * Note: *usable* means *usable for detecting children presence*. 721 */ 722 private boolean isNumSubOrdinatesUsable() throws NamingException { 723 SearchResult entry = getDisplayedEntry(); 724 boolean hasSubOrdinates = BrowserController.getHasSubOrdinates(entry); 725 if (!hasSubOrdinates) 726 { 727 LDAPURL url = getDisplayedUrl(); 728 return !controller.getNumSubordinateHacker().contains(url); 729 } 730 // Other values are usable 731 return true; 732 } 733 734 /** 735 * Searches for the children. 736 * @throws SearchAbandonException if an error occurs. 737 */ 738 private void runSearchChildren() throws SearchAbandonException { 739 InitialLdapContext ctx = null; 740 BasicNode parentNode = getNode(); 741 parentNode.setSizeLimitReached(false); 742 743 try { 744 // Send an LDAP search 745 SearchControls ctls = controller.getBasicSearchControls(); 746 if (useCustomFilter()) 747 { 748 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 749 } 750 else 751 { 752 ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE); 753 } 754 ctls.setReturningAttributes(controller.getAttrsForRedSearch()); 755 ctx = controller.findConnectionForDisplayedEntry(parentNode); 756 String parentDn = controller.findBaseDNForChildEntries(parentNode); 757 int parentComponents; 758 try 759 { 760 DN dn = DN.valueOf(parentDn); 761 parentComponents = dn.size(); 762 } 763 catch (Throwable t) 764 { 765 throw new RuntimeException("Error decoding dn: "+parentDn+" . "+t, 766 t); 767 } 768 NamingEnumeration<SearchResult> entries = ctx.search( 769 new LdapName(parentDn), 770 controller.getChildSearchFilter(), 771 ctls); 772 773 try 774 { 775 while (entries.hasMore()) 776 { 777 SearchResult r = entries.next(); 778 String name; 779 if (r.getName().length() == 0) 780 { 781 continue; 782 } 783 else 784 { 785 name = unquoteRelativeName(r.getName())+","+parentDn; 786 } 787 boolean add = false; 788 if (useCustomFilter()) 789 { 790 // Check that is an immediate child: use a faster method by just 791 // comparing the number of components. 792 DN dn = null; 793 try 794 { 795 dn = DN.valueOf(name); 796 add = dn.size() == parentComponents + 1; 797 } 798 catch (Throwable t) 799 { 800 throw new RuntimeException("Error decoding dns: "+t, t); 801 } 802 803 if (!add) 804 { 805 // Is not a direct child. Check if the parent has been added, 806 // if it is the case, do not add the parent. If is not the case, 807 // search for the parent and add it. 808 RDN[] rdns = new RDN[parentComponents + 1]; 809 int diff = dn.size() - rdns.length; 810 for (int i=0; i < rdns.length; i++) 811 { 812 rdns[i] = dn.getRDN(i + diff); 813 } 814 final DN parentToAddDN = new DN(rdns); 815 boolean mustAddParent = true; 816 for (SearchResult addedEntry : childEntries) 817 { 818 try 819 { 820 DN addedDN = DN.valueOf(addedEntry.getName()); 821 if (addedDN.equals(parentToAddDN)) 822 { 823 mustAddParent = false; 824 break; 825 } 826 } 827 catch (Throwable t) 828 { 829 throw new RuntimeException("Error decoding dn: "+ 830 addedEntry.getName()+" . "+t, t); 831 } 832 } 833 if (mustAddParent) 834 { 835 final boolean resultValue[] = {true}; 836 // Check the children added to the tree 837 try 838 { 839 SwingUtilities.invokeAndWait(new Runnable() 840 { 841 @Override 842 public void run() 843 { 844 for (int i=0; i<getNode().getChildCount(); i++) 845 { 846 BasicNode node = (BasicNode)getNode().getChildAt(i); 847 try 848 { 849 DN dn = DN.valueOf(node.getDN()); 850 if (dn.equals(parentToAddDN)) 851 { 852 resultValue[0] = false; 853 break; 854 } 855 } 856 catch (Throwable t) 857 { 858 throw new RuntimeException("Error decoding dn: "+ 859 node.getDN()+" . "+t, t); 860 } 861 } 862 } 863 }); 864 } 865 catch (Throwable t) 866 { 867 // Ignore 868 } 869 mustAddParent = resultValue[0]; 870 } 871 if (mustAddParent) 872 { 873 SearchResult parentResult = searchManuallyEntry(ctx, 874 parentToAddDN.toString()); 875 childEntries.add(parentResult); 876 } 877 } 878 } 879 else 880 { 881 add = true; 882 } 883 if (add) 884 { 885 r.setName(name); 886 childEntries.add(r); 887 // Time to time we update the display 888 if (childEntries.size() >= 20) { 889 changeStateTo(State.SEARCHING_CHILDREN); 890 childEntries.clear(); 891 } 892 } 893 throwAbandonIfNeeded(null); 894 } 895 } 896 finally 897 { 898 entries.close(); 899 } 900 } 901 catch (SizeLimitExceededException slee) 902 { 903 parentNode.setSizeLimitReached(true); 904 } 905 catch (NamingException x) { 906 throwAbandonIfNeeded(x); 907 } 908 finally { 909 if (ctx != null) 910 { 911 controller.releaseLDAPConnection(ctx); 912 } 913 } 914 } 915 916 /** 917 * Returns the entry for the given dn. 918 * The code assumes that the request controls are set in the connection. 919 * @param ctx the connection to be used. 920 * @param dn the DN of the entry to be searched. 921 * @throws NamingException if an error occurs. 922 */ 923 private SearchResult searchManuallyEntry(InitialLdapContext ctx, String dn) 924 throws NamingException 925 { 926 SearchResult sr = null; 927// Send an LDAP search 928 SearchControls ctls = controller.getBasicSearchControls(); 929 ctls.setSearchScope(SearchControls.OBJECT_SCOPE); 930 ctls.setReturningAttributes(controller.getAttrsForRedSearch()); 931 NamingEnumeration<SearchResult> entries = ctx.search( 932 new LdapName(dn), 933 controller.getObjectSearchFilter(), 934 ctls); 935 936 try 937 { 938 while (entries.hasMore()) 939 { 940 sr = entries.next(); 941 sr.setName(dn); 942 } 943 } 944 finally 945 { 946 entries.close(); 947 } 948 return sr; 949 } 950 951 /** Utilities. */ 952 953 /** 954 * Change the state of the task and inform the BrowserController. 955 * @param newState the new state for the refresher. 956 */ 957 private void changeStateTo(State newState) throws SearchAbandonException { 958 State oldState = state; 959 state = newState; 960 try { 961 controller.invokeRefreshTaskDidProgress(this, oldState, newState); 962 } 963 catch(InterruptedException x) { 964 throwAbandonIfNeeded(x); 965 } 966 } 967 968 /** 969 * Transform an exception into a TaskAbandonException. 970 * If no exception is passed, the routine checks if the task has 971 * been canceled and throws an TaskAbandonException accordingly. 972 * @param x the exception. 973 * @throws SearchAbandonException if the task/refresher must be abandoned. 974 */ 975 private void throwAbandonIfNeeded(Exception x) throws SearchAbandonException { 976 SearchAbandonException tax = null; 977 if (x != null) { 978 if (x instanceof InterruptedException || x instanceof InterruptedNamingException) 979 { 980 tax = new SearchAbandonException(State.INTERRUPTED, x, null); 981 } 982 else { 983 tax = new SearchAbandonException(State.FAILED, x, null); 984 } 985 } 986 else if (isCanceled()) { 987 tax = new SearchAbandonException(State.CANCELLED, null, null); 988 } 989 if (tax != null) { 990 throw tax; 991 } 992 } 993 994 /** 995 * Removes the quotes surrounding the provided name. JNDI can return relative 996 * names with this format. 997 * @param name the relative name to be treated. 998 * @return an String representing the provided relative name without 999 * surrounding quotes. 1000 */ 1001 private String unquoteRelativeName(String name) 1002 { 1003 if (name.length() > 0 && name.charAt(0) == '"') 1004 { 1005 if (name.charAt(name.length() - 1) == '"') 1006 { 1007 return name.substring(1, name.length() - 1); 1008 } 1009 else 1010 { 1011 return name.substring(1); 1012 } 1013 } 1014 else 1015 { 1016 return name; 1017 } 1018 } 1019 1020 /** DEBUG : Dump the state of the task. */ 1021 void dump() { 1022 System.out.println("============="); 1023 System.out.println(" node: " + getNode().getDN()); 1024 System.out.println(" recursive: " + recursive); 1025 System.out.println(" differential: " + differential); 1026 1027 System.out.println(" state: " + state); 1028 System.out.println(" localEntry: " + localEntry); 1029 System.out.println(" remoteEntry: " + remoteEntry); 1030 System.out.println(" remoteUrl: " + remoteUrl); 1031 System.out.println(" isLeafNode: " + isLeafNode); 1032 System.out.println(" exception: " + exception); 1033 System.out.println(" exceptionArg: " + exceptionArg); 1034 System.out.println("============="); 1035 } 1036 1037 /** 1038 * Checks that the entry's objectClass contains 'referral' and that the 1039 * attribute 'ref' is present. 1040 * @param entry the search result. 1041 * @return <CODE>true</CODE> if the entry's objectClass contains 'referral' 1042 * and the attribute 'ref' is present and <CODE>false</CODE> otherwise. 1043 * @throws NamingException if an error occurs. 1044 */ 1045 static boolean isReferralEntry(SearchResult entry) throws NamingException { 1046 boolean result = false; 1047 Set<String> ocValues = ConnectionUtils.getValues(entry, "objectClass"); 1048 if (ocValues != null) { 1049 for (String value : ocValues) 1050 { 1051 boolean isReferral = "referral".equalsIgnoreCase(value); 1052 1053 if (isReferral) { 1054 result = ConnectionUtils.getFirstValue(entry, "ref") != null; 1055 break; 1056 } 1057 } 1058 } 1059 return result; 1060 } 1061 1062 /** 1063 * Returns the scope to be used in a JNDI request based on the information 1064 * of an LDAP URL. 1065 * @param url the LDAP URL. 1066 * @return the scope to be used in a JNDI request. 1067 */ 1068 private int getJNDIScope(LDAPURL url) 1069 { 1070 int scope; 1071 if (url.getScope() != null) 1072 { 1073 switch (url.getScope().asEnum()) 1074 { 1075 case BASE_OBJECT: 1076 scope = SearchControls.OBJECT_SCOPE; 1077 break; 1078 case WHOLE_SUBTREE: 1079 scope = SearchControls.SUBTREE_SCOPE; 1080 break; 1081 case SUBORDINATES: 1082 scope = SearchControls.ONELEVEL_SCOPE; 1083 break; 1084 case SINGLE_LEVEL: 1085 scope = SearchControls.ONELEVEL_SCOPE; 1086 break; 1087 default: 1088 scope = SearchControls.OBJECT_SCOPE; 1089 } 1090 } 1091 else 1092 { 1093 scope = SearchControls.OBJECT_SCOPE; 1094 } 1095 return scope; 1096 } 1097 1098 /** 1099 * Returns the filter to be used in a JNDI request based on the information 1100 * of an LDAP URL. 1101 * @param url the LDAP URL. 1102 * @return the filter. 1103 */ 1104 private String getJNDIFilter(LDAPURL url) 1105 { 1106 String filter = url.getRawFilter(); 1107 if (filter == null) 1108 { 1109 filter = controller.getObjectSearchFilter(); 1110 } 1111 return filter; 1112 } 1113 1114 /** 1115 * Check that there is no loop in terms of DIT (the check basically identifies 1116 * whether we are pointing to an entry above in the same server). 1117 * @param url the URL to the remote entry. It is assumed that the base DN 1118 * of the URL points to the remote entry. 1119 * @param referral the referral used to retrieve the remote entry. 1120 * @throws SearchAbandonException if there is a loop issue (the remoteEntry 1121 * is actually an entry in the same server as the local entry but above in the 1122 * DIT). 1123 */ 1124 private void checkLoopInReferral(LDAPURL url, 1125 String referral) throws SearchAbandonException 1126 { 1127 boolean checkSucceeded = true; 1128 try 1129 { 1130 DN dn1 = DN.valueOf(getNode().getDN()); 1131 DN dn2 = url.getBaseDN(); 1132 if (dn2.isAncestorOf(dn1)) 1133 { 1134 String host = url.getHost(); 1135 int port = url.getPort(); 1136 String adminHost = ConnectionUtils.getHostName( 1137 controller.getConfigurationConnection()); 1138 int adminPort = 1139 ConnectionUtils.getPort(controller.getConfigurationConnection()); 1140 checkSucceeded = port != adminPort || 1141 !adminHost.equalsIgnoreCase(host); 1142 1143 if (checkSucceeded) 1144 { 1145 String hostUserData = ConnectionUtils.getHostName( 1146 controller.getUserDataConnection()); 1147 int portUserData = 1148 ConnectionUtils.getPort(controller.getUserDataConnection()); 1149 checkSucceeded = port != portUserData || 1150 !hostUserData.equalsIgnoreCase(host); 1151 } 1152 } 1153 } 1154 catch (OpenDsException odse) 1155 { 1156 // Ignore 1157 } 1158 if (!checkSucceeded) 1159 { 1160 throw new SearchAbandonException( 1161 State.FAILED, new ReferralLimitExceededException( 1162 ERR_CTRL_PANEL_REFERRAL_LOOP.get(url.getRawBaseDN())), referral); 1163 } 1164 } 1165}