Entry.java revision d25372dc8e65a9ed019a88fdf659ca61313f1b31
/*
* 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.types;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.AttributeValueDecoder;
import org.opends.server.api.CompressedSchema;
import org.opends.server.api.ProtocolElement;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.util.LDIFException;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.messages.UtilityMessages.*;
import static org.opends.server.util.LDIFWriter.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
* This class defines a data structure for a Directory Server entry.
* It includes a DN and a set of attributes.
* <BR><BR>
* The entry also contains a volatile attachment object, which should
* be used to associate the entry with a special type of object that
* is based on its contents. For example, if the entry holds access
* control information, then the attachment might be an object that
* contains a representation of that access control definition in a
* more useful form. This is only useful if the entry is to be
* cached, since the attachment may be accessed if the entry is
* retrieved from the cache, but if the entry is retrieved from the
* backend repository it cannot be guaranteed to contain any
* attachment (and in most cases will not). This attachment is
* volatile in that it is not always guaranteed to be present, it may
* be removed or overwritten at any time, and it will be invalidated
* and removed if the entry is altered in any way.
*/
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
mayInstantiate=true,
mayExtend=false,
mayInvoke=true)
public class Entry
implements ProtocolElement
{
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
// Indicates whether virtual attribute processing has been performed
// for this entry.
private boolean virtualAttributeProcessingPerformed;
// The set of operational attributes for this entry.
private Map<AttributeType,List<Attribute>> operationalAttributes;
// The set of user attributes for this entry.
private Map<AttributeType,List<Attribute>> userAttributes;
// The set of suppressed real attributes for this entry.
private Map<AttributeType,List<Attribute>> suppressedAttributes;
// The set of objectclasses for this entry.
private Map<ObjectClass,String> objectClasses;
// The DN for this entry.
private DN dn;
// A generic attachment that may be used to associate this entry
// with some other object.
private transient Object attachment;
// The schema used to govern this entry.
private Schema schema;
/**
* Creates a new entry with the provided information.
*
* @param dn The distinguished name for this
* entry.
* @param objectClasses The set of objectclasses for this
* entry as a mapping between the
* objectclass and the name to use to
* reference it.
* @param userAttributes The set of user attributes for
* this entry as a mapping between
* the attribute type and the list of
* attributes with that type.
* @param operationalAttributes The set of operational attributes
* for this entry as a mapping
* between the attribute type and the
* list of attributes with that type.
*/
public Entry(DN dn, Map<ObjectClass,String> objectClasses,
Map<AttributeType,List<Attribute>> userAttributes,
Map<AttributeType,List<Attribute>>
operationalAttributes)
{
attachment = null;
schema = DirectoryServer.getSchema();
virtualAttributeProcessingPerformed = false;
suppressedAttributes =
new LinkedHashMap<AttributeType,List<Attribute>>();
if (dn == null)
{
this.dn = DN.nullDN();
}
else
{
this.dn = dn;
}
if (objectClasses == null)
{
this.objectClasses = new HashMap<ObjectClass,String>();
}
else
{
this.objectClasses = objectClasses;
}
if (userAttributes == null)
{
this.userAttributes =
new HashMap<AttributeType,List<Attribute>>();
}
else
{
this.userAttributes = userAttributes;
}
if (operationalAttributes == null)
{
this.operationalAttributes =
new HashMap<AttributeType,List<Attribute>>();
}
else
{
this.operationalAttributes = operationalAttributes;
}
}
/**
* Retrieves the distinguished name for this entry.
*
* @return The distinguished name for this entry.
*/
public DN getDN()
{
return dn;
}
/**
* Specifies the distinguished name for this entry.
*
* @param dn The distinguished name for this entry.
*/
public void setDN(DN dn)
{
if (dn == null)
{
this.dn = DN.nullDN();
}
else
{
this.dn = dn;
}
attachment = null;
}
/**
* Retrieves the set of objectclasses defined for this entry. The
* caller should be allowed to modify the contents of this list, but
* if it does then it should also invalidate the attachment.
*
* @return The set of objectclasses defined for this entry.
*/
public Map<ObjectClass,String> getObjectClasses()
{
return objectClasses;
}
/**
* Indicates whether this entry has the specified objectclass.
*
* @param objectClass The objectclass for which to make the
* determination.
*
* @return <CODE>true</CODE> if this entry has the specified
* objectclass, or <CODE>false</CODE> if not.
*/
public boolean hasObjectClass(ObjectClass objectClass)
{
return objectClasses.containsKey(objectClass);
}
/**
* Retrieves the structural objectclass for this entry.
*
* @return The structural objectclass for this entry, or
* <CODE>null</CODE> if there is none for some reason. If
* there are multiple structural classes in the entry, then
* the first will be returned.
*/
public ObjectClass getStructuralObjectClass()
{
ObjectClass structuralClass = null;
for (ObjectClass oc : objectClasses.keySet())
{
if (oc.getObjectClassType() == ObjectClassType.STRUCTURAL)
{
if (structuralClass == null)
{
structuralClass = oc;
}
else
{
if (oc.isDescendantOf(structuralClass))
{
structuralClass = oc;
}
}
}
}
return structuralClass;
}
/**
* Specifies the set of objectclasses for this entry.
*
* @param objectClassNames The values containing the names or OIDs
* of the objectClasses for this entry.
*
* @throws DirectoryException If a problem occurs while attempting
* to set the objectclasses for this
* entry.
*/
public void setObjectClasses(
Collection<AttributeValue> objectClassNames)
throws DirectoryException
{
attachment = null;
// Iterate through all the provided objectclass names and make
// sure that they are names of valid objectclasses.
LinkedHashMap<ObjectClass,String> ocMap =
new LinkedHashMap<ObjectClass,String>();
for (AttributeValue v : objectClassNames)
{
String name = v.getStringValue();
String lowerName;
try
{
lowerName = v.getNormalizedStringValue();
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
lowerName = toLowerCase(v.getStringValue());
}
ObjectClass oc = DirectoryServer.getObjectClass(lowerName);
if (oc == null)
{
Message message =
ERR_ENTRY_ADD_UNKNOWN_OC.get(name, String.valueOf(dn));
throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
message);
}
ocMap.put(oc, name);
}
// If we've gotten here, then everything is fine so put the new
// set of objectclasses.
objectClasses = ocMap;
}
/**
* Adds the provided objectClass to this entry.
*
* @param oc The objectClass to add to this entry.
*
* @throws DirectoryException If a problem occurs while attempting
* to add the objectclass to this
* entry.
*/
public void addObjectClass(ObjectClass oc)
throws DirectoryException
{
attachment = null;
if (objectClasses.containsKey(oc))
{
Message message = ERR_ENTRY_ADD_DUPLICATE_OC.get(
oc.getNameOrOID(), String.valueOf(dn));
throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
message);
}
objectClasses.put(oc, oc.getNameOrOID());
}
/**
* Retrieves the entire set of attributes for this entry. This will
* include both user and operational attributes. The caller must
* not modify the contents of this list. Also note that this method
* is less efficient than calling either (or both)
* <CODE>getUserAttributes</CODE> or
* <CODE>getOperationalAttributes</CODE>, so it should only be used
* when calls to those methods are not appropriate.
*
* @return The entire set of attributes for this entry.
*/
public List<Attribute> getAttributes()
{
ArrayList<Attribute> attributes = new ArrayList<Attribute>();
for (List<Attribute> list : userAttributes.values())
{
for (Attribute a : list)
{
attributes.add(a);
}
}
for (List<Attribute> list : operationalAttributes.values())
{
for (Attribute a : list)
{
attributes.add(a);
}
}
return attributes;
}
/**
* Retrieves the entire set of user (i.e., non-operational)
* attributes for this entry. The caller should be allowed to
* modify the contents of this list, but if it does then it should
* also invalidate the attachment.
*
* @return The entire set of user attributes for this entry.
*/
public Map<AttributeType,List<Attribute>> getUserAttributes()
{
return userAttributes;
}
/**
* Retrieves the entire set of operational attributes for this
* entry. The caller should be allowed to modify the contents of
* this list, but if it does then it should also invalidate the
* attachment.
*
* @return The entire set of operational attributes for this entry.
*/
public Map<AttributeType,List<Attribute>> getOperationalAttributes()
{
return operationalAttributes;
}
/**
* Retrieves an attribute holding the objectclass information for
* this entry. The returned attribute must not be altered.
*
* @return An attribute holding the objectclass information for
* this entry, or <CODE>null</CODE> if it does not have any
* objectclass information.
*/
public Attribute getObjectClassAttribute()
{
if ((objectClasses == null) || objectClasses.isEmpty())
{
return null;
}
AttributeType ocType =
DirectoryServer.getObjectClassAttributeType();
AttributeBuilder builder =
new AttributeBuilder(ocType, ATTR_OBJECTCLASS);
for (String s : objectClasses.values())
{
builder.add(new AttributeValue(ocType, new ASN1OctetString(s)));
}
return builder.toAttribute();
}
/**
* Indicates whether this entry contains the specified attribute.
* Any subordinate attribute of the specified attribute will also be
* used in the determination.
*
* @param attributeType
* The attribute type for which to make the determination.
* @return <CODE>true</CODE> if this entry contains the specified
* attribute, or <CODE>false</CODE> if not.
*/
public boolean hasAttribute(AttributeType attributeType)
{
return hasAttribute(attributeType, null, true);
}
/**
* Indicates whether this entry contains the specified attribute.
*
* @param attributeType The attribute type for which to
* make the determination.
* @param includeSubordinates Whether to include any subordinate
* attributes of the attribute type
* being retrieved.
*
* @return <CODE>true</CODE> if this entry contains the specified
* attribute, or <CODE>false</CODE> if not.
*/
public boolean hasAttribute(AttributeType attributeType,
boolean includeSubordinates)
{
return hasAttribute(attributeType, null, includeSubordinates);
}
/**
* Indicates whether this entry contains the specified attribute
* with all of the options in the provided set. Any subordinate
* attribute of the specified attribute will also be used in the
* determination.
*
* @param attributeType
* The attribute type for which to make the determination.
* @param options
* The set of options to use in the determination.
* @return <CODE>true</CODE> if this entry contains the specified
* attribute, or <CODE>false</CODE> if not.
*/
public boolean hasAttribute(
AttributeType attributeType,
Set<String> options)
{
return hasAttribute(attributeType, options, true);
}
/**
* Indicates whether this entry contains the specified attribute
* with all of the options in the provided set.
*
* @param attributeType
* The attribute type for which to make the determination.
* @param options
* The set of options to use in the determination.
* @param includeSubordinates
* Whether to include any subordinate attributes of the
* attribute type being retrieved.
* @return <CODE>true</CODE> if this entry contains the specified
* attribute, or <CODE>false</CODE> if not.
*/
public boolean hasAttribute(
AttributeType attributeType,
Set<String> options,
boolean includeSubordinates)
{
// Handle object class.
if (attributeType.isObjectClassType())
{
if (!objectClasses.isEmpty())
{
return (options == null || options.isEmpty());
}
return false;
}
if (!includeSubordinates)
{
// It's possible that there could be an attribute without any
// values, which we should treat as not having the requested
// attribute.
Attribute attribute = getExactAttribute(attributeType, options);
return attribute != null && !attribute.isEmpty();
}
// Check all matching attributes.
List<Attribute> attributes;
if (attributeType.isOperational())
{
attributes = operationalAttributes.get(attributeType);
}
else
{
attributes = userAttributes.get(attributeType);
}
if (attributes != null)
{
for (Attribute attribute : attributes)
{
// It's possible that there could be an attribute without any
// values, which we should treat as not having the requested
// attribute.
if (!attribute.isEmpty() && attribute.hasAllOptions(options))
{
return true;
}
}
}
// Check sub-types.
if (attributeType.mayHaveSubordinateTypes())
{
for (AttributeType subType : schema.getSubTypes(attributeType))
{
if (subType.isOperational())
{
attributes = operationalAttributes.get(subType);
}
else
{
attributes = userAttributes.get(subType);
}
if (attributes != null)
{
for (Attribute attribute : attributes)
{
// It's possible that there could be an attribute without
// any values, which we should treat as not having the
// requested attribute.
if (!attribute.isEmpty()
&& attribute.hasAllOptions(options))
{
return true;
}
}
}
}
}
return false;
}
/**
* Retrieves the requested attribute element(s) for the specified
* attribute type. The list returned may include multiple elements
* if the same attribute exists in the entry multiple times with
* different sets of options. It may also include any subordinate
* attributes of the attribute being retrieved.
*
* @param attributeType
* The attribute type to retrieve.
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if the specified
* attribute type is not present in this entry.
*/
public List<Attribute> getAttribute(AttributeType attributeType)
{
return getAttribute(attributeType, true);
}
/**
* Retrieves the requested attribute element(s) for the specified
* attribute type. The list returned may include multiple elements
* if the same attribute exists in the entry multiple times with
* different sets of options.
*
* @param attributeType The attribute type to retrieve.
* @param includeSubordinates Whether to include any subordinate
* attributes of the attribute type
* being retrieved.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if the specified
* attribute type is not present in this entry.
*/
public List<Attribute> getAttribute(AttributeType attributeType,
boolean includeSubordinates)
{
if (includeSubordinates &&
attributeType.mayHaveSubordinateTypes())
{
List<Attribute> attributes = new LinkedList<Attribute>();
List<Attribute> attrs = userAttributes.get(attributeType);
if (attrs != null)
{
attributes.addAll(attrs);
}
attrs = operationalAttributes.get(attributeType);
if (attrs != null)
{
attributes.addAll(attrs);
}
for (AttributeType at : schema.getSubTypes(attributeType))
{
attrs = userAttributes.get(at);
if (attrs != null)
{
attributes.addAll(attrs);
}
attrs = operationalAttributes.get(at);
if (attrs != null)
{
attributes.addAll(attrs);
}
}
if (attributes.isEmpty())
{
return null;
}
else
{
return attributes;
}
}
else
{
List<Attribute> attributes = userAttributes.get(attributeType);
if (attributes == null)
{
attributes = operationalAttributes.get(attributeType);
if (attributes == null)
{
if (attributeType.isObjectClassType() &&
(! objectClasses.isEmpty()))
{
attributes = new ArrayList<Attribute>(1);
attributes.add(getObjectClassAttribute());
return attributes;
}
else
{
return null;
}
}
else
{
return attributes;
}
}
else
{
return attributes;
}
}
}
/**
* Retrieves the requested attribute element(s) for the attribute
* with the specified name or OID. The list returned may include
* multiple elements if the same attribute exists in the entry
* multiple times with different sets of options. It may also
* include any subordinate attributes of the attribute being
* retrieved.
* <BR><BR>
* Note that this method should only be used in cases in which the
* Directory Server schema has no reference of an attribute type
* with the specified name. It is not as accurate or efficient as
* the version of this method that takes an
* <CODE>AttributeType</CODE> argument.
*
* @param lowerName The name or OID of the attribute to return,
* formatted in all lowercase characters.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if the specified
* attribute type is not present in this entry.
*/
public List<Attribute> getAttribute(String lowerName)
{
for (AttributeType attr : userAttributes.keySet())
{
if (attr.hasNameOrOID(lowerName))
{
return getAttribute(attr, true);
}
}
for (AttributeType attr : operationalAttributes.keySet())
{
if (attr.hasNameOrOID(lowerName))
{
return getAttribute(attr, true);
}
}
if (lowerName.equals(OBJECTCLASS_ATTRIBUTE_TYPE_NAME) &&
(! objectClasses.isEmpty()))
{
LinkedList<Attribute> attrList = new LinkedList<Attribute>();
attrList.add(getObjectClassAttribute());
return attrList;
}
return null;
}
/**
* Retrieves the requested attribute element(s) for the specified
* attribute type. The list returned may include multiple elements
* if the same attribute exists in the entry multiple times with
* different sets of options. It may also include any subordinate
* attributes of the attribute being retrieved.
*
* @param attributeType The attribute type to retrieve.
* @param options The set of attribute options to
* include in matching elements.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if the specified
* attribute type is not present in this entry with the
* provided set of options.
*/
public List<Attribute> getAttribute(AttributeType attributeType,
Set<String> options)
{
return getAttribute(attributeType, true, options);
}
/**
* Retrieves the requested attribute element(s) for the specified
* attribute type. The list returned may include multiple elements
* if the same attribute exists in the entry multiple times with
* different sets of options.
*
* @param attributeType The attribute type to retrieve.
* @param includeSubordinates Whether to include any subordinate
* attributes of the attribute type
* being retrieved.
* @param options The set of attribute options to
* include in matching elements.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if the specified
* attribute type is not present in this entry with the
* provided set of options.
*/
public List<Attribute> getAttribute(AttributeType attributeType,
boolean includeSubordinates,
Set<String> options)
{
List<Attribute> attributes = new LinkedList<Attribute>();
if (includeSubordinates &&
attributeType.mayHaveSubordinateTypes())
{
List<Attribute> attrs = userAttributes.get(attributeType);
if (attrs != null)
{
attributes.addAll(attrs);
}
attrs = operationalAttributes.get(attributeType);
if (attrs != null)
{
attributes.addAll(attrs);
}
for (AttributeType at : schema.getSubTypes(attributeType))
{
attrs = userAttributes.get(at);
if (attrs != null)
{
attributes.addAll(attrs);
}
attrs = operationalAttributes.get(at);
if (attrs != null)
{
attributes.addAll(attrs);
}
}
}
else
{
List<Attribute> attrs = userAttributes.get(attributeType);
if (attrs == null)
{
attrs = operationalAttributes.get(attributeType);
if (attrs == null)
{
if (attributeType.isObjectClassType() &&
(! objectClasses.isEmpty()) &&
((options == null) || options.isEmpty()))
{
attributes.add(getObjectClassAttribute());
return attributes;
}
else
{
return null;
}
}
else
{
attributes.addAll(attrs);
}
}
else
{
attributes.addAll(attrs);
}
}
Iterator<Attribute> iterator = attributes.iterator();
while (iterator.hasNext())
{
Attribute a = iterator.next();
if (! a.hasAllOptions(options))
{
iterator.remove();
}
}
if (attributes.isEmpty())
{
return null;
}
else
{
return attributes;
}
}
/**
* Retrieves the requested attribute element(s) for the attribute
* with the specified name or OID and set of options. The list
* returned may include multiple elements if the same attribute
* exists in the entry multiple times with different sets of
* matching options.
* <BR><BR>
* Note that this method should only be used in cases in which the
* Directory Server schema has no reference of an attribute type
* with the specified name. It is not as accurate or efficient as
* the version of this method that takes an
* <CODE>AttributeType</CODE> argument.
*
* @param lowerName The name or OID of the attribute to return,
* formatted in all lowercase characters.
* @param options The set of attribute options to include in
* matching elements.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if the specified
* attribute type is not present in this entry.
*/
public List<Attribute> getAttribute(String lowerName,
Set<String> options)
{
for (AttributeType attr : userAttributes.keySet())
{
if (attr.hasNameOrOID(lowerName))
{
return getAttribute(attr, options);
}
}
for (AttributeType attr : operationalAttributes.keySet())
{
if (attr.hasNameOrOID(lowerName))
{
return getAttribute(attr, options);
}
}
if (lowerName.equals(OBJECTCLASS_ATTRIBUTE_TYPE_NAME) &&
((options == null) || options.isEmpty()))
{
LinkedList<Attribute> attributes = new LinkedList<Attribute>();
attributes.add(getObjectClassAttribute());
return attributes;
}
else
{
return null;
}
}
/**
* Retrieves the requested attribute type from the entry and decodes
* a single value as an object of type T.
* <p>
* If the requested attribute type is not present then
* <code>null</code> is returned. If more than one attribute value
* is present, then the first value found will be decoded and
* returned.
* <p>
* The attribute value is decoded using the specified
* {@link org.opends.server.api.AttributeValueDecoder}.
*
* @param <T>
* Decode the attribute value to an object of this type.
* @param attributeType
* The attribute type to retrieve.
* @param decoder
* The attribute value decoder.
* @return The decoded attribute value or <code>null</code> if no
* attribute value having the specified attribute type was
* found.
* @throws DirectoryException
* If the requested attribute value could not be decoded
* successfully.
*/
public final <T> T getAttributeValue(AttributeType attributeType,
AttributeValueDecoder<T> decoder) throws DirectoryException
{
List<Attribute> attributes = getAttribute(attributeType, true);
AttributeValueIterable values =
new AttributeValueIterable(attributes);
Iterator<AttributeValue> iterator = values.iterator();
if (iterator.hasNext())
{
return decoder.decode(iterator.next());
}
else
{
return null;
}
}
/**
* Retrieves the requested attribute type from the entry and decodes
* any values as objects of type T and then places them in the
* specified collection.
* <p>
* If the requested attribute type is not present then no decoded
* values will be added to the container.
* <p>
* The attribute value is decoded using the specified
* {@link org.opends.server.api.AttributeValueDecoder}.
*
* @param <T>
* Decode the attribute values to objects of this type.
* @param attributeType
* The attribute type to retrieve.
* @param decoder
* The attribute value decoder.
* @param collection
* The collection to which decoded values should be added.
* @return The collection containing the decoded attribute value.
* @throws DirectoryException
* If one or more of the requested attribute values could
* not be decoded successfully.
*/
public final <T> Collection<T> getAttributeValues(
AttributeType attributeType,
AttributeValueDecoder<? extends T> decoder,
Collection<T> collection)
throws DirectoryException
{
List<Attribute> attributes = getAttribute(attributeType, true);
AttributeValueIterable values =
new AttributeValueIterable(attributes);
for (AttributeValue value : values)
{
collection.add(decoder.decode(value));
}
return collection;
}
/**
* Indicates whether this entry contains the specified user
* attribute.
*
* @param attributeType
* The attribute type for which to make the determination.
* @return <CODE>true</CODE> if this entry contains the specified
* user attribute, or <CODE>false</CODE> if not.
*/
public boolean hasUserAttribute(AttributeType attributeType)
{
if (userAttributes.containsKey(attributeType))
{
return true;
}
if (attributeType.mayHaveSubordinateTypes())
{
for (AttributeType at : schema.getSubTypes(attributeType))
{
if (userAttributes.containsKey(at))
{
return true;
}
}
}
return false;
}
/**
* Retrieves the requested user attribute element(s) for the
* specified attribute type. The list returned may include multiple
* elements if the same attribute exists in the entry multiple times
* with different sets of options.
*
* @param attributeType The attribute type to retrieve.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if there is no such
* user attribute.
*/
public List<Attribute> getUserAttribute(AttributeType attributeType)
{
if (attributeType.mayHaveSubordinateTypes())
{
LinkedList<Attribute> attributes = new LinkedList<Attribute>();
List<Attribute> attrs = userAttributes.get(attributeType);
if (attrs != null)
{
attributes.addAll(attrs);
}
for (AttributeType at : schema.getSubTypes(attributeType))
{
attrs = userAttributes.get(at);
if (attrs != null)
{
attributes.addAll(attrs);
}
}
if (attributes.isEmpty())
{
return null;
}
else
{
return attributes;
}
}
else
{
return userAttributes.get(attributeType);
}
}
/**
* Retrieves the requested user attribute element(s) for the
* specified attribute type. The list returned may include multiple
* elements if the same attribute exists in the entry multiple times
* with different sets of options.
*
* @param attributeType The attribute type to retrieve.
* @param options The set of attribute options to include in
* matching elements.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if there is no such
* user attribute with the specified set of options.
*/
public List<Attribute> getUserAttribute(AttributeType attributeType,
Set<String> options)
{
LinkedList<Attribute> attributes = new LinkedList<Attribute>();
List<Attribute> attrs = userAttributes.get(attributeType);
if (attrs != null)
{
attributes.addAll(attrs);
}
if (attributeType.mayHaveSubordinateTypes())
{
for (AttributeType at : schema.getSubTypes(attributeType))
{
attrs = userAttributes.get(at);
if (attrs != null)
{
attributes.addAll(attrs);
}
}
}
Iterator<Attribute> iterator = attributes.iterator();
while (iterator.hasNext())
{
Attribute a = iterator.next();
if (! a.hasAllOptions(options))
{
iterator.remove();
}
}
if (attributes.isEmpty())
{
return null;
}
else
{
return attributes;
}
}
/**
* Makes a copy of attributes matching the specified options.
*
* @param attrList The attributes to be copied.
* @param options The set of attribute options to include in
* matching elements.
* @param omitValues <CODE>true</CODE> if the values are to be
* omitted.
*
* @return A copy of the attributes matching the specified options,
* or <CODE>null</CODE> if there is no such attribute with
* the specified set of options.
*/
private static List<Attribute> duplicateAttribute(
List<Attribute> attrList,
Set<String> options,
boolean omitValues)
{
if (attrList == null)
{
return null;
}
ArrayList<Attribute> duplicateList =
new ArrayList<Attribute>(attrList.size());
for (Attribute a : attrList)
{
if (a.hasAllOptions(options))
{
if (omitValues)
{
duplicateList.add(Attributes.empty(a));
}
else
{
duplicateList.add(a);
}
}
}
if (duplicateList.isEmpty())
{
return null;
}
else
{
return duplicateList;
}
}
/**
* Retrieves a copy of the requested user attribute element(s) for
* the specified attribute type. The list returned may include
* multiple elements if the same attribute exists in the entry
* multiple times with different sets of options.
*
* @param attributeType The attribute type to retrieve.
* @param options The set of attribute options to include in
* matching elements.
* @param omitValues <CODE>true</CODE> if the values are to be
* omitted.
*
* @return A copy of the requested attribute element(s) for the
* specified attribute type, or <CODE>null</CODE> if there
* is no such user attribute with the specified set of
* options.
*/
public List<Attribute> duplicateUserAttribute(
AttributeType attributeType,
Set<String> options,
boolean omitValues)
{
List<Attribute> currentList = getUserAttribute(attributeType);
return duplicateAttribute(currentList, options, omitValues);
}
/**
* Retrieves a copy of the requested operational attribute
* element(s) for the specified attribute type. The list returned
* may include multiple elements if the same attribute exists in
* the entry multiple times with different sets of options.
*
* @param attributeType The attribute type to retrieve.
* @param options The set of attribute options to include in
* matching elements.
* @param omitValues <CODE>true</CODE> if the values are to be
* omitted.
*
* @return A copy of the requested attribute element(s) for the
* specified attribute type, or <CODE>null</CODE> if there
* is no such user attribute with the specified set of
* options.
*/
public List<Attribute> duplicateOperationalAttribute(
AttributeType attributeType,
Set<String> options,
boolean omitValues)
{
List<Attribute> currentList =
getOperationalAttribute(attributeType);
return duplicateAttribute(currentList, options, omitValues);
}
/**
* Indicates whether this entry contains the specified operational
* attribute.
*
* @param attributeType The attribute type for which to make the
* determination.
*
* @return <CODE>true</CODE> if this entry contains the specified
* operational attribute, or <CODE>false</CODE> if not.
*/
public boolean hasOperationalAttribute(AttributeType attributeType)
{
if (operationalAttributes.containsKey(attributeType))
{
return true;
}
if (attributeType.mayHaveSubordinateTypes())
{
for (AttributeType at : schema.getSubTypes(attributeType))
{
if (operationalAttributes.containsKey(at))
{
return true;
}
}
}
return false;
}
/**
* Retrieves the requested operational attribute element(s) for the
* specified attribute type. The list returned may include multiple
* elements if the same attribute exists in the entry multiple times
* with different sets of options.
*
* @param attributeType The attribute type to retrieve.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if there is no such
* operational attribute.
*/
public List<Attribute> getOperationalAttribute(
AttributeType attributeType)
{
if (attributeType.mayHaveSubordinateTypes())
{
LinkedList<Attribute> attributes = new LinkedList<Attribute>();
List<Attribute> attrs =
operationalAttributes.get(attributeType);
if (attrs != null)
{
attributes.addAll(attrs);
}
for (AttributeType at : schema.getSubTypes(attributeType))
{
attrs = operationalAttributes.get(at);
if (attrs != null)
{
attributes.addAll(attrs);
}
}
if (attributes.isEmpty())
{
return null;
}
else
{
return attributes;
}
}
else
{
return operationalAttributes.get(attributeType);
}
}
/**
* Retrieves the requested operational attribute element(s) for the
* specified attribute type. The list returned may include multiple
* elements if the same attribute exists in the entry multiple times
* with different sets of options.
*
* @param attributeType The attribute type to retrieve.
* @param options The set of attribute options to include in
* matching elements.
*
* @return The requested attribute element(s) for the specified
* attribute type, or <CODE>null</CODE> if there is no such
* operational attribute with the specified set of options.
*/
public List<Attribute> getOperationalAttribute(
AttributeType attributeType,
Set<String> options)
{
LinkedList<Attribute> attributes = new LinkedList<Attribute>();
List<Attribute> attrs = operationalAttributes.get(attributeType);
if (attrs != null)
{
attributes.addAll(attrs);
}
if (attributeType.mayHaveSubordinateTypes())
{
for (AttributeType at : schema.getSubTypes(attributeType))
{
attrs = operationalAttributes.get(at);
if (attrs != null)
{
attributes.addAll(attrs);
}
}
}
Iterator<Attribute> iterator = attributes.iterator();
while (iterator.hasNext())
{
Attribute a = iterator.next();
if (! a.hasAllOptions(options))
{
iterator.remove();
}
}
if (attributes.isEmpty())
{
return null;
}
else
{
return attributes;
}
}
/**
* Puts the provided attribute in this entry. If an attribute
* already exists with the provided type, it will be overwritten.
* Otherwise, a new attribute will be added. Note that no
* validation will be performed.
*
* @param attributeType The attribute type for the set of
* attributes to add.
* @param attributeList The set of attributes to add for the given
* type.
*/
public void putAttribute(AttributeType attributeType,
List<Attribute> attributeList)
{
attachment = null;
// See if there is already a set of attributes with the specified
// type. If so, then overwrite it.
List<Attribute> attrList = userAttributes.get(attributeType);
if (attrList != null)
{
userAttributes.put(attributeType, attributeList);
return;
}
attrList = operationalAttributes.get(attributeType);
if (attrList != null)
{
operationalAttributes.put(attributeType, attributeList);
return;
}
// This is a new attribute, so add it to the set of user or
// operational attributes as appropriate.
if (attributeType.isOperational())
{
operationalAttributes.put(attributeType, attributeList);
}
else
{
userAttributes.put(attributeType, attributeList);
}
}
/**
* Ensures that this entry contains the provided attribute and its
* values. If an attribute with the provided type already exists,
* then its attribute values will be merged.
* <p>
* This method handles object class additions but will not perform
* any object class validation. In particular, it will create
* default object classes when an object class is unknown.
* <p>
* This method implements LDAP modification add semantics, with the
* exception that it allows empty attributes to be added.
*
* @param attribute
* The attribute to add or merge with this entry.
* @param duplicateValues
* A list to which any duplicate values will be added.
*/
public void addAttribute(Attribute attribute,
List<AttributeValue> duplicateValues)
{
setAttribute(attribute, duplicateValues, false /* merge */);
}
/**
* Puts the provided attribute into this entry. If an attribute with
* the provided type and options already exists, then it will be
* replaced. If the provided attribute is empty then any existing
* attribute will be completely removed.
* <p>
* This method handles object class replacements but will not
* perform any object class validation. In particular, it will
* create default object classes when an object class is unknown.
* <p>
* This method implements LDAP modification replace semantics.
*
* @param attribute
* The attribute to replace in this entry.
*/
public void replaceAttribute(Attribute attribute)
{
// There can never be duplicate values for a replace.
setAttribute(attribute, null, true /* replace */);
}
/**
* Increments an attribute in this entry by the amount specified in
* the provided attribute.
*
* @param attribute
* The attribute identifying the attribute to be increment
* and the amount it is to be incremented by. The attribute
* must contain a single value.
* @throws DirectoryException
* If a problem occurs while attempting to increment the
* provided attribute. This may occur if the provided
* attribute was not single valued or if it could not be
* parsed as an integer of if the existing attribute
* values could not be parsed as integers.
*/
public void incrementAttribute(
Attribute attribute) throws DirectoryException
{
// Get the attribute that is to be incremented.
AttributeType attributeType = attribute.getAttributeType();
Attribute a =
getExactAttribute(attributeType, attribute.getOptions());
if (a == null)
{
Message message = ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE.get(
attribute.getName());
throw new DirectoryException(
ResultCode.NO_SUCH_ATTRIBUTE, message);
}
// Decode the increment.
Iterator<AttributeValue> i = attribute.iterator();
if (!i.hasNext())
{
Message message = ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.get(
attribute.getName());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
String incrementValue = i.next().getStringValue();
long increment;
try
{
increment = Long.parseLong(incrementValue);
}
catch (NumberFormatException e)
{
Message message = ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(
attribute.getName());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
if (i.hasNext())
{
Message message = ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.get(
attribute.getName());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
// Increment each attribute value by the specified amount.
AttributeBuilder builder = new AttributeBuilder(a, true);
for (AttributeValue v : a)
{
String s = v.getStringValue();
long currentValue;
try
{
currentValue = Long.parseLong(s);
}
catch (NumberFormatException e)
{
Message message = ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(
attribute.getName());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
long newValue = currentValue + increment;
builder.add(new AttributeValue(attributeType, String
.valueOf(newValue)));
}
replaceAttribute(builder.toAttribute());
}
/**
* Removes all instances of the specified attribute type from this
* entry, including any instances with options. If the provided
* attribute type is the objectclass type, then all objectclass
* values will be removed (but must be replaced for the entry to be
* valid). If the specified attribute type is not present in this
* entry, then this method will have no effect.
*
* @param attributeType
* The attribute type for the attribute to remove from this
* entry.
* @return <CODE>true</CODE> if the attribute was found and
* removed, or <CODE>false</CODE> if it was not present in
* the entry.
*/
public boolean removeAttribute(AttributeType attributeType)
{
attachment = null;
if (attributeType.isObjectClassType())
{
objectClasses.clear();
return true;
}
else
{
return ((userAttributes.remove(attributeType) != null) ||
(operationalAttributes.remove(attributeType) != null));
}
}
/**
* Ensures that this entry does not contain the provided attribute
* values. If the provided attribute is empty, then all values of
* the associated attribute type will be removed. Otherwise, only
* the specified values will be removed.
* <p>
* This method handles object class deletions.
* <p>
* This method implements LDAP modification delete semantics.
*
* @param attribute
* The attribute containing the information to use to
* perform the removal.
* @param missingValues
* A list to which any values contained in the provided
* attribute but not present in the entry will be added.
* @return <CODE>true</CODE> if the attribute type was present and
* the specified values that were present were removed, or
* <CODE>false</CODE> if the attribute type was not
* present in the entry. If the attribute type was present
* but only contained some of the values in the provided
* attribute, then this method will return <CODE>true</CODE>
* but will add those values to the provided list.
*/
public boolean removeAttribute(Attribute attribute,
List<AttributeValue> missingValues)
{
attachment = null;
if (attribute.getAttributeType().isObjectClassType())
{
if (attribute.isEmpty())
{
objectClasses.clear();
return true;
}
boolean allSuccessful = true;
for (AttributeValue v : attribute)
{
String ocName;
try
{
ocName = v.getNormalizedStringValue();
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
ocName = toLowerCase(v.getStringValue());
}
boolean matchFound = false;
for (ObjectClass oc : objectClasses.keySet())
{
if (oc.hasNameOrOID(ocName))
{
matchFound = true;
objectClasses.remove(oc);
break;
}
}
if (!matchFound)
{
allSuccessful = false;
missingValues.add(v);
}
}
return allSuccessful;
}
AttributeType attributeType = attribute.getAttributeType();
List<Attribute> attributes;
if (attributeType.isOperational())
{
attributes = operationalAttributes.get(attributeType);
}
else
{
attributes = userAttributes.get(attributeType);
}
if (attributes == null)
{
// There are no attributes with the same attribute type.
for (AttributeValue v : attribute)
{
missingValues.add(v);
}
return false;
}
// There are already attributes with the same attribute type.
Set<String> options = attribute.getOptions();
for (int i = 0; i < attributes.size(); i++)
{
Attribute a = attributes.get(i);
if (a.optionsEqual(options))
{
if (attribute.isEmpty())
{
// Remove the entire attribute.
attributes.remove(i);
}
else
{
// Remove Specified values.
AttributeBuilder builder = new AttributeBuilder(a);
for (AttributeValue v : attribute)
{
if (!builder.remove(v))
{
missingValues.add(v);
}
}
// Remove / replace the attribute as necessary.
if (!builder.isEmpty())
{
attributes.set(i, builder.toAttribute());
}
else
{
attributes.remove(i);
}
}
// If the attribute list is now empty remove it.
if (attributes.isEmpty())
{
if (attributeType.isOperational())
{
operationalAttributes.remove(attributeType);
}
else
{
userAttributes.remove(attributeType);
}
}
return true;
}
}
// No matching attribute found.
return false;
}
/**
* Indicates whether this entry contains the specified attribute
* value.
*
* @param attributeType The attribute type for the attribute.
* @param options The set of options for the attribute.
* @param value The value for the attribute.
*
* @return <CODE>true</CODE> if this entry contains the specified
* attribute value, or <CODE>false</CODE> if it does not.
*/
public boolean hasValue(AttributeType attributeType,
Set<String> options, AttributeValue value)
{
List<Attribute> attrList = getAttribute(attributeType, true);
if ((attrList == null) || attrList.isEmpty())
{
return false;
}
for (Attribute a : attrList)
{
if (a.optionsEqual(options))
{
return a.contains(value);
}
}
return false;
}
/**
* Applies the provided modification to this entry. No schema
* checking will be performed.
*
* @param mod The modification to apply to this entry.
*
* @throws DirectoryException If a problem occurs while attempting
* to apply the modification. Note
* that even if a problem occurs, then
* the entry may have been altered in
* some way.
*/
public void applyModification(Modification mod)
throws DirectoryException
{
Attribute a = mod.getAttribute();
AttributeType t = a.getAttributeType();
// We'll need to handle changes to the objectclass attribute in a
// special way.
if (t.isObjectClassType())
{
LinkedHashMap<ObjectClass,String> ocs = new
LinkedHashMap<ObjectClass,String>();
for (AttributeValue v : a)
{
String ocName = v.getStringValue();
String lowerName = toLowerCase(ocName);
ObjectClass oc =
DirectoryServer.getObjectClass(lowerName, true);
ocs.put(oc, ocName);
}
switch (mod.getModificationType())
{
case ADD:
for (ObjectClass oc : ocs.keySet())
{
if (objectClasses.containsKey(oc))
{
Message message =
ERR_ENTRY_DUPLICATE_VALUES.get(a.getName());
throw new DirectoryException(
ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
message);
}
else
{
objectClasses.put(oc, ocs.get(oc));
}
}
break;
case DELETE:
for (ObjectClass oc : ocs.keySet())
{
if (objectClasses.remove(oc) == null)
{
Message message =
ERR_ENTRY_NO_SUCH_VALUE.get(a.getName());
throw new DirectoryException(
ResultCode.NO_SUCH_ATTRIBUTE, message);
}
}
break;
case REPLACE:
objectClasses = ocs;
break;
case INCREMENT:
Message message =
ERR_ENTRY_OC_INCREMENT_NOT_SUPPORTED.get();
throw new DirectoryException(
ResultCode.UNWILLING_TO_PERFORM, message);
default:
message = ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(
String.valueOf(mod.getModificationType()));
throw new DirectoryException(
ResultCode.UNWILLING_TO_PERFORM, message);
}
return;
}
switch (mod.getModificationType())
{
case ADD:
LinkedList<AttributeValue> duplicateValues =
new LinkedList<AttributeValue>();
addAttribute(a, duplicateValues);
if (! duplicateValues.isEmpty())
{
Message message =
ERR_ENTRY_DUPLICATE_VALUES.get(a.getName());
throw new DirectoryException(
ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
message);
}
break;
case DELETE:
LinkedList<AttributeValue> missingValues =
new LinkedList<AttributeValue>();
removeAttribute(a, missingValues);
if (! missingValues.isEmpty())
{
Message message = ERR_ENTRY_NO_SUCH_VALUE.get(a.getName());
throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
message);
}
break;
case REPLACE:
replaceAttribute(a);
break;
case INCREMENT:
incrementAttribute(a);
break;
default:
Message message = ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(
String.valueOf(mod.getModificationType()));
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
message);
}
}
/**
* Applies all of the provided modifications to this entry.
*
* @param mods The modifications to apply to this entry.
*
* @throws DirectoryException If a problem occurs while attempting
* to apply the modifications. Note
* that even if a problem occurs, then
* the entry may have been altered in
* some way.
*/
public void applyModifications(List<Modification> mods)
throws DirectoryException
{
for (Modification m : mods)
{
applyModification(m);
}
}
/**
* Indicates whether this entry conforms to the server's schema
* requirements. The checks performed by this method include:
*
* <UL>
* <LI>Make sure that all required attributes are present, either
* in the list of user or operational attributes.</LI>
* <LI>Make sure that all user attributes are allowed by at least
* one of the objectclasses. The operational attributes will
* not be checked in this manner.</LI>
* <LI>Make sure that all single-valued attributes contained in
* the entry have only a single value.</LI>
* <LI>Make sure that the entry contains a single structural
* objectclass.</LI>
* <LI>Make sure that the entry complies with any defined name
* forms, DIT content rules, and DIT structure rules.</LI>
* </UL>
*
* @param parentEntry The entry that is the immediate
* parent of this entry, which may
* be checked for DIT structure rule
* conformance. This may be
* {@code null} if there is no
* parent or if it is unavailable
* to the caller.
* @param parentProvided Indicates whether the caller
* attempted to provide the parent.
* If not, then the parent entry
* will be loaded on demand if it is
* required.
* @param validateNameForms Indicates whether to validate the
* entry against name form
* definitions. This should only be
* {@code true} for add and modify
* DN operations, as well as for
* for imports.
* @param validateStructureRules Indicates whether to validate the
* entry against DIT structure rule
* definitions. This should only
* be {@code true} for add and
* modify DN operations.
* @param invalidReason The buffer to which an
* explanation will be appended if
* this entry does not conform to
* the server's schema
* configuration.
*
* @return {@code true} if this entry conforms to the server's
* schema requirements, or {@code false} if it does not.
*/
public boolean conformsToSchema(Entry parentEntry,
boolean parentProvided,
boolean validateNameForms,
boolean validateStructureRules,
MessageBuilder invalidReason)
{
// Get the structural objectclass for the entry. If there isn't
// one, or if there's more than one, then see if that's OK.
AcceptRejectWarn structuralPolicy =
DirectoryServer.getSingleStructuralObjectClassPolicy();
ObjectClass structuralClass = null;
boolean multipleOCErrorLogged = false;
for (ObjectClass oc : objectClasses.keySet())
{
if (oc.getObjectClassType() == ObjectClassType.STRUCTURAL)
{
if ((structuralClass == null) ||
oc.isDescendantOf(structuralClass))
{
structuralClass = oc;
}
else if (! structuralClass.isDescendantOf(oc))
{
Message message =
ERR_ENTRY_SCHEMA_MULTIPLE_STRUCTURAL_CLASSES.get(
String.valueOf(dn),
structuralClass.getNameOrOID(),
oc.getNameOrOID());
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
if (! multipleOCErrorLogged)
{
logError(message);
multipleOCErrorLogged = true;
}
}
}
}
}
NameForm nameForm = null;
DITContentRule ditContentRule = null;
DITStructureRule ditStructureRule = null;
if (structuralClass == null)
{
Message message = ERR_ENTRY_SCHEMA_NO_STRUCTURAL_CLASS.get(
String.valueOf(dn));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
else
{
ditContentRule =
DirectoryServer.getDITContentRule(structuralClass);
if ((ditContentRule != null) && ditContentRule.isObsolete())
{
ditContentRule = null;
}
if (validateNameForms)
{
nameForm = DirectoryServer.getNameForm(structuralClass);
if ((nameForm != null) && nameForm.isObsolete())
{
nameForm = null;
}
if (validateStructureRules && (nameForm != null))
{
ditStructureRule =
DirectoryServer.getDITStructureRule(nameForm);
if ((ditStructureRule != null) &&
ditStructureRule.isObsolete())
{
ditStructureRule = null;
}
}
}
}
if (! checkAttributesAndObjectClasses(ditContentRule,
structuralPolicy, invalidReason))
{
return false;
}
// If there is a name form for this entry, then make sure that the
// RDN for the entry is in compliance with it.
if (nameForm != null)
{
if (! checkNameForm(nameForm, structuralPolicy, invalidReason))
{
return false;
}
}
// If there is a DIT content rule for this entry, then make sure
// that the entry is in compliance with it.
if (ditContentRule != null)
{
if (! checkDITContentRule(ditContentRule, structuralPolicy,
invalidReason))
{
return false;
}
}
if (! checkDITStructureRule(ditStructureRule, structuralClass,
parentEntry, parentProvided, validateStructureRules,
structuralPolicy, invalidReason))
{
return false;
}
// If we've gotten here, then the entry is acceptable.
return true;
}
/**
* Checks the attributes and object classes contained in this entry
* to determine whether they conform to the server schema
* requirements.
*
* @param ditContentRule The DIT content rule for this entry, if
* any.
* @param structuralPolicy The policy that should be used for
* structural object class compliance.
* @param invalidReason A buffer into which an invalid reason
* may be added.
*
* @return {@code true} if this entry passes all of the checks, or
* {@code false} if there are any failures.
*/
private boolean checkAttributesAndObjectClasses(
DITContentRule ditContentRule,
AcceptRejectWarn structuralPolicy,
MessageBuilder invalidReason)
{
// Make sure that we recognize all of the objectclasses, that all
// auxiliary classes are allowed by the DIT content rule, and that
// all attributes required by the object classes are present.
for (ObjectClass o : objectClasses.keySet())
{
if (DirectoryServer.getObjectClass(o.getOID()) == null)
{
Message message = ERR_ENTRY_SCHEMA_UNKNOWN_OC.get(
String.valueOf(dn), o
.getNameOrOID());
invalidReason.append(message);
return false;
}
if ((o.getObjectClassType() == ObjectClassType.AUXILIARY) &&
(ditContentRule != null) &&
(! ditContentRule.getAuxiliaryClasses().contains(o)))
{
Message message =
ERR_ENTRY_SCHEMA_DISALLOWED_AUXILIARY_CLASS.get(
String.valueOf(dn),
o.getNameOrOID(),
ditContentRule.getName());
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
for (AttributeType t : o.getRequiredAttributes())
{
if (! (userAttributes.containsKey(t) ||
operationalAttributes.containsKey(t) ||
t.isObjectClassType()))
{
Message message =
ERR_ENTRY_SCHEMA_MISSING_REQUIRED_ATTR_FOR_OC.get(
String.valueOf(dn),
t.getNameOrOID(),
o.getNameOrOID());
invalidReason.append(message);
return false;
}
}
}
// Make sure all the user attributes are allowed, have at least
// one value, and if they are single-valued that they have exactly
// one value.
for (AttributeType t : userAttributes.keySet())
{
boolean found = false;
for (ObjectClass o : objectClasses.keySet())
{
if (o.isRequiredOrOptional(t))
{
found = true;
break;
}
}
if ((! found) && (ditContentRule != null))
{
if (ditContentRule.isRequiredOrOptional(t))
{
found = true;
}
}
if (! found)
{
Message message =
ERR_ENTRY_SCHEMA_DISALLOWED_USER_ATTR_FOR_OC.get(
String.valueOf(dn),
t.getNameOrOID());
invalidReason.append(message);
return false;
}
List<Attribute> attrList = userAttributes.get(t);
if (attrList != null)
{
for (Attribute a : attrList)
{
if (a.isEmpty())
{
Message message = ERR_ENTRY_SCHEMA_ATTR_NO_VALUES.get(
String.valueOf(dn),
t.getNameOrOID());
invalidReason.append(message);
return false;
}
else if (t.isSingleValue() && (a.size() != 1))
{
Message message = ERR_ENTRY_SCHEMA_ATTR_SINGLE_VALUED.get(
String.valueOf(dn),
t.getNameOrOID());
invalidReason.append(message);
return false;
}
}
}
}
// Iterate through all of the operational attributes and make sure
// that all of the single-valued attributes only have one value.
for (AttributeType t : operationalAttributes.keySet())
{
if (t.isSingleValue())
{
List<Attribute> attrList = operationalAttributes.get(t);
if (attrList != null)
{
for (Attribute a : attrList)
{
if (a.size() > 1)
{
Message message =
ERR_ENTRY_SCHEMA_ATTR_SINGLE_VALUED.get(
String.valueOf(dn),
t.getNameOrOID());
invalidReason.append(message);
return false;
}
}
}
}
}
// If we've gotten here, then things are OK.
return true;
}
/**
* Performs any processing needed for name form validation.
*
* @param nameForm The name form to validate against this
* entry.
* @param structuralPolicy The policy that should be used for
* structural object class compliance.
* @param invalidReason A buffer into which an invalid reason
* may be added.
*
* @return {@code true} if this entry passes all of the checks, or
* {@code false} if there are any failures.
*/
private boolean checkNameForm(NameForm nameForm,
AcceptRejectWarn structuralPolicy,
MessageBuilder invalidReason)
{
RDN rdn = dn.getRDN();
if (rdn != null)
{
// Make sure that all the required attributes are present.
for (AttributeType t : nameForm.getRequiredAttributes())
{
if (! rdn.hasAttributeType(t))
{
Message message =
ERR_ENTRY_SCHEMA_RDN_MISSING_REQUIRED_ATTR.get(
String.valueOf(dn),
t.getNameOrOID(),
nameForm.getNameOrOID());
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
}
// Make sure that all attributes in the RDN are allowed.
int numAVAs = rdn.getNumValues();
for (int i = 0; i < numAVAs; i++)
{
AttributeType t = rdn.getAttributeType(i);
if (! nameForm.isRequiredOrOptional(t))
{
Message message =
ERR_ENTRY_SCHEMA_RDN_DISALLOWED_ATTR.get(
String.valueOf(dn),
t.getNameOrOID(),
nameForm.getNameOrOID());
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
}
}
// If we've gotten here, then things are OK.
return true;
}
/**
* Performs any processing needed for DIT content rule validation.
*
* @param ditContentRule The DIT content rule to validate
* against this entry.
* @param structuralPolicy The policy that should be used for
* structural object class compliance.
* @param invalidReason A buffer into which an invalid reason
* may be added.
*
* @return {@code true} if this entry passes all of the checks, or
* {@code false} if there are any failures.
*/
private boolean checkDITContentRule(DITContentRule ditContentRule,
AcceptRejectWarn structuralPolicy,
MessageBuilder invalidReason)
{
// Make sure that all of the required attributes are present.
for (AttributeType t : ditContentRule.getRequiredAttributes())
{
if (! (userAttributes.containsKey(t) ||
operationalAttributes.containsKey(t) ||
t.isObjectClassType()))
{
Message message =
ERR_ENTRY_SCHEMA_MISSING_REQUIRED_ATTR_FOR_DCR.get(
String.valueOf(dn),
t.getNameOrOID(),
ditContentRule.getName());
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
}
// Make sure that none of the prohibited attributes are present.
for (AttributeType t : ditContentRule.getProhibitedAttributes())
{
if (userAttributes.containsKey(t) ||
operationalAttributes.containsKey(t))
{
Message message =
ERR_ENTRY_SCHEMA_PROHIBITED_ATTR_FOR_DCR.get(
String.valueOf(dn),
t.getNameOrOID(),
ditContentRule.getName());
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
}
// If we've gotten here, then things are OK.
return true;
}
/**
* Performs any processing needed for DIT structure rule validation.
*
* @param ditStructureRule The DIT structure rule for this
* entry.
* @param structuralClass The structural object class for
* this entry.
* @param parentEntry The parent entry, if available
* and applicable.
* @param parentProvided Indicates whether the parent
* entry was provided.
* @param validateStructureRules Indicates whether to check to see
* if this entry violates a DIT
* structure rule for its parent.
* @param structuralPolicy The policy that should be used
* for structural object class
* compliance.
* @param invalidReason A buffer into which an invalid
* reason may be added.
*
* @return {@code true} if this entry passes all of the checks, or
* {@code false} if there are any failures.
*/
private boolean checkDITStructureRule(
DITStructureRule ditStructureRule,
ObjectClass structuralClass,
Entry parentEntry, boolean parentProvided,
boolean validateStructureRules,
AcceptRejectWarn structuralPolicy,
MessageBuilder invalidReason)
{
// If there is a DIT structure rule for this entry, then make sure
// that the entry is in compliance with it.
if ((ditStructureRule != null) &&
ditStructureRule.hasSuperiorRules())
{
if (parentProvided)
{
if (parentEntry != null)
{
boolean dsrValid =
validateDITStructureRule(ditStructureRule,
structuralClass, parentEntry,
structuralPolicy,
invalidReason);
if (! dsrValid)
{
return false;
}
}
}
else
{
// Get the DN of the parent entry if possible.
DN parentDN = dn.getParentDNInSuffix();
if (parentDN != null)
{
// Get the parent entry and check its structural class.
Lock lock = null;
for (int i=0; i < 3; i++)
{
lock = LockManager.lockRead(parentDN);
if (lock != null)
{
break;
}
}
if (lock == null)
{
Message message =
ERR_ENTRY_SCHEMA_DSR_COULD_NOT_LOCK_PARENT.get(
String.valueOf(dn), String.valueOf(parentDN));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
else
{
try
{
parentEntry = DirectoryServer.getEntry(parentDN);
if (parentEntry == null)
{
Message message =
ERR_ENTRY_SCHEMA_DSR_NO_PARENT_ENTRY.get(
String.valueOf(dn),
String.valueOf(parentDN));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
else
{
boolean dsrValid =
validateDITStructureRule(ditStructureRule,
structuralClass,
parentEntry,
structuralPolicy,
invalidReason);
if (! dsrValid)
{
return false;
}
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
Message message =
ERR_ENTRY_SCHEMA_COULD_NOT_CHECK_DSR.get(
String.valueOf(dn),
ditStructureRule.getNameOrRuleID(),
getExceptionMessage(e));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
finally
{
LockManager.unlock(parentDN, lock);
}
}
}
}
}
else if (validateStructureRules)
{
// There is no DIT structure rule for this entry, but there may
// be one for the parent entry. If there is such a rule for the
// parent entry, then this entry will not be valid.
boolean parentExists = false;
ObjectClass parentStructuralClass = null;
if (parentEntry != null)
{
parentExists = true;
parentStructuralClass =
parentEntry.getStructuralObjectClass();
}
else if (! parentProvided)
{
DN parentDN = getDN().getParentDNInSuffix();
if (parentDN != null)
{
// Get the parent entry and check its structural class.
Lock lock = null;
for (int i=0; i < 3; i++)
{
lock = LockManager.lockRead(parentDN);
if (lock != null)
{
break;
}
}
if (lock == null)
{
Message message =
ERR_ENTRY_SCHEMA_DSR_COULD_NOT_LOCK_PARENT.get(
String.valueOf(dn),
String.valueOf(parentDN));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
else
{
try
{
parentEntry = DirectoryServer.getEntry(parentDN);
if (parentEntry == null)
{
Message message =
ERR_ENTRY_SCHEMA_DSR_NO_PARENT_ENTRY.get(
String.valueOf(dn),
String.valueOf(parentDN));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
else
{
parentExists = true;
parentStructuralClass =
parentEntry.getStructuralObjectClass();
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
Message message =
ERR_ENTRY_SCHEMA_COULD_NOT_CHECK_PARENT_DSR.get(
String.valueOf(dn),
getExceptionMessage(e));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
finally
{
LockManager.unlock(parentDN, lock);
}
}
}
}
if (parentExists)
{
if (parentStructuralClass == null)
{
Message message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get(
String.valueOf(dn),
String.valueOf(parentEntry.getDN()));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
else
{
NameForm parentNF =
DirectoryServer.getNameForm(parentStructuralClass);
if ((parentNF != null) && (! parentNF.isObsolete()))
{
DITStructureRule parentDSR =
DirectoryServer.getDITStructureRule(parentNF);
if ((parentDSR != null) && (! parentDSR.isObsolete()))
{
Message message =
ERR_ENTRY_SCHEMA_VIOLATES_PARENT_DSR.get(
String.valueOf(dn),
String.valueOf(parentEntry.getDN()));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
}
}
}
}
// If we've gotten here, then things are OK.
return true;
}
/**
* Determines whether this entry is in conformance to the provided
* DIT structure rule.
*
* @param dsr The DIT structure rule to use in the
* determination.
* @param structuralClass The structural objectclass for this
* entry to use in the determination.
* @param parentEntry The reference to the parent entry to
* check.
* @param structuralPolicy The policy that should be used around
* enforcement of DIT structure rules.
* @param invalidReason The buffer to which the invalid reason
* should be appended if a problem is
* found.
*
* @return <CODE>true</CODE> if this entry conforms to the provided
* DIT structure rule, or <CODE>false</CODE> if not.
*/
private boolean validateDITStructureRule(DITStructureRule dsr,
ObjectClass structuralClass, Entry parentEntry,
AcceptRejectWarn structuralPolicy,
MessageBuilder invalidReason)
{
ObjectClass oc = parentEntry.getStructuralObjectClass();
if (oc == null)
{
Message message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get(
String.valueOf(dn),
String.valueOf(parentEntry.getDN()));
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
boolean matchFound = false;
for (DITStructureRule dsr2 : dsr.getSuperiorRules())
{
if (dsr2.getStructuralClass().equals(oc))
{
matchFound = true;
}
}
if (! matchFound)
{
Message message =
ERR_ENTRY_SCHEMA_DSR_DISALLOWED_SUPERIOR_OC.get(
String.valueOf(dn),
dsr.getNameOrRuleID(),
structuralClass.getNameOrOID(),
oc.getNameOrOID());
if (structuralPolicy == AcceptRejectWarn.REJECT)
{
invalidReason.append(message);
return false;
}
else if (structuralPolicy == AcceptRejectWarn.WARN)
{
logError(message);
}
}
return true;
}
/**
* Retrieves the attachment for this entry.
*
* @return The attachment for this entry, or <CODE>null</CODE> if
* there is none.
*/
public Object getAttachment()
{
return attachment;
}
/**
* Specifies the attachment for this entry. This will replace any
* existing attachment that might be defined.
*
* @param attachment The attachment for this entry, or
* <CODE>null</CODE> if there should not be an
* attachment.
*/
public void setAttachment(Object attachment)
{
this.attachment = attachment;
}
/**
* Creates a duplicate of this entry that may be altered without
* impacting the information in this entry.
*
* @param processVirtual Indicates whether virtual attribute
* processing should be performed for the
* entry.
*
* @return A duplicate of this entry that may be altered without
* impacting the information in this entry.
*/
public Entry duplicate(boolean processVirtual)
{
HashMap<ObjectClass,String> objectClassesCopy =
new HashMap<ObjectClass,String>(objectClasses);
HashMap<AttributeType,List<Attribute>> userAttrsCopy =
new HashMap<AttributeType,List<Attribute>>(
userAttributes.size());
deepCopy(userAttributes, userAttrsCopy, false);
HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
new HashMap<AttributeType,List<Attribute>>(
operationalAttributes.size());
deepCopy(operationalAttributes, operationalAttrsCopy, false);
for (AttributeType t : suppressedAttributes.keySet())
{
List<Attribute> attrList = suppressedAttributes.get(t);
if (t.isOperational())
{
operationalAttributes.put(t, attrList);
}
else
{
userAttributes.put(t, attrList);
}
}
Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy,
operationalAttrsCopy);
if (processVirtual)
{
e.processVirtualAttributes();
}
return e;
}
/**
* Creates a duplicate of this entry without any operational
* attributes that may be altered without impacting the information
* in this entry.
*
* @param typesOnly Indicates whether to include attribute
* types only without values.
* @param processVirtual Indicates whether virtual attribute
* processing should be performed for the
* entry.
*
* @return A duplicate of this entry that may be altered without
* impacting the information in this entry and that does
* not contain any operational attributes.
*/
public Entry duplicateWithoutOperationalAttributes(
boolean typesOnly, boolean processVirtual)
{
HashMap<ObjectClass,String> objectClassesCopy;
if (typesOnly)
{
objectClassesCopy = new HashMap<ObjectClass,String>(0);
}
else
{
objectClassesCopy =
new HashMap<ObjectClass,String>(objectClasses);
}
HashMap<AttributeType,List<Attribute>> userAttrsCopy =
new HashMap<AttributeType,List<Attribute>>(
userAttributes.size());
if (typesOnly)
{
// Make sure to include the objectClass attribute here because
// it won't make it in otherwise.
AttributeType ocType =
DirectoryServer.getObjectClassAttributeType();
ArrayList<Attribute> ocList = new ArrayList<Attribute>(1);
ocList.add(Attributes.empty(ocType));
userAttrsCopy.put(ocType, ocList);
}
deepCopy(userAttributes, userAttrsCopy, typesOnly);
HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
new HashMap<AttributeType,List<Attribute>>(0);
for (AttributeType t : suppressedAttributes.keySet())
{
List<Attribute> attrList = suppressedAttributes.get(t);
if (! t.isOperational())
{
userAttributes.put(t, attrList);
}
}
Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy,
operationalAttrsCopy);
if (processVirtual)
{
e.processVirtualAttributes(false);
}
return e;
}
/**
* Performs a deep copy from the source map to the target map. In
* this case, the attributes in the list will be duplicates rather
* than re-using the same reference. Virtual attributes will not be
* included when making the copy.
*
* @param source The source map from which to obtain the
* information.
* @param target The target map into which to place the
* copied information.
* @param omitValues Indicates whether to omit attribute values
* when processing.
*/
private void deepCopy(Map<AttributeType,List<Attribute>> source,
Map<AttributeType,List<Attribute>> target,
boolean omitValues)
{
for (AttributeType t : source.keySet())
{
List<Attribute> sourceList = source.get(t);
ArrayList<Attribute> targetList =
new ArrayList<Attribute>(sourceList.size());
for (Attribute a : sourceList)
{
if (a.isVirtual())
{
continue;
}
if (omitValues)
{
targetList.add(Attributes.empty(a));
}
else
{
targetList.add(a);
}
}
if (! targetList.isEmpty())
{
target.put(t, targetList);
}
}
}
/**
* Creates a duplicate of this entry without any attribute or
* objectclass information (i.e., it will just contain the DN and
* placeholders for adding attributes) and objectclasses.
*
* @return A duplicate of this entry that may be altered without
* impacting the information in this entry and that does
* not contain attribute or objectclass information.
*/
public Entry duplicateWithoutAttributes()
{
HashMap<ObjectClass,String> objectClassesCopy =
new HashMap<ObjectClass,String>(objectClasses.size());
HashMap<AttributeType,List<Attribute>> userAttrsCopy =
new HashMap<AttributeType,List<Attribute>>(
userAttributes.size());
HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
new HashMap<AttributeType,List<Attribute>>(
operationalAttributes.size());
return new Entry(dn, objectClassesCopy, userAttrsCopy,
operationalAttrsCopy);
}
/**
* Indicates whether this entry meets the criteria to consider it a
* referral (e.g., it contains the "referral" objectclass and a
* "ref" attribute).
*
* @return <CODE>true</CODE> if this entry meets the criteria to
* consider it a referral, or <CODE>false</CODE> if not.
*/
public boolean isReferral()
{
ObjectClass referralOC =
DirectoryServer.getObjectClass(OC_REFERRAL);
if (referralOC == null)
{
// This should not happen -- The server doesn't have a referral
// objectclass defined.
if (debugEnabled())
{
TRACER.debugWarning(
"No %s objectclass is defined in the server schema.",
OC_REFERRAL);
}
for (String ocName : objectClasses.values())
{
if (ocName.equalsIgnoreCase(OC_REFERRAL))
{
return true;
}
}
return false;
}
if (! objectClasses.containsKey(referralOC))
{
return false;
}
AttributeType referralType =
DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
if (referralType == null)
{
// This should not happen -- The server doesn't have a ref
// attribute type defined.
if (debugEnabled())
{
TRACER.debugWarning(
"No %s attribute type is defined in the server schema.",
ATTR_REFERRAL_URL);
}
return false;
}
return (userAttributes.containsKey(referralType) ||
operationalAttributes.containsKey(referralType));
}
/**
* Retrieves the set of referral URLs that are included in this
* referral entry. This should only be called if
* <CODE>isReferral()</CODE> returns <CODE>true</CODE>.
*
* @return The set of referral URLs that are included in this entry
* if it is a referral, or <CODE>null</CODE> if it is not a
* referral.
*/
public LinkedHashSet<String> getReferralURLs()
{
AttributeType referralType =
DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
if (referralType == null)
{
// This should not happen -- The server doesn't have a ref
// attribute type defined.
if (debugEnabled())
{
TRACER.debugWarning(
"No %s attribute type is defined in the server schema.",
ATTR_REFERRAL_URL);
}
return null;
}
List<Attribute> refAttrs = userAttributes.get(referralType);
if (refAttrs == null)
{
refAttrs = operationalAttributes.get(referralType);
if (refAttrs == null)
{
return null;
}
}
LinkedHashSet<String> referralURLs = new LinkedHashSet<String>();
for (Attribute a : refAttrs)
{
for (AttributeValue v : a)
{
referralURLs.add(v.getStringValue());
}
}
return referralURLs;
}
/**
* Indicates whether this entry meets the criteria to consider it an
* alias (e.g., it contains the "aliasObject" objectclass and a
* "alias" attribute).
*
* @return <CODE>true</CODE> if this entry meets the criteria to
* consider it an alias, or <CODE>false</CODE> if not.
*/
public boolean isAlias()
{
ObjectClass aliasOC = DirectoryServer.getObjectClass(OC_ALIAS);
if (aliasOC == null)
{
// This should not happen -- The server doesn't have an alias
// objectclass defined.
if (debugEnabled())
{
TRACER.debugWarning(
"No %s objectclass is defined in the server schema.",
OC_ALIAS);
}
for (String ocName : objectClasses.values())
{
if (ocName.equalsIgnoreCase(OC_ALIAS))
{
return true;
}
}
return false;
}
if (! objectClasses.containsKey(aliasOC))
{
return false;
}
AttributeType aliasType =
DirectoryServer.getAttributeType(ATTR_ALIAS_DN);
if (aliasType == null)
{
// This should not happen -- The server doesn't have an
// aliasedObjectName attribute type defined.
if (debugEnabled())
{
TRACER.debugWarning(
"No %s attribute type is defined in the server schema.",
ATTR_ALIAS_DN);
}
return false;
}
return (userAttributes.containsKey(aliasType) ||
operationalAttributes.containsKey(aliasType));
}
/**
* Retrieves the DN of the entry referenced by this alias entry.
* This should only be called if <CODE>isAlias()</CODE> returns
* <CODE>true</CODE>.
*
* @return The DN of the entry referenced by this alias entry, or
* <CODE>null</CODE> if it is not an alias.
*
* @throws DirectoryException If there is an aliasedObjectName
* attribute but its value cannot be
* parsed as a DN.
*/
public DN getAliasedDN()
throws DirectoryException
{
AttributeType aliasType =
DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
if (aliasType == null)
{
// This should not happen -- The server doesn't have an
// aliasedObjectName attribute type defined.
if (debugEnabled())
{
TRACER.debugWarning(
"No %s attribute type is defined in the server schema.",
ATTR_ALIAS_DN);
}
return null;
}
List<Attribute> aliasAttrs = userAttributes.get(aliasType);
if (aliasAttrs == null)
{
aliasAttrs = operationalAttributes.get(aliasType);
if (aliasAttrs == null)
{
return null;
}
}
if (aliasAttrs.isEmpty())
{
return null;
}
else
{
// There should only be a single alias attribute in an entry,
// and we'll skip the check for others for performance reasons.
// We would just end up taking the first one anyway. The same
// is true with the set of values, since it should be a
// single-valued attribute.
Attribute aliasAttr = aliasAttrs.get(0);
if (aliasAttr.isEmpty())
{
return null;
}
else
{
return DN.decode(
aliasAttr.iterator().next().getStringValue());
}
}
}
/**
* Indicates whether this entry meets the criteria to consider it an
* LDAP subentry (i.e., it contains the "ldapSubentry" objectclass).
*
* @return <CODE>true</CODE> if this entry meets the criteria to
* consider it an LDAP subentry, or <CODE>false</CODE> if
* not.
*/
public boolean isLDAPSubentry()
{
ObjectClass ldapSubentryOC =
DirectoryServer.getObjectClass(OC_LDAP_SUBENTRY_LC);
if (ldapSubentryOC == null)
{
// This should not happen -- The server doesn't have an
// ldapsubentry objectclass defined.
if (debugEnabled())
{
TRACER.debugWarning(
"No %s objectclass is defined in the server schema.",
OC_LDAP_SUBENTRY);
}
for (String ocName : objectClasses.values())
{
if (ocName.equalsIgnoreCase(OC_LDAP_SUBENTRY))
{
return true;
}
}
return false;
}
// Make the determination based on whether this entry has the
// ldapSubentry objectclass.
return objectClasses.containsKey(ldapSubentryOC);
}
/**
* Indicates whether this entry falls within the range of the
* provided search base DN and scope.
*
* @param baseDN The base DN for which to make the determination.
* @param scope The search scope for which to make the
* determination.
*
* @return <CODE>true</CODE> if this entry is within the given
* base and scope, or <CODE>false</CODE> if it is not.
*/
public boolean matchesBaseAndScope(DN baseDN, SearchScope scope)
{
return dn.matchesBaseAndScope(baseDN, scope);
}
/**
* Performs any necessary virtual attribute processing for this
* entry. This should only be called at the time the entry is
* decoded or created within the backend.
*/
public void processVirtualAttributes()
{
processVirtualAttributes(true);
}
/**
* Performs any necessary virtual attribute processing for this
* entry. This should only be called at the time the entry is
* decoded or created within the backend.
*
* @param includeOperational Indicates whether to include
* operational attributes.
*/
public void processVirtualAttributes(boolean includeOperational)
{
for (VirtualAttributeRule rule :
DirectoryServer.getVirtualAttributes(this))
{
AttributeType attributeType = rule.getAttributeType();
if (attributeType.isOperational() && (! includeOperational))
{
continue;
}
List<Attribute> attrList = userAttributes.get(attributeType);
if ((attrList == null) || attrList.isEmpty())
{
attrList = operationalAttributes.get(attributeType);
if ((attrList == null) || attrList.isEmpty())
{
// There aren't any conflicts, so we can just add the
// attribute to the entry.
attrList = new LinkedList<Attribute>();
attrList.add(new VirtualAttribute(attributeType, this,
rule));
if (attributeType.isOperational())
{
operationalAttributes.put(attributeType, attrList);
}
else
{
userAttributes.put(attributeType, attrList);
}
}
else
{
// There is a conflict with an existing operational
// attribute.
if (attrList.get(0).isVirtual())
{
// The existing attribute is already virtual, so we've got
// a different conflict, but we'll let the first win.
// FIXME -- Should we handle this differently?
continue;
}
// The conflict is with a real attribute. See what the
// conflict behavior is and figure out how to handle it.
switch (rule.getConflictBehavior())
{
case REAL_OVERRIDES_VIRTUAL:
// We don't need to update the entry because the real
// attribute will take precedence.
break;
case VIRTUAL_OVERRIDES_REAL:
// We need to move the real attribute to the suppressed
// list and replace it with the virtual attribute.
suppressedAttributes.put(attributeType, attrList);
attrList = new LinkedList<Attribute>();
attrList.add(new VirtualAttribute(attributeType, this,
rule));
operationalAttributes.put(attributeType, attrList);
break;
case MERGE_REAL_AND_VIRTUAL:
// We need to add the virtual attribute to the list and
// keep the existing real attribute(s).
attrList.add(new VirtualAttribute(attributeType, this,
rule));
break;
}
}
}
else
{
// There is a conflict with an existing user attribute.
if (attrList.get(0).isVirtual())
{
// The existing attribute is already virtual, so we've got
// a different conflict, but we'll let the first win.
// FIXME -- Should we handle this differently?
continue;
}
// The conflict is with a real attribute. See what the
// conflict behavior is and figure out how to handle it.
switch (rule.getConflictBehavior())
{
case REAL_OVERRIDES_VIRTUAL:
// We don't need to update the entry because the real
// attribute will take precedence.
break;
case VIRTUAL_OVERRIDES_REAL:
// We need to move the real attribute to the suppressed
// list and replace it with the virtual attribute.
suppressedAttributes.put(attributeType, attrList);
attrList = new LinkedList<Attribute>();
attrList.add(new VirtualAttribute(attributeType, this,
rule));
userAttributes.put(attributeType, attrList);
break;
case MERGE_REAL_AND_VIRTUAL:
// We need to add the virtual attribute to the list and
// keep the existing real attribute(s).
attrList.add(new VirtualAttribute(attributeType, this,
rule));
break;
}
}
}
virtualAttributeProcessingPerformed = true;
}
/**
* Indicates whether virtual attribute processing has been performed
* for this entry.
*
* @return {@code true} if virtual attribute processing has been
* performed for this entry, or {@code false} if not.
*/
public boolean virtualAttributeProcessingPerformed()
{
return virtualAttributeProcessingPerformed;
}
/**
* Strips out all real attributes from this entry so that it only
* contains virtual attributes.
*/
public void stripRealAttributes()
{
// The objectClass attribute will always be a real attribute.
objectClasses.clear();
Iterator<Map.Entry<AttributeType,List<Attribute>>>
attrListIterator = userAttributes.entrySet().iterator();
while (attrListIterator.hasNext())
{
Map.Entry<AttributeType,List<Attribute>> mapEntry =
attrListIterator.next();
Iterator<Attribute> attrIterator =
mapEntry.getValue().iterator();
while (attrIterator.hasNext())
{
Attribute a = attrIterator.next();
if (! a.isVirtual())
{
attrIterator.remove();
}
}
if (mapEntry.getValue().isEmpty())
{
attrListIterator.remove();
}
}
attrListIterator = operationalAttributes.entrySet().iterator();
while (attrListIterator.hasNext())
{
Map.Entry<AttributeType,List<Attribute>> mapEntry =
attrListIterator.next();
Iterator<Attribute> attrIterator =
mapEntry.getValue().iterator();
while (attrIterator.hasNext())
{
Attribute a = attrIterator.next();
if (! a.isVirtual())
{
attrIterator.remove();
}
}
if (mapEntry.getValue().isEmpty())
{
attrListIterator.remove();
}
}
}
/**
* Strips out all virtual attributes from this entry so that it only
* contains real attributes.
*/
public void stripVirtualAttributes()
{
Iterator<Map.Entry<AttributeType,List<Attribute>>>
attrListIterator = userAttributes.entrySet().iterator();
while (attrListIterator.hasNext())
{
Map.Entry<AttributeType,List<Attribute>> mapEntry =
attrListIterator.next();
Iterator<Attribute> attrIterator =
mapEntry.getValue().iterator();
while (attrIterator.hasNext())
{
Attribute a = attrIterator.next();
if (a.isVirtual())
{
attrIterator.remove();
}
}
if (mapEntry.getValue().isEmpty())
{
attrListIterator.remove();
}
}
attrListIterator = operationalAttributes.entrySet().iterator();
while (attrListIterator.hasNext())
{
Map.Entry<AttributeType,List<Attribute>> mapEntry =
attrListIterator.next();
Iterator<Attribute> attrIterator =
mapEntry.getValue().iterator();
while (attrIterator.hasNext())
{
Attribute a = attrIterator.next();
if (a.isVirtual())
{
attrIterator.remove();
}
}
if (mapEntry.getValue().isEmpty())
{
attrListIterator.remove();
}
}
}
/**
* Encodes this entry into a form that is suitable for long-term
* persistent storage. The encoding will have a version number so
* that if the way we store entries changes in the future we will
* still be able to read entries encoded in an older format.
*
* @param config The configuration that may be used to control how
* the entry is encoded.
*
* @return The entry encoded in a form that is suitable for
* long-term persistent storage.
*
* @throws DirectoryException If a problem occurs while attempting
* to encode the entry.
*/
public byte[] encode(EntryEncodeConfig config)
throws DirectoryException
{
return encodeV2(config);
}
/**
* Encodes this entry using the V1 encoding.
*
* @return The entry encoded in the V1 encoding.
*/
public byte[] encodeV1()
{
// The version number will be one byte. We'll add that later.
// The DN will be encoded as a one-to-five byte length followed
// byte the UTF-8 byte representation.
byte[] dnBytes = getBytes(dn.toString());
byte[] dnLength = ASN1Element.encodeLength(dnBytes.length);
int totalBytes = 1 + dnBytes.length + dnLength.length;
// The object classes will be encoded as one-to-five byte length
// followed by a zero-delimited UTF-8 byte representation of the
// names (e.g., top\0person\0organizationalPerson\0inetOrgPerson).
int i=0;
int totalOCBytes = objectClasses.size() - 1;
byte[][] ocBytes = new byte[objectClasses.size()][];
for (String ocName : objectClasses.values())
{
ocBytes[i] = getBytes(ocName);
totalOCBytes += ocBytes[i++].length;
}
byte[] ocLength = ASN1Element.encodeLength(totalOCBytes);
totalBytes += totalOCBytes + ocLength.length;
// The user attributes will be encoded as a one-to-five byte
// number of attributes followed by a sequence of:
// - A UTF-8 byte representation of the attribute name.
// - A zero delimiter
// - A one-to-five byte number of values for the attribute
// - A sequence of:
// - A one-to-five byte length for the value
// - A UTF-8 byte representation for the value
i=0;
int numUserAttributes = 0;
int totalUserAttrBytes = 0;
LinkedList<byte[]> userAttrBytes = new LinkedList<byte[]>();
for (List<Attribute> attrList : userAttributes.values())
{
for (Attribute a : attrList)
{
if (a.isVirtual() || a.isEmpty())
{
continue;
}
numUserAttributes++;
byte[] nameBytes = getBytes(a.getNameWithOptions());
int numValues = 0;
int totalValueBytes = 0;
LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
for (AttributeValue v : a)
{
numValues++;
byte[] vBytes = v.getValueBytes();
byte[] vLength = ASN1Element.encodeLength(vBytes.length);
valueBytes.add(vLength);
valueBytes.add(vBytes);
totalValueBytes += vLength.length + vBytes.length;
}
byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
byte[] attrBytes = new byte[nameBytes.length +
numValuesBytes.length +
totalValueBytes + 1];
System.arraycopy(nameBytes, 0, attrBytes, 0,
nameBytes.length);
int pos = nameBytes.length+1;
System.arraycopy(numValuesBytes, 0, attrBytes, pos,
numValuesBytes.length);
pos += numValuesBytes.length;
for (byte[] b : valueBytes)
{
System.arraycopy(b, 0, attrBytes, pos, b.length);
pos += b.length;
}
userAttrBytes.add(attrBytes);
totalUserAttrBytes += attrBytes.length;
}
}
byte[] userAttrCount =
ASN1OctetString.encodeLength(numUserAttributes);
totalBytes += totalUserAttrBytes + userAttrCount.length;
// The operational attributes will be encoded in the same way as
// the user attributes.
i=0;
int numOperationalAttributes = 0;
int totalOperationalAttrBytes = 0;
LinkedList<byte[]> operationalAttrBytes =
new LinkedList<byte[]>();
for (List<Attribute> attrList : operationalAttributes.values())
{
for (Attribute a : attrList)
{
if (a.isVirtual() || a.isEmpty())
{
continue;
}
numOperationalAttributes++;
byte[] nameBytes = getBytes(a.getNameWithOptions());
int numValues = 0;
int totalValueBytes = 0;
LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
for (AttributeValue v : a)
{
numValues++;
byte[] vBytes = v.getValueBytes();
byte[] vLength = ASN1Element.encodeLength(vBytes.length);
valueBytes.add(vLength);
valueBytes.add(vBytes);
totalValueBytes += vLength.length + vBytes.length;
}
byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
byte[] attrBytes = new byte[nameBytes.length +
numValuesBytes.length +
totalValueBytes + 1];
System.arraycopy(nameBytes, 0, attrBytes, 0,
nameBytes.length);
int pos = nameBytes.length+1;
System.arraycopy(numValuesBytes, 0, attrBytes, pos,
numValuesBytes.length);
pos += numValuesBytes.length;
for (byte[] b : valueBytes)
{
System.arraycopy(b, 0, attrBytes, pos, b.length);
pos += b.length;
}
operationalAttrBytes.add(attrBytes);
totalOperationalAttrBytes += attrBytes.length;
}
}
byte[] operationalAttrCount =
ASN1OctetString.encodeLength(numOperationalAttributes);
totalBytes += totalOperationalAttrBytes +
operationalAttrCount.length;
// Now we've got all the data that we need. Create a big byte
// array to hold it all and pack it in.
byte[] entryBytes = new byte[totalBytes];
// Add the entry version number as the first byte.
entryBytes[0] = 0x01;
// Next, add the DN length and value.
System.arraycopy(dnLength, 0, entryBytes, 1, dnLength.length);
int pos = 1 + dnLength.length;
System.arraycopy(dnBytes, 0, entryBytes, pos, dnBytes.length);
pos += dnBytes.length;
// Next, add the object classes length and values.
System.arraycopy(ocLength, 0, entryBytes, pos, ocLength.length);
pos += ocLength.length;
for (byte[] b : ocBytes)
{
System.arraycopy(b, 0, entryBytes, pos, b.length);
pos += b.length + 1;
}
// We need to back up one because there's no zero-teriminator
// after the last object class name.
pos--;
// Next, add the user attribute count and the user attribute
// data.
System.arraycopy(userAttrCount, 0, entryBytes, pos,
userAttrCount.length);
pos += userAttrCount.length;
for (byte[] b : userAttrBytes)
{
System.arraycopy(b, 0, entryBytes, pos, b.length);
pos += b.length;
}
// Finally, add the operational attribute count and the
// operational attribute data.
System.arraycopy(operationalAttrCount, 0, entryBytes, pos,
operationalAttrCount.length);
pos += operationalAttrCount.length;
for (byte[] b : operationalAttrBytes)
{
System.arraycopy(b, 0, entryBytes, pos, b.length);
pos += b.length;
}
return entryBytes;
}
/**
* Encodes this entry using the V2 encoding.
*
* @param config The configuration that should be used to encode
* the entry.
*
* @return The entry encoded in the V2 encoding.
*
* @throws DirectoryException If a problem occurs while attempting
* to encode the entry.
*/
public byte[] encodeV2(EntryEncodeConfig config)
throws DirectoryException
{
// The version number will be one byte. We'll add that later.
// Get the encoded respresentation of the config.
byte[] configBytes = config.encode();
byte[] configLength =
ASN1Element.encodeLength(configBytes.length);
int totalBytes = 1 + configBytes.length + configLength.length;
// If we should include the DN, then it will be encoded as a
// one-to-five byte length followed by the UTF-8 byte
// representation.
byte[] dnBytes = null;
byte[] dnLength = null;
if (! config.excludeDN())
{
dnBytes = getBytes(dn.toString());
dnLength = ASN1Element.encodeLength(dnBytes.length);
totalBytes += dnBytes.length + dnLength.length;
}
// Encode the object classes in the appropriate manner.
byte[] ocLength;
LinkedList<byte[]> ocBytes = new LinkedList<byte[]>();
if (config.compressObjectClassSets())
{
byte[] b = config.getCompressedSchema().
encodeObjectClasses(objectClasses);
ocBytes.add(b);
ocLength = ASN1Element.encodeLength(b.length);
totalBytes += ocLength.length + b.length;
}
else
{
int totalOCBytes = objectClasses.size() - 1;
for (String ocName : objectClasses.values())
{
byte[] b = getBytes(ocName);
ocBytes.add(b);
totalOCBytes += b.length;
}
ocLength = ASN1Element.encodeLength(totalOCBytes);
totalBytes += totalOCBytes + ocLength.length;
}
// Encode the user attributes in the appropriate manner.
int numUserAttributes = 0;
int totalUserAttrBytes = 0;
LinkedList<byte[]> userAttrBytes = new LinkedList<byte[]>();
if (config.compressAttributeDescriptions())
{
for (List<Attribute> attrList : userAttributes.values())
{
for (Attribute a : attrList)
{
if (a.isVirtual() || a.isEmpty())
{
continue;
}
numUserAttributes++;
byte[] attrBytes =
config.getCompressedSchema().encodeAttribute(a);
byte[] lengthBytes =
ASN1Element.encodeLength(attrBytes.length);
userAttrBytes.add(lengthBytes);
userAttrBytes.add(attrBytes);
totalUserAttrBytes += lengthBytes.length + attrBytes.length;
}
}
}
else
{
// The user attributes will be encoded as a one-to-five byte
// number of attributes followed by a sequence of:
// - A UTF-8 byte representation of the attribute name.
// - A zero delimiter
// - A one-to-five byte number of values for the attribute
// - A sequence of:
// - A one-to-five byte length for the value
// - A UTF-8 byte representation for the value
for (List<Attribute> attrList : userAttributes.values())
{
for (Attribute a : attrList)
{
if (a.isVirtual() || a.isEmpty())
{
continue;
}
numUserAttributes++;
byte[] nameBytes = getBytes(a.getNameWithOptions());
int numValues = 0;
int totalValueBytes = 0;
LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
for (AttributeValue v : a)
{
numValues++;
byte[] vBytes = v.getValueBytes();
byte[] vLength = ASN1Element.encodeLength(vBytes.length);
valueBytes.add(vLength);
valueBytes.add(vBytes);
totalValueBytes += vLength.length + vBytes.length;
}
byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
byte[] attrBytes = new byte[nameBytes.length +
numValuesBytes.length +
totalValueBytes + 1];
System.arraycopy(nameBytes, 0, attrBytes, 0,
nameBytes.length);
int pos = nameBytes.length+1;
System.arraycopy(numValuesBytes, 0, attrBytes, pos,
numValuesBytes.length);
pos += numValuesBytes.length;
for (byte[] b : valueBytes)
{
System.arraycopy(b, 0, attrBytes, pos, b.length);
pos += b.length;
}
userAttrBytes.add(attrBytes);
totalUserAttrBytes += attrBytes.length;
}
}
}
byte[] userAttrCount =
ASN1OctetString.encodeLength(numUserAttributes);
totalBytes += totalUserAttrBytes + userAttrCount.length;
// Encode the operational attributes in the appropriate manner.
int numOperationalAttributes = 0;
int totalOperationalAttrBytes = 0;
LinkedList<byte[]> operationalAttrBytes =
new LinkedList<byte[]>();
if (config.compressAttributeDescriptions())
{
for (List<Attribute> attrList : operationalAttributes.values())
{
for (Attribute a : attrList)
{
if (a.isVirtual() || a.isEmpty())
{
continue;
}
numOperationalAttributes++;
byte[] attrBytes =
config.getCompressedSchema().encodeAttribute(a);
byte[] lengthBytes =
ASN1Element.encodeLength(attrBytes.length);
operationalAttrBytes.add(lengthBytes);
operationalAttrBytes.add(attrBytes);
totalOperationalAttrBytes +=
lengthBytes.length + attrBytes.length;
}
}
}
else
{
// Encode the operational attributes in the same way as the user
// attributes.
for (List<Attribute> attrList : operationalAttributes.values())
{
for (Attribute a : attrList)
{
if (a.isVirtual() || a.isEmpty())
{
continue;
}
numOperationalAttributes++;
byte[] nameBytes = getBytes(a.getNameWithOptions());
int numValues = 0;
int totalValueBytes = 0;
LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
for (AttributeValue v : a)
{
numValues++;
byte[] vBytes = v.getValueBytes();
byte[] vLength = ASN1Element.encodeLength(vBytes.length);
valueBytes.add(vLength);
valueBytes.add(vBytes);
totalValueBytes += vLength.length + vBytes.length;
}
byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
byte[] attrBytes = new byte[nameBytes.length +
numValuesBytes.length +
totalValueBytes + 1];
System.arraycopy(nameBytes, 0, attrBytes, 0,
nameBytes.length);
int pos = nameBytes.length+1;
System.arraycopy(numValuesBytes, 0, attrBytes, pos,
numValuesBytes.length);
pos += numValuesBytes.length;
for (byte[] b : valueBytes)
{
System.arraycopy(b, 0, attrBytes, pos, b.length);
pos += b.length;
}
operationalAttrBytes.add(attrBytes);
totalOperationalAttrBytes += attrBytes.length;
}
}
}
byte[] operationalAttrCount =
ASN1OctetString.encodeLength(numOperationalAttributes);
totalBytes += totalOperationalAttrBytes +
operationalAttrCount.length;
// Now we've got all the data that we need. Create a big byte
// array to hold it all and pack it in.
byte[] entryBytes = new byte[totalBytes];
// Add the entry version number as the first byte.
entryBytes[0] = 0x02;
// Next, add the encoded config.
System.arraycopy(configLength, 0, entryBytes, 1,
configLength.length);
int pos = 1 + configLength.length;
System.arraycopy(configBytes, 0, entryBytes, pos,
configBytes.length);
pos += configBytes.length;
// Next, add the DN length and value.
if (! config.excludeDN())
{
System.arraycopy(dnLength, 0, entryBytes, pos, dnLength.length);
pos += dnLength.length;
System.arraycopy(dnBytes, 0, entryBytes, pos, dnBytes.length);
pos += dnBytes.length;
}
// Next, add the object classes length and values.
System.arraycopy(ocLength, 0, entryBytes, pos, ocLength.length);
pos += ocLength.length;
if (config.compressObjectClassSets())
{
for (byte[] b : ocBytes)
{
System.arraycopy(b, 0, entryBytes, pos, b.length);
pos += b.length;
}
}
else
{
for (byte[] b : ocBytes)
{
System.arraycopy(b, 0, entryBytes, pos, b.length);
pos += b.length + 1;
}
// We need to back up one because there's no zero-teriminator
// after the last object class name.
pos--;
}
// Next, add the user attribute count and the user attribute
// data.
System.arraycopy(userAttrCount, 0, entryBytes, pos,
userAttrCount.length);
pos += userAttrCount.length;
for (byte[] b : userAttrBytes)
{
System.arraycopy(b, 0, entryBytes, pos, b.length);
pos += b.length;
}
// Finally, add the operational attribute count and the
// operational attribute data.
System.arraycopy(operationalAttrCount, 0, entryBytes, pos,
operationalAttrCount.length);
pos += operationalAttrCount.length;
for (byte[] b : operationalAttrBytes)
{
System.arraycopy(b, 0, entryBytes, pos, b.length);
pos += b.length;
}
return entryBytes;
}
/**
* Decodes the provided byte array as an entry.
*
* @param entryBytes The byte array containing the data to be
* decoded.
*
* @return The decoded entry.
*
* @throws DirectoryException If the provided byte array cannot be
* decoded as an entry.
*/
public static Entry decode(byte[] entryBytes)
throws DirectoryException
{
return decode(entryBytes,
DirectoryServer.getDefaultCompressedSchema());
}
/**
* Decodes the provided byte array as an entry.
*
* @param entryBytes The byte array containing the data to
* be decoded.
* @param compressedSchema The compressed schema manager to use
* when decoding tokenized schema
* elements.
*
* @return The decoded entry.
*
* @throws DirectoryException If the provided byte array cannot be
* decoded as an entry.
*/
public static Entry decode(byte[] entryBytes,
CompressedSchema compressedSchema)
throws DirectoryException
{
switch(entryBytes[0])
{
case 0x01:
return decodeV1(entryBytes);
case 0x02:
return decodeV2(entryBytes, compressedSchema);
default:
Message message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
byteToHex(entryBytes[0]));
throw new DirectoryException(
DirectoryServer.getServerErrorResultCode(),
message);
}
}
/**
* Decodes the provided byte array as an entry using the V1
* encoding.
*
* @param entryBytes The byte array containing the data to be
* decoded.
*
* @return The decoded entry.
*
* @throws DirectoryException If the provided byte array cannot be
* decoded as an entry.
*/
public static Entry decodeV1(byte[] entryBytes)
throws DirectoryException
{
try
{
// The first byte must be the entry version. If it's not one
// we recognize, then that's an error.
if (entryBytes[0] != 0x01)
{
Message message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
byteToHex(entryBytes[0]));
throw new DirectoryException(
DirectoryServer.getServerErrorResultCode(),
message);
}
// Next is the length of the DN. It may be a single byte or
// multiple bytes.
int pos = 1;
int dnLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != dnLength)
{
int numLengthBytes = dnLength;
dnLength = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
dnLength = (dnLength << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next is the DN itself.
byte[] dnBytes = new byte[dnLength];
System.arraycopy(entryBytes, pos, dnBytes, 0, dnLength);
pos += dnLength;
DN dn = DN.decode(new ASN1OctetString(dnBytes));
// Next is the length of the object classes. It may be a single
// byte or multiple bytes.
int ocLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != ocLength)
{
int numLengthBytes = ocLength;
ocLength = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
ocLength = (ocLength << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next is the encoded set of object classes. It will be a
// single string with the object class names separated by zeros.
LinkedHashMap<ObjectClass,String> objectClasses =
new LinkedHashMap<ObjectClass,String>();
int startPos = pos;
for (int i=0; i < ocLength; i++,pos++)
{
if (entryBytes[pos] == 0x00)
{
String name = new String(entryBytes, startPos, pos-startPos,
"UTF-8");
String lowerName = toLowerCase(name);
ObjectClass oc =
DirectoryServer.getObjectClass(lowerName, true);
objectClasses.put(oc, name);
startPos = pos+1;
}
}
String name = new String(entryBytes, startPos, pos-startPos,
"UTF-8");
String lowerName = toLowerCase(name);
ObjectClass oc =
DirectoryServer.getObjectClass(lowerName, true);
objectClasses.put(oc, name);
// Next is the total number of user attributes. It may be a
// single byte or multiple bytes.
int numUserAttrs = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != numUserAttrs)
{
int numLengthBytes = numUserAttrs;
numUserAttrs = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
numUserAttrs = (numUserAttrs << 8) |
(entryBytes[pos] & 0xFF);
}
}
// Now, we should iterate through the user attributes and decode
// each one.
LinkedHashMap<AttributeType,List<Attribute>> userAttributes =
new LinkedHashMap<AttributeType,List<Attribute>>();
AttributeBuilder builder = new AttributeBuilder();
for (int i=0; i < numUserAttrs; i++)
{
AttributeType attributeType;
// First, we have the zero-terminated attribute name.
startPos = pos;
while (entryBytes[pos] != 0x00)
{
pos++;
}
name = new String(entryBytes, startPos, pos-startPos,
"UTF-8");
int semicolonPos = name.indexOf(';');
if (semicolonPos > 0)
{
builder.setAttributeType(name.substring(0, semicolonPos));
attributeType = builder.getAttributeType();
int nextPos = name.indexOf(';', semicolonPos+1);
while (nextPos > 0)
{
String option = name.substring(semicolonPos+1, nextPos);
if (option.length() > 0)
{
builder.setOption(option);
}
semicolonPos = nextPos;
nextPos = name.indexOf(';', semicolonPos+1);
}
String option = name.substring(semicolonPos+1);
if (option.length() > 0)
{
builder.setOption(option);
}
}
else
{
builder.setAttributeType(name);
attributeType = builder.getAttributeType();
}
// Next, we have the number of values.
int numValues = entryBytes[++pos] & 0x7F;
if (entryBytes[pos++] != numValues)
{
int numLengthBytes = numValues;
numValues = 0;
for (int j=0; j < numLengthBytes; j++, pos++)
{
numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next, we have the sequence of length-value pairs.
builder.setInitialCapacity(numValues);
for (int j=0; j < numValues; j++)
{
int valueLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != valueLength)
{
int numLengthBytes = valueLength;
valueLength = 0;
for (int k=0; k < numLengthBytes; k++, pos++)
{
valueLength = (valueLength << 8) |
(entryBytes[pos] & 0xFF);
}
}
byte[] valueBytes = new byte[valueLength];
System.arraycopy(entryBytes, pos, valueBytes, 0,
valueLength);
builder.add(new AttributeValue(attributeType,
new ASN1OctetString(valueBytes)));
pos += valueLength;
}
// Create the attribute and add it to the set of user
// attributes.
Attribute a = builder.toAttribute();
List<Attribute> attrList = userAttributes.get(attributeType);
if (attrList == null)
{
attrList = new ArrayList<Attribute>(1);
attrList.add(a);
userAttributes.put(attributeType, attrList);
}
else
{
attrList.add(a);
}
}
// Next is the total number of operational attributes. It may
// be a single byte or multiple bytes.
int numOperationalAttrs = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != numOperationalAttrs)
{
int numLengthBytes = numOperationalAttrs;
numOperationalAttrs = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
numOperationalAttrs =
(numOperationalAttrs << 8) | (entryBytes[pos] & 0xFF);
}
}
// Now, we should iterate through the operational attributes and
// decode each one.
LinkedHashMap<AttributeType,List<Attribute>>
operationalAttributes =
new LinkedHashMap<AttributeType,List<Attribute>>();
for (int i=0; i < numOperationalAttrs; i++)
{
AttributeType attributeType;
// First, we have the zero-terminated attribute name.
startPos = pos;
while (entryBytes[pos] != 0x00)
{
pos++;
}
name = new String(entryBytes, startPos, pos-startPos,
"UTF-8");
int semicolonPos = name.indexOf(';');
if (semicolonPos > 0)
{
builder.setAttributeType(name.substring(0, semicolonPos));
attributeType = builder.getAttributeType();
int nextPos = name.indexOf(';', semicolonPos+1);
while (nextPos > 0)
{
String option = name.substring(semicolonPos+1, nextPos);
if (option.length() > 0)
{
builder.setOption(option);
}
semicolonPos = nextPos;
nextPos = name.indexOf(';', semicolonPos+1);
}
String option = name.substring(semicolonPos+1);
if (option.length() > 0)
{
builder.setOption(option);
}
}
else
{
builder.setAttributeType(name);
attributeType = builder.getAttributeType();
}
// Next, we have the number of values.
int numValues = entryBytes[++pos] & 0x7F;
if (entryBytes[pos++] != numValues)
{
int numLengthBytes = numValues;
numValues = 0;
for (int j=0; j < numLengthBytes; j++, pos++)
{
numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next, we have the sequence of length-value pairs.
builder.setInitialCapacity(numValues);
for (int j=0; j < numValues; j++)
{
int valueLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != valueLength)
{
int numLengthBytes = valueLength;
valueLength = 0;
for (int k=0; k < numLengthBytes; k++, pos++)
{
valueLength = (valueLength << 8) |
(entryBytes[pos] & 0xFF);
}
}
byte[] valueBytes = new byte[valueLength];
System.arraycopy(
entryBytes, pos, valueBytes, 0, valueLength);
builder.add(new AttributeValue(attributeType,
new ASN1OctetString(valueBytes)));
pos += valueLength;
}
// Create the attribute and add it to the set of operational
// attributes.
Attribute a = builder.toAttribute();
List<Attribute> attrList =
operationalAttributes.get(attributeType);
if (attrList == null)
{
attrList = new ArrayList<Attribute>(1);
attrList.add(a);
operationalAttributes.put(attributeType, attrList);
}
else
{
attrList.add(a);
}
}
// We've got everything that we need, so create and return the
// entry.
return new Entry(dn, objectClasses, userAttributes,
operationalAttributes);
}
catch (DirectoryException de)
{
throw de;
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
Message message =
ERR_ENTRY_DECODE_EXCEPTION.get(getExceptionMessage(e));
throw new DirectoryException(
DirectoryServer.getServerErrorResultCode(),
message, e);
}
}
/**
* Decodes the provided byte array as an entry using the V2
* encoding.
*
* @param entryBytes The byte array containing the data to
* be decoded.
* @param compressedSchema The compressed schema manager to use
* when decoding tokenized schema
* elements.
*
* @return The decoded entry.
*
* @throws DirectoryException If the provided byte array cannot be
* decoded as an entry.
*/
public static Entry decodeV2(byte[] entryBytes,
CompressedSchema compressedSchema)
throws DirectoryException
{
try
{
// The first byte must be the entry version. If it's not one
// we recognize, then that's an error.
if (entryBytes[0] != 0x02)
{
Message message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
byteToHex(entryBytes[0]));
throw new DirectoryException(
DirectoryServer.getServerErrorResultCode(),
message);
}
// Next is the length of the encoded configuration. It may be a
// single byte or multiple bytes.
int pos = 1;
int configLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != configLength)
{
int numLengthBytes = configLength;
configLength = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
configLength =
(configLength << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next is the encoded configuration itself.
EntryEncodeConfig config =
EntryEncodeConfig.decode(entryBytes, pos, configLength,
compressedSchema);
pos += configLength;
// If we should have included the DN in the entry, then it's
// next.
DN dn;
if (config.excludeDN())
{
dn = DN.NULL_DN;
}
else
{
// Next is the length of the DN. It may be a single byte or
// multiple bytes.
int dnLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != dnLength)
{
int numLengthBytes = dnLength;
dnLength = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
dnLength = (dnLength << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next is the DN itself.
byte[] dnBytes = new byte[dnLength];
System.arraycopy(entryBytes, pos, dnBytes, 0, dnLength);
pos += dnLength;
dn = DN.decode(new ASN1OctetString(dnBytes));
}
// Next is the length of the object classes. It may be a single
// byte or multiple bytes.
int ocLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != ocLength)
{
int numLengthBytes = ocLength;
ocLength = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
ocLength = (ocLength << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next is the set of encoded object classes. The encoding will
// depend on the configuration.
Map<ObjectClass,String> objectClasses;
if (config.compressObjectClassSets())
{
byte[] ocBytes = new byte[ocLength];
System.arraycopy(entryBytes, pos, ocBytes, 0, ocLength);
objectClasses = config.getCompressedSchema().
decodeObjectClasses(ocBytes);
pos += ocLength;
}
else
{
// The set of object classes will be encoded as a single
// string with the oibject class names separated by zeros.
objectClasses = new LinkedHashMap<ObjectClass,String>();
int startPos = pos;
for (int i=0; i < ocLength; i++,pos++)
{
if (entryBytes[pos] == 0x00)
{
String name = new String(entryBytes, startPos,
pos-startPos, "UTF-8");
String lowerName = toLowerCase(name);
ObjectClass oc =
DirectoryServer.getObjectClass(lowerName, true);
objectClasses.put(oc, name);
startPos = pos+1;
}
}
String name = new String(entryBytes, startPos, pos-startPos,
"UTF-8");
String lowerName = toLowerCase(name);
ObjectClass oc =
DirectoryServer.getObjectClass(lowerName, true);
objectClasses.put(oc, name);
}
// Next is the total number of user attributes. It may be a
// single byte or multiple bytes.
int numUserAttrs = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != numUserAttrs)
{
int numLengthBytes = numUserAttrs;
numUserAttrs = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
numUserAttrs = (numUserAttrs << 8) |
(entryBytes[pos] & 0xFF);
}
}
// Now, we should iterate through the user attributes and decode
// each one.
LinkedHashMap<AttributeType,List<Attribute>> userAttributes =
new LinkedHashMap<AttributeType,List<Attribute>>();
if (config.compressAttributeDescriptions())
{
for (int i=0; i < numUserAttrs; i++)
{
// Get the length of the attribute.
int attrLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != attrLength)
{
int attrLengthBytes = attrLength;
attrLength = 0;
for (int j=0; j < attrLengthBytes; j++, pos++)
{
attrLength = (attrLength << 8) |
(entryBytes[pos] & 0xFF);
}
}
// Decode the attribute.
Attribute a = config.getCompressedSchema().decodeAttribute(
entryBytes, pos, attrLength);
List<Attribute> attrList =
userAttributes.get(a.getAttributeType());
if (attrList == null)
{
attrList = new ArrayList<Attribute>(1);
userAttributes.put(a.getAttributeType(), attrList);
}
attrList.add(a);
pos += attrLength;
}
}
else
{
AttributeBuilder builder = new AttributeBuilder();
for (int i=0; i < numUserAttrs; i++)
{
AttributeType attributeType;
// First, we have the zero-terminated attribute name.
int startPos = pos;
while (entryBytes[pos] != 0x00)
{
pos++;
}
String name = new String(entryBytes, startPos, pos-startPos,
"UTF-8");
int semicolonPos = name.indexOf(';');
if (semicolonPos > 0)
{
builder.setAttributeType(name.substring(0, semicolonPos));
attributeType = builder.getAttributeType();
int nextPos = name.indexOf(';', semicolonPos+1);
while (nextPos > 0)
{
String option = name.substring(semicolonPos+1, nextPos);
if (option.length() > 0)
{
builder.setOption(option);
}
semicolonPos = nextPos;
nextPos = name.indexOf(';', semicolonPos+1);
}
String option = name.substring(semicolonPos+1);
if (option.length() > 0)
{
builder.setOption(option);
}
}
else
{
builder.setAttributeType(name);
attributeType = builder.getAttributeType();
}
// Next, we have the number of values.
int numValues = entryBytes[++pos] & 0x7F;
if (entryBytes[pos++] != numValues)
{
int numLengthBytes = numValues;
numValues = 0;
for (int j=0; j < numLengthBytes; j++, pos++)
{
numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next, we have the sequence of length-value pairs.
builder.setInitialCapacity(numValues);
for (int j=0; j < numValues; j++)
{
int valueLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != valueLength)
{
int numLengthBytes = valueLength;
valueLength = 0;
for (int k=0; k < numLengthBytes; k++, pos++)
{
valueLength = (valueLength << 8) |
(entryBytes[pos] & 0xFF);
}
}
byte[] valueBytes = new byte[valueLength];
System.arraycopy(entryBytes, pos, valueBytes, 0,
valueLength);
builder.add(new AttributeValue(attributeType,
new ASN1OctetString(valueBytes)));
pos += valueLength;
}
// Create the attribute and add it to the set of user
// attributes.
Attribute a = builder.toAttribute();
List<Attribute> attrList =
userAttributes.get(attributeType);
if (attrList == null)
{
attrList = new ArrayList<Attribute>(1);
attrList.add(a);
userAttributes.put(attributeType, attrList);
}
else
{
attrList.add(a);
}
}
}
// Next is the total number of operational attributes. It may
// be a single byte or multiple bytes.
int numOperationalAttrs = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != numOperationalAttrs)
{
int numLengthBytes = numOperationalAttrs;
numOperationalAttrs = 0;
for (int i=0; i < numLengthBytes; i++, pos++)
{
numOperationalAttrs =
(numOperationalAttrs << 8) | (entryBytes[pos] & 0xFF);
}
}
// Now, we should iterate through the operational attributes and
// decode each one.
LinkedHashMap<AttributeType,List<Attribute>>
operationalAttributes =
new LinkedHashMap<AttributeType,List<Attribute>>();
if (config.compressAttributeDescriptions())
{
for (int i=0; i < numOperationalAttrs; i++)
{
// Get the length of the attribute.
int attrLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != attrLength)
{
int attrLengthBytes = attrLength;
attrLength = 0;
for (int j=0; j < attrLengthBytes; j++, pos++)
{
attrLength = (attrLength << 8) |
(entryBytes[pos] & 0xFF);
}
}
// Decode the attribute.
Attribute a = config.getCompressedSchema().decodeAttribute(
entryBytes, pos, attrLength);
List<Attribute> attrList =
operationalAttributes.get(a.getAttributeType());
if (attrList == null)
{
attrList = new ArrayList<Attribute>(1);
operationalAttributes.put(a.getAttributeType(), attrList);
}
attrList.add(a);
pos += attrLength;
}
}
else
{
AttributeBuilder builder = new AttributeBuilder();
for (int i=0; i < numOperationalAttrs; i++)
{
AttributeType attributeType;
// First, we have the zero-terminated attribute name.
int startPos = pos;
while (entryBytes[pos] != 0x00)
{
pos++;
}
String name = new String(entryBytes, startPos, pos-startPos,
"UTF-8");
int semicolonPos = name.indexOf(';');
if (semicolonPos > 0)
{
builder.setAttributeType(name.substring(0, semicolonPos));
attributeType = builder.getAttributeType();
int nextPos = name.indexOf(';', semicolonPos+1);
while (nextPos > 0)
{
String option = name.substring(semicolonPos+1, nextPos);
if (option.length() > 0)
{
builder.setOption(option);
}
semicolonPos = nextPos;
nextPos = name.indexOf(';', semicolonPos+1);
}
String option = name.substring(semicolonPos+1);
if (option.length() > 0)
{
builder.setOption(option);
}
}
else
{
builder.setAttributeType(name);
attributeType = builder.getAttributeType();
}
// Next, we have the number of values.
int numValues = entryBytes[++pos] & 0x7F;
if (entryBytes[pos++] != numValues)
{
int numLengthBytes = numValues;
numValues = 0;
for (int j=0; j < numLengthBytes; j++, pos++)
{
numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
}
}
// Next, we have the sequence of length-value pairs.
builder.setInitialCapacity(numValues);
for (int j=0; j < numValues; j++)
{
int valueLength = entryBytes[pos] & 0x7F;
if (entryBytes[pos++] != valueLength)
{
int numLengthBytes = valueLength;
valueLength = 0;
for (int k=0; k < numLengthBytes; k++, pos++)
{
valueLength = (valueLength << 8) |
(entryBytes[pos] & 0xFF);
}
}
byte[] valueBytes = new byte[valueLength];
System.arraycopy(
entryBytes, pos, valueBytes, 0, valueLength);
builder.add(new AttributeValue(attributeType,
new ASN1OctetString(valueBytes)));
pos += valueLength;
}
// Create the attribute and add it to the set of operational
// attributes.
Attribute a = builder.toAttribute();
List<Attribute> attrList =
operationalAttributes.get(attributeType);
if (attrList == null)
{
attrList = new ArrayList<Attribute>(1);
attrList.add(a);
operationalAttributes.put(attributeType, attrList);
}
else
{
attrList.add(a);
}
}
}
// We've got everything that we need, so create and return the
// entry.
return new Entry(dn, objectClasses, userAttributes,
operationalAttributes);
}
catch (DirectoryException de)
{
throw de;
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
Message message =
ERR_ENTRY_DECODE_EXCEPTION.get(getExceptionMessage(e));
throw new DirectoryException(
DirectoryServer.getServerErrorResultCode(),
message, e);
}
}
/**
* Retrieves a list of the lines for this entry in LDIF form. Long
* lines will not be wrapped automatically.
*
* @return A list of the lines for this entry in LDIF form.
*/
public List<StringBuilder> toLDIF()
{
LinkedList<StringBuilder> ldifLines =
new LinkedList<StringBuilder>();
// First, append the DN.
StringBuilder dnLine = new StringBuilder();
dnLine.append("dn");
appendLDIFSeparatorAndValue(dnLine, getBytes(dn.toString()));
ldifLines.add(dnLine);
// Next, add the set of objectclasses.
for (String s : objectClasses.values())
{
StringBuilder ocLine = new StringBuilder();
ocLine.append("objectClass: ");
ocLine.append(s);
ldifLines.add(ocLine);
}
// Next, add the set of user attributes.
for (List<Attribute> attrList : userAttributes.values())
{
for (Attribute a : attrList)
{
StringBuilder attrName = new StringBuilder(a.getName());
for (String o : a.getOptions())
{
attrName.append(";");
attrName.append(o);
}
for (AttributeValue v : a)
{
StringBuilder attrLine = new StringBuilder();
attrLine.append(attrName);
appendLDIFSeparatorAndValue(attrLine, v.getValueBytes());
ldifLines.add(attrLine);
}
}
}
// Finally, add the set of operational attributes.
for (List<Attribute> attrList : operationalAttributes.values())
{
for (Attribute a : attrList)
{
StringBuilder attrName = new StringBuilder(a.getName());
for (String o : a.getOptions())
{
attrName.append(";");
attrName.append(o);
}
for (AttributeValue v : a)
{
StringBuilder attrLine = new StringBuilder();
attrLine.append(attrName);
appendLDIFSeparatorAndValue(attrLine, v.getValueBytes());
ldifLines.add(attrLine);
}
}
}
return ldifLines;
}
/**
* Writes this entry in LDIF form according to the provided
* configuration.
*
* @param exportConfig The configuration that specifies how the
* entry should be written.
*
* @return <CODE>true</CODE> if the entry is actually written, or
* <CODE>false</CODE> if it is not for some reason.
*
* @throws IOException If a problem occurs while writing the
* information.
*
* @throws LDIFException If a problem occurs while trying to
* determine whether to write the entry.
*/
public boolean toLDIF(LDIFExportConfig exportConfig)
throws IOException, LDIFException
{
// See if this entry should be included in the export at all.
try
{
if (! exportConfig.includeEntry(this))
{
if (debugEnabled())
{
TRACER.debugInfo(
"Skipping entry %s because of the export " +
"configuration.", String.valueOf(dn));
}
return false;
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
Message message =
ERR_LDIF_COULD_NOT_EVALUATE_FILTERS_FOR_EXPORT.
get(String.valueOf(dn), String.valueOf(e));
throw new LDIFException(message, e);
}
// Invoke LDIF export plugins on the entry if appropriate.
if (exportConfig.invokeExportPlugins())
{
PluginConfigManager pluginConfigManager =
DirectoryServer.getPluginConfigManager();
PluginResult.ImportLDIF pluginResult =
pluginConfigManager.invokeLDIFExportPlugins(exportConfig,
this);
if (! pluginResult.continueProcessing())
{
return false;
}
}
// Get the information necessary to write the LDIF.
BufferedWriter writer = exportConfig.getWriter();
int wrapColumn = exportConfig.getWrapColumn();
boolean wrapLines = (wrapColumn > 1);
// First, write the DN. It will always be included.
StringBuilder dnLine = new StringBuilder();
dnLine.append("dn");
appendLDIFSeparatorAndValue(dnLine, getBytes(dn.toString()));
writeLDIFLine(dnLine, writer, wrapLines, wrapColumn);
// Next, the set of objectclasses.
final boolean typesOnly = exportConfig.typesOnly();
if (exportConfig.includeObjectClasses())
{
if (typesOnly)
{
StringBuilder ocLine = new StringBuilder("objectClass:");
writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
}
else
{
for (String s : objectClasses.values())
{
StringBuilder ocLine = new StringBuilder();
ocLine.append("objectClass: ");
ocLine.append(s);
writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
}
}
}
else
{
if (debugEnabled())
{
TRACER.debugVerbose(
"Skipping objectclasses for entry %s because of " +
"the export configuration.", String.valueOf(dn));
}
}
// Now the set of user attributes.
for (AttributeType attrType : userAttributes.keySet())
{
if (exportConfig.includeAttribute(attrType))
{
List<Attribute> attrList = userAttributes.get(attrType);
for (Attribute a : attrList)
{
if (a.isVirtual() &&
(! exportConfig.includeVirtualAttributes()))
{
continue;
}
if (typesOnly)
{
StringBuilder attrName = new StringBuilder(a.getName());
for (String o : a.getOptions())
{
attrName.append(";");
attrName.append(o);
}
attrName.append(":");
writeLDIFLine(attrName, writer, wrapLines, wrapColumn);
}
else
{
StringBuilder attrName = new StringBuilder(a.getName());
for (String o : a.getOptions())
{
attrName.append(";");
attrName.append(o);
}
for (AttributeValue v : a)
{
StringBuilder attrLine = new StringBuilder();
attrLine.append(attrName);
appendLDIFSeparatorAndValue(attrLine,
v.getValueBytes());
writeLDIFLine(attrLine, writer, wrapLines, wrapColumn);
}
}
}
}
else
{
if (debugEnabled())
{
TRACER.debugVerbose(
"Skipping user attribute %s for entry %s because of " +
"the export configuration.",
attrType.getNameOrOID(), String.valueOf(dn));
}
}
}
// Next, the set of operational attributes.
if (exportConfig.includeOperationalAttributes())
{
for (AttributeType attrType : operationalAttributes.keySet())
{
if (exportConfig.includeAttribute(attrType))
{
List<Attribute> attrList =
operationalAttributes.get(attrType);
for (Attribute a : attrList)
{
if (a.isVirtual() &&
(! exportConfig.includeVirtualAttributes()))
{
continue;
}
if (typesOnly)
{
StringBuilder attrName = new StringBuilder(a.getName());
for (String o : a.getOptions())
{
attrName.append(";");
attrName.append(o);
}
attrName.append(":");
writeLDIFLine(attrName, writer, wrapLines, wrapColumn);
}
else
{
StringBuilder attrName = new StringBuilder(a.getName());
for (String o : a.getOptions())
{
attrName.append(";");
attrName.append(o);
}
for (AttributeValue v : a)
{
StringBuilder attrLine = new StringBuilder();
attrLine.append(attrName);
appendLDIFSeparatorAndValue(attrLine,
v.getValueBytes());
writeLDIFLine(attrLine, writer, wrapLines,
wrapColumn);
}
}
}
}
else
{
if (debugEnabled())
{
TRACER.debugVerbose(
"Skipping operational attribute %s for entry %s " +
"because of the export configuration.",
attrType.getNameOrOID(), String.valueOf(dn));
}
}
}
}
else
{
if (debugEnabled())
{
TRACER.debugVerbose(
"Skipping all operational attributes for entry %s " +
"because of the export configuration.",
String.valueOf(dn));
}
}
// If we are not supposed to include virtual attributes, then
// write any attributes that may normally be suppressed by a
// virtual attribute.
if (! exportConfig.includeVirtualAttributes())
{
for (AttributeType t : suppressedAttributes.keySet())
{
if (exportConfig.includeAttribute(t))
{
for (Attribute a : suppressedAttributes.get(t))
{
if (typesOnly)
{
StringBuilder attrName = new StringBuilder(a.getName());
for (String o : a.getOptions())
{
attrName.append(";");
attrName.append(o);
}
attrName.append(":");
writeLDIFLine(attrName, writer, wrapLines, wrapColumn);
}
else
{
StringBuilder attrName = new StringBuilder(a.getName());
for (String o : a.getOptions())
{
attrName.append(";");
attrName.append(o);
}
for (AttributeValue v : a)
{
StringBuilder attrLine = new StringBuilder();
attrLine.append(attrName);
appendLDIFSeparatorAndValue(attrLine,
v.getValueBytes());
writeLDIFLine(attrLine, writer, wrapLines,
wrapColumn);
}
}
}
}
}
}
// Make sure there is a blank line after the entry.
writer.newLine();
return true;
}
/**
* Retrieves the name of the protocol associated with this protocol
* element.
*
* @return The name of the protocol associated with this protocol
* element.
*/
public String getProtocolElementName()
{
return "Entry";
}
/**
* Retrieves a hash code for this entry.
*
* @return The hash code for this entry.
*/
@Override()
public int hashCode()
{
int hashCode = dn.hashCode();
for (ObjectClass oc : objectClasses.keySet())
{
hashCode += oc.hashCode();
}
for (List<Attribute> attrList : userAttributes.values())
{
for (Attribute a : attrList)
{
hashCode += a.hashCode();
}
}
for (List<Attribute> attrList : operationalAttributes.values())
{
for (Attribute a : attrList)
{
hashCode += a.hashCode();
}
}
return hashCode;
}
/**
* Indicates whether the provided object is equal to this entry. In
* order for the object to be considered equal, it must be an entry
* with the same DN, set of object classes, and set of user and
* operational attributes.
*
* @param o The object for which to make the determination.
*
* @return {@code true} if the provided object may be considered
* equal to this entry, or {@code false} if not.
*/
@Override()
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null)
{
return false;
}
if (! (o instanceof Entry))
{
return false;
}
Entry e = (Entry) o;
if (! dn.equals(e.dn))
{
return false;
}
if (! objectClasses.keySet().equals(e.objectClasses.keySet()))
{
return false;
}
for (AttributeType at : userAttributes.keySet())
{
List<Attribute> list1 = userAttributes.get(at);
List<Attribute> list2 = e.userAttributes.get(at);
if ((list2 == null) || (list1.size() != list2.size()))
{
return false;
}
for (Attribute a : list1)
{
if (! list2.contains(a))
{
return false;
}
}
}
for (AttributeType at : operationalAttributes.keySet())
{
List<Attribute> list1 = operationalAttributes.get(at);
List<Attribute> list2 = e.operationalAttributes.get(at);
if ((list2 == null) || (list1.size() != list2.size()))
{
return false;
}
for (Attribute a : list1)
{
if (! list2.contains(a))
{
return false;
}
}
}
return true;
}
/**
* Retrieves a string representation of this protocol element.
*
* @return A string representation of this protocol element.
*/
public String toString()
{
StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
/**
* Appends a string representation of this protocol element to the
* provided buffer.
*
* @param buffer The buffer into which the string representation
* should be written.
*/
public void toString(StringBuilder buffer)
{
buffer.append("Entry(dn=\"");
dn.toString(buffer);
buffer.append("\", objectClasses={");
if (! objectClasses.isEmpty())
{
Iterator<String> ocNames = objectClasses.values().iterator();
buffer.append(ocNames.next());
while (ocNames.hasNext())
{
buffer.append(",");
buffer.append(ocNames.next());
}
}
buffer.append("}, userAttrs={");
if (! userAttributes.isEmpty())
{
Iterator<AttributeType> attrs =
userAttributes.keySet().iterator();
buffer.append(attrs.next().getNameOrOID());
while (attrs.hasNext())
{
buffer.append(",");
buffer.append(attrs.next().getNameOrOID());
}
}
buffer.append("}, operationalAttrs={");
if (! operationalAttributes.isEmpty())
{
Iterator<AttributeType> attrs =
operationalAttributes.keySet().iterator();
buffer.append(attrs.next().getNameOrOID());
while (attrs.hasNext())
{
buffer.append(",");
buffer.append(attrs.next().getNameOrOID());
}
}
buffer.append("})");
}
/**
* Appends a string representation of this protocol element to the
* provided buffer.
*
* @param buffer The buffer into which the string representation
* should be written.
* @param indent The number of spaces that should be used to
* indent the resulting string representation.
*/
public void toString(StringBuilder buffer, int indent)
{
StringBuilder indentBuf = new StringBuilder(indent);
for (int i=0 ; i < indent; i++)
{
indentBuf.append(' ');
}
for (StringBuilder b : toLDIF())
{
buffer.append(indentBuf);
buffer.append(b);
buffer.append(EOL);
}
}
/**
* Retrieves a string representation of this entry in LDIF form.
*
* @return A string representation of this entry in LDIF form.
*/
public String toLDIFString()
{
StringBuilder buffer = new StringBuilder();
for (StringBuilder ldifLine : toLDIF())
{
buffer.append(ldifLine);
buffer.append(EOL);
}
return buffer.toString();
}
/**
* Retrieves a one-line representation of this entry.
*
* @return A one-line representation of this entry.
*/
public String toSingleLineString()
{
StringBuilder buffer = new StringBuilder();
toSingleLineString(buffer);
return buffer.toString();
}
/**
* Appends a single-line representation of this entry to the
* provided buffer.
*
* @param buffer The buffer to which the information should be
* written.
*/
public void toSingleLineString(StringBuilder buffer)
{
buffer.append("Entry(dn=\"");
dn.toString(buffer);
buffer.append("\",objectClasses={");
Iterator<String> iterator = objectClasses.values().iterator();
if (iterator.hasNext())
{
buffer.append(iterator.next());
while (iterator.hasNext())
{
buffer.append(",");
buffer.append(iterator.next());
}
}
buffer.append("},userAttrs={");
boolean firstAttr = true;
for (List<Attribute> attrList : userAttributes.values())
{
for (Attribute a : attrList)
{
if (firstAttr)
{
firstAttr = false;
}
else
{
buffer.append(",");
}
buffer.append(a.getName());
if (a.hasOptions())
{
for (String optionString : a.getOptions())
{
buffer.append(";");
buffer.append(optionString);
}
}
buffer.append("={");
Iterator<AttributeValue> valueIterator = a.iterator();
if (valueIterator.hasNext())
{
buffer.append(valueIterator.next().getStringValue());
while (valueIterator.hasNext())
{
buffer.append(",");
buffer.append(valueIterator.next().getStringValue());
}
}
buffer.append("}");
}
}
buffer.append("},operationalAttrs={");
for (List<Attribute> attrList : operationalAttributes.values())
{
for (Attribute a : attrList)
{
if (firstAttr)
{
firstAttr = false;
}
else
{
buffer.append(",");
}
buffer.append(a.getName());
if (a.hasOptions())
{
for (String optionString : a.getOptions())
{
buffer.append(";");
buffer.append(optionString);
}
}
buffer.append("={");
Iterator<AttributeValue> valueIterator = a.iterator();
if (valueIterator.hasNext())
{
buffer.append(valueIterator.next().getStringValue());
while (valueIterator.hasNext())
{
buffer.append(",");
buffer.append(valueIterator.next().getStringValue());
}
}
buffer.append("}");
}
}
buffer.append("})");
}
/**
* Retrieves the requested attribute element for the specified
* attribute type and options or <code>null</code> if this entry
* does not contain an attribute with the specified attribute type
* and options.
*
* @param attributeType
* The attribute type to retrieve.
* @param options
* The set of attribute options.
* @return The requested attribute element for the specified
* attribute type and options, or <code>null</code> if the
* specified attribute type is not present in this entry
* with the provided set of options.
*/
public Attribute getExactAttribute(AttributeType attributeType,
Set<String> options)
{
List<Attribute> attributes;
if (attributeType.isOperational())
{
attributes = operationalAttributes.get(attributeType);
}
else
{
attributes = userAttributes.get(attributeType);
}
if (attributes != null)
{
for (Attribute attribute : attributes)
{
if (attribute.optionsEqual(options))
{
return attribute;
}
}
}
return null;
}
/**
* Adds the provided attribute to this entry. If an attribute with
* the provided type and options already exists, then it will be
* either merged or replaced depending on the value of
* <code>replace</code>.
*
* @param attribute
* The attribute to add/replace in this entry.
* @param duplicateValues
* A list to which any duplicate values will be added.
* @param replace
* <code>true</code> if the attribute should replace any
* existing attribute.
*/
private void setAttribute(Attribute attribute,
List<AttributeValue> duplicateValues, boolean replace)
{
attachment = null;
AttributeType attributeType = attribute.getAttributeType();
if (attribute.getAttributeType().isObjectClassType())
{
// We will not do any validation of the object classes - this is
// left to the caller.
if (replace)
{
objectClasses.clear();
}
for (AttributeValue v : attribute)
{
String name = v.getStringValue();
String lowerName;
try
{
lowerName = v.getNormalizedStringValue();
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
lowerName = toLowerCase(v.getStringValue());
}
// Create a default object class if necessary.
ObjectClass oc =
DirectoryServer.getObjectClass(lowerName, true);
if (replace)
{
objectClasses.put(oc, name);
}
else
{
if (objectClasses.containsKey(oc))
{
duplicateValues.add(v);
}
else
{
objectClasses.put(oc, name);
}
}
}
return;
}
List<Attribute> attributes;
if (attributeType.isOperational())
{
attributes = operationalAttributes.get(attributeType);
}
else
{
attributes = userAttributes.get(attributeType);
}
if (attributes == null)
{
// Do nothing if we are deleting a non-existing attribute.
if (replace && attribute.isEmpty())
{
return;
}
// We are adding the first attribute with this attribute type.
attributes = new ArrayList<Attribute>(1);
attributes.add(attribute);
if (attributeType.isOperational())
{
operationalAttributes.put(attributeType, attributes);
}
else
{
userAttributes.put(attributeType, attributes);
}
return;
}
// There are already attributes with the same attribute type.
Set<String> options = attribute.getOptions();
for (int i = 0; i < attributes.size(); i++)
{
Attribute a = attributes.get(i);
if (a.optionsEqual(options))
{
if (replace)
{
if (!attribute.isEmpty())
{
attributes.set(i, attribute);
}
else
{
attributes.remove(i);
if (attributes.isEmpty())
{
if (attributeType.isOperational())
{
operationalAttributes.remove(attributeType);
}
else
{
userAttributes.remove(attributeType);
}
}
}
}
else
{
AttributeBuilder builder = new AttributeBuilder(a);
for (AttributeValue v : attribute)
{
if (!builder.add(v))
{
duplicateValues.add(v);
}
}
attributes.set(i, builder.toAttribute());
}
return;
}
}
// There were no attributes with the same options.
if (replace && attribute.isEmpty())
{
// Do nothing.
return;
}
attributes.add(attribute);
}
}