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

matthew_swift
03.37.2009 95df5cfdba474acb03076953e992b898fbb277a8
opends/src/server/org/opends/server/core/networkgroups/NetworkGroup.java
@@ -26,177 +26,1375 @@
 */
package org.opends.server.core.networkgroups;
import org.opends.messages.Message;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.Validator.ensureNotNull;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.Validator.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.server.admin.std.meta.
        NetworkGroupResourceLimitsCfgDefn.ReferralBindPolicy;
import org.opends.server.admin.std.meta.
        NetworkGroupResourceLimitsCfgDefn.ReferralPolicy;
import org.opends.messages.Message;
import org.opends.server.admin.ClassPropertyDefinition;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.std.meta.QOSPolicyCfgDefn;
import org.opends.server.admin.std.server.NetworkGroupCfg;
import org.opends.server.admin.std.server.QOSPolicyCfg;
import org.opends.server.api.ClientConnection;
import org.opends.server.core.*;
import org.opends.server.api.QOSPolicy;
import org.opends.server.api.QOSPolicyFactory;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.RootDseWorkflowTopology;
import org.opends.server.core.Workflow;
import org.opends.server.core.WorkflowImpl;
import org.opends.server.core.WorkflowTopologyNode;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.types.AuthenticationType;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import org.opends.server.types.operation.PreParseOperation;
import org.opends.server.workflowelement.WorkflowElement;
/**
 * This class defines the network group. A network group is used to categorize
 * client connections. A network group is defined by a set of criteria, a
 * set of policies and a set of workflow nodes. A client connection belongs to
 * a network group whenever it satisfies all the network group criteria. As
 * soon as a client connection belongs to a network group, it has to comply
 * with all the network group policies. Any cleared client operation can be
 * routed to one the network group workflow nodes.
 * This class defines the network group. A network group is used to
 * categorize client connections. A network group is defined by a set of
 * criteria, a set of policies and a set of workflow nodes. A client
 * connection belongs to a network group whenever it satisfies all the
 * network group criteria. As soon as a client connection belongs to a
 * network group, it has to comply with all the network group policies.
 * Any cleared client operation can be routed to one the network group
 * workflow nodes.
 */
