/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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.
* Portions Copyright 2014-2015 ForgeRock AS
*/
package org.opends.server.config;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
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.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 static org.opends.messages.ConfigMessages.*;
import static org.opends.server.config.ConfigConstants.*;
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
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** The set of immediate children for this configuration entry. */
private final ConcurrentMap<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 final CopyOnWriteArrayList<ConfigAddListener> addListeners;
/** The set of change listeners that have been registered with this entry. */
private final CopyOnWriteArrayList<ConfigChangeListener> changeListeners;
/** The set of delete listeners that have been registered with this entry. */
private final 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<>();
addListeners = new CopyOnWriteArrayList<>();
changeListeners = new CopyOnWriteArrayList<>();
deleteListeners = new CopyOnWriteArrayList<>();
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.getName();
}
/**
* 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.getAttributeTypeOrDefault(attrName.toLowerCase(), attrName);
List<Attribute> attrList = entry.getAttribute(attrType);
if (attrList != null && !attrList.isEmpty())
{
return stub.getConfigAttribute(attrList);
}
return null;
}
/**
* 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.getAttributeTypeOrDefault(
name.toLowerCase(), name, attribute.getSyntax());
List<Attribute> attrs = new ArrayList<>(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 ConcurrentMap<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)
{
throw new ConfigException(ERR_CONFIG_ENTRY_CONFLICTING_CHILD.get(
conflictingChild.getDN(), entry.getName()));
}
}
/**
* 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)
{
throw new ConfigException(ERR_CONFIG_ENTRY_NO_SUCH_CHILD.get(
childDN, entry.getName()));
}
if (childEntry.hasChildren())
{
throw new ConfigException(ERR_CONFIG_ENTRY_CANNOT_REMOVE_NONLEAF.get(
childDN, entry.getName()));
}
children.remove(childDN);
return childEntry;
}
catch (ConfigException ce)
{
throw ce;
}
catch (Exception e)
{
logger.traceException(e);
LocalizableMessage message = ERR_CONFIG_ENTRY_CANNOT_REMOVE_CHILD.
get(childDN, entry.getName(), 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);
}
/** {@inheritDoc} */
@Override
public String toString()
{
return entry.getName().toString();
}
}