/*
* 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 2006-2008 Sun Microsystems, Inc.
*/
package org.opends.server.config;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.opends.messages.Message;
import org.opends.server.api.ConfigAddListener;
import org.opends.server.api.ConfigChangeListener;
import org.opends.server.api.ConfigDeleteListener;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.DebugLogLevel;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
/**
* This class defines a configuration entry, which can hold zero or more
* attributes that may control the configuration of various components of the
* Directory Server.
*/
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.VOLATILE,
mayInstantiate=true,
mayExtend=false,
mayInvoke=true)
public final class ConfigEntry
{
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
// The set of immediate children for this configuration entry.
private ConcurrentHashMap<DN,ConfigEntry> children;
// The immediate parent for this configuration entry.
private ConfigEntry parent;
// The set of add listeners that have been registered with this entry.
private CopyOnWriteArrayList<ConfigAddListener> addListeners;
// The set of change listeners that have been registered with this entry.
private CopyOnWriteArrayList<ConfigChangeListener> changeListeners;
// The set of delete listeners that have been registered with this entry.
private CopyOnWriteArrayList<ConfigDeleteListener> deleteListeners;
// The actual entry wrapped by this configuration entry.
private Entry entry;
// The lock used to provide threadsafe access to this configuration entry.
private Object entryLock;
/**
* Creates a new config entry with the provided information.
*
* @param entry The entry that will be encapsulated by this config entry.
* @param parent The configuration entry that is the immediate parent for
* this configuration entry. It may be <CODE>null</CODE> if
* this entry is the configuration root.
*/
public ConfigEntry(Entry entry, ConfigEntry parent)
{
this.entry = entry;
this.parent = parent;
children = new ConcurrentHashMap<DN,ConfigEntry>();
addListeners = new CopyOnWriteArrayList<ConfigAddListener>();
changeListeners = new CopyOnWriteArrayList<ConfigChangeListener>();
deleteListeners = new CopyOnWriteArrayList<ConfigDeleteListener>();
entryLock = new Object();
}
/**
* Retrieves the actual entry wrapped by this configuration entry.
*
* @return The actual entry wrapped by this configuration entry.
*/
public Entry getEntry()
{
return entry;
}
/**
* Replaces the actual entry wrapped by this configuration entry with the
* provided entry. The given entry must be non-null and must have the same DN
* as the current entry. No validation will be performed on the target entry.
* All add/delete/change listeners that have been registered will be
* maintained, it will keep the same parent and set of children, and all other
* settings will remain the same.
*
* @param entry The new entry to store in this config entry.
*/
public void setEntry(Entry entry)
{
synchronized (entryLock)
{
this.entry = entry;
}
}
/**
* Retrieves the DN for this configuration entry.
*
* @return The DN for this configuration entry.
*/
public DN getDN()
{
return entry.getDN();
}
/**
* Indicates whether this configuration entry contains the specified
* objectclass.
*
* @param name The name of the objectclass for which to make the
* determination.
*
* @return <CODE>true</CODE> if this configuration entry contains the
* specified objectclass, or <CODE>false</CODE> if not.
*/
public boolean hasObjectClass(String name)
{
ObjectClass oc = DirectoryServer.getObjectClass(name.toLowerCase());
if (oc == null)
{
oc = DirectoryServer.getDefaultObjectClass(name);
}
return entry.hasObjectClass(oc);
}
/**
* Retrieves the specified configuration attribute from this configuration
* entry.
*
* @param stub The stub to use to format the returned configuration
* attribute.
*
* @return The requested configuration attribute from this configuration
* entry, or <CODE>null</CODE> if no such attribute is present in
* this entry.
*
* @throws ConfigException If the specified attribute exists but cannot be
* interpreted as the specified type of
* configuration attribute.
*/
public ConfigAttribute getConfigAttribute(ConfigAttribute stub)
throws ConfigException
{
String attrName = stub.getName();
AttributeType attrType =
DirectoryServer.getAttributeType(attrName.toLowerCase());
if (attrType == null)
{
attrType = DirectoryServer.getDefaultAttributeType(attrName);
}
List<Attribute> attrList = entry.getAttribute(attrType);
if ((attrList == null) || (attrList.isEmpty()))
{
return null;
}
return stub.getConfigAttribute(attrList);
}
/**
* Puts the provided configuration attribute in this entry (adding a new
* attribute if one doesn't exist, or replacing it if one does). This must
* only be performed on a duplicate of a configuration entry and never on a
* configuration entry itself.
*
* @param attribute The configuration attribute to use.
*/
public void putConfigAttribute(ConfigAttribute attribute)
{
String name = attribute.getName();
AttributeType attrType =
DirectoryServer.getAttributeType(name.toLowerCase());
if (attrType == null)
{
attrType =
DirectoryServer.getDefaultAttributeType(name, attribute.getSyntax());
}
ArrayList<Attribute> attrs = new ArrayList<Attribute>(2);
AttributeBuilder builder = new AttributeBuilder(attrType, name);
builder.addAll(attribute.getActiveValues());
attrs.add(builder.toAttribute());
if (attribute.hasPendingValues())
{
builder = new AttributeBuilder(attrType, name);
builder.setOption(OPTION_PENDING_VALUES);
builder.addAll(attribute.getPendingValues());
attrs.add(builder.toAttribute());
}
entry.putAttribute(attrType, attrs);
}
/**
* Removes the specified configuration attribute from the entry. This will
* have no impact if the specified attribute is not contained in the entry.
*
* @param lowerName The name of the configuration attribute to remove from
* the entry, formatted in all lowercase characters.
*
* @return <CODE>true</CODE> if the requested attribute was found and
* removed, or <CODE>false</CODE> if not.
*/
public boolean removeConfigAttribute(String lowerName)
{
for (AttributeType t : entry.getUserAttributes().keySet())
{
if (t.hasNameOrOID(lowerName))
{
entry.getUserAttributes().remove(t);
return true;
}
}
for (AttributeType t : entry.getOperationalAttributes().keySet())
{
if (t.hasNameOrOID(lowerName))
{
entry.getOperationalAttributes().remove(t);
return true;
}
}
return false;
}
/**
* Retrieves the configuration entry that is the immediate parent for this
* configuration entry.
*
* @return The configuration entry that is the immediate parent for this
* configuration entry. It may be <CODE>null</CODE> if this entry is
* the configuration root.
*/
public ConfigEntry getParent()
{
return parent;
}
/**
* Retrieves the set of children associated with this configuration entry.
* This list should not be altered by the caller.
*
* @return The set of children associated with this configuration entry.
*/
public ConcurrentHashMap<DN,ConfigEntry> getChildren()
{
return children;
}
/**
* Indicates whether this entry has any children.
*
* @return <CODE>true</CODE> if this entry has one or more children, or
* <CODE>false</CODE> if not.
*/
public boolean hasChildren()
{
return (! children.isEmpty());
}
/**
* Adds the specified entry as a child of this configuration entry. No check
* will be made to determine whether the specified entry actually should be a
* child of this entry, and this method will not notify any add listeners that
* might be registered with this configuration entry.
*
* @param childEntry The entry to add as a child of this configuration
* entry.
*
* @throws ConfigException If the provided entry could not be added as a
* child of this configuration entry (e.g., because
* another entry already exists with the same DN).
*/
public void addChild(ConfigEntry childEntry)
throws ConfigException
{
ConfigEntry conflictingChild;
synchronized (entryLock)
{
conflictingChild = children.putIfAbsent(childEntry.getDN(), childEntry);
}
if (conflictingChild != null)
{
Message message = ERR_CONFIG_ENTRY_CONFLICTING_CHILD.get(
conflictingChild.getDN().toString(), entry.getDN().toString());
throw new ConfigException(message);
}
}
/**
* Attempts to remove the child entry with the specified DN. This method will
* not notify any delete listeners that might be registered with this
* configuration entry.
*
* @param childDN The DN of the child entry to remove from this config
* entry.
*
* @return The configuration entry that was removed as a child of this
* entry.
*
* @throws ConfigException If the specified child entry did not exist or if
* it had children of its own.
*/
public ConfigEntry removeChild(DN childDN)
throws ConfigException
{
synchronized (entryLock)
{
try
{
ConfigEntry childEntry = children.get(childDN);
if (childEntry == null)
{
Message message = ERR_CONFIG_ENTRY_NO_SUCH_CHILD.get(
childDN.toString(), entry.getDN().toString());
throw new ConfigException(message);
}
if (childEntry.hasChildren())
{
Message message = ERR_CONFIG_ENTRY_CANNOT_REMOVE_NONLEAF.get(
childDN.toString(), entry.getDN().toString());
throw new ConfigException(message);
}
children.remove(childDN);
return childEntry;
}
catch (ConfigException ce)
{
throw ce;
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
Message message = ERR_CONFIG_ENTRY_CANNOT_REMOVE_CHILD.
get(String.valueOf(childDN), String.valueOf(entry.getDN()),
stackTraceToSingleLineString(e));
throw new ConfigException(message, e);
}
}
}
/**
* Creates a duplicate of this configuration entry that should be used when
* making changes to this entry. Changes should only be made to the duplicate
* (never the original) and then applied to the original. Note that this
* method and the other methods used to make changes to the entry contents are
* not threadsafe and therefore must be externally synchronized to ensure that
* only one change may be in progress at any given time.
*
* @return A duplicate of this configuration entry that should be used when
* making changes to this entry.
*/
public ConfigEntry duplicate()
{
return new ConfigEntry(entry.duplicate(false), parent);
}
/**
* Retrieves the set of change listeners that have been registered with this
* configuration entry.
*
* @return The set of change listeners that have been registered with this
* configuration entry.
*/
public CopyOnWriteArrayList<ConfigChangeListener> getChangeListeners()
{
return changeListeners;
}
/**
* Registers the provided change listener so that it will be notified of any
* changes to this configuration entry. No check will be made to determine
* whether the provided listener is already registered.
*
* @param listener The change listener to register with this config entry.
*/
public void registerChangeListener(ConfigChangeListener listener)
{
changeListeners.add(listener);
}
/**
* Attempts to deregister the provided change listener with this configuration
* entry.
*
* @param listener The change listener to deregister with this config entry.
*
* @return <CODE>true</CODE> if the specified listener was deregistered, or
* <CODE>false</CODE> if it was not.
*/
public boolean deregisterChangeListener(ConfigChangeListener listener)
{
return changeListeners.remove(listener);
}
/**
* Retrieves the set of config add listeners that have been registered for
* this entry.
*
* @return The set of config add listeners that have been registered for this
* entry.
*/
public CopyOnWriteArrayList<ConfigAddListener> getAddListeners()
{
return addListeners;
}
/**
* Registers the provided add listener so that it will be notified if any new
* entries are added immediately below this configuration entry.
*
* @param listener The add listener that should be registered.
*/
public void registerAddListener(ConfigAddListener listener)
{
addListeners.addIfAbsent(listener);
}
/**
* Deregisters the provided add listener so that it will no longer be
* notified if any new entries are added immediately below this configuration
* entry.
*
* @param listener The add listener that should be deregistered.
*/
public void deregisterAddListener(ConfigAddListener listener)
{
addListeners.remove(listener);
}
/**
* Retrieves the set of config delete listeners that have been registered for
* this entry.
*
* @return The set of config delete listeners that have been registered for
* this entry.
*/
public CopyOnWriteArrayList<ConfigDeleteListener> getDeleteListeners()
{
return deleteListeners;
}
/**
* Registers the provided delete listener so that it will be notified if any
* entries are deleted immediately below this configuration entry.
*
* @param listener The delete listener that should be registered.
*/
public void registerDeleteListener(ConfigDeleteListener listener)
{
deleteListeners.addIfAbsent(listener);
}
/**
* Deregisters the provided delete listener so that it will no longer be
* notified if any new are removed immediately below this configuration entry.
*
* @param listener The delete listener that should be deregistered.
*/
public void deregisterDeleteListener(ConfigDeleteListener listener)
{
deleteListeners.remove(listener);
}
}