/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2012-2013 ForgeRock AS */ package org.opends.guitools.controlpanel.browser; import static org.opends.messages.AdminToolMessages.*; import java.util.ArrayList; import java.util.Set; import javax.naming.InterruptedNamingException; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.SizeLimitExceededException; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapName; import javax.swing.SwingUtilities; import javax.swing.tree.TreeNode; import org.opends.admin.ads.util.ConnectionUtils; import org.opends.guitools.controlpanel.ui.nodes.BasicNode; import org.opends.messages.AdminToolMessages; import org.opends.server.schema.SchemaConstants; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.LDAPURL; import org.opends.server.types.OpenDsException; import org.opends.server.types.RDN; import org.opends.server.types.SearchScope; /** * The class that is in charge of doing the LDAP searches required to update a * node: search the local entry, detect if it has children, retrieve the * attributes required to render the node, etc. */ public class NodeRefresher extends AbstractNodeTask { /** * The enumeration containing all the states the refresher can have. * */ public enum State { /** * The refresher is queued, but not started. */ QUEUED, /** * The refresher is reading the local entry. */ READING_LOCAL_ENTRY, /** * The refresher is solving a referral. */ SOLVING_REFERRAL, /** * The refresher is detecting whether the entry has children or not. */ DETECTING_CHILDREN, /** * The refresher is searching for the children of the entry. */ SEARCHING_CHILDREN, /** * The refresher is finished. */ FINISHED, /** * The refresher is cancelled. */ CANCELLED, /** * The refresher has been interrupted. */ INTERRUPTED, /** * The refresher has failed. */ FAILED } BrowserController controller; State state; boolean recursive; SearchResult localEntry; SearchResult remoteEntry; LDAPURL remoteUrl; boolean isLeafNode; ArrayList<SearchResult> childEntries = new ArrayList<SearchResult>(); boolean differential; Exception exception; Object exceptionArg; /** * The constructor of the refresher object. * @param node the node on the tree to be updated. * @param ctlr the BrowserController. * @param localEntry the local entry corresponding to the node. * @param recursive whether this task is recursive or not (children must be * searched). */ public NodeRefresher(BasicNode node, BrowserController ctlr, SearchResult localEntry, boolean recursive) { super(node); controller = ctlr; state = State.QUEUED; this.recursive = recursive; this.localEntry = localEntry; } /** * Returns the local entry the refresher is handling. * @return the local entry the refresher is handling. */ public SearchResult getLocalEntry() { return localEntry; } /** * Returns the remote entry for the node. It will be <CODE>null</CODE> if * the entry is not a referral. * @return the remote entry for the node. */ public SearchResult getRemoteEntry() { return remoteEntry; } /** * Returns the URL of the remote entry. It will be <CODE>null</CODE> if * the entry is not a referral. * @return the URL of the remote entry. */ public LDAPURL getRemoteUrl() { return remoteUrl; } /** * Tells whether the node is a leaf or not. * @return <CODE>true</CODE> if the node is a leaf and <CODE>false</CODE> * otherwise. */ public boolean isLeafNode() { return isLeafNode; } /** * Returns the child entries of the node. * @return the child entries of the node. */ public ArrayList<SearchResult> getChildEntries() { return childEntries; } /** * Returns whether this refresher object is working on differential mode or * not. * @return <CODE>true</CODE> if the refresher is working on differential * mode and <CODE>false</CODE> otherwise. */ public boolean isDifferential() { return differential; } /** * Returns the exception that occurred during the processing. It returns * <CODE>null</CODE> if no exception occurred. * @return the exception that occurred during the processing. */ public Exception getException() { return exception; } /** * Returns the argument of the exception that occurred during the processing. * It returns <CODE>null</CODE> if no exception occurred or if the exception * has no arguments. * @return the argument exception that occurred during the processing. */ public Object getExceptionArg() { return exceptionArg; } /** * Returns the displayed entry in the browser. This depends on the * visualization options in the BrowserController. * @return the remote entry if the entry is a referral and the * BrowserController is following referrals and the local entry otherwise. */ public SearchResult getDisplayedEntry() { SearchResult result; if (controller.getFollowReferrals() && (remoteEntry != null)) { result = remoteEntry; } else { result = localEntry; } return result; } /** * Returns the LDAP URL of the displayed entry in the browser. This depends * on the visualization options in the BrowserController. * @return the remote entry LDAP URL if the entry is a referral and the * BrowserController is following referrals and the local entry LDAP URL * otherwise. */ public LDAPURL getDisplayedUrl() { LDAPURL result; if (controller.getFollowReferrals() && (remoteUrl != null)) { result = remoteUrl; } else { result = controller.findUrlForLocalEntry(getNode()); } return result; } /** * Returns whether the refresh is over or not. * @return <CODE>true</CODE> if the refresh is over and <CODE>false</CODE> * otherwise. */ public boolean isInFinalState() { return ( (state == State.FINISHED) || (state == State.CANCELLED) || (state == State.FAILED) || (state == State.INTERRUPTED) ); } /** * The method that actually does the refresh. */ @Override public void run() { final BasicNode node = getNode(); try { boolean checkExpand = false; if (localEntry == null) { changeStateTo(State.READING_LOCAL_ENTRY); runReadLocalEntry(); } if (!isInFinalState()) { if (controller.getFollowReferrals() && isReferralEntry(localEntry)) { changeStateTo(State.SOLVING_REFERRAL); runSolveReferral(); } if (node.isLeaf()) { changeStateTo(State.DETECTING_CHILDREN); runDetectChildren(); } if (controller.nodeIsExpanded(node) && recursive) { changeStateTo(State.SEARCHING_CHILDREN); runSearchChildren(); /* If the node is not expanded, we have to refresh its children when we expand it */ } else if (recursive && (!node.isLeaf() || !isLeafNode)) { node.setRefreshNeededOnExpansion(true); checkExpand = true; } changeStateTo(State.FINISHED); if (checkExpand && mustAutomaticallyExpand(node)) { SwingUtilities.invokeLater(new Runnable() { public void run() { controller.expandNode(node); } }); } } } catch (NamingException ne) { exception = ne; exceptionArg = null; } catch(SearchAbandonException x) { exception = x.getException(); exceptionArg = x.getArg(); try { changeStateTo(x.getState()); } catch(SearchAbandonException xx) { // We've done all what we can... } } } /** * Tells whether a custom filter is being used (specified by the user in the * browser dialog) or not. * @return <CODE>true</CODE> if a custom filter is being used and * <CODE>false</CODE> otherwise. */ private boolean useCustomFilter() { boolean result=false; if (controller.getFilter()!=null) result = !controller.getFilter().equals(BrowserController.ALL_OBJECTS_FILTER); return result; } /** * Performs the search in the case the user specified a custom filter. * @param node the parent node we perform the search from. * @param ctx the connection to be used. * @throws NamingException if a problem occurred. */ private void searchForCustomFilter(BasicNode node, InitialLdapContext ctx) throws NamingException { SearchControls ctls = controller.getBasicSearchControls(); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); ctls.setReturningAttributes(new String[] { SchemaConstants.NO_ATTRIBUTES }); ctls.setCountLimit(1); NamingEnumeration<SearchResult> s = ctx.search(new LdapName(node.getDN()), controller.getFilter(), ctls); try { if (!s.hasMore()) { throw new NameNotFoundException("Entry "+node.getDN()+ " does not verify filter "+controller.getFilter()); } while (s.hasMore()) { s.next(); } } catch (SizeLimitExceededException slme) { // We are just searching for an entry, but if there is more than one // this exception will be thrown. We call sr.hasMore after the // first entry has been retrieved to avoid sending a systematic // abandon when closing the s NamingEnumeration. // See CR 6976906. } finally { s.close(); } } /** * Performs the search in the case the user specified a custom filter. * @param dn the parent DN we perform the search from. * @param ctx the connection to be used. * @throws NamingException if a problem occurred. */ private void searchForCustomFilter(String dn, InitialLdapContext ctx) throws NamingException { SearchControls ctls = controller.getBasicSearchControls(); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); ctls.setReturningAttributes(new String[]{}); ctls.setCountLimit(1); NamingEnumeration<SearchResult> s = ctx.search(new LdapName(dn), controller.getFilter(), ctls); try { if (!s.hasMore()) { throw new NameNotFoundException("Entry "+dn+ " does not verify filter "+controller.getFilter()); } while (s.hasMore()) { s.next(); } } catch (SizeLimitExceededException slme) { // We are just searching for an entry, but if there is more than one // this exception will be thrown. We call sr.hasMore after the // first entry has been retrieved to avoid sending a systematic // abandon when closing the s NamingEnumeration. // See CR 6976906. } finally { s.close(); } } /** * Read the local entry associated to the current node. */ private void runReadLocalEntry() throws SearchAbandonException { BasicNode node = getNode(); InitialLdapContext ctx = null; try { ctx = controller.findConnectionForLocalEntry(node); if (ctx != null) { if (useCustomFilter()) { // Check that the entry verifies the filter searchForCustomFilter(node, ctx); } SearchControls ctls = controller.getBasicSearchControls(); ctls.setReturningAttributes(controller.getAttrsForRedSearch()); ctls.setSearchScope(SearchControls.OBJECT_SCOPE); NamingEnumeration<SearchResult> s = ctx.search(new LdapName(node.getDN()), controller.getObjectSearchFilter(), ctls); try { while (s.hasMore()) { localEntry = s.next(); localEntry.setName(node.getDN()); } } finally { s.close(); } if (localEntry == null) { /* Not enough rights to read the entry or the entry simply does not exist */ throw new NameNotFoundException("Can't find entry: "+node.getDN()); } throwAbandonIfNeeded(null); } else { changeStateTo(State.FINISHED); } } catch(NamingException x) { throwAbandonIfNeeded(x); } finally { if (ctx != null) { controller.releaseLDAPConnection(ctx); } } } /** * Solve the referral associated to the current node. * This routine assumes that node.getReferral() is non null * and that BrowserController.getFollowReferrals() == true. * It also protect the browser against looping referrals by * limiting the number of hops. * @throws SearchAbandonException if the hop count limit for referrals has * been exceeded. * @throws NamingException if an error occurred searching the entry. */ private void runSolveReferral() throws SearchAbandonException, NamingException { int hopCount = 0; String[] referral = getNode().getReferral(); while ((referral != null) && (hopCount < 10)) { readRemoteEntry(referral); referral = BrowserController.getReferral(remoteEntry); hopCount++; } if (referral != null) { // -> hopCount has reached the max throwAbandonIfNeeded(new ReferralLimitExceededException( AdminToolMessages.ERR_REFERRAL_LIMIT_EXCEEDED.get(hopCount))); } } /** * Searches for the remote entry. * @param referral the referral list to be used to search the remote entry. * @throws SearchAbandonException if an error occurs. */ private void readRemoteEntry(String[] referral) throws SearchAbandonException { LDAPConnectionPool connectionPool = controller.getConnectionPool(); LDAPURL url = null; SearchResult entry = null; String remoteDn = null; Exception lastException = null; Object lastExceptionArg = null; int i = 0; while ((i < referral.length) && (entry == null)) { InitialLdapContext ctx = null; try { url = LDAPURL.decode(referral[i], false); if (url.getHost() == null) { // Use the local server connection. ctx = controller.getUserDataConnection(); url.setHost(ConnectionUtils.getHostName(ctx)); url.setPort(ConnectionUtils.getPort(ctx)); url.setScheme(ConnectionUtils.isSSL(ctx)?"ldaps":"ldap"); } ctx = connectionPool.getConnection(url); remoteDn = url.getRawBaseDN(); if ((remoteDn == null) || remoteDn.equals("")) { /* The referral has not a target DN specified: we have to use the DN of the entry that contains the referral... */ if (remoteEntry != null) { remoteDn = remoteEntry.getName(); } else { remoteDn = localEntry.getName(); } /* We have to recreate the url including the target DN we are using */ url = new LDAPURL(url.getScheme(), url.getHost(), url.getPort(), remoteDn, url.getAttributes(), url.getScope(), url.getRawFilter(), url.getExtensions()); } if (useCustomFilter() && url.getScope() == SearchScope.BASE_OBJECT) { // Check that the entry verifies the filter searchForCustomFilter(remoteDn, ctx); } int scope = getJNDIScope(url); String filter = getJNDIFilter(url); SearchControls ctls = controller.getBasicSearchControls(); ctls.setReturningAttributes(controller.getAttrsForBlackSearch()); ctls.setSearchScope(scope); ctls.setCountLimit(1); NamingEnumeration<SearchResult> sr = ctx.search(remoteDn, filter, ctls); try { boolean found = false; while (sr.hasMore()) { entry = sr.next(); String name; if (entry.getName().length() == 0) { name = remoteDn; } else { name = unquoteRelativeName(entry.getName())+","+remoteDn; } entry.setName(name); found = true; } if (!found) { throw new NameNotFoundException(); } } catch (SizeLimitExceededException sle) { // We are just searching for an entry, but if there is more than one // this exception will be thrown. We call sr.hasMore after the // first entry has been retrieved to avoid sending a systematic // abandon when closing the sr NamingEnumeration. // See CR 6976906. } finally { sr.close(); } throwAbandonIfNeeded(null); } catch (InterruptedNamingException x) { throwAbandonIfNeeded(x); } catch (NamingException x) { lastException = x; lastExceptionArg = referral[i]; } catch (DirectoryException de) { lastException = de; lastExceptionArg = referral[i]; } finally { if (ctx != null) { connectionPool.releaseConnection(ctx); } } i = i + 1; } if (entry == null) { throw new SearchAbandonException( State.FAILED, lastException, lastExceptionArg); } else { if (url.getScope() != SearchScope.BASE_OBJECT) { // The URL is to be transformed: the code assumes that the URL points // to the remote entry. url = new LDAPURL(url.getScheme(), url.getHost(), url.getPort(), entry.getName(), url.getAttributes(), SearchScope.BASE_OBJECT, null, url.getExtensions()); } checkLoopInReferral(url, referral[i-1]); remoteUrl = url; remoteEntry = entry; } } /** * Tells whether the provided node must be automatically expanded or not. * This is used when the user provides a custom filter, in this case we * expand automatically the tree. * @param node the node to analyze. * @return <CODE>true</CODE> if the node must be expanded and * <CODE>false</CODE> otherwise. */ private boolean mustAutomaticallyExpand(BasicNode node) { boolean mustAutomaticallyExpand = false; if (controller.isAutomaticExpand()) { // Limit the number of expansion levels to 3 int nLevels = 0; TreeNode parent = node; while (parent != null) { nLevels ++; parent = parent.getParent(); } mustAutomaticallyExpand = nLevels <= 4; } return mustAutomaticallyExpand; } /** * Detects whether the entries has children or not. * @throws SearchAbandonException if the search was abandoned. * @throws NamingException if an error during the search occurred. */ private void runDetectChildren() throws SearchAbandonException, NamingException { if (controller.isShowContainerOnly() || !isNumSubOrdinatesUsable()) { runDetectChildrenManually(); } else { SearchResult entry = getDisplayedEntry(); isLeafNode = !BrowserController.getHasSubOrdinates(entry); } } /** * Detects whether the entry has children by performing a search using the * entry as base DN. * @throws SearchAbandonException if there is an error. */ private void runDetectChildrenManually() throws SearchAbandonException { BasicNode parentNode = getNode(); InitialLdapContext ctx = null; NamingEnumeration<SearchResult> searchResults = null; try { // We set the search constraints so that only one entry is returned. // It's enough to know if the entry has children or not. SearchControls ctls = controller.getBasicSearchControls(); ctls.setCountLimit(1); ctls.setReturningAttributes( new String[] { SchemaConstants.NO_ATTRIBUTES }); if (useCustomFilter()) { ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); } else { ctls.setSearchScope(SearchControls.OBJECT_SCOPE); } // Send an LDAP search ctx = controller.findConnectionForDisplayedEntry(parentNode); searchResults = ctx.search( new LdapName(controller.findBaseDNForChildEntries(parentNode)), controller.getChildSearchFilter(), ctls); throwAbandonIfNeeded(null); isLeafNode = true; // Check if parentNode has children while (searchResults.hasMoreElements()) { isLeafNode = false; } } catch (SizeLimitExceededException e) { // We are just searching for an entry, but if there is more than one // this exception will be thrown. We call sr.hasMore after the // first entry has been retrieved to avoid sending a systematic // abandon when closing the searchResults NamingEnumeration. // See CR 6976906. } catch (NamingException x) { throwAbandonIfNeeded(x); } finally { if (ctx != null) { controller.releaseLDAPConnection(ctx); } if (searchResults != null) { try { searchResults.close(); } catch (NamingException x) { throwAbandonIfNeeded(x); } } } } // NUMSUBORDINATE HACK // numsubordinates is not usable if the displayed entry // is listed in in the hacker. // Note: *usable* means *usable for detecting children presence*. private boolean isNumSubOrdinatesUsable() throws NamingException { boolean result; SearchResult entry = getDisplayedEntry(); boolean hasSubOrdinates = BrowserController.getHasSubOrdinates(entry); if (!hasSubOrdinates) { // We must check LDAPURL url = getDisplayedUrl(); if (controller.getNumSubordinateHacker().contains(url)) { // The numSubOrdinate we have is unreliable. result = false; // System.out.println("numSubOrdinates of " + url + // " is not reliable"); } else { result = true; } } else { // Other values are usable result = true; } return result; } /** * Searchs for the children. * @throws SearchAbandonException if an error occurs. */ private void runSearchChildren() throws SearchAbandonException { InitialLdapContext ctx = null; BasicNode parentNode = getNode(); parentNode.setSizeLimitReached(false); try { // Send an LDAP search SearchControls ctls = controller.getBasicSearchControls(); if (useCustomFilter()) { ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); } else { ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE); } ctls.setReturningAttributes(controller.getAttrsForRedSearch()); ctx = controller.findConnectionForDisplayedEntry(parentNode); String parentDn = controller.findBaseDNForChildEntries(parentNode); int parentComponents; try { DN dn = DN.decode(parentDn); parentComponents = dn.getNumComponents(); } catch (Throwable t) { throw new RuntimeException("Error decoding dn: "+parentDn+" . "+t, t); } NamingEnumeration<SearchResult> entries = ctx.search( new LdapName(parentDn), controller.getChildSearchFilter(), ctls); try { while (entries.hasMore()) { SearchResult r = entries.next(); String name; if (r.getName().length() == 0) { continue; } else { name = unquoteRelativeName(r.getName())+","+parentDn; } boolean add = false; if (useCustomFilter()) { // Check that is an immediate child: use a faster method by just // comparing the number of components. DN dn = null; try { dn = DN.decode(name); add = dn.getNumComponents() == parentComponents + 1; } catch (Throwable t) { throw new RuntimeException("Error decoding dns: "+t, t); } if (!add) { // Is not a direct child. Check if the parent has been added, // if it is the case, do not add the parent. If is not the case, // search for the parent and add it. RDN[] rdns = new RDN[parentComponents + 1]; int diff = dn.getNumComponents() - rdns.length; for (int i=0; i < rdns.length; i++) { rdns[i] = dn.getRDN(i + diff); } final DN parentToAddDN = new DN(rdns); boolean mustAddParent = true; for (SearchResult addedEntry : childEntries) { try { DN addedDN = DN.decode(addedEntry.getName()); if (addedDN.equals(parentToAddDN)) { mustAddParent = false; break; } } catch (Throwable t) { throw new RuntimeException("Error decoding dn: "+ addedEntry.getName()+" . "+t, t); } } if (mustAddParent) { final boolean resultValue[] = {true}; // Check the children added to the tree try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { for (int i=0; i<getNode().getChildCount(); i++) { BasicNode node = (BasicNode)getNode().getChildAt(i); try { DN dn = DN.decode(node.getDN()); if (dn.equals(parentToAddDN)) { resultValue[0] = false; break; } } catch (Throwable t) { throw new RuntimeException("Error decoding dn: "+ node.getDN()+" . "+t, t); } } } }); } catch (Throwable t) { // Ignore } mustAddParent = resultValue[0]; } if (mustAddParent) { SearchResult parentResult = searchManuallyEntry(ctx, parentToAddDN.toString()); childEntries.add(parentResult); } } } else { add = true; } if (add) { r.setName(name); childEntries.add(r); // Time to time we update the display if (childEntries.size() >= 20) { changeStateTo(State.SEARCHING_CHILDREN); childEntries.clear(); } } throwAbandonIfNeeded(null); } } finally { entries.close(); } } catch (SizeLimitExceededException slee) { parentNode.setSizeLimitReached(true); } catch (NamingException x) { throwAbandonIfNeeded(x); } finally { if (ctx != null) { controller.releaseLDAPConnection(ctx); } } } /** * Returns the entry for the given dn. * The code assumes that the request controls are set in the connection. * @param ctx the connection to be used. * @param dn the DN of the entry to be searched. * @throws NamingException if an error occurs. */ private SearchResult searchManuallyEntry(InitialLdapContext ctx, String dn) throws NamingException { SearchResult sr = null; // Send an LDAP search SearchControls ctls = controller.getBasicSearchControls(); ctls.setSearchScope(SearchControls.OBJECT_SCOPE); ctls.setReturningAttributes(controller.getAttrsForRedSearch()); NamingEnumeration<SearchResult> entries = ctx.search( new LdapName(dn), controller.getObjectSearchFilter(), ctls); try { while (entries.hasMore()) { sr = entries.next(); sr.setName(dn); } } finally { entries.close(); } return sr; } /** * Utilities */ /** * Change the state of the task and inform the BrowserController. * @param newState the new state for the refresher. */ private void changeStateTo(State newState) throws SearchAbandonException { State oldState = state; state = newState; try { controller.invokeRefreshTaskDidProgress(this, oldState, newState); } catch(InterruptedException x) { throwAbandonIfNeeded(x); } } /** * Transform an exception into a TaskAbandonException. * If no exception is passed, the routine checks if the task has * been canceled and throws an TaskAbandonException accordingly. * @param x the exception. * @throws SearchAbandonException if the task/refresher must be abandoned. */ private void throwAbandonIfNeeded(Exception x) throws SearchAbandonException { SearchAbandonException tax = null; if (x != null) { if ((x instanceof InterruptedException) || (x instanceof InterruptedNamingException)) { tax = new SearchAbandonException(State.INTERRUPTED, x, null); } else { tax = new SearchAbandonException(State.FAILED, x, null); } } else if (isCanceled()) { tax = new SearchAbandonException(State.CANCELLED, null, null); } if (tax != null) { throw tax; } } /** * Removes the quotes surrounding the provided name. JNDI can return relative * names with this format. * @param name the relative name to be treated. * @return an String representing the provided relative name without * surrounding quotes. */ private String unquoteRelativeName(String name) { if ((name.length() > 0) && (name.charAt(0) == '"')) { if (name.charAt(name.length() - 1) == '"') { return name.substring(1, name.length() - 1); } else { return name.substring(1); } } else { return name; } } /** * DEBUG : Dump the state of the task. */ void dump() { System.out.println("============="); System.out.println(" node: " + getNode().getDN()); System.out.println(" recursive: " + recursive); System.out.println(" differential: " + differential); System.out.println(" state: " + state); System.out.println(" localEntry: " + localEntry); System.out.println(" remoteEntry: " + remoteEntry); System.out.println(" remoteUrl: " + remoteUrl); System.out.println(" isLeafNode: " + isLeafNode); System.out.println(" exception: " + exception); System.out.println(" exceptionArg: " + exceptionArg); System.out.println("============="); } /** * Checks that the entry's objectClass contains 'referral' and that the * attribute 'ref' is present. * @param entry the search result. * @return <CODE>true</CODE> if the entry's objectClass contains 'referral' * and the attribute 'ref' is present and <CODE>false</CODE> otherwise. * @throws NamingException if an error occurs. */ static boolean isReferralEntry(SearchResult entry) throws NamingException { boolean result = false; Set<String> ocValues = ConnectionUtils.getValues(entry, "objectClass"); if (ocValues != null) { for (String value : ocValues) { boolean isReferral = value.equalsIgnoreCase("referral"); if (isReferral) { result = (ConnectionUtils.getFirstValue(entry, "ref") != null); break; } } } return result; } /** * Returns the scope to be used in a JNDI request based on the information * of an LDAP URL. * @param url the LDAP URL. * @return the scope to be used in a JNDI request. */ private int getJNDIScope(LDAPURL url) { int scope; if (url.getScope() != null) { switch (url.getScope()) { case BASE_OBJECT: scope = SearchControls.OBJECT_SCOPE; break; case WHOLE_SUBTREE: scope = SearchControls.SUBTREE_SCOPE; break; case SUBORDINATE_SUBTREE: scope = SearchControls.ONELEVEL_SCOPE; break; case SINGLE_LEVEL: scope = SearchControls.ONELEVEL_SCOPE; break; default: scope = SearchControls.OBJECT_SCOPE; } } else { scope = SearchControls.OBJECT_SCOPE; } return scope; } /** * Returns the filter to be used in a JNDI request based on the information * of an LDAP URL. * @param url the LDAP URL. * @return the filter. */ private String getJNDIFilter(LDAPURL url) { String filter = url.getRawFilter(); if (filter == null) { filter = controller.getObjectSearchFilter(); } return filter; } /** * Check that there is no loop in terms of DIT (the check basically identifies * whether we are pointing to an entry above in the same server). * @param url the URL to the remote entry. It is assumed that the base DN * of the URL points to the remote entry. * @param referral the referral used to retrieve the remote entry. * @throws SearchAbandonException if there is a loop issue (the remoteEntry * is actually an entry in the same server as the local entry but above in the * DIT). */ private void checkLoopInReferral(LDAPURL url, String referral) throws SearchAbandonException { boolean checkSucceeded = true; try { DN dn1 = DN.decode(getNode().getDN()); DN dn2 = url.getBaseDN(); if (dn2.isAncestorOf(dn1)) { String host = url.getHost(); int port = url.getPort(); String adminHost = ConnectionUtils.getHostName( controller.getConfigurationConnection()); int adminPort = ConnectionUtils.getPort(controller.getConfigurationConnection()); checkSucceeded = (port != adminPort) || !adminHost.equalsIgnoreCase(host); if (checkSucceeded) { String hostUserData = ConnectionUtils.getHostName( controller.getUserDataConnection()); int portUserData = ConnectionUtils.getPort(controller.getUserDataConnection()); checkSucceeded = (port != portUserData) || !hostUserData.equalsIgnoreCase(host); } } } catch (OpenDsException odse) { // Ignore } if (!checkSucceeded) { throw new SearchAbandonException( State.FAILED, new ReferralLimitExceededException( ERR_CTRL_PANEL_REFERRAL_LOOP.get(url.getRawBaseDN())), referral); } } }