/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2007-2010 Sun Microsystems, Inc.
*/
package org.opends.server.core.networkgroups;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.CoreMessages.*;
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.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.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.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.
*/
public class NetworkGroup
{
/**
* 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);
}
/**
* {@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);
}
/**
* {@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);
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationAddAcceptable(
QOSPolicyCfg configuration, List<Message> unacceptableReasons)
{
return isNetworkGroupQOSPolicyConfigurationAcceptable(
configuration, unacceptableReasons);
}
/**
* {@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 static NetworkGroup defaultNetworkGroup =
new NetworkGroup(DEFAULT_NETWORK_GROUP_NAME);
// 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 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.
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 tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
/**
* 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");
}
}
/**
* 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).toString();
}
// 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;
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();
// Remove the stats
statistics.finalizeStatistics();
}
/**
* Retrieves the network group ID.
*
* @return a string indicating the network group ID
*/
public String getID()
{
return networkGroupID;
}
/**
* Gets the minimum string length of a substring filter in a search
* operation.
*
* @return the minimum substring length
*/
public int getMinSubstring()
{
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.
//First search the private workflows
// The order is important to ensure that the admin network group
// is not broken and can always find cn=config
for (WorkflowTopologyNode curWorkflow : namingContexts
.getPrivateNamingContexts())
{
workflowCandidate = curWorkflow.getWorkflowCandidate(baseDN);
if (workflowCandidate != null)
{
break;
}
}
// If not found, search the public
if (workflowCandidate == null) {
for (WorkflowTopologyNode curWorkflow : namingContexts
.getPublicNamingContexts())
{
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 InitializationException
* If the network group ID for the provided network group
* conflicts with the network group ID of an existing
* network group.
*/
void register() throws InitializationException
{
ensureNotNull(networkGroupID);
synchronized (registeredNetworkGroupsLock)
{
// The network group must not be already registered
if (registeredNetworkGroups.containsKey(networkGroupID))
{
Message message =
ERR_REGISTER_NETWORK_GROUP_ALREADY_EXISTS
.get(networkGroupID);
throw new InitializationException(message);
}
TreeMap<String, NetworkGroup> newRegisteredNetworkGroups =
new TreeMap<String, NetworkGroup>(registeredNetworkGroups);
newRegisteredNetworkGroups.put(networkGroupID, this);
registeredNetworkGroups = newRegisteredNetworkGroups;
// Insert the network group 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 connection criteria.
* <p>
* This method is intended for testing only.
*
* @param criteria
* The connection criteria.
*/
void setConnectionCriteria(ConnectionCriteria criteria)
{
this.criteria = criteria;
}
/**
* Sets the network group priority.
* <p>
* This methods is package private for testing purposes.
*
* @param prio
* the network group priority
*/
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);
}
}
}
}
/**
* Dumps info from the current network group for debug purpose.
* <p>
* This method is intended for testing only.
*
* @param leftMargin
* white spaces used to indent traces
* @return a string buffer that contains trace information
*/
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;
}
/**
* 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);
}
}
}
// 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
{
// Is it the rootDSE workflow?
DN baseDN = workflow.getBaseDN();
if (baseDN.isNullDN())
{
// NOTE - The rootDSE workflow is stored with the
// registeredWorkflows.
rootDSEWorkflowNode =
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);
// 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())
{
// Try to insert the new workflow under an existing workflow...
if (curNode.insertSubordinate(workflowNode))
{
// new workflow has been inserted in the topology
continue;
}
// ... or try to insert the existing workflow below the new
// workflow
if (workflowNode.insertSubordinate(curNode))
{
// new workflow has been inserted in the topology
continue;
}
}
// Rebuild the list of naming context handled by the network group
rebuildNamingContextList();
// 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
&& !isDefaultNetworkGroup)
{
workflow.incrementReferenceCounter();
}
}
}
/**
* 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.
*/
private void registerWorkflowNode(WorkflowTopologyNode workflowNode)
throws DirectoryException
{
String workflowID = workflowNode.getWorkflowImpl().getWorkflowId();
ensureNotNull(workflowID);
synchronized (registeredWorkflowNodesLock)
{
// 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);
}
// The workflow base DN should not be already present in the
// network group. Bypass the check for the private workflows...
checkWorkflowBaseDN(workflowNode);
// All is fine, let's register the workflow
TreeMap<String, WorkflowTopologyNode> newRegisteredWorkflowNodes =
new TreeMap<String, WorkflowTopologyNode>(
registeredWorkflowNodes);
newRegisteredWorkflowNodes.put(workflowID, workflowNode);
registeredWorkflowNodes = newRegisteredWorkflowNodes;
}
}
/**
* Resets the configuration of the current network group.
*/
private void reset()
{
synchronized (registeredWorkflowNodesLock)
{
registeredWorkflowNodes =
new TreeMap<String, WorkflowTopologyNode>();
rootDSEWorkflowNode = null;
namingContexts = new NetworkGroupNamingContexts();
}
}
}