mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

jvergara
13.19.2010 7399fdb018d2248ec36edb30f5339cdcafbe2e98
Fix for issues https://opends.dev.java.net/issues/show_bug.cgi?id=4484 and https://opends.dev.java.net/issues/show_bug.cgi?id=4485.

Both issues are related to referrals. With these modifications:
1. If the user selects to follow referrals and they cannot be followed, an error message appears when the user selects the referral entry.
2. If the LDAP URL in the referral has no host/port specified, the local server is used.
3. An error message is displayed when there is a DIT loop condition (a referral in a server points to an entry in the same server that is an ascentor of the referral entry).
4. The scope and filter of the referral are honoured in a best-effort mode: the first entry of the referral filter is used to 'follow' the referral. So there is an accepted limitation in this area: if a referral has a filter where several entries can be retrieved, the first entry found is used to represent the node (and thus different refreshes in the browser can make that different entries appear in the place of the referral entry).
10 files modified
437 ■■■■ changed files
opends/src/guitools/org/opends/guitools/controlpanel/browser/BrowserController.java 25 ●●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/browser/IconPool.java 4 ●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/browser/NodeRefresher.java 167 ●●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/browser/ReferralLimitExceededException.java 4 ●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/ui/BrowseEntriesPanel.java 52 ●●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/ui/ErrorSearchingEntryPanel.java 131 ●●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/ui/LDAPEntryPanel.java 22 ●●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/ui/StatusGenericPanel.java 2 ●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/ui/nodes/BasicNode.java 14 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/admin_tool.properties 16 ●●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/browser/BrowserController.java
@@ -159,7 +159,7 @@
    tree.setCellRenderer(new BrowserCellRenderer());
    displayFlags = DISPLAY_ACI_COUNT;
    displayAttribute = RDN_ATTRIBUTE;
    followReferrals = false;
    followReferrals = true;
    sorted = false;
    showContainerOnly = true;
    containerClasses = new String[0];