public class NetworkGroup
{
  // Workflow nodes registered with the current network group.
  // Keys are workflowIDs.
  private TreeMap<String, WorkflowTopologyNode> registeredWorkflowNodes =
      new TreeMap<String, WorkflowTopologyNode>();
  /**
   * Configuration change listener for user network groups.
   */
  private final class ChangeListener implements
      ConfigurationChangeListener<NetworkGroupCfg>
  {
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationChange(
        NetworkGroupCfg configuration)
    {
      ResultCode resultCode = ResultCode.SUCCESS;
      boolean adminActionRequired = false;
      List<Message> messages = new ArrayList<Message>();
      // Update the priority.
      setNetworkGroupPriority(configuration.getPriority());
      // Deregister any workflows that have been removed.
      SortedSet<String> configWorkflows = configuration.getWorkflow();
      for (String id : getRegisteredWorkflows())
      {
        if (!configWorkflows.contains(id))
        {
          deregisterWorkflow(id);
        }
      }
      // Register any workflows that have been added.
      List<String> ngWorkflows = getRegisteredWorkflows();
      for (String id : configuration.getWorkflow())
      {
        if (!ngWorkflows.contains(id))
        {
          WorkflowImpl workflowImpl =
              (WorkflowImpl) WorkflowImpl.getWorkflow(id);
          try
          {
            registerWorkflow(workflowImpl);
          }
          catch (DirectoryException e)
          {
            if (resultCode == ResultCode.SUCCESS)
            {
              resultCode = e.getResultCode();
            }
            messages.add(e.getMessageObject());
          }
        }
      }
      try
      {
        criteria = decodeConnectionCriteriaConfiguration(configuration);
      }
      catch (ConfigException e)
      {
        resultCode = DirectoryServer.getServerErrorResultCode();
        messages.add(e.getMessageObject());
      }
      // Update the configuration.
      NetworkGroup.this.configuration = configuration;
      return new ConfigChangeResult(resultCode, adminActionRequired,
          messages);
    }
  // A lock to protect concurrent access to the registered Workflow nodes.
  private Object registeredWorkflowNodesLock = new Object();
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationChangeAcceptable(
        NetworkGroupCfg configuration, List<Message> unacceptableReasons)
    {
      return isConfigurationAcceptable(configuration,
          unacceptableReasons);
    }
  }
  /**
   * Configuration change listener for user network group QOS policies.
   */
  private final class QOSPolicyListener implements
      ConfigurationAddListener<QOSPolicyCfg>,
      ConfigurationDeleteListener<QOSPolicyCfg>
  {
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationAdd(
        QOSPolicyCfg configuration)
    {
      ResultCode resultCode = ResultCode.SUCCESS;
      boolean adminActionRequired = false;
      List<Message> messages = new ArrayList<Message>();
      try
      {
        createNetworkGroupQOSPolicy(configuration);
      }
      catch (ConfigException e)
      {
        messages.add(e.getMessageObject());
        resultCode = DirectoryServer.getServerErrorResultCode();
      }
      catch (InitializationException e)
      {
        messages.add(e.getMessageObject());
        resultCode = DirectoryServer.getServerErrorResultCode();
      }
      return new ConfigChangeResult(resultCode, adminActionRequired,
          messages);
    }
  // The workflow node for the rootDSE entry. The RootDSE workflow node
  // is not stored in the list of registered workflow nodes.
  private RootDseWorkflowTopology rootDSEWorkflowNode = null;
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationDelete(
        QOSPolicyCfg configuration)
    {
      QOSPolicy policy = policies.remove(configuration.dn());
      if (policy != null)
      {
        if (requestFilteringPolicy == policy)
        {
          requestFilteringPolicy = null;
        }
        else if (resourceLimitsPolicy == policy)
        {
          resourceLimitsPolicy = null;
        }
        policy.finalizeQOSPolicy();
      }
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
  // List of naming contexts handled by the network group.
  private NetworkGroupNamingContexts namingContexts =
      new NetworkGroupNamingContexts();
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationAddAcceptable(
        QOSPolicyCfg configuration, List<Message> unacceptableReasons)
    {
      return isNetworkGroupQOSPolicyConfigurationAcceptable(
          configuration, unacceptableReasons);
    }
  // The default network group (singleton).
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationDeleteAcceptable(
        QOSPolicyCfg configuration, List<Message> unacceptableReasons)
    {
      // Always ok.
      return true;
    }
  }
  // The admin network group has no criterion, no policy,
  // and gives access to all the workflows.
  private static final String ADMIN_NETWORK_GROUP_NAME = "admin";
  private static NetworkGroup adminNetworkGroup =
      new NetworkGroup(ADMIN_NETWORK_GROUP_NAME);
  // The default network group has no criterion, no policy, and gives
  // access to all the workflows. The purpose of the default network
  // group is to allow new clients to perform a first operation before
  // they can be attached to a specific network group.
  private static final String DEFAULT_NETWORK_GROUP_NAME = "default";
  private final boolean isDefaultNetworkGroup;
  private static NetworkGroup defaultNetworkGroup =
      new NetworkGroup (DEFAULT_NETWORK_GROUP_NAME);
      new NetworkGroup(DEFAULT_NETWORK_GROUP_NAME);
  // The admin network group (singleton).
  // The admin network group has no criterion, no policy, and gives
  // access to all the workflows.
  private static final String ADMIN_NETWORK_GROUP_NAME = "admin";
  private final boolean isAdminNetworkGroup;
  private static NetworkGroup adminNetworkGroup =
      new NetworkGroup (ADMIN_NETWORK_GROUP_NAME);
  // The internal network group (singleton).
  // The internal network group has no criterion, no policy, and gives
  // access to all the workflows. The purpose of the internal network
  // group is to allow internal connections to perform operations.
  private static final String INTERNAL_NETWORK_GROUP_NAME = "internal";
  private boolean isInternalNetworkGroup;
  private static NetworkGroup internalNetworkGroup =
      new NetworkGroup(INTERNAL_NETWORK_GROUP_NAME);
  // The ordered list of network groups.
  private static List<NetworkGroup> orderedNetworkGroups =
      new ArrayList<NetworkGroup>();
  // The list of all network groups that are registered with the server.
  // The defaultNetworkGroup is not in the list of registered network groups.
  // The defaultNetworkGroup is not in the list of registered network
  // groups.
  private static TreeMap<String, NetworkGroup> registeredNetworkGroups =
      new TreeMap<String, NetworkGroup>();
  // A lock to protect concurrent access to the registeredNetworkGroups.
  private static Object registeredNetworkGroupsLock = new Object();
  // The ordered list of network groups.
  private static List<NetworkGroup> orderedNetworkGroups =
      new ArrayList<NetworkGroup>();
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The network group internal identifier.
  private String networkGroupID = null;
  // The network group priority
  private int priority = 100;
  // The network group criteria.
  private NetworkGroupCriteria criteria = null;
  // The network group resource limits
  private ResourceLimits resourceLimits = null;
  // The network group request filtering policy
  private RequestFilteringPolicy requestFilteringPolicy = null;
  // The statistics
  private NetworkGroupStatistics stats;
  // The client connection affinity policy.
  private ClientConnectionAffinityPolicy affinityPolicy =
    ClientConnectionAffinityPolicy.NONE;
  // The client connection affinity timeout (number of seconds).
  private long affinityTimeout = 0;
  /**
   * Creates a new instance of the network group.
   *
   * @param networkGroupID  the network group internal identifier
   * Deregisters all network groups that have been registered. This
   * should be called when the server is shutting down.
   */
  public NetworkGroup(
      String networkGroupID
      )
  public static void deregisterAllOnShutdown()
  {
    synchronized (registeredNetworkGroupsLock)
    {
      // Invalidate all NetworkGroups so they cannot accidentally be
      // used after a restart.
      Collection<NetworkGroup> networkGroups =
          registeredNetworkGroups.values();
      for (NetworkGroup networkGroup : networkGroups)
      {
        networkGroup.invalidate();
      }
      defaultNetworkGroup.invalidate();
      adminNetworkGroup.invalidate();
      internalNetworkGroup.invalidate();
      registeredNetworkGroups = new TreeMap<String, NetworkGroup>();
      orderedNetworkGroups = new ArrayList<NetworkGroup>();
      defaultNetworkGroup = new NetworkGroup("default");
      adminNetworkGroup = new NetworkGroup("admin");
      internalNetworkGroup = new NetworkGroup("internal");
    }
  }
  /**
   * Gets the highest priority matching network group for a BIND op.
   *
   * @param connection
   *          the client connection
   * @param dn
   *          the operation bindDN
   * @param authType
   *          the operation authentication type
   * @param isSecure
   *          a boolean indicating whether the operation is secured
   * @return matching network group
   */
  static NetworkGroup findBindMatchingNetworkGroup(
      ClientConnection connection, DN dn, AuthenticationType authType,
      boolean isSecure)
  {
    for (NetworkGroup ng : orderedNetworkGroups)
    {
      if (ng.matchAfterBind(connection, dn, authType, isSecure))
      {
        return ng;
      }
    }
    return defaultNetworkGroup;
  }
  /**
   * Gets the highest priority matching network group.
   *
   * @param connection
   *          the client connection
   * @return matching network group
   */
  static NetworkGroup findMatchingNetworkGroup(
      ClientConnection connection)
  {
    for (NetworkGroup ng : orderedNetworkGroups)
    {
      if (ng.match(connection))
      {
        return ng;
      }
    }
    return defaultNetworkGroup;
  }
  /**
   * Returns the admin network group.
   *
   * @return the admin network group
   */
  public static NetworkGroup getAdminNetworkGroup()
  {
    return adminNetworkGroup;
  }
  /**
   * Returns the default network group. The default network group is
   * always defined and has no criterion, no policy and provide full
   * access to all the registered workflows.
   *
   * @return the default network group
   */
  public static NetworkGroup getDefaultNetworkGroup()
  {
    return defaultNetworkGroup;
  }
  /**
   * Returns the internal network group.
   *
   * @return the internal network group
   */
  public static NetworkGroup getInternalNetworkGroup()
  {
    return internalNetworkGroup;
  }
  /**
   * Gets the network group having the specified ID.
   * <p>
   * This method is for testing only.
   *
   * @param networkGroupID
   *          The network group ID.
   * @return The network group, of <code>null</code> if no match was found.
   */
  public static NetworkGroup getNetworkGroup(String networkGroupID)
  {
    return registeredNetworkGroups.get(networkGroupID);
  }
  /**
   * Resets the configuration of all the registered network groups.
   */
  public static void resetConfig()
  {
    // Reset the default network group
    defaultNetworkGroup.reset();
    adminNetworkGroup.reset();
    internalNetworkGroup.reset();
    // Reset all the registered network group
    synchronized (registeredNetworkGroupsLock)
    {
      registeredNetworkGroups = new TreeMap<String, NetworkGroup>();
      orderedNetworkGroups = new ArrayList<NetworkGroup>();
    }
  }
  /**
   * Initializes this network group as a user network group using the
   * provided configuration. The network group will monitor the
   * configuration and update its configuration when necessary.
   *
   * @param configuration
   *          The network group configuration.
   * @return The new user network group.
   * @throws ConfigException
   *           If an unrecoverable problem arises during initialization
   *           of the user network group as a result of the server
   *           configuration.
   * @throws InitializationException
   *           If a problem occurs during initialization of the user
   *           network group that is not related to the server
   *           configuration.
   */
  static NetworkGroup createUserNetworkGroup(
      NetworkGroupCfg configuration) throws InitializationException,
      ConfigException
  {
    NetworkGroup networkGroup = new NetworkGroup(configuration);
    try
    {
      // Set the priority.
      networkGroup.priority = configuration.getPriority();
      // Initialize the network group criteria.
      networkGroup.criteria =
          decodeConnectionCriteriaConfiguration(configuration);
      // Initialize the network group policies.
      for (String policyName : configuration
          .listNetworkGroupQOSPolicies())
      {
        QOSPolicyCfg policyConfiguration =
            configuration.getNetworkGroupQOSPolicy(policyName);
        networkGroup.createNetworkGroupQOSPolicy(policyConfiguration);
      }
      // Register the root DSE workflow with the network group.
      WorkflowImpl rootDSEworkflow =
          (WorkflowImpl) WorkflowImpl.getWorkflow("__root.dse__#");
      networkGroup.registerWorkflow(rootDSEworkflow);
      // Register the workflows with the network group.
      for (String workflowID : configuration.getWorkflow())
      {
        WorkflowImpl workflowImpl =
            (WorkflowImpl) WorkflowImpl.getWorkflow(workflowID);
        if (workflowImpl == null)
        {
          // The workflow does not exist, log an error message
          // and skip the workflow.
          Message message =
              INFO_ERR_WORKFLOW_DOES_NOT_EXIST.get(workflowID,
                  networkGroup.getID());
          logError(message);
        }
        else
        {
          networkGroup.registerWorkflow(workflowImpl);
        }
      }
      // Register all configuration change listeners.
      configuration.addChangeListener(networkGroup.changeListener);
      configuration
          .addNetworkGroupQOSPolicyAddListener(networkGroup.policyListener);
      configuration
          .addNetworkGroupQOSPolicyDeleteListener(networkGroup.policyListener);
      // Register the network group with the server.
      networkGroup.register();
    }
    catch (DirectoryException e)
    {
      networkGroup.finalizeNetworkGroup();
      throw new InitializationException(e.getMessageObject());
    }
    catch (InitializationException e)
    {
      networkGroup.finalizeNetworkGroup();
      throw e;
    }
    catch (ConfigException e)
    {
      networkGroup.finalizeNetworkGroup();
      throw e;
    }
    return networkGroup;
  }
  /**
   * Indicates whether the provided network group configuration is
   * acceptable.
   *
   * @param configuration
   *          The network group configuration.
   * @param unacceptableReasons
   *          A list that can be used to hold messages about why the
   *          provided configuration is not acceptable.
   * @return Returns <code>true</code> if the provided network group
   *         configuration is acceptable, or <code>false</code> if it is
   *         not.
   */
  static boolean isConfigurationAcceptable(
      NetworkGroupCfg configuration, List<Message> unacceptableReasons)
  {
    // The configuration is always acceptable if disabled.
    if (!configuration.isEnabled())
    {
      return true;
    }
    // Check that all the workflows in the network group have a
    // different base DN.
    boolean isAcceptable = true;
    Set<String> allBaseDNs = new HashSet<String>();
    for (String workflowId : configuration.getWorkflow())
    {
      WorkflowImpl workflow =
          (WorkflowImpl) WorkflowImpl.getWorkflow(workflowId);
      String baseDN = workflow.getBaseDN().toNormalizedString();
      if (allBaseDNs.contains(baseDN))
      {
        // This baseDN is duplicated
        Message message =
            ERR_WORKFLOW_BASE_DN_DUPLICATED_IN_NG.get(baseDN,
                getNameFromConfiguration(configuration));
        unacceptableReasons.add(message);
        isAcceptable = false;
        break;
      }
      else
      {
        allBaseDNs.add(baseDN);
      }
    }
    // Validate any policy configurations.
    for (String policyName : configuration
        .listNetworkGroupQOSPolicies())
    {
      try
      {
        QOSPolicyCfg policyCfg =
            configuration.getNetworkGroupQOSPolicy(policyName);
        if (!isNetworkGroupQOSPolicyConfigurationAcceptable(policyCfg,
            unacceptableReasons))
        {
          isAcceptable = false;
        }
      }
      catch (ConfigException e)
      {
        // This is bad - give up immediately.
        unacceptableReasons.add(e.getMessageObject());
        return false;
      }
    }
    // The bind DN patterns may be malformed.
    if (!configuration.getAllowedBindDN().isEmpty())
    {
      try
      {
        BindDNConnectionCriteria.decode(configuration
            .getAllowedBindDN());
      }
      catch (DirectoryException e)
      {
        unacceptableReasons.add(e.getMessageObject());
        isAcceptable = false;
      }
    }
    return isAcceptable;
  }
  // Decodes connection criteria configuration.
  private static ConnectionCriteria decodeConnectionCriteriaConfiguration(
      NetworkGroupCfg configuration) throws ConfigException
  {
    List<ConnectionCriteria> filters =
        new LinkedList<ConnectionCriteria>();
    if (!configuration.getAllowedAuthMethod().isEmpty())
    {
      filters.add(new AuthMethodConnectionCriteria(configuration
          .getAllowedAuthMethod()));
    }
    if (!configuration.getAllowedBindDN().isEmpty())
    {
      try
      {
        filters.add(BindDNConnectionCriteria.decode(configuration
            .getAllowedBindDN()));
      }
      catch (DirectoryException e)
      {
        throw new ConfigException(e.getMessageObject());
      }
    }
    if (!configuration.getAllowedClient().isEmpty()
        || !configuration.getDeniedClient().isEmpty())
    {
      filters.add(new IPConnectionCriteria(configuration
          .getAllowedClient(), configuration.getDeniedClient()));
    }
    if (!configuration.getAllowedProtocol().isEmpty())
    {
      filters.add(new ProtocolConnectionCriteria(configuration
          .getAllowedProtocol()));
    }
    if (configuration.isIsSecurityMandatory())
    {
      filters.add(SecurityConnectionCriteria.SECURITY_REQUIRED);
    }
    if (filters.isEmpty())
    {
      return ConnectionCriteria.TRUE;
    }
    else
    {
      return new ANDConnectionCriteria(filters);
    }
  }
  /**
   * Gets the name of the network group configuration.
   *
   * @param configuration
   *          The configuration.
   * @return The network group name.
   */
  private static String getNameFromConfiguration(NetworkGroupCfg configuration)
  {
    DN dn = configuration.dn();
    return dn.getRDN().getAttributeValue(0).getStringValue();
  }
  // Determines whether or not the new network group configuration's
  // implementation class is acceptable.
  private static boolean isNetworkGroupQOSPolicyConfigurationAcceptable(
      QOSPolicyCfg policyConfiguration,
      List<Message> unacceptableReasons)
  {
    String className = policyConfiguration.getJavaClass();
    QOSPolicyCfgDefn d = QOSPolicyCfgDefn.getInstance();
    ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition();
    // Validate the configuration.
    try
    {
      // Load the class and cast it to a network group policy factory.
      Class<? extends QOSPolicyFactory> theClass;
      QOSPolicyFactory factory;
      theClass = pd.loadClass(className, QOSPolicyFactory.class);
      factory = theClass.newInstance();
      // Determine the initialization method to use: it must take a
      // single parameter which is the exact type of the configuration
      // object.
      Method method =
          theClass.getMethod("isConfigurationAcceptable",
              QOSPolicyCfg.class, List.class);
      Boolean acceptable =
          (Boolean) method.invoke(factory, policyConfiguration,
              unacceptableReasons);
      if (!acceptable)
      {
        return false;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      unacceptableReasons
          .add(ERR_CONFIG_NETWORK_GROUP_POLICY_CANNOT_INITIALIZE.get(
              String.valueOf(className), String
                  .valueOf(policyConfiguration.dn()),
              stackTraceToSingleLineString(e)));
      return false;
    }
    // The configuration is valid as far as we can tell.
    return true;
  }
  // Change listener (active for user network groups).
  private final ChangeListener changeListener;
  // Current configuration (active for user network groups).
  private NetworkGroupCfg configuration = null;
  // The network group connection criteria.
  private ConnectionCriteria criteria = ConnectionCriteria.TRUE;
  private final boolean isAdminNetworkGroup;
  private final boolean isDefaultNetworkGroup;
  private final boolean isInternalNetworkGroup;
  // List of naming contexts handled by the network group.
  private NetworkGroupNamingContexts namingContexts =
      new NetworkGroupNamingContexts();
  // The network group internal identifier.
  private final String networkGroupID;
  // All network group policies mapping factory class name to policy.
  private final Map<DN, QOSPolicy> policies =
      new ConcurrentHashMap<DN, QOSPolicy>();
  // Add/delete policy listener (active for user network groups).
  private final QOSPolicyListener policyListener;
  // The network group priority.
  private int priority = 100;
  // Workflow nodes registered with the current network group.
  // Keys are workflowIDs.
  private TreeMap<String, WorkflowTopologyNode> registeredWorkflowNodes =
      new TreeMap<String, WorkflowTopologyNode>();
  // A lock to protect concurrent access to the registered Workflow
  // nodes.
  private final Object registeredWorkflowNodesLock = new Object();
  // The network group request filtering policy.
  private RequestFilteringPolicy requestFilteringPolicy = null;
  // The network group resource limits policy.
  private ResourceLimitsPolicy resourceLimitsPolicy = null;
  // The workflow node for the rootDSE entry. The RootDSE workflow node
  // is not stored in the list of registered workflow nodes.
  private RootDseWorkflowTopology rootDSEWorkflowNode = null;
  // The network group statistics.
  private final NetworkGroupStatistics statistics;
  /**
   * Creates a new system network group using the provided ID.
   *
   * @param networkGroupID
   *          The network group internal identifier.
   */
  public NetworkGroup(String networkGroupID)
  {
    this.networkGroupID = networkGroupID;
    isInternalNetworkGroup = INTERNAL_NETWORK_GROUP_NAME.equals(networkGroupID);
    isAdminNetworkGroup    = ADMIN_NETWORK_GROUP_NAME.equals(networkGroupID);
    isDefaultNetworkGroup  = DEFAULT_NETWORK_GROUP_NAME.equals(networkGroupID);
    stats = new NetworkGroupStatistics(this);
    this.isInternalNetworkGroup =
        INTERNAL_NETWORK_GROUP_NAME.equals(networkGroupID);
    this.isAdminNetworkGroup =
        ADMIN_NETWORK_GROUP_NAME.equals(networkGroupID);
    this.isDefaultNetworkGroup =
        DEFAULT_NETWORK_GROUP_NAME.equals(networkGroupID);
    this.statistics = new NetworkGroupStatistics(this);
    this.configuration = null;
    this.changeListener = null;
    this.policyListener = null;
  }
  /**
   * Creates a new user network group using the provided configuration.
   */
  private NetworkGroup(NetworkGroupCfg configuration)
  {
    this.networkGroupID = getNameFromConfiguration(configuration);
    this.isInternalNetworkGroup = false;
    this.isAdminNetworkGroup = false;
    this.isDefaultNetworkGroup = false;
    this.statistics = new NetworkGroupStatistics(this);
    this.configuration = configuration;
    this.changeListener = new ChangeListener();
    this.policyListener = new QOSPolicyListener();
  }
  /**
   * Adds a connection to the group.
   *
   * @param connection
   *          the ClientConnection
   */
  public void addConnection(ClientConnection connection)
  {
    if (resourceLimitsPolicy != null)
    {
      resourceLimitsPolicy.addConnection(connection);
    }
  }
  /**
   * Checks the request filtering policy.
   *
   * @param operation
   *          the operation to be checked
   * @param messages
   *          the error messages
   * @return boolean indicating whether the operation conforms to the
   *         network group request filtering policy
   */
  boolean checkRequestFilteringPolicy(
      PreParseOperation operation, List<Message> messages)
  {
    if (requestFilteringPolicy != null)
    {
      return requestFilteringPolicy.isAllowed(operation, messages);
    }
    else
    {
      return true;
    }
  }
  /**
   * Checks the resource limits policy.
   *
   * @param connection
   *          the client connection
   * @param operation
   *          the ongoing operation
   * @param fullCheck
   *          a boolean indicating the level of checking: full/partial
   * @param messages
   *          the messages indicating the cause of the failure.
   * @return a boolean indicating whether resource limits are exceeded
   */
  boolean checkResourceLimitsPolicy(ClientConnection connection,
      PreParseOperation operation, boolean fullCheck,
      List<Message> messages)
  {
    if (resourceLimitsPolicy != null)
    {
      return resourceLimitsPolicy.isAllowed(connection, operation,
          fullCheck, messages);
    }
    else
    {
      return true;
    }
  }
  /**
   * Deregisters a workflow with the network group. The workflow to
   * deregister is identified by its baseDN.
   *
   * @param baseDN
   *          the baseDN of the workflow to deregister, may be null
   * @return the deregistered workflow
   */
  public Workflow deregisterWorkflow(DN baseDN)
  {
    Workflow workflow = null;
    if (baseDN == null)
    {
      return workflow;
    }
    if (baseDN.isNullDN())
    {
      // deregister the rootDSE
      deregisterWorkflow(rootDSEWorkflowNode);
      workflow = rootDSEWorkflowNode.getWorkflowImpl();
    }
    else
    {
      // deregister a workflow node
      synchronized (registeredWorkflowNodesLock)
      {
        for (WorkflowTopologyNode node : registeredWorkflowNodes
            .values())
        {
          DN curDN = node.getBaseDN();
          if (curDN.equals(baseDN))
          {
            // Call deregisterWorkflow() instead of
            // deregisterWorkflowNode() because we want the naming
            // context list to be updated as well.
            deregisterWorkflow(node);
            workflow = node.getWorkflowImpl();
            // Only one workflow can match the baseDN, so we can break
            // the loop here.
            break;
          }
        }
      }
    }
    // Now that the workflow node has been deregistered with the network
    // group, update the reference counter of the workflow.
    if ((workflow != null) && !isAdminNetworkGroup
        && !isInternalNetworkGroup && !isDefaultNetworkGroup)
    {
      WorkflowImpl workflowImpl = (WorkflowImpl) workflow;
      workflowImpl.decrementReferenceCounter();
    }
    return workflow;
  }
  /**
   * Deregisters a workflow with the network group. The workflow to
   * deregister is identified by its workflow ID.
   *
   * @param workflowID
   *          the workflow identifier of the workflow to deregister
   * @return the deregistered workflow
   */
  public Workflow deregisterWorkflow(String workflowID)
  {
    Workflow workflow = null;
    String rootDSEWorkflowID = null;
    if (rootDSEWorkflowNode != null)
    {
      rootDSEWorkflowID =
          rootDSEWorkflowNode.getWorkflowImpl().getWorkflowId();
    }
    if (workflowID.equalsIgnoreCase(rootDSEWorkflowID))
    {
      // deregister the rootDSE
      deregisterWorkflow(rootDSEWorkflowNode);
      workflow = rootDSEWorkflowNode.getWorkflowImpl();
    }
    else
    {
      // deregister a workflow node
      synchronized (registeredWorkflowNodesLock)
      {
        for (WorkflowTopologyNode node : registeredWorkflowNodes
            .values())
        {
          String curID = node.getWorkflowImpl().getWorkflowId();
          if (curID.equals(workflowID))
          {
            // Call deregisterWorkflow() instead of
            // deregisterWorkflowNode() because we want the naming
            // context list to be updated as well.
            deregisterWorkflow(node);
            workflow = node.getWorkflowImpl();
            // Only one workflow can match the baseDN, so we can break
            // the loop here.
            break;
          }
        }
      }
    }
    // Now that the workflow node has been deregistered with the network
    // group, update the reference counter of the workflow.
    if ((workflow != null) && !isAdminNetworkGroup
        && !isInternalNetworkGroup && !isDefaultNetworkGroup)
    {
      WorkflowImpl workflowImpl = (WorkflowImpl) workflow;
      workflowImpl.decrementReferenceCounter();
    }
    return workflow;
  }
  /**
   * Performs any finalization that might be required when this network
   * group is unloaded. No action is taken in the default
   * implementation.
   */
  public void finalizeNetworkGroup()
  {
    if (configuration != null)
    {
      // Finalization specific to user network groups.
      deregister();
      // Remove all change listeners.
      configuration.removeChangeListener(changeListener);
      configuration
          .removeNetworkGroupQOSPolicyAddListener(policyListener);
      configuration
          .removeNetworkGroupQOSPolicyDeleteListener(policyListener);
      configuration = null;
    }
    // Clean up policies.
    for (QOSPolicy policy : policies.values())
    {
      policy.finalizeQOSPolicy();
    }
    requestFilteringPolicy = null;
    resourceLimitsPolicy = null;
    criteria = ConnectionCriteria.TRUE;
    policies.clear();
  }
  /**
   * Retrieves the network group ID.
   *
   * @return a string indicating the network group ID
   */
  public String getID() {
  public String getID()
  {
    return networkGroupID;
  }
  /**
   * Performs any finalization that might be required when this
   * network group is unloaded.  No action is taken in the
   * default implementation.
   * Gets the minimum string length of a substring filter in a search
   * operation.
   *
   * @return the minimum substring length
   */
  public void finalizeNetworkGroup()
  public int getMinSubstring()
  {
    // No action is required by default.
    if (resourceLimitsPolicy != null)
    {
      return resourceLimitsPolicy.getMinSubstring();
    }
    else
    {
      return 0;
    }
  }
  /**
   * Returns the list of naming contexts handled by the network group.
   *
   * @return the list of naming contexts
   */
  public NetworkGroupNamingContexts getNamingContexts()
  {
    return namingContexts;
  }
  /**
   * Returns the QOS policy associated with this network group having
   * the specified class.
   *
   * @param <T>
   *          The type of QOS policy.
   * @param clazz
   *          The class of QOS policy requested.
   * @return The QOS policy associated with this network group having
   *         the specified class, or <code>null</code> if none was
   *         found.
   */
  public <T extends QOSPolicy> T getNetworkGroupQOSPolicy(Class<T> clazz)
  {
    for (QOSPolicy policy : policies.values())
    {
      if (clazz.isAssignableFrom(policy.getClass()))
      {
        return clazz.cast(policy);
      }
    }
    return null;
  }
  /**
   * Gets the search size limit, i.e. the maximum number of entries
   * returned by a search.
   *
   * @return the maximum number of entries returned by a search
   */
  public int getSizeLimit()
  {
    if (resourceLimitsPolicy != null)
    {
      return resourceLimitsPolicy.getSizeLimit();
    }
    else
    {
      return DirectoryServer.getSizeLimit();
    }
  }
  /**
   * Gets the search duration limit, i.e. the maximum duration of a
   * search operation.
   *
   * @return the maximum duration in ms of a search operation
   */
  public int getTimeLimit()
  {
    if (resourceLimitsPolicy != null)
    {
      return resourceLimitsPolicy.getTimeLimit();
    }
    else
    {
      return DirectoryServer.getTimeLimit();
    }
  }
  /**
   * Gets the highest workflow in the topology that can handle the
   * baseDN.
   *
   * @param baseDN
   *          the base DN of the request
   * @return the highest workflow in the topology that can handle the
   *         base DN, <code>null</code> if none was found
   */
  public Workflow getWorkflowCandidate(DN baseDN)
  {
    // the top workflow to return
    Workflow workflowCandidate = null;
    // get the list of workflow candidates
    if (baseDN.isNullDN())
    {
      // The rootDSE workflow is the candidate.
      workflowCandidate = rootDSEWorkflowNode;
    }
    else
    {
      // Search the highest workflow in the topology that can handle
      // the baseDN.
      for (WorkflowTopologyNode curWorkflow : namingContexts
          .getNamingContexts())
      {
        workflowCandidate = curWorkflow.getWorkflowCandidate(baseDN);
        if (workflowCandidate != null)
        {
          break;
        }
      }
    }
    return workflowCandidate;
  }
  /**
   * Registers a workflow with the network group.
   *
   * @param workflow
   *          the workflow to register
   * @throws DirectoryException
   *           If the workflow ID for the provided workflow conflicts
   *           with the workflow ID of an existing workflow.
   */
  public void registerWorkflow(WorkflowImpl workflow)
      throws DirectoryException
  {
    // The workflow is registered with no pre/post workflow element.
    registerWorkflow(workflow, null, null);
  }
  /**
   * Removes a connection from the group.
   *
   * @param connection
   *          the ClientConnection
   */
  public void removeConnection(ClientConnection connection)
  {
    if (resourceLimitsPolicy != null)
    {
      resourceLimitsPolicy.removeConnection(connection);
    }
  }
  /**
   * Updates the operations statistics.
   *
   * @param message
   *          The LDAP message being processed
   */
  public void updateMessageRead(LDAPMessage message)
  {
    statistics.updateMessageRead(message);
  }
  /**
   * Deregisters the current network group (this) with the server. The
   * method also decrements the reference counter of the workflows so
   * that workflows can be disabled or deleted if needed.
   * <p>
   * This methods is package private for testing purposes.
   */
  void deregister()
  {
    // Finalization specific to user network groups.
    synchronized (registeredNetworkGroupsLock)
    {
      // Deregister this network group.
      TreeMap<String, NetworkGroup> networkGroups =
          new TreeMap<String, NetworkGroup>(registeredNetworkGroups);
      networkGroups.remove(networkGroupID);
      registeredNetworkGroups = networkGroups;
      orderedNetworkGroups.remove(this);
      // Decrement the reference counter of the workflows registered
      // with this network group.
      synchronized (registeredWorkflowNodesLock)
      {
        for (WorkflowTopologyNode workflowNode : registeredWorkflowNodes
            .values())
        {
          WorkflowImpl workflowImpl = workflowNode.getWorkflowImpl();
          workflowImpl.decrementReferenceCounter();
        }
      }
    }
  }
  /**
   * Returns the request filtering policy statistics associated with
   * this network group.
   *
   * @return The request filtering policy statistics associated with
   *         this network group.
   */
  RequestFilteringPolicyStatistics getRequestFilteringPolicyStatistics()
  {
    if (requestFilteringPolicy != null)
    {
      return requestFilteringPolicy.getStatistics();
    }
    else
    {
      return null;
    }
  }
  /**
   * Returns the resource limits policy statistics associated with this
   * network group.
   *
   * @return The resource limits policy statistics associated with this
   *         network group.
   */
  ResourceLimitsPolicyStatistics getResourceLimitsPolicyStatistics()
  {
    if (resourceLimitsPolicy != null)
    {
      return resourceLimitsPolicy.getStatistics();
    }
    else
    {
      return null;
    }
  }
  /**
   * Registers the current network group (this) with the server.
   * <p>
   * This methods is package private for testing purposes.
   *
   * @throws  DirectoryException  If the network group ID for the provided
   *                              network group conflicts with the network
   *                              group ID of an existing network group.
   * @throws InitializationException
   *           If the network group ID for the provided network group
   *           conflicts with the network group ID of an existing
   *           network group.
   */
  public void register()
      throws DirectoryException
  void register() throws InitializationException
  {
    ensureNotNull(networkGroupID);
@@ -205,24 +1403,28 @@
      // The network group must not be already registered
      if (registeredNetworkGroups.containsKey(networkGroupID))
      {
        Message message = ERR_REGISTER_NETWORK_GROUP_ALREADY_EXISTS.get(
                          networkGroupID);
        throw new DirectoryException(
            ResultCode.UNWILLING_TO_PERFORM, message);
        Message message =
            ERR_REGISTER_NETWORK_GROUP_ALREADY_EXISTS
                .get(networkGroupID);
        throw new InitializationException(message);
      }
      TreeMap<String, NetworkGroup> newRegisteredNetworkGroups =
        new TreeMap<String, NetworkGroup>(registeredNetworkGroups);
          new TreeMap<String, NetworkGroup>(registeredNetworkGroups);
      newRegisteredNetworkGroups.put(networkGroupID, this);
      registeredNetworkGroups = newRegisteredNetworkGroups;
      // Insert the network group at the right position in the ordered list
      // Insert the network group at the right position in the ordered
      // list.
      int index = 0;
      for (NetworkGroup ng : registeredNetworkGroups.values()) {
        if (ng.equals(this)) {
      for (NetworkGroup ng : registeredNetworkGroups.values())
      {
        if (ng.equals(this))
        {
          continue;
        }
        if (this.priority > ng.priority) {
        if (this.priority > ng.priority)
        {
          index++;
        }
      }
@@ -231,103 +1433,474 @@
  }
  /**
   * Deregisters the current network group (this) with the server.
   * The method also decrements the reference counter of the workflows
   * so that workflows can be disabled or deleted if needed.
   */
  public void deregister()
  {
    synchronized (registeredNetworkGroupsLock)
    {
      TreeMap<String, NetworkGroup> networkGroups =
        new TreeMap<String, NetworkGroup>(registeredNetworkGroups);
      networkGroups.remove(networkGroupID);
      registeredNetworkGroups = networkGroups;
      orderedNetworkGroups.remove(this);
      // decrement the reference counter of the workflows registered with
      // this network group
      updateWorkflowReferenceCounters();
    }
  /**
   * Sets the network group connection criteria.
   * <p>
   * This method is intended for testing only.
   *
   * @param criteria
   *          The connection criteria.
   */
  void setConnectionCriteria(ConnectionCriteria criteria)
  {
    this.criteria = criteria;
  }
  /**
   * Decrements the workflow reference counters of all the workflows
   * registered with this network group.
   * Sets the network group priority.
   * <p>
   * This methods is package private for testing purposes.
   *
   * @param prio
   *          the network group priority
   */
  private void updateWorkflowReferenceCounters()
  void setNetworkGroupPriority(int prio)
  {
    synchronized (registeredWorkflowNodesLock)
    // Check whether the priority has changed
    if (priority != prio)
    {
      for (WorkflowTopologyNode workflowNode: registeredWorkflowNodes.values())
      synchronized (registeredNetworkGroupsLock)
      {
        WorkflowImpl workflowImpl = workflowNode.getWorkflowImpl();
        workflowImpl.decrementReferenceCounter();
        priority = prio;
        // Nothing to do if the network group is not registered
        if (registeredNetworkGroups.containsKey(networkGroupID))
        {
          // If the network group was already registered, remove it from
          // the ordered list
          orderedNetworkGroups.remove(this);
          // Then insert it at the right position in the ordered list
          int index = 0;
          for (NetworkGroup ng : registeredNetworkGroups.values())
          {
            if (ng.equals(this))
            {
              continue;
            }
            if (this.priority > ng.priority)
            {
              index++;
            }
          }
          orderedNetworkGroups.add(index, this);
        }
      }
    }
  }
  /**
   * Registers a workflow with the network group.
   * Dumps info from the current network group for debug purpose.
   * <p>
   * This method is intended for testing only.
   *
   * @param workflow  the workflow to register
   *
   * @throws  DirectoryException  If the workflow ID for the provided
   *                              workflow conflicts with the workflow
   *                              ID of an existing workflow.
   * @param leftMargin
   *          white spaces used to indent traces
   * @return a string buffer that contains trace information
   */
  public void registerWorkflow(
      WorkflowImpl workflow
      ) throws DirectoryException
  StringBuilder toString(String leftMargin)
  {
    // The workflow is registered with no pre/post workflow element.
    registerWorkflow(workflow, null, null);
    StringBuilder sb = new StringBuilder();
    String newMargin = leftMargin + "   ";
    sb.append(leftMargin + "Networkgroup (" + networkGroupID + "\n");
    sb.append(leftMargin + "List of registered workflows:\n");
    for (WorkflowTopologyNode node : registeredWorkflowNodes.values())
    {
      sb.append(node.toString(newMargin));
    }
    namingContexts.toString(leftMargin);
    sb.append(leftMargin + "rootDSEWorkflow:\n");
    if (rootDSEWorkflowNode == null)
    {
      sb.append(newMargin + "null\n");
    }
    else
    {
      sb.append(rootDSEWorkflowNode.toString(newMargin));
    }
    return sb;
  }
  /**
   * Registers a workflow with the network group and the workflow may have
   * pre and post workflow element.
   * Checks whether the base DN of a new workflow to register is present
   * in a workflow already registered with the network group.
   *
   * @param workflow              the workflow to register
   * @param preWorkflowElements   the tasks to execute before the workflow
   * @param postWorkflowElements  the tasks to execute after the workflow
   *
   * @throws  DirectoryException  If the workflow ID for the provided
   *          workflow conflicts with the workflow ID of an existing
   *          workflow or if the base DN of the workflow is the same
   *          than the base DN of another workflow already registered
   * @param workflowNode
   *          the workflow to check
   * @throws DirectoryException
   *           If the base DN of the workflow is already present in the
   *           network group
   */
  private void registerWorkflow(
      WorkflowImpl workflow,
  private void checkWorkflowBaseDN(WorkflowTopologyNode workflowNode)
      throws DirectoryException
  {
    String workflowID = workflowNode.getWorkflowImpl().getWorkflowId();
    ensureNotNull(workflowID);
    // If the network group is the "internal" network group then bypass
    // the check because the internal network group may contain
    // duplicates of base DNs.
    if (isInternalNetworkGroup)
    {
      return;
    }
    // If the network group is the "admin" network group then bypass
    // the check because the internal network group may contain
    // duplicates of base DNs.
    if (isAdminNetworkGroup)
    {
      return;
    }
    // The workflow base DN should not be already present in the
    // network group. Bypass the check for the private workflows...
    for (WorkflowTopologyNode node : registeredWorkflowNodes.values())
    {
      DN nodeBaseDN = node.getBaseDN();
      if (nodeBaseDN.equals(workflowNode.getBaseDN()))
      {
        // The base DN is already registered in the network group,
        // we must reject the registration request
        Message message =
            ERR_REGISTER_WORKFLOW_BASE_DN_ALREADY_EXISTS.get(
                workflowID, networkGroupID, node.getWorkflowImpl()
                    .getWorkflowId(), workflowNode.getWorkflowImpl()
                    .getBaseDN().toString());
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
            message);
      }
    }
  }
  // Creates and registers the provided network group policy
  // configuration.
  private void createNetworkGroupQOSPolicy(
      QOSPolicyCfg policyConfiguration) throws ConfigException,
      InitializationException
  {
    String className = policyConfiguration.getJavaClass();
    QOSPolicyCfgDefn d = QOSPolicyCfgDefn.getInstance();
    ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition();
    // Load the class and cast it to a network group policy.
    Class<? extends QOSPolicyFactory> theClass;
    QOSPolicyFactory factory;
    try
    {
      theClass = pd.loadClass(className, QOSPolicyFactory.class);
      factory = theClass.newInstance();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message =
          ERR_CONFIG_NETWORK_GROUP_POLICY_CANNOT_INITIALIZE.get(String
              .valueOf(className), String.valueOf(policyConfiguration
              .dn()), stackTraceToSingleLineString(e));
      throw new InitializationException(message, e);
    }
    // Perform the necessary initialization for the network group
    // policy.
    QOSPolicy policy;
    try
    {
      // Determine the initialization method to use: it must take a
      // single parameter which is the exact type of the configuration
      // object.
      Method method =
          theClass.getMethod("createQOSPolicy", policyConfiguration
              .configurationClass());
      policy = (QOSPolicy) method.invoke(factory, policyConfiguration);
    }
    catch (Exception e)
    {
      if (e instanceof InvocationTargetException)
      {
        Throwable t = e.getCause();
        if (t instanceof InitializationException)
        {
          throw (InitializationException) t;
        }
        else if (t instanceof ConfigException)
        {
          throw (ConfigException) t;
        }
      }
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message =
          ERR_CONFIG_NETWORK_GROUP_POLICY_CANNOT_INITIALIZE.get(String
              .valueOf(className), String.valueOf(policyConfiguration
              .dn()), stackTraceToSingleLineString(e));
      throw new InitializationException(message, e);
    }
    // The network group has been successfully initialized - so register
    // it.
    QOSPolicy oldPolicy =
        policies.put(policyConfiguration.dn(), policy);
    if (policy instanceof RequestFilteringPolicy)
    {
      requestFilteringPolicy = (RequestFilteringPolicy) policy;
    }
    else if (policy instanceof ResourceLimitsPolicy)
    {
      resourceLimitsPolicy = (ResourceLimitsPolicy) policy;
    }
    if (oldPolicy != null)
    {
      oldPolicy.finalizeQOSPolicy();
    }
  }
  /**
   * Deregisters a workflow node with the network group.
   *
   * @param workflow
   *          the workflow node to deregister
   * @return <code>true</code> when the workflow has been successfully
   *         deregistered
   */
  private boolean deregisterWorkflow(Workflow workflow)
  {
    // true as soon as the workflow has been deregistered
    boolean deregistered = false;
    // Is it the rootDSE workflow?
    if (workflow == rootDSEWorkflowNode)
    {
      rootDSEWorkflowNode = null;
      deregistered = true;
    }
    else
    {
      // Deregister the workflow with the network group.
      WorkflowTopologyNode workflowNode =
          (WorkflowTopologyNode) workflow;
      deregisterWorkflowNode(workflowNode);
      deregistered = true;
      // The workflow to deregister is not the root DSE workflow.
      // Remove it from the workflow topology.
      workflowNode.remove();
      // Rebuild the list of naming context handled by the network group
      rebuildNamingContextList();
    }
    return deregistered;
  }
  /**
   * Deregisters the current workflow (this) with the server.
   *
   * @param workflowNode
   *          the workflow node to deregister
   */
  private void deregisterWorkflowNode(WorkflowTopologyNode workflowNode)
  {
    synchronized (registeredWorkflowNodesLock)
    {
      TreeMap<String, WorkflowTopologyNode> newWorkflowNodes =
          new TreeMap<String, WorkflowTopologyNode>(
              registeredWorkflowNodes);
      newWorkflowNodes.remove(workflowNode.getWorkflowImpl()
          .getWorkflowId());
      registeredWorkflowNodes = newWorkflowNodes;
    }
  }
  /**
   * Retrieves the list of registered workflows.
   *
   * @return a list of workflow ids
   */
  private List<String> getRegisteredWorkflows()
  {
    List<String> workflowIDs = new ArrayList<String>();
    synchronized (registeredWorkflowNodesLock)
    {
      for (WorkflowTopologyNode node : registeredWorkflowNodes.values())
      {
        workflowIDs.add(node.getWorkflowImpl().getWorkflowId());
      }
    }
    return workflowIDs;
  }
  /**
   * We've seen parts of the server hold references to a NetworkGroup
   * during an in-core server restart. To help detect when this happens,
   * we null out the member variables, so we will fail fast with an NPE
   * if an invalidate NetworkGroup is used.
   */
  private void invalidate()
  {
    namingContexts = null;
    rootDSEWorkflowNode = null;
    registeredWorkflowNodes = null;
  }
  /**
   * Checks whether the connection matches the network group criteria.
   *
   * @param connection
   *          the client connection
   * @return a boolean indicating the match
   */
  private boolean match(ClientConnection connection)
  {
    if (criteria != null)
    {
      return criteria.matches(connection);
    }
    else
    {
      return true;
    }
  }
  /**
   * Checks whether the client connection matches the criteria after
   * bind.
   *
   * @param connection
   *          the ClientConnection
   * @param bindDN
   *          the DN used to bind
   * @param authType
   *          the authentication type
   * @param isSecure
   *          a boolean indicating whether the connection is secure
   * @return a boolean indicating whether the connection matches the
   *         criteria
   */
  private boolean matchAfterBind(ClientConnection connection,
      DN bindDN, AuthenticationType authType, boolean isSecure)
  {
    if (criteria != null)
    {
      return criteria.willMatchAfterBind(connection, bindDN, authType,
          isSecure);
    }
    else
    {
      return true;
    }
  }
  /**
   * Rebuilds the list of naming contexts handled by the network group.
   * This operation should be performed whenever a workflow topology has
   * been updated (workflow registration or de-registration).
   */
  private void rebuildNamingContextList()
  {
    // reset lists of naming contexts
    namingContexts.resetLists();
    // a registered workflow with no parent is a naming context
    for (WorkflowTopologyNode workflowNode : registeredWorkflowNodes
        .values())
    {
      WorkflowTopologyNode parent = workflowNode.getParent();
      if (parent == null)
      {
        namingContexts.addNamingContext(workflowNode);
      }
    }
  }
  /**
   * Registers a workflow with the network group and the workflow may
   * have pre and post workflow element.
   *
   * @param workflow
   *          the workflow to register
   * @param preWorkflowElements
   *          the tasks to execute before the workflow
   * @param postWorkflowElements
   *          the tasks to execute after the workflow
   * @throws DirectoryException
   *           If the workflow ID for the provided workflow conflicts
   *           with the workflow ID of an existing workflow or if the
   *           base DN of the workflow is the same than the base DN of
   *           another workflow already registered
   */
  private void registerWorkflow(WorkflowImpl workflow,
      WorkflowElement<?>[] preWorkflowElements,
      WorkflowElement<?>[] postWorkflowElements
      ) throws DirectoryException
      WorkflowElement<?>[] postWorkflowElements)
      throws DirectoryException
  {
    // Is it the rootDSE workflow?
    DN baseDN = workflow.getBaseDN();
    if (baseDN.isNullDN())
    {
      // NOTE - The rootDSE workflow is stored with the registeredWorkflows.
      // NOTE - The rootDSE workflow is stored with the
      // registeredWorkflows.
      rootDSEWorkflowNode =
        new RootDseWorkflowTopology(workflow, namingContexts);
          new RootDseWorkflowTopology(workflow, namingContexts);
    }
    else
    {
      // This workflow is not the rootDSE workflow. Try to insert it in the
      // workflow topology.
      WorkflowTopologyNode workflowNode = new WorkflowTopologyNode(
          workflow, preWorkflowElements, postWorkflowElements);
      // This workflow is not the rootDSE workflow. Try to insert it in
      // the workflow topology.
      WorkflowTopologyNode workflowNode =
          new WorkflowTopologyNode(workflow, preWorkflowElements,
              postWorkflowElements);
      // Register the workflow node with the network group. If the workflow
      // ID is already existing then an exception is raised.
      // Register the workflow node with the network group. If the
      // workflow ID is already existing then an exception is raised.
      registerWorkflowNode(workflowNode);
      // Now add the workflow in the workflow topology...
      for (WorkflowTopologyNode curNode: registeredWorkflowNodes.values())
      for (WorkflowTopologyNode curNode : registeredWorkflowNodes
          .values())
      {
        // Try to insert the new workflow under an existing workflow...
        if (curNode.insertSubordinate(workflowNode))
@@ -350,10 +1923,9 @@
      // Now that the workflow node has been registered with the network
      // group, update the reference counter of the workflow, unless
      // the network group is either default, or administration, or internal
      // network group.
      if (!isAdminNetworkGroup
          && !isInternalNetworkGroup
      // the network group is either default, or administration, or
      // internal network group.
      if (!isAdminNetworkGroup && !isInternalNetworkGroup
          && !isDefaultNetworkGroup)
      {
        workflow.incrementReferenceCounter();
@@ -362,196 +1934,19 @@
  }
  /**
   * Deregisters a workflow with the network group. The workflow to
   * deregister is identified by its baseDN.
   *
   * @param baseDN  the baseDN of the workflow to deregister, may be null
   *
   * @return the deregistered workflow
   */
  public Workflow deregisterWorkflow(
      DN baseDN
      )
  {
    Workflow workflow = null;
    if (baseDN == null)
    {
      return workflow;
    }
    if (baseDN.isNullDN())
    {
      // deregister the rootDSE
      deregisterWorkflow(rootDSEWorkflowNode);
      workflow = rootDSEWorkflowNode.getWorkflowImpl();
    }
    else
    {
      // deregister a workflow node
      synchronized (registeredWorkflowNodesLock)
      {
        for (WorkflowTopologyNode node: registeredWorkflowNodes.values())
        {
          DN curDN = node.getBaseDN();
          if (curDN.equals(baseDN))
          {
            // Call deregisterWorkflow() instead of deregisterWorkflowNode()
            // because we want the naming context list to be updated as well.
            deregisterWorkflow(node);
            workflow = node.getWorkflowImpl();
            // Only one workflow can match the baseDN, so we can break
            // the loop here.
            break;
          }
        }
      }
    }
    // Now that the workflow node has been deregistered with the network
    // group, update the reference counter of the workflow.
    if ((workflow != null)
        && !isAdminNetworkGroup
        && !isInternalNetworkGroup
        && !isDefaultNetworkGroup)
    {
      WorkflowImpl workflowImpl = (WorkflowImpl) workflow;
      workflowImpl.decrementReferenceCounter();
    }
    return workflow;
  }
  /**
   * Deregisters a workflow with the network group. The workflow to
   * deregister is identified by its workflow ID.
   *
   * @param workflowID the workflow identifier of the workflow to deregister
   * @return the deregistered workflow
   */
  public Workflow deregisterWorkflow(
      String workflowID
      )
  {
    Workflow workflow = null;
    String rootDSEWorkflowID = null;
    if (rootDSEWorkflowNode != null)
    {
      rootDSEWorkflowID = rootDSEWorkflowNode.getWorkflowImpl().getWorkflowId();
    }
    if (workflowID.equalsIgnoreCase(rootDSEWorkflowID))
    {
      // deregister the rootDSE
      deregisterWorkflow(rootDSEWorkflowNode);
      workflow = rootDSEWorkflowNode.getWorkflowImpl();
    }
    else
    {
      // deregister a workflow node
      synchronized (registeredWorkflowNodesLock)
      {
        for (WorkflowTopologyNode node: registeredWorkflowNodes.values())
        {
          String curID = node.getWorkflowImpl().getWorkflowId();
          if (curID.equals(workflowID))
          {
            // Call deregisterWorkflow() instead of deregisterWorkflowNode()
            // because we want the naming context list to be updated as well.
            deregisterWorkflow(node);
            workflow = node.getWorkflowImpl();
            // Only one workflow can match the baseDN, so we can break
            // the loop here.
            break;
          }
        }
      }
    }
    // Now that the workflow node has been deregistered with the network
    // group, update the reference counter of the workflow.
    if ((workflow != null)
        && !isAdminNetworkGroup
        && !isInternalNetworkGroup
        && !isDefaultNetworkGroup)
    {
      WorkflowImpl workflowImpl = (WorkflowImpl) workflow;
      workflowImpl.decrementReferenceCounter();
    }
    return workflow;
  }
  /**
   * Deregisters a workflow node with the network group.
   *
   * @param workflow  the workflow node to deregister
   * @return <code>true</code> when the workflow has been successfully
   *         deregistered
   */
  private boolean deregisterWorkflow(Workflow workflow)
  {
    // true as soon as the workflow has been deregistered
    boolean deregistered = false;
    // Is it the rootDSE workflow?
    if (workflow == rootDSEWorkflowNode)
    {
      rootDSEWorkflowNode = null;
      deregistered = true;
    }
    else
    {
      // Deregister the workflow with the network group.
      WorkflowTopologyNode workflowNode = (WorkflowTopologyNode) workflow;
      deregisterWorkflowNode(workflowNode);
      deregistered = true;
      // The workflow to deregister is not the root DSE workflow.
      // Remove it from the workflow topology.
      workflowNode.remove();
      // Rebuild the list of naming context handled by the network group
      rebuildNamingContextList();
    }
    return deregistered;
  }
  /**
   * Retrieves the list of registered workflows.
   * @return a list of workflow ids
   */
  public List<String> getRegisteredWorkflows() {
    List<String> workflowIDs = new ArrayList<String>();
    synchronized (registeredWorkflowNodesLock) {
      for (WorkflowTopologyNode node : registeredWorkflowNodes.values()) {
        workflowIDs.add(node.getWorkflowImpl().getWorkflowId());
      }
    }
    return workflowIDs;
  }
  /**
   * Registers a workflow node with the network group.
   *
   * @param workflowNode  the workflow node to register
   *
   * @throws  DirectoryException  If the workflow node ID for the provided
   *                              workflow node conflicts with the workflow
   *                              node ID of an existing workflow node.
   * @param workflowNode
   *          the workflow node to register
   * @throws DirectoryException
   *           If the workflow node ID for the provided workflow node
   *           conflicts with the workflow node ID of an existing
   *           workflow node.
   */
  private void registerWorkflowNode(
      WorkflowTopologyNode workflowNode
      ) throws DirectoryException
  private void registerWorkflowNode(WorkflowTopologyNode workflowNode)
      throws DirectoryException
  {
    String workflowID = workflowNode.getWorkflowImpl().getWorkflowId();
    ensureNotNull(workflowID);
@@ -561,10 +1956,11 @@
      // The workflow must not be already registered
      if (registeredWorkflowNodes.containsKey(workflowID))
      {
        Message message = ERR_REGISTER_WORKFLOW_NODE_ALREADY_EXISTS.get(
          workflowID, networkGroupID);
        throw new DirectoryException(
            ResultCode.UNWILLING_TO_PERFORM, message);
        Message message =
            ERR_REGISTER_WORKFLOW_NODE_ALREADY_EXISTS.get(workflowID,
                networkGroupID);
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
            message);
      }
      // The workflow base DN should not be already present in the
@@ -573,685 +1969,26 @@
      // All is fine, let's register the workflow
      TreeMap<String, WorkflowTopologyNode> newRegisteredWorkflowNodes =
        new TreeMap<String, WorkflowTopologyNode>(registeredWorkflowNodes);
          new TreeMap<String, WorkflowTopologyNode>(
              registeredWorkflowNodes);
      newRegisteredWorkflowNodes.put(workflowID, workflowNode);
      registeredWorkflowNodes = newRegisteredWorkflowNodes;
    }
  }
  /**
   * Checks whether the base DN of a new workflow to register is
   * present in a workflow already registered with the network group.
   *
   * @param workflowNode  the workflow to check
   *
   * @throws  DirectoryException  If the base DN of the workflow is already
   *                              present in the network group
   */
  private void checkWorkflowBaseDN(
      WorkflowTopologyNode workflowNode
      ) throws DirectoryException
  {
    String workflowID = workflowNode.getWorkflowImpl().getWorkflowId();
    ensureNotNull(workflowID);
    // If the network group is the "internal" network group then bypass
    // the check because the internal network group may contain duplicates
    // of base DNs.
    if (isInternalNetworkGroup)
    {
      return;
    }
    // If the network group is the "admin" network group then bypass
    // the check because the internal network group may contain duplicates
    // of base DNs.
    if (isAdminNetworkGroup)
    {
      return;
    }
    // The workflow base DN should not be already present in the
    // network group. Bypass the check for the private workflows...
    for (WorkflowTopologyNode node: registeredWorkflowNodes.values())
    {
      DN nodeBaseDN = node.getBaseDN();
      if (nodeBaseDN.equals(workflowNode.getBaseDN()))
      {
        // The base DN is already registered in the network group,
        // we must reject the registration request
        Message message = ERR_REGISTER_WORKFLOW_BASE_DN_ALREADY_EXISTS.get(
          workflowID,
          networkGroupID,
          node.getWorkflowImpl().getWorkflowId(),
          workflowNode.getWorkflowImpl().getBaseDN().toString());
        throw new DirectoryException(
            ResultCode.UNWILLING_TO_PERFORM, message);
      }
    }
  }
  /**
   * Deregisters the current workflow (this) with the server.
   *
   * @param workflowNode  the workflow node to deregister
   */
  private void deregisterWorkflowNode(
      WorkflowTopologyNode workflowNode
      )
  {
    synchronized (registeredWorkflowNodesLock)
    {
      TreeMap<String, WorkflowTopologyNode> newWorkflowNodes =
        new TreeMap<String, WorkflowTopologyNode>(registeredWorkflowNodes);
      newWorkflowNodes.remove(workflowNode.getWorkflowImpl().getWorkflowId());
      registeredWorkflowNodes = newWorkflowNodes;
    }
  }
  /**
   * Adds a connection to the group.
   *
   * @param connection the ClientConnection
   */
  public void addConnection(ClientConnection connection) {
    if (resourceLimits != null) {
      resourceLimits.addConnection(connection);
    }
  }
  /**
   * Removes a connection from the group.
   *
   * @param connection the ClientConnection
   */
  public void removeConnection(ClientConnection connection) {
    if (resourceLimits != null) {
      resourceLimits.removeConnection(connection);
    }
  }
  /**
   *
   * Sets the network group priority.
   *
   * @param prio the network group priority
   */
  public void setNetworkGroupPriority(int prio) {
    // Check whether the priority has changed
    if (priority != prio) {
      synchronized (registeredNetworkGroupsLock)
      {
        priority = prio;
        // Nothing to do if the network group is not registered
        if (registeredNetworkGroups.containsKey(networkGroupID)) {
          // If the network group was already registered, remove it from the
          // ordered list
          orderedNetworkGroups.remove(this);
          // Then insert it at the right position in the ordered list
          int index = 0;
          for (NetworkGroup ng : registeredNetworkGroups.values()) {
            if (ng.equals(this)) {
              continue;
            }
            if (this.priority > ng.priority) {
              index++;
            }
          }
          orderedNetworkGroups.add(index, this);
        }
      }
    }
  }
  /**
   *
   * Sets the network group criteria.
   *
   * @param ngCriteria the criteria
   */
  public void setCriteria(NetworkGroupCriteria ngCriteria) {
    criteria = ngCriteria;
  }
  /**
   * Sets the Resource Limits.
   *
   * @param limits the new resource limits
   */
  public void setResourceLimits(ResourceLimits limits) {
    resourceLimits = limits;
  }
  /**
   * Sets the Request Filtering Policy.
   *
   * @param policy the new request filtering policy
   */
  public void setRequestFilteringPolicy(RequestFilteringPolicy policy) {
    requestFilteringPolicy = policy;
  }
  /**
   * Sets the affinity policy. The client connection affinity is the ability
   * for the server to bypass a route algorithm like "load balancing" so
   * that a request is always sent to the same data source regardless the
   * route algorithm.
   *
   * @param  affinityPolicy
   *         The client connection affinity policy of the network group.
   */
  public void setAffinityPolicy(
      ClientConnectionAffinityPolicy affinityPolicy)
  {
    this.affinityPolicy = affinityPolicy;
  }
  /**
   * Sets the affinity timeout value. The client connection affinity, when
   * set, remains active until the time out expires. When the time out
   * value is set to 0 then an active affinity never expires.
   *
   * @param timeout
   *        The affinity timeout value (0 means never expire).
   */
  public void setAffinityTimeout(long timeout)
  {
    this.affinityTimeout = timeout;
  }
  /**
   * Gets the highest priority matching network group.
   *
   * @param connection the client connection
   * @return matching network group
   */
  public static NetworkGroup findMatchingNetworkGroup(
          ClientConnection connection) {
    for (NetworkGroup ng : getOrderedNetworkGroups()) {
      if (ng.match(connection)) {
        return ng;
      }
    }
    return defaultNetworkGroup;
  }
  /**
   * Gets the highest priority matching network group for a BIND op.
   *
   * @param connection the client connection
   * @param dn the operation bindDN
   * @param authType the operation authentication type
   * @param isSecure a boolean indicating whether the operation is secured
   * @return matching network group
   */
  public static NetworkGroup findBindMatchingNetworkGroup(
          ClientConnection connection, DN dn, AuthenticationType authType,
          boolean isSecure) {
    for (NetworkGroup ng:getOrderedNetworkGroups()) {
      if (ng.matchAfterBind(connection, dn, authType, isSecure)) {
        return ng;
      }
    }
    return defaultNetworkGroup;
  }
  /**
   * Checks whether the connection matches the network group criteria.
   *
   * @param connection  the client connection
   * @return a boolean indicating the match
   */
  private boolean match(ClientConnection connection) {
    if (criteria != null) {
      return (criteria.match(connection));
    }
    return (true);
  }
  /**
   * Checks whether the client connection matches the criteria after bind.
   *
   * @param connection the ClientConnection
   * @param bindDN the DN used to bind
   * @param authType the authentication type
   * @param isSecure a boolean indicating whether the connection is secure
   * @return a boolean indicating whether the connection matches the criteria
   */
  private boolean matchAfterBind(ClientConnection connection, DN bindDN,
          AuthenticationType authType, boolean isSecure) {
    if (criteria != null) {
      return (criteria.matchAfterBind(connection, bindDN, authType, isSecure));
    }
    return (true);
  }
  /**
   * Checks the resource limits.
   *
   * @param connection the client connection
   * @param operation the ongoing operation
   * @param fullCheck a boolean indicating the level of checking: full/partial
   * @param messages the messages indicating the cause of the failure.
   * @return a boolean indicating whether resource limits are exceeded
   */
  public boolean checkResourceLimits(
          ClientConnection connection,
          PreParseOperation operation,
          boolean fullCheck,
          List<Message> messages)
  {
    if (resourceLimits != null) {
      return (resourceLimits.checkLimits(connection, operation,
              fullCheck, messages));
    }
    return (true);
  }
  /**
   * Gets the search size limit, i.e. the maximum number of entries returned
   * by a search.
   * @return the maximum number of entries returned by a search
   */
  public int getSearchSizeLimit() {
    if (resourceLimits != null) {
      return resourceLimits.getSizeLimit();
    }
    return -1;
  }
  /**
   * Gets the search duration limit, i.e. the maximum duration of a search
   * operation.
   * @return the maximum duration in ms of a search operation
   */
  public int getSearchDurationLimit() {
    if (resourceLimits != null) {
      return resourceLimits.getTimeLimit();
    }
    return -1;
  }
  /**
   * Gets the minimum string length of a substring filter in a search
   * operation.
   * @return the minimum substring length
   */
  public int getMinSubstring() {
    if (resourceLimits != null) {
      return resourceLimits.getMinSubstring();
    }
    return 0;
  }
  /**
   * Gets the referral policy. The referral policy defines the behavior
   * when a referral or a search continuation reference is received.
   * The referral can either be discarded (ie an error is returned to the
   * client), forwarded (ie the result is passed as-is to the client) or
   * followed (ie the server contacts the server targeted by the referral to
   * pursue the request).
   * @return the referral policy for this network group
   */
  public ReferralPolicy getReferralPolicy() {
    if (resourceLimits != null) {
      return resourceLimits.getReferralPolicy();
    }
    return ReferralPolicy.FORWARD;
  }
  /**
   * Gets the referral bind policy. The referral bind policy defines
   * the bind credentials used when the server tries to follow a referral. It
   * can either bind to the referred server anonymously, or using the same
   * credentials as in the original request.
   * @return the referral binf policy
   */
  public ReferralBindPolicy getReferralBindPolicy() {
    if (resourceLimits != null) {
      return resourceLimits.getReferralBindPolicy();
    }
    return ReferralBindPolicy.ANONYMOUS;
  }
  /**
   * Gets the referral hop limit. When configured to follow referrals,
   * the request to the referred server can also contain a referral. The hop
   * limit is the maximum number of subsequent operations.
   * @return the referral hop limit
   */
  public int getReferralHopLimit() {
    if (resourceLimits != null) {
      return resourceLimits.getReferralHopLimit();
    }
    return 0;
  }
  /**
   * Gets the affinity policy. The client connection affinity is the ability
   * for the server to bypass a route algorithm like "load balancing" so
   * that a request is always sent to the same data source regardless the
   * route algorithm.
   *
   * @return the client connection affinity policy of the network group
   */
  public ClientConnectionAffinityPolicy getAffinityPolicy()
  {
    return this.affinityPolicy;
  }
  /**
   * Gets the affinity timeout value. The client connection affinity, when
   * set, is active for a period of time. Once that period of time has
   * expired, the client connection affinity is reset. A value of 0 means
   * "no limit" - when an affinity is set it remains active for ever.
   *
   * @return the affinity timeout value (0 means no limit).
   */
  public long getAffinityTimeout()
  {
    return this.affinityTimeout;
  }
  /**
   * Checks the request filtering policy.
   * @param operation the operation to be checked
   * @param messages the error messages
   * @return boolean indicating whether the operation conforms to the
   *         network group request filtering policy
   */
  public boolean checkRequestFilteringPolicy(
          PreParseOperation operation,
          List<Message> messages) {
    if (requestFilteringPolicy != null) {
      return requestFilteringPolicy.checkPolicy(operation, messages);
    }
    return true;
  }
  /**
   * Gets the highest workflow in the topology that can handle the baseDN.
   *
   * @param baseDN  the base DN of the request
   * @return the highest workflow in the topology that can handle the base DN,
   *         <code>null</code> if none was found
   */
  public Workflow getWorkflowCandidate(
      DN baseDN
      )
  {
    // the top workflow to return
    Workflow workflowCandidate = null;
    // get the list of workflow candidates
    if (baseDN.isNullDN())
    {
      // The rootDSE workflow is the candidate.
      workflowCandidate = rootDSEWorkflowNode;
    }
    else
    {
      // Search the highest workflow in the topology that can handle
      // the baseDN.
      for (WorkflowTopologyNode curWorkflow: namingContexts.getNamingContexts())
      {
        workflowCandidate = curWorkflow.getWorkflowCandidate (baseDN);
        if (workflowCandidate != null)
        {
          break;
        }
      }
    }
    return workflowCandidate;
  }
  /**
   * Returns the default network group. The default network group is always
   * defined and has no criterion, no policy and provide full access to
   * all the registered workflows.
   *
   * @return the default network group
   */
  public static NetworkGroup getDefaultNetworkGroup()
  {
    return defaultNetworkGroup;
  }
  /**
   * Returns the admin network group.
   * @return the admin network group
   */
  public static NetworkGroup getAdminNetworkGroup()
  {
    return adminNetworkGroup;
  }
  /**
   * Returns the internal network group.
   * @return the internal network group
   */
  public static NetworkGroup getInternalNetworkGroup()
  {
    return internalNetworkGroup;
  }
  /**
   * Rebuilds the list of naming contexts handled by the network group.
   * This operation should be performed whenever a workflow topology
   * has been updated (workflow registration or de-registration).
   */
  private void rebuildNamingContextList()
  {
    // reset lists of naming contexts
    namingContexts.resetLists();
    // a registered workflow with no parent is a naming context
    for (WorkflowTopologyNode workflowNode: registeredWorkflowNodes.values())
    {
      WorkflowTopologyNode parent = workflowNode.getParent();
      if (parent == null)
      {
        namingContexts.addNamingContext (workflowNode);
      }
    }
  }
  /**
   * Returns the list of naming contexts handled by the network group.
   *
   * @return the list of naming contexts
   */
  public NetworkGroupNamingContexts getNamingContexts()
  {
    return namingContexts;
  }
  /**
   * Dumps info from the current network group for debug purpose.
   *
   * @param  leftMargin  white spaces used to indent traces
   * @return a string buffer that contains trace information
   */
  public StringBuilder toString(String leftMargin)
  {
    StringBuilder sb = new StringBuilder();
    String newMargin = leftMargin + "   ";
    sb.append (leftMargin + "Networkgroup (" + networkGroupID+ "\n");
    sb.append (leftMargin + "List of registered workflows:\n");
    for (WorkflowTopologyNode node: registeredWorkflowNodes.values())
    {
      sb.append (node.toString (newMargin));
    }
    namingContexts.toString (leftMargin);
    sb.append (leftMargin + "rootDSEWorkflow:\n");
    if (rootDSEWorkflowNode == null)
    {
      sb.append (newMargin + "null\n");
    }
    else
    {
      sb.append (rootDSEWorkflowNode.toString (newMargin));
    }
    return sb;
  }
  /**
   * Deregisters all network groups that have been registered.  This should be
   * called when the server is shutting down.
   */
  public static void deregisterAllOnShutdown()
  {
    synchronized (registeredNetworkGroupsLock)
    {
      // Invalidate all NetworkGroups so they cannot accidentally be used
      // after a restart.
      Collection<NetworkGroup> networkGroups = registeredNetworkGroups.values();
      for (NetworkGroup networkGroup: networkGroups)
      {
        networkGroup.invalidate();
      }
      defaultNetworkGroup.invalidate();
      adminNetworkGroup.invalidate();
      internalNetworkGroup.invalidate();
      registeredNetworkGroups = new TreeMap<String,NetworkGroup>();
      orderedNetworkGroups = new ArrayList<NetworkGroup>();
      defaultNetworkGroup = new NetworkGroup ("default");
      adminNetworkGroup = new NetworkGroup ("admin");
      internalNetworkGroup = new NetworkGroup("internal");
    }
  }
  /**
   * We've seen parts of the server hold references to a NetworkGroup
   * during an in-core server restart.  To help detect when this happens,
   * we null out the member variables, so we will fail fast with an NPE if an
   * invalidate NetworkGroup is used.
   */
  private void invalidate()
  {
    namingContexts = null;
    networkGroupID = null;
    rootDSEWorkflowNode = null;
    registeredWorkflowNodes = null;
  }
  /**
   * Provides the list of network group registered with the server.
   *
   * @return the list of registered network groups
   */
  public static Collection<NetworkGroup> getRegisteredNetworkGroups()
  {
    return registeredNetworkGroups.values();
  }
  /**
   * Provides the ordered list of registered Network groups.
   *
   * @return the ordered list of registered network groups
   */
  private static List<NetworkGroup> getOrderedNetworkGroups()
  {
    return orderedNetworkGroups;
  }
  /**
   * Returns a specific NetworkGroup.
   *
   * @param networkGroupId  the identifier of the requested network group
   * @return the requested NetworkGroup
   */
  public static NetworkGroup getNetworkGroup(String networkGroupId)
  {
    return registeredNetworkGroups.get(networkGroupId);
  }
  /**
   * Resets the configuration of all the registered network groups.
   */
  public static void resetConfig()
  {
    // Reset the default network group
    defaultNetworkGroup.reset();
    adminNetworkGroup.reset();
    internalNetworkGroup.reset();
    // Reset all the registered network group
    synchronized (registeredNetworkGroupsLock)
    {
      registeredNetworkGroups = new TreeMap<String, NetworkGroup>();
      orderedNetworkGroups = new ArrayList<NetworkGroup>();
    }
  }
  /**
   * Resets the configuration of the current network group.
   */
  public void reset()
  private void reset()
  {
    synchronized (registeredWorkflowNodesLock)
    {
      registeredWorkflowNodes = new TreeMap<String, WorkflowTopologyNode>();
      registeredWorkflowNodes =
          new TreeMap<String, WorkflowTopologyNode>();
      rootDSEWorkflowNode = null;
      namingContexts = new NetworkGroupNamingContexts();
    }
  }
  /**
   * Retrieves the statistics associated to the request filtering policy.
   *
   * @return the statistics associated to the request filtering policy
   */
  public RequestFilteringPolicyStat getRequestFilteringPolicyStat() {
    if (requestFilteringPolicy != null) {
      return requestFilteringPolicy.getStat();
    }
    return null;
  }
  /**
   * Retrieves the statistics associated to the resource limits.
   *
   * @return the statistics associated to the resource limits
   */
  public ResourceLimitsStat getResourceLimitStat() {
    if (resourceLimits != null) {
      return resourceLimits.getStat();
    }
    return null;
  }
  /**
   * Updates the operations statistics.
   * @param message The LDAP message being processed
   */
  public void updateMessageRead(LDAPMessage message) {
    stats.updateMessageRead(message);
  }
}