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