@@ -1436,20 +1436,23 @@
        removeOneNode(node);
      }
      else {
        if (oldState == NodeRefresher.State.SOLVING_REFERRAL) {
        if (oldState == NodeRefresher.State.SOLVING_REFERRAL)
        {
          node.setRemoteUrl(task.getRemoteUrl());
          if (task.getRemoteEntry() != null) {
          if (task.getRemoteEntry() != null)
          {
            /* This is the case when there are multiple hops in the referral
             and so we have a remote referral entry but not the entry that it
             points to */
           and so we have a remote referral entry but not the entry that it
           points to */
            updateNodeRendering(node, task.getRemoteEntry());
          }
          /* It is a referral and we try to follow referrals.
           We remove its children (that are supposed to be
           entries on the remote server).
           If this referral entry has children locally (even if this goes
           against the recommendation of the standards) these children will
           NOT be displayed. */
         We remove its children (that are supposed to be
         entries on the remote server).
         If this referral entry has children locally (even if this goes
         against the recommendation of the standards) these children will
         NOT be displayed. */
          node.setLeaf(true);
          removeAllChildNodes(node, true /* Keep suffixes */);
        }
@@ -1793,7 +1796,7 @@
      newIcon = iconPool.getIcon(objectClasses, modifiers);
    }
    // Contruct the icon text according the dn, the aci count...
    // Construct the icon text according the dn, the aci count...
    StringBuilder sb2 = new StringBuilder();
    if (aciCount >= 1) {
      sb2.append(String.valueOf(aciCount));
opends/src/guitools/org/opends/guitools/controlpanel/browser/IconPool.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.guitools.controlpanel.browser;
@@ -304,7 +304,7 @@
      result = getReferralMaskIcon();
    }
    if ((modifiers & MODIFIER_ERROR) != 0) {
      result = maskedIcon(result, getErrorMaskIcon());
      result = getErrorMaskIcon();
    }
    return result;
opends/src/guitools/org/opends/guitools/controlpanel/browser/NodeRefresher.java
@@ -22,11 +22,13 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.guitools.controlpanel.browser;
import static org.opends.messages.AdminToolMessages.*;
import java.util.ArrayList;
import java.util.Set;
@@ -42,26 +44,29 @@
import javax.swing.SwingUtilities;
import javax.swing.tree.TreeNode;
import org.opends.admin.ads.ServerDescriptor;
import org.opends.admin.ads.util.ConnectionUtils;
import org.opends.guitools.controlpanel.ui.nodes.BasicNode;
import org.opends.messages.AdminToolMessages;
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.
 */
class NodeRefresher extends AbstractNodeTask {
public class NodeRefresher extends AbstractNodeTask {
  /**
   * The enumeration containing all the states the refresher can have.
   *
   */
  enum State
  public enum State
  {
    /**
     * The refresher is queued, but not started.
@@ -464,6 +469,14 @@
      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) ||
@@ -482,21 +495,39 @@
              remoteDn, url.getAttributes(), url.getScope(), url.getRawFilter(),
                 url.getExtensions());
        }
        if (useCustomFilter())
        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(SearchControls.OBJECT_SCOPE);
        ctls.setSearchScope(scope);
        ctls.setCountLimit(1);
        NamingEnumeration<SearchResult> sr = ctx.search(remoteDn,
              controller.getObjectSearchFilter(),
              ctls);
            filter,
            ctls);
        if (sr.hasMore())
        {
          entry = sr.next();
          entry.setName(remoteDn);
          String name;
          if (entry.getName().length() == 0)
          {
            name = remoteDn;
          }
          else
          {
            name = unquoteRelativeName(entry.getName())+","+remoteDn;
          }
          entry.setName(name);
        }
        else
        {
          throw new NameNotFoundException();
        }
        throwAbandonIfNeeded(null);
      }
@@ -522,7 +553,17 @@
      throw new SearchAbandonException(
          State.FAILED, lastException, lastExceptionArg);
    }
    else {
    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;
    }
@@ -877,7 +918,7 @@
  /**
   * Transform an exception into a TaskAbandonException.
   * If no exception is passed, the routine checks if the task has
   * been cancelled and throws an TaskAbandonException accordingly.
   * been canceled and throws an TaskAbandonException accordingly.
   * @param x the exception.
   * @throws SearchAbandonException if the task/refresher must be abandoned.
   */
@@ -970,4 +1011,110 @@
    }
    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)
    {
      String hostPort = ServerDescriptor.getServerRepresentation(url.getHost(),
          url.getPort());
      throw new SearchAbandonException(
          State.FAILED, new ReferralLimitExceededException(
              ERR_CTRL_PANEL_REFERRAL_LOOP.get(url.getRawBaseDN())), referral);
    }
  }
}
opends/src/guitools/org/opends/guitools/controlpanel/browser/ReferralLimitExceededException.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.guitools.controlpanel.browser;
@@ -36,7 +36,7 @@
 * following referrals.
 *
 */
class ReferralLimitExceededException extends NamingException
public class ReferralLimitExceededException extends NamingException
{
  private static final long serialVersionUID = -5640515839144837865L;
opends/src/guitools/org/opends/guitools/controlpanel/ui/BrowseEntriesPanel.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.guitools.controlpanel.ui;
@@ -72,6 +72,7 @@
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import org.opends.guitools.controlpanel.browser.NodeRefresher;
import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
@@ -544,7 +545,19 @@
    if (node != null)
    {
      String dn;
      if (controller.getFollowReferrals() && node.getRemoteUrl() != null)
      if (controller.getFollowReferrals() &&
          node.getReferral() != null &&
          node.getRemoteUrl() == null &&
          node.getError() != null &&
          node.getError().getState() == NodeRefresher.State.SOLVING_REFERRAL)
      {
        // We are in the case where we are following referrals but the referral
        // could not be resolved.  Display an error.
        entryPane.referralSolveError(node.getDN(), node.getReferral(),
            node.getError());
        dn = null;
      }
      else if (controller.getFollowReferrals() && node.getRemoteUrl() != null)
      {
        dn = node.getRemoteUrl().getRawBaseDN();
      }
@@ -553,24 +566,27 @@
        dn = node.getDN();
      }
      try
      if (dn != null)
      {
        InitialLdapContext ctx =
          controller.findConnectionForDisplayedEntry(node);
        LDAPEntryReader reader = new LDAPEntryReader(dn, ctx);
        reader.addEntryReadListener(entryPane);
        // Required to update the browser controller properly if the entry is
        // deleted.
        entryPane.setTreePath(path);
        stopCurrentReader();
        startReader(reader);
      }
      catch (Throwable t)
      {
        if (!isInterruptedException(t))
        try
        {
          EntryReadErrorEvent ev = new EntryReadErrorEvent(this, dn, t);
          entryPane.entryReadError(ev);
          InitialLdapContext ctx =
            controller.findConnectionForDisplayedEntry(node);
          LDAPEntryReader reader = new LDAPEntryReader(dn, ctx);
          reader.addEntryReadListener(entryPane);
          // Required to update the browser controller properly if the entry is
          // deleted.
          entryPane.setTreePath(path);
          stopCurrentReader();
          startReader(reader);
        }
        catch (Throwable t)
        {
          if (!isInterruptedException(t))
          {
            EntryReadErrorEvent ev = new EntryReadErrorEvent(this, dn, t);
            entryPane.entryReadError(ev);
          }
        }
      }
    }
opends/src/guitools/org/opends/guitools/controlpanel/ui/ErrorSearchingEntryPanel.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.guitools.controlpanel.ui;
@@ -31,9 +31,18 @@
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import org.opends.guitools.controlpanel.browser.BasicNodeError;
import org.opends.guitools.controlpanel.browser.ReferralLimitExceededException;
import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.quicksetup.util.Utils;
import org.opends.server.types.LDAPURL;
import org.opends.server.types.OpenDsException;
/**
@@ -52,7 +61,16 @@
  {
    super();
    GridBagConstraints gbc = new GridBagConstraints();
    addErrorPane(gbc);
    gbc.gridx = 0;
    gbc.gridy = 0;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    gbc.weightx = 1.0;
    gbc.anchor = GridBagConstraints.CENTER;
    gbc.fill = GridBagConstraints.BOTH;
    gbc.insets = new Insets(20, 20, 0, 20);
    createErrorPane();
    add(errorPane, gbc);
    errorPane.setVisible(true);
  }
@@ -109,4 +127,113 @@
    updateErrorPane(errorPane, title, ColorAndFontConstants.errorTitleFont,
        details, ColorAndFontConstants.defaultFont);
  }
  /**
   * Sets the error to be displayed in the panel.
   * @param dn the DN of the local entry.
   * @param referrals the list of referrals defined in the entry.
   * @param error the error that occurred resolving the referral.
   */
  public void setReferralError(String dn, String[] referrals,
      BasicNodeError error)
  {
    Message title = INFO_CTRL_PANEL_ERROR_RESOLVING_REFERRAL_TITLE.get();
    MessageBuilder details = new MessageBuilder();
    StringBuilder sb = new StringBuilder();
    for (String ref: referrals)
    {
      if (sb.length() > 0)
      {
        sb.append("<br>");
      }
      sb.append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"+ref);
    }
    details.append(INFO_CTRL_PANEL_ERROR_RESOLVING_REFERRAL_MSG.get(dn, sb));
    Exception ex = error.getException();
    if (ex instanceof NamingException)
    {
      Object arg = error.getArg();
      Message msg = null;
      if (arg != null)
      {
        // Maybe is the LDAPURL
        try
        {
          LDAPURL url = LDAPURL.decode(arg.toString(), false);
          if (url.getHost() != null)
          {
            String hostPort = url.getHost()+":"+url.getPort();
            if (ex instanceof ReferralLimitExceededException)
            {
              msg = Message.raw(ex.getLocalizedMessage());
            }
            else if (ex instanceof NameNotFoundException)
            {
              msg =
                ERR_CTRL_PANEL_COULD_NOT_FIND_PROVIDED_ENTRY_IN_REFERRAL.get(
                    arg.toString(), hostPort);
            }
            else
            {
              msg = Utils.getMessageForException((NamingException)ex, hostPort);
            }
          }
          else
          {
            if (ex instanceof ReferralLimitExceededException)
            {
              msg = Message.raw(ex.getLocalizedMessage());
            }
            else if (ex instanceof NameNotFoundException)
            {
              msg =
           ERR_CTRL_PANEL_COULD_NOT_FIND_PROVIDED_ENTRY_IN_REFERRAL_NO_HOST.get(
                    arg.toString());
            }
            else
            {
              msg = Utils.getMessageForException((NamingException)ex);
            }
          }
        }
        catch (Throwable t)
        {
        }
      }
      if (msg == null)
      {
        if (ex instanceof ReferralLimitExceededException)
        {
          msg = Message.raw(ex.getLocalizedMessage());
        }
        else
        {
          msg = Utils.getMessageForException((NamingException)ex);
        }
      }
      if (arg != null)
      {
        details.append("<br><br>"+
            ERR_CTRL_PANEL_RESOLVING_REFERRAL_DETAILS.get(arg.toString(),
                msg));
      }
      else
      {
        details.append("<br><br>"+INFO_CTRL_PANEL_DETAILS_THROWABLE.get(msg));
      }
    }
    else if (ex != null)
    {
      String msg = ex.getLocalizedMessage();
      if (msg == null)
      {
        msg = ex.toString();
      }
      details.append("<br><br>"+INFO_CTRL_PANEL_DETAILS_THROWABLE.get(msg));
    }
    details.append("<br><br>"+INFO_CTRL_PANEL_HOW_TO_EDIT_REFERRALS.get());
    updateErrorPane(errorPane, title, ColorAndFontConstants.errorTitleFont,
        details.toMessage(), ColorAndFontConstants.defaultFont);
  }
}
opends/src/guitools/org/opends/guitools/controlpanel/ui/LDAPEntryPanel.java
@@ -45,6 +45,7 @@
import javax.swing.border.EmptyBorder;
import javax.swing.tree.TreePath;
import org.opends.guitools.controlpanel.browser.BasicNodeError;
import org.opends.guitools.controlpanel.browser.BrowserController;
import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
@@ -347,6 +348,27 @@
  }
  /**
   * Displays a message informing that an error occurred resolving a referral.
   * @param dn the DN of the local entry.
   * @param referrals the list of referrals defined in the entry.
   * @param error the error that occurred resolving the referral.
   */
  public void referralSolveError(String dn, String[] referrals,
      BasicNodeError error)
  {
    searchResult = null;
    errorSearchingPanel.setReferralError(dn, referrals, error);
    delete.setVisible(false);
    saveChanges.setVisible(false);
    cardLayout.show(mainPanel, ERROR_SEARCHING);
    displayedEntryPanel = null;
  }
  /**
   * Displays a panel informing that nothing is selected.
   *
   */
opends/src/guitools/org/opends/guitools/controlpanel/ui/StatusGenericPanel.java
@@ -1154,7 +1154,7 @@
  protected void displayMessage(Message msg)
  {
    message.setText(Utilities.applyFont(msg.toString(),
        ColorAndFontConstants.progressFont));
        ColorAndFontConstants.defaultFont));
    cardLayout.show(cardPanel, MESSAGE_PANEL);
  }
opends/src/guitools/org/opends/guitools/controlpanel/ui/nodes/BasicNode.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.guitools.controlpanel.ui.nodes;
@@ -120,8 +120,16 @@
   */
  public void setRemoteUrl(LDAPURL url) {
    remoteUrl = url;
    remoteRdn = extractRDN(remoteUrl.getRawBaseDN());
    remoteRdnWithAttributeName = extractRDN(remoteUrl.getRawBaseDN(), true);
    if (remoteUrl != null)
    {
      remoteRdn = extractRDN(remoteUrl.getRawBaseDN());
      remoteRdnWithAttributeName = extractRDN(remoteUrl.getRawBaseDN(), true);
    }
    else
    {
      remoteRdn = null;
      remoteRdnWithAttributeName = null;
    }
  }
  /**
opends/src/messages/messages/admin_tool.properties
@@ -1748,6 +1748,22 @@
INFO_CTRL_PANEL_CONFIRMATION_DELETE_BASE_DNS_DETAILS=The following base DN's \
 will be deleted.  All the entries defined on the base DN's will be deleted.
INFO_CTRL_PANEL_ERROR_SEARCHING_ENTRY_TITLE=Error searching entry
INFO_CTRL_PANEL_ERROR_RESOLVING_REFERRAL_TITLE=Error resolving referral
INFO_CTRL_PANEL_ERROR_RESOLVING_REFERRAL_MSG=Could not resolve the referrals \
 defined in entry '%s'.<br><br>The referrals of the entry are:<br>%s
MILD_ERR_CTRL_PANEL_RESOLVING_REFERRAL_DETAILS=The error occurred solving \
 referral '%s'.<br>Details: %s
MILD_ERR_CTRL_PANEL_COULD_NOT_FIND_PROVIDED_ENTRY_IN_REFERRAL=Could not find \
 entry specified in '%s'.  Check that the entry exists in server %s.
MILD_ERR_CTRL_PANEL_COULD_NOT_FIND_PROVIDED_ENTRY_IN_REFERRAL_NO_HOST=Could \
 not find entry specified in '%s'.  Check that the entry exists in server.
INFO_CTRL_PANEL_HOW_TO_EDIT_REFERRALS=To edit the referral in the entry, \
 deselect the 'Follow Referrals' option in the 'View' menu.
MILD_ERR_CTRL_PANEL_REFERRAL_LOOP=The selected referral is defined in server \
 %s and is referencing to an entry in the same server that is an ascentor of \
 the entry.  This configuration generates a loop in the DIT structure that \
 should be avoided.
#
# Note that the following property contains line breaks in HTML format (<br>)
#