/* * 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-2010 Sun Microsystems, Inc. * Portions copyright 2011-2013 ForgeRock AS */ package org.opends.server.types; import java.io.BufferedWriter; import java.io.IOException; import java.util.*; import java.util.concurrent.locks.Lock; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; 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.core.SubentryManager; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.SubEntry.CollectiveConflictBehavior; import org.opends.server.util.LDIFException; import org.opends.server.util.LDIFWriter; import static org.opends.messages.CoreMessages.*; import static org.opends.messages.UtilityMessages.*; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.ErrorLogger.*; import static org.opends.server.loggers.debug.DebugLogger.*; 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(); // 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. It contains real * attributes that have been overridden by virtual attributes. */ private final Map<AttributeType, List<Attribute>> suppressedAttributes = new LinkedHashMap<AttributeType, List<Attribute>>(); // The set of objectclasses for this entry. private Map<ObjectClass,String> objectClasses; private Attribute objectClassAttribute; // 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 final 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(); 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.getValue().toString(); String lowerName; try { lowerName = v.getNormalizedValue().toString(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } lowerName = toLowerCase(v.getValue().toString()); } 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 append the new // set of objectclasses. objectClasses = ocMap; objectClassAttribute = null; } /** * 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() { // Estimate the size. int size = userAttributes.size() + operationalAttributes.size(); ArrayList<Attribute> attributes = new ArrayList<Attribute>(size); 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; } if(objectClassAttribute == null) { AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); AttributeBuilder builder = new AttributeBuilder(ocType, ATTR_OBJECTCLASS); for (Map.Entry<ObjectClass, String> e : objectClasses.entrySet()) { builder.add(AttributeValues.create( ByteString.valueOf(e.getValue()), ByteString.valueOf(e.getKey() .getNormalizedPrimaryName()))); } objectClassAttribute = builder.toAttribute(); } return objectClassAttribute; } /** * 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; } } /** * 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().getValue().toString(); 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.getValue().toString(); 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(AttributeValues.create(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.getNormalizedValue().toString(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } ocName = toLowerCase(v.getValue().toString()); } 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)) { if (a.contains(value)) { return true; } } } return false; } /** * Applies the provided modification to this entry. No schema * checking will be performed. * * @param mod The modification to apply to this entry. * @param relaxConstraints indicates if the modification * constraints are relaxed to match * the ones of a set (add existing * value and delete absent value do not * fail) * * @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, boolean relaxConstraints) 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.getValue().toString(); 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)) { if (!relaxConstraints) { 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)); } } objectClassAttribute = null; break; case DELETE: for (ObjectClass oc : ocs.keySet()) { if (objectClasses.remove(oc) == null) { if (! relaxConstraints) { Message message = ERR_ENTRY_NO_SUCH_VALUE.get(a.getName()); throw new DirectoryException( ResultCode.NO_SUCH_ATTRIBUTE, message); } } } objectClassAttribute = null; break; case REPLACE: objectClasses = ocs; objectClassAttribute = null; break; case INCREMENT: Message message = ERR_ENTRY_OC_INCREMENT_NOT_SUPPORTED.get(); throw new DirectoryException( ResultCode.CONSTRAINT_VIOLATION, 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()) && (! relaxConstraints)) { 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()) && (! relaxConstraints)) { 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 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 { applyModification(mod, false); } /** * 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 (! checkAttributesAndObjectClasses(ditContentRule, structuralPolicy, invalidReason)) { return false; } if (validateNameForms) { /** * There may be multiple nameforms registered with this * structural objectclass.However, we need to select only one * of the nameforms and its corresponding DITstructure rule. * We will iterate over all the nameforms and see if atleast * one is acceptable before rejecting the entry. * DITStructureRules corresponding to other non-acceptable * nameforms are not applied. */ List<NameForm> listForms = DirectoryServer.getNameForm(structuralClass); if(listForms != null) { boolean matchFound = false; boolean obsolete = true; for(int index=0; index <listForms.size(); index++) { NameForm nf = listForms.get(index); if(!nf.isObsolete()) { obsolete = false; matchFound = checkNameForm(nf, structuralPolicy, invalidReason); if(matchFound) { nameForm = nf; break; } if(index != listForms.size()-1) { invalidReason.append(","); } } } if(! obsolete && !matchFound) { //We couldn't match this entry against any of the // nameforms. return false; } } if (validateStructureRules && (nameForm != null)) { ditStructureRule = DirectoryServer.getDITStructureRule(nameForm); if ((ditStructureRule != null) && ditStructureRule.isObsolete()) { ditStructureRule = null; } } } } // 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. final Lock lock = LockManager.lockRead(parentDN); 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. final Lock lock = LockManager.lockRead(parentDN); 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 { List<NameForm> allNFs = DirectoryServer.getNameForm(parentStructuralClass); if(allNFs != null) { for(NameForm parentNF : allNFs) { 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, false, false, true, false); HashMap<AttributeType,List<Attribute>> operationalAttrsCopy = new HashMap<AttributeType,List<Attribute>>( operationalAttributes.size()); deepCopy(operationalAttributes, operationalAttrsCopy, false, false, false, true, false); // Put back all the suppressed attributes where they belonged to. // Then hopefully processVirtualAttributes() will rebuild the suppressed // attribute list correctly. for (AttributeType t : suppressedAttributes.keySet()) { List<Attribute> attrList = suppressedAttributes.get(t); if (t.isOperational()) { operationalAttrsCopy.put(t, attrList); } else { userAttrsCopy.put(t, attrList); } } Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy, operationalAttrsCopy); if (processVirtual) { e.processVirtualAttributes(); } 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. * * @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. * @param omitEmpty * Indicates whether to omit empty attributes when * processing. * @param omitReal * Indicates whether to exclude real attributes. * @param omitVirtual * Indicates whether to exclude virtual attributes. * @param mergeDuplicates * Indicates whether duplicate attributes should be merged. */ private void deepCopy(Map<AttributeType,List<Attribute>> source, Map<AttributeType,List<Attribute>> target, boolean omitValues, boolean omitEmpty, boolean omitReal, boolean omitVirtual, boolean mergeDuplicates) { for (Map.Entry<AttributeType, List<Attribute>> mapEntry : source.entrySet()) { AttributeType t = mapEntry.getKey(); List<Attribute> sourceList = mapEntry.getValue(); ArrayList<Attribute> targetList = new ArrayList<Attribute>(sourceList.size()); for (Attribute a : sourceList) { if (omitReal && !a.isVirtual()) { continue; } else if (omitVirtual && a.isVirtual()) { continue; } else if (omitEmpty && a.isEmpty()) { continue; } if (omitValues) { a = Attributes.empty(a); } if (!targetList.isEmpty() && mergeDuplicates) { // Ensure that there is only one attribute with the same // type and options. This is not very efficient but will // occur very rarely. boolean found = false; for (int i = 0; i < targetList.size(); i++) { Attribute otherAttribute = targetList.get(i); if (otherAttribute.optionsEqual(a.getOptions())) { targetList.set(i, Attributes.merge(a, otherAttribute)); found = true; } } if (!found) { targetList.add(a); } } else { targetList.add(a); } } if (!targetList.isEmpty()) { target.put(t, targetList); } } } /** * 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.getValue().toString()); } } 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().getValue().toString()); } } } /** * 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 meets the criteria to consider it * an RFC 3672 LDAP subentry (i.e., it contains the "subentry" * objectclass). * * @return <CODE>true</CODE> if this entry meets the criteria to * consider it an RFC 3672 LDAP subentry, or <CODE>false * </CODE> if not. */ public boolean isSubentry() { ObjectClass subentryOC = DirectoryServer.getObjectClass(OC_SUBENTRY); if (subentryOC == null) { // This should not happen -- The server doesn't // have a subentry objectclass defined. if (debugEnabled()) { TRACER.debugWarning( "No %s objectclass is defined in the server schema.", OC_SUBENTRY); } for (String ocName : objectClasses.values()) { if (ocName.equalsIgnoreCase(OC_SUBENTRY)) { return true; } } return false; } // Make the determination based on whether this // entry has the subentry objectclass. return objectClasses.containsKey(subentryOC); } /** * Indicates whether the entry meets the criteria to consider it an * RFC 3671 LDAP collective attributes subentry (i.e., it contains * the "collectiveAttributeSubentry" objectclass). * * @return <CODE>true</CODE> if this entry meets the criteria to * consider it an RFC 3671 LDAP collective attributes * subentry, or <CODE>false</CODE> if not. */ public boolean isCollectiveAttributeSubentry() { ObjectClass collectiveAttributeSubentryOC = DirectoryServer.getObjectClass( OC_COLLECTIVE_ATTR_SUBENTRY_LC); if (collectiveAttributeSubentryOC == null) { // This should not happen -- The server doesn't have // a collectiveAttributeSubentry objectclass defined. if (debugEnabled()) { TRACER.debugWarning( "No %s objectclass is defined in the server schema.", OC_COLLECTIVE_ATTR_SUBENTRY); } for (String ocName : objectClasses.values()) { if (ocName.equalsIgnoreCase(OC_COLLECTIVE_ATTR_SUBENTRY)) { return true; } } return false; } // Make the determination based on whether this entry // has the collectiveAttributeSubentry objectclass. return objectClasses.containsKey(collectiveAttributeSubentryOC); } /** * Indicates whether the entry meets the criteria to consider it an * inherited collective attributes subentry (i.e., it contains * the "inheritedCollectiveAttributeSubentry" objectclass). * * @return <CODE>true</CODE> if this entry meets the criteria to * consider it an inherited collective attributes * subentry, or <CODE>false</CODE> if not. */ public boolean isInheritedCollectiveAttributeSubentry() { ObjectClass inheritedCollectiveAttributeSubentryOC = DirectoryServer.getObjectClass( OC_INHERITED_COLLECTIVE_ATTR_SUBENTRY_LC); if (inheritedCollectiveAttributeSubentryOC == null) { // This should not happen -- The server doesn't have // an inheritedCollectiveAttributeSubentry object // class defined. if (debugEnabled()) { TRACER.debugWarning( "No %s objectclass is defined in the server schema.", OC_INHERITED_COLLECTIVE_ATTR_SUBENTRY); } for (String ocName : objectClasses.values()) { if (ocName.equalsIgnoreCase( OC_INHERITED_COLLECTIVE_ATTR_SUBENTRY)) { return true; } } return false; } // Make the determination based on whether this entry // has the inheritedCollectiveAttributeSubentry // objectclass. return objectClasses.containsKey( inheritedCollectiveAttributeSubentryOC); } /** * Indicates whether the entry meets the criteria to consider it * an inherited from DN collective attributes subentry (i.e., it * contains the "inheritedFromDNCollectiveAttributeSubentry" * objectclass). * * @return <CODE>true</CODE> if this entry meets the criteria to * consider it an inherited from DN collective attributes * subentry, or <CODE>false</CODE> if not. */ public boolean isInheritedFromDNCollectiveAttributeSubentry() { ObjectClass inheritedFromDNCollectiveAttributeSubentryOC = DirectoryServer.getObjectClass( OC_INHERITED_FROM_DN_COLLECTIVE_ATTR_SUBENTRY_LC); if (inheritedFromDNCollectiveAttributeSubentryOC == null) { // This should not happen -- The server doesn't have // an inheritedFromDNCollectiveAttributeSubentry // object class defined. if (debugEnabled()) { TRACER.debugWarning( "No %s objectclass is defined in the server schema.", OC_INHERITED_FROM_DN_COLLECTIVE_ATTR_SUBENTRY); } for (String ocName : objectClasses.values()) { if (ocName.equalsIgnoreCase( OC_INHERITED_FROM_DN_COLLECTIVE_ATTR_SUBENTRY)) { return true; } } return false; } // Make the determination based on whether this entry // has the inheritedCollectiveAttributeSubentry // objectclass. return objectClasses.containsKey( inheritedFromDNCollectiveAttributeSubentryOC); } /** * Indicates whether the entry meets the criteria to consider it * an inherited from RDN collective attributes subentry (i.e., * it contains the "inheritedFromRDNCollectiveAttributeSubentry" * objectclass). * * @return <CODE>true</CODE> if this entry meets the criteria to * consider it an inherited from RDN collective attributes * subentry, or <CODE>false</CODE> if not. */ public boolean isInheritedFromRDNCollectiveAttributeSubentry() { ObjectClass inheritedFromRDNCollectiveAttributeSubentryOC = DirectoryServer.getObjectClass( OC_INHERITED_FROM_RDN_COLLECTIVE_ATTR_SUBENTRY_LC); if (inheritedFromRDNCollectiveAttributeSubentryOC == null) { // This should not happen -- The server doesn't have // an inheritedFromRDNCollectiveAttributeSubentry // object class defined. if (debugEnabled()) { TRACER.debugWarning( "No %s objectclass is defined in the server schema.", OC_INHERITED_FROM_RDN_COLLECTIVE_ATTR_SUBENTRY); } for (String ocName : objectClasses.values()) { if (ocName.equalsIgnoreCase( OC_INHERITED_FROM_RDN_COLLECTIVE_ATTR_SUBENTRY)) { return true; } } return false; } // Make the determination based on whether this entry // has the inheritedCollectiveAttributeSubentry // objectclass. return objectClasses.containsKey( inheritedFromRDNCollectiveAttributeSubentryOC); } /** * Indicates whether the entry meets the criteria to consider it a * LDAP password policy subentry (i.e., it contains the "pwdPolicy" * objectclass of LDAP Password Policy Internet-Draft). * * @return <CODE>true</CODE> if this entry meets the criteria to * consider it a LDAP Password Policy Internet-Draft * subentry, or <CODE>false</CODE> if not. */ public boolean isPasswordPolicySubentry() { ObjectClass passwordPolicySubentryOC = DirectoryServer.getObjectClass(OC_PWD_POLICY_SUBENTRY_LC); if (passwordPolicySubentryOC == null) { // This should not happen -- The server doesn't have // a pwdPolicy objectclass defined. if (debugEnabled()) { TRACER.debugWarning( "No %s objectclass is defined in the server schema.", OC_PWD_POLICY_SUBENTRY); } for (String ocName : objectClasses.values()) { if (ocName.equalsIgnoreCase(OC_PWD_POLICY_SUBENTRY)) { return true; } } return false; } // Make the determination based on whether this entry // has the pwdPolicy objectclass. return objectClasses.containsKey(passwordPolicySubentryOC); } /** * 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 collective attribute processing for this * entry. This should only be called at the time the entry is * decoded or created within the backend. */ private void processCollectiveAttributes() { if (this.isSubentry() || this.isLDAPSubentry()) { return; } SubentryManager manager = DirectoryServer.getSubentryManager(); if(manager == null) { //Subentry manager may not have been initialized by //a component that doesn't require it. return; } // Get applicable collective subentries. List<SubEntry> collectiveAttrSubentries = manager.getCollectiveSubentries(this); if ((collectiveAttrSubentries == null) || collectiveAttrSubentries.isEmpty()) { // Nothing to see here, move along. return; } // Get collective attribute exclusions. AttributeType exclusionsType = DirectoryServer.getAttributeType( ATTR_COLLECTIVE_EXCLUSIONS_LC); List<Attribute> exclusionsAttrList = operationalAttributes.get(exclusionsType); Set<String> exclusionsNameSet = new HashSet<String>(); if ((exclusionsAttrList != null) && !exclusionsAttrList.isEmpty()) { for (Attribute attr : exclusionsAttrList) { for (AttributeValue attrValue : attr) { String exclusionsName = attrValue.toString().toLowerCase(); if (exclusionsName.equals( VALUE_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL_LC) || exclusionsName.equals( OID_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL)) { return; } exclusionsNameSet.add(exclusionsName); } } } // Process collective attributes. for (SubEntry subEntry : collectiveAttrSubentries) { if (subEntry.isCollective() || subEntry.isInheritedCollective()) { Entry inheritFromEntry = null; if (subEntry.isInheritedCollective()) { if (subEntry.isInheritedFromDNCollective() && hasAttribute(subEntry.getInheritFromDNType())) { try { DN inheritFromDN = null; for (Attribute attr : getAttribute( subEntry.getInheritFromDNType())) { for (AttributeValue value : attr) { inheritFromDN = DN.decode( value.getNormalizedValue()); // Respect subentry root scope. if (!inheritFromDN.isDescendantOf( subEntry.getDN().getParent())) { inheritFromDN = null; } break; } } if (inheritFromDN == null) { continue; } // TODO : ACI check; needs re-factoring to happen. inheritFromEntry = DirectoryServer.getEntry( inheritFromDN); } catch (DirectoryException de) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, de); } } } else if (subEntry.isInheritedFromRDNCollective() && hasAttribute(subEntry.getInheritFromRDNAttrType())) { DN inheritFromDN = subEntry.getInheritFromBaseDN(); if (inheritFromDN != null) { try { for (Attribute attr : getAttribute( subEntry.getInheritFromRDNAttrType())) { inheritFromDN = subEntry.getInheritFromBaseDN(); for (AttributeValue value : attr) { inheritFromDN = inheritFromDN.concat( RDN.create(subEntry.getInheritFromRDNType(), value)); break; } } // TODO : ACI check; needs re-factoring to happen. inheritFromEntry = DirectoryServer.getEntry( inheritFromDN); } catch (DirectoryException de) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, de); } } } else { continue; } } } List<Attribute> collectiveAttrList = subEntry.getCollectiveAttributes(); for (Attribute collectiveAttr : collectiveAttrList) { AttributeType attributeType = collectiveAttr.getAttributeType(); if (exclusionsNameSet.contains( attributeType.getNormalizedPrimaryNameOrOID())) { continue; } if (subEntry.isInheritedCollective()) { if (inheritFromEntry != null) { collectiveAttr = inheritFromEntry.getExactAttribute( collectiveAttr.getAttributeType(), collectiveAttr.getOptions()); if ((collectiveAttr == null) || (collectiveAttr.isEmpty())) { continue; } collectiveAttr = new CollectiveVirtualAttribute( collectiveAttr); } else { 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(collectiveAttr); if (attributeType.isOperational()) { operationalAttributes.put(attributeType, attrList); } else { userAttributes.put(attributeType, attrList); } } else { // There is a conflict with an existing operational // attribute. resolveCollectiveConflict(subEntry.getConflictBehavior(), collectiveAttr, attrList, operationalAttributes, attributeType); } } else { // There is a conflict with an existing user attribute. resolveCollectiveConflict(subEntry.getConflictBehavior(), collectiveAttr, attrList, userAttributes, attributeType); } } } } } /** * Resolves a conflict arising with a collective attribute. * * @param conflictBehavior * the behavior of the conflict * @param collectiveAttr * the attribute in conflict * @param attrList * the List of attribute where to resolve the conflict * @param attributes * the Map of attributes where to solve the conflict * @param attributeType * the attribute type used with the Map */ private void resolveCollectiveConflict( CollectiveConflictBehavior conflictBehavior, Attribute collectiveAttr, List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes, AttributeType attributeType) { 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? return; } // The conflict is with a real attribute. See what the // conflict behavior is and figure out how to handle it. switch (conflictBehavior) { 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(collectiveAttr); attributes.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(collectiveAttr); break; } } /** * 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() { // Virtual attributes. for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes(this)) { AttributeType attributeType = rule.getAttributeType(); 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. resolveVirtualConflict(rule, attrList, operationalAttributes, attributeType); } } else { // There is a conflict with an existing user attribute. resolveVirtualConflict(rule, attrList, userAttributes, attributeType); } } // Collective attributes. processCollectiveAttributes(); } /** * Resolves a conflict arising with a virtual attribute. * * @param rule * the VirtualAttributeRule in conflict * @param attrList * the List of attribute where to resolve the conflict * @param attributes * the Map of attribute where to resolve the conflict * @param attributeType * the attribute type used with the Map */ private void resolveVirtualConflict(VirtualAttributeRule rule, List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes, AttributeType attributeType) { 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? return; } // 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)); attributes.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; } } /** * 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 buffer The buffer to encode into. * @param config The configuration that may be used to control how * the entry is encoded. * * @throws DirectoryException If a problem occurs while attempting * to encode the entry. */ public void encode(ByteStringBuilder buffer, EntryEncodeConfig config) throws DirectoryException { encodeV3(buffer, config); } /** * Encodes this entry using the V3 encoding. * * @param buffer The buffer to encode into. * @param config The configuration that should be used to encode * the entry. * * @throws DirectoryException If a problem occurs while attempting * to encode the entry. */ private void encodeV3(ByteStringBuilder buffer, EntryEncodeConfig config) throws DirectoryException { // The version number will be one byte. buffer.append((byte)0x03); // Get the encoded respresentation of the config. config.encode(buffer); // 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. if (! config.excludeDN()) { // TODO: Can we encode the DN directly into buffer? byte[] dnBytes = getBytes(dn.toString()); buffer.appendBERLength(dnBytes.length); buffer.append(dnBytes); } // Encode the object classes in the appropriate manner. if (config.compressObjectClassSets()) { config.getCompressedSchema().encodeObjectClasses(buffer, objectClasses); } else { // Encode number of OCs and 0 terminated names. buffer.appendBERLength(objectClasses.size()); for (String ocName : objectClasses.values()) { buffer.append(ocName); buffer.append((byte)0x00); } } // Encode the user attributes in the appropriate manner. encodeAttributes(buffer, userAttributes, config); // The operational attributes will be encoded in the same way as // the user attributes. encodeAttributes(buffer, operationalAttributes, config); } /** * Encode the given attributes of an entry. * * @param buffer The buffer to encode into. * @param attributes The attributes to encode. * @param config The configuration that may be used to control how * the entry is encoded. * * @throws DirectoryException If a problem occurs while attempting * to encode the entry. */ private void encodeAttributes(ByteStringBuilder buffer, Map<AttributeType,List<Attribute>> attributes, EntryEncodeConfig config) throws DirectoryException { int numAttributes = 0; // First count how many attributes are there to encode. for (List<Attribute> attrList : attributes.values()) { Attribute a; for (int i = 0; i < attrList.size(); i++) { a = attrList.get(i); if (a.isVirtual() || a.isEmpty()) { continue; } numAttributes++; } } // Encoded one-to-five byte number of attributes buffer.appendBERLength(numAttributes); if (config.compressAttributeDescriptions()) { for (List<Attribute> attrList : attributes.values()) { for (Attribute a : attrList) { if (a.isVirtual() || a.isEmpty()) { continue; } config.getCompressedSchema().encodeAttribute(buffer, a); } } } else { // The attributes will be encoded as 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 : attributes.values()) { for (Attribute a : attrList) { byte[] nameBytes = getBytes(a.getNameWithOptions()); buffer.append(nameBytes); buffer.append((byte)0x00); buffer.appendBERLength(a.size()); for(AttributeValue v : a) { buffer.appendBERLength(v.getValue().length()); buffer.append(v.getValue()); } } } } } /** * Decodes the provided byte array as an entry. * * @param entryBuffer 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(ByteSequenceReader entryBuffer) throws DirectoryException { return decode(entryBuffer, DirectoryServer.getDefaultCompressedSchema()); } /** * Decodes the provided byte array as an entry using the V3 * encoding. * * @param entryBuffer The byte buffer 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(ByteSequenceReader entryBuffer, 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. Byte version = entryBuffer.get(); if (version != 0x03 && version != 0x02 && version != 0x01) { Message message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get( byteToHex(version)); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message); } EntryEncodeConfig config; if(version != 0x01) { // Next is the length of the encoded configuration. int configLength = entryBuffer.getBERLength(); // Next is the encoded configuration itself. config = EntryEncodeConfig.decode(entryBuffer, configLength, compressedSchema); } else { config = EntryEncodeConfig.DEFAULT_CONFIG; } // 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 = entryBuffer.getBERLength(); // Next is the DN itself. ByteSequence dnBytes = entryBuffer.getByteSequence(dnLength); dn = DN.decode(dnBytes.toByteString()); } // Next is the set of encoded object classes. The encoding will // depend on the configuration. Map<ObjectClass,String> objectClasses = decodeObjectClasses(version, entryBuffer, config); // Now, we should iterate through the user attributes and decode // each one. LinkedHashMap<AttributeType,List<Attribute>> userAttributes = decodeAttributes(version, entryBuffer, config); // Now, we should iterate through the operational attributes and // decode each one. LinkedHashMap<AttributeType,List<Attribute>> operationalAttributes = decodeAttributes(version, entryBuffer, config); // 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); } } /** * Decode the object classes of an encoded entry. * * @param ver The version of the entry encoding. * @param entryBuffer The byte sequence containing the encoded * entry. * @param config The configuration that may be used to control how * the entry is encoded. * * @return A map of the decoded object classes. * @throws DirectoryException If a problem occurs while attempting * to encode the entry. */ private static Map<ObjectClass,String> decodeObjectClasses( byte ver, ByteSequenceReader entryBuffer, EntryEncodeConfig config) throws DirectoryException { // Next is the set of encoded object classes. The encoding will // depend on the configuration. Map<ObjectClass,String> objectClasses; if (config.compressObjectClassSets()) { objectClasses = config.getCompressedSchema(). decodeObjectClasses(entryBuffer); } else { if(ver < 0x03) { // Next is the length of the object classes. It may be a // single byte or multiple bytes. int ocLength = entryBuffer.getBERLength(); // The set of object classes will be encoded as a single // string with the object class names separated by zeros. objectClasses = new LinkedHashMap<ObjectClass,String>(); int startPos = entryBuffer.position(); int endPos; for (int i=0; i < ocLength; i++) { if (entryBuffer.get() == 0x00) { endPos = entryBuffer.position()-1; entryBuffer.position(startPos); String name = entryBuffer.getString(endPos - startPos); String lowerName = toLowerCase(name); ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); objectClasses.put(oc, name); entryBuffer.skip(1); startPos = entryBuffer.position(); } } endPos = entryBuffer.position(); entryBuffer.position(startPos); String name = entryBuffer.getString(endPos - startPos); String lowerName = toLowerCase(name); ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); objectClasses.put(oc, name); } else { // Next is the number of zero terminated object classes. int numOC = entryBuffer.getBERLength(); int startPos; int endPos; objectClasses = new LinkedHashMap<ObjectClass,String>(numOC); for(int i = 0; i < numOC; i++) { startPos = entryBuffer.position(); while(entryBuffer.get() != 0x00) {} endPos = entryBuffer.position()-1; entryBuffer.position(startPos); String name = entryBuffer.getString(endPos - startPos); String lowerName = toLowerCase(name); ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); objectClasses.put(oc, name); entryBuffer.skip(1); } } } return objectClasses; } /** * Decode the attributes of an encoded entry. * * @param ver The version of the entry encoding. * @param entryBuffer The byte sequence containing the encoded * entry. * @param config The configuration that may be used to control how * the entry is encoded. * * @return A map of the decoded object classes. * @throws DirectoryException If a problem occurs while attempting * to encode the entry. */ private static LinkedHashMap<AttributeType,List<Attribute>> decodeAttributes(Byte ver, ByteSequenceReader entryBuffer, EntryEncodeConfig config) throws DirectoryException { // Next is the total number of attributes. It may be a // single byte or multiple bytes. int attrs = entryBuffer.getBERLength(); // Now, we should iterate through the attributes and decode // each one. LinkedHashMap<AttributeType,List<Attribute>> attributes = new LinkedHashMap<AttributeType,List<Attribute>>(attrs); if (config.compressAttributeDescriptions()) { for (int i=0; i < attrs; i++) { if(ver < 0x03) { // Version 2 includes a total attribute length entryBuffer.getBERLength(); } // Decode the attribute. Attribute a = config.getCompressedSchema().decodeAttribute( entryBuffer); List<Attribute> attrList = attributes.get(a.getAttributeType()); if (attrList == null) { attrList = new ArrayList<Attribute>(1); attributes.put(a.getAttributeType(), attrList); } attrList.add(a); } } else { AttributeBuilder builder = new AttributeBuilder(); int startPos; int endPos; for (int i=0; i < attrs; i++) { AttributeType attributeType; // First, we have the zero-terminated attribute name. startPos = entryBuffer.position(); while (entryBuffer.get() != 0x00) {} endPos = entryBuffer.position()-1; entryBuffer.position(startPos); String name = entryBuffer.getString(endPos - startPos); entryBuffer.skip(1); 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 = entryBuffer.getBERLength(); // Next, we have the sequence of length-value pairs. builder.setInitialCapacity(numValues); for (int j=0; j < numValues; j++) { int valueLength = entryBuffer.getBERLength(); ByteString valueBytes = entryBuffer.getByteSequence(valueLength).toByteString(); builder.add(AttributeValues.create(attributeType, valueBytes)); } // Create the attribute and add it to the set of // attributes. Attribute a = builder.toAttribute(); List<Attribute> attrList = attributes.get(attributeType); if (attrList == null) { attrList = new ArrayList<Attribute>(1); attrList.add(a); attributes.put(attributeType, attrList); } else { attrList.add(a); } } } return attributes; } /** * 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, ByteString.valueOf(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. addLinesForAttributes(ldifLines, userAttributes); // Finally, add the set of operational attributes. addLinesForAttributes(ldifLines, operationalAttributes); return ldifLines; } /** * Add LDIF lines for each passed in attributes. * * @param ldifLines * the List where to add the LDIF lines * @param attributes * the List of attributes to convert into LDIf lines */ private void addLinesForAttributes(List<StringBuilder> ldifLines, Map<AttributeType, List<Attribute>> attributes) { for (List<Attribute> attrList : attributes.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(attrName); appendLDIFSeparatorAndValue(attrLine, v.getValue()); ldifLines.add(attrLine); } } } } /** * 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, ByteString.valueOf(dn.toString())); LDIFWriter.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:"); LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn); } else { for (String s : objectClasses.values()) { StringBuilder ocLine = new StringBuilder(); ocLine.append("objectClass: "); ocLine.append(s); LDIFWriter.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. writeLDIFLines(userAttributes, typesOnly, "user", exportConfig, writer, wrapColumn, wrapLines); // Next, the set of operational attributes. if (exportConfig.includeOperationalAttributes()) { writeLDIFLines(operationalAttributes, typesOnly, "operational", exportConfig, writer, wrapColumn, wrapLines); } 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)) { writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn); } } } } // Make sure there is a blank line after the entry. writer.newLine(); return true; } /** * Writes the provided List of attributes to LDIF using the provided * information. * * @param attributes * the List of attributes to write as LDIF * @param typesOnly * if true, only writes the type information, else writes the type * information and values for the attribute. * @param attributeType * the type of attribute being written to LDIF * @param exportConfig * configures the export to LDIF * @param writer * The writer to which the data should be written. It must not be * <CODE>null</CODE>. * @param wrapLines * Indicates whether to wrap long lines. * @param wrapColumn * The column at which long lines should be wrapped. * @throws IOException * If a problem occurs while writing the information. */ private void writeLDIFLines(Map<AttributeType, List<Attribute>> attributes, final boolean typesOnly, String attributeType, LDIFExportConfig exportConfig, BufferedWriter writer, int wrapColumn, boolean wrapLines) throws IOException { for (AttributeType attrType : attributes.keySet()) { if (exportConfig.includeAttribute(attrType)) { List<Attribute> attrList = attributes.get(attrType); for (Attribute a : attrList) { if (a.isVirtual() && (! exportConfig.includeVirtualAttributes())) { continue; } writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn); } } else { if (debugEnabled()) { TRACER.debugVerbose("Skipping %s attribute %s for entry %s " + "because of the export configuration.", attributeType, attrType .getNameOrOID(), String.valueOf(dn)); } } } } /** * Writes the provided attribute to LDIF using the provided information. * * @param attribute * the attribute to write to LDIF * @param typesOnly * if true, only writes the type information, else writes the type * information and values for the attribute. * @param writer * The writer to which the data should be written. It must not be * <CODE>null</CODE>. * @param wrapLines * Indicates whether to wrap long lines. * @param wrapColumn * The column at which long lines should be wrapped. * @throws IOException * If a problem occurs while writing the information. */ private void writeLDIFLine(Attribute attribute, final boolean typesOnly, BufferedWriter writer, boolean wrapLines, int wrapColumn) throws IOException { StringBuilder attrName = new StringBuilder(attribute.getName()); for (String o : attribute.getOptions()) { attrName.append(";"); attrName.append(o); } if (typesOnly) { attrName.append(":"); LDIFWriter.writeLDIFLine(attrName, writer, wrapLines, wrapColumn); } else { for (AttributeValue v : attribute) { StringBuilder attrLine = new StringBuilder(attrName); appendLDIFSeparatorAndValue(attrLine, v.getValue()); LDIFWriter.writeLDIFLine(attrLine, writer, wrapLines, wrapColumn); } } } /** * Retrieves the name of the protocol associated with this protocol * element. * * @return The name of the protocol associated with this protocol * element. */ @Override 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. */ @Override public String toString() { return toLDIFString(); } /** * Appends a string representation of this protocol element to the * provided buffer. * * @param buffer The buffer into which the string representation * should be written. */ @Override public void toString(StringBuilder buffer) { buffer.append(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. * @param indent The number of spaces that should be used to * indent the resulting string representation. */ @Override 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().getValue().toString()); while (valueIterator.hasNext()) { buffer.append(","); buffer.append(valueIterator.next().getValue().toString()); } } 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().getValue().toString()); while (valueIterator.hasNext()) { buffer.append(","); buffer.append(valueIterator.next().getValue().toString()); } } 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.getValue().toString(); String lowerName; try { lowerName = v.getNormalizedValue().toString(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } lowerName = toLowerCase(v.getValue().toString()); } // 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); } /** * Returns an entry containing only those attributes of this entry * which match the provided criteria. * * @param attrNameList * The list of attributes to include, may include wild * cards. * @param omitValues * Indicates whether to omit attribute values when * processing. * @param omitReal * Indicates whether to exclude real attributes. * @param omitVirtual * Indicates whether to exclude virtual attributes. * @return An entry containing only those attributes of this entry * which match the provided criteria. */ public Entry filterEntry(Set<String> attrNameList, boolean omitValues, boolean omitReal, boolean omitVirtual) { HashMap<ObjectClass, String> objectClassesCopy; HashMap<AttributeType, List<Attribute>> userAttrsCopy; HashMap<AttributeType, List<Attribute>> operationalAttrsCopy; if (attrNameList == null || attrNameList.isEmpty()) { // Common case: return filtered user attributes. userAttrsCopy = new HashMap<AttributeType, List<Attribute>>(userAttributes .size()); operationalAttrsCopy = new HashMap<AttributeType, List<Attribute>>(0); if (omitReal) { objectClassesCopy = new HashMap<ObjectClass, String>(0); } else if (omitValues) { objectClassesCopy = new HashMap<ObjectClass, String>(0); // Add empty object class attribute. AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); ArrayList<Attribute> ocList = new ArrayList<Attribute>(1); ocList.add(Attributes.empty(ocType)); userAttrsCopy.put(ocType, ocList); } else { objectClassesCopy = new HashMap<ObjectClass, String>(objectClasses); // First, add the objectclass attribute. Attribute ocAttr = getObjectClassAttribute(); if (ocAttr != null) { AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); ArrayList<Attribute> ocList = new ArrayList<Attribute>(1); ocList.add(ocAttr); userAttrsCopy.put(ocType, ocList); } } // Copy all user attributes. deepCopy(userAttributes, userAttrsCopy, omitValues, true, omitReal, omitVirtual, true); } else { // Incrementally build table of attributes. if (omitReal || omitValues) { objectClassesCopy = new HashMap<ObjectClass, String>(0); } else { objectClassesCopy = new HashMap<ObjectClass, String>(objectClasses.size()); } userAttrsCopy = new HashMap<AttributeType, List<Attribute>>(userAttributes .size()); operationalAttrsCopy = new HashMap<AttributeType, List<Attribute>>( operationalAttributes.size()); for (String attrName : attrNameList) { if (attrName.equals("*")) { // This is a special placeholder indicating that all user // attributes should be returned. if (!omitReal) { if (omitValues) { // Add empty object class attribute. AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); ArrayList<Attribute> ocList = new ArrayList<Attribute>(1); ocList.add(Attributes.empty(ocType)); userAttrsCopy.put(ocType, ocList); } else { // Add the objectclass attribute. objectClassesCopy.putAll(objectClasses); Attribute ocAttr = getObjectClassAttribute(); if (ocAttr != null) { AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); ArrayList<Attribute> ocList = new ArrayList<Attribute>(1); ocList.add(ocAttr); userAttrsCopy.put(ocType, ocList); } } } // Copy all user attributes. deepCopy(userAttributes, userAttrsCopy, omitValues, true, omitReal, omitVirtual, true); continue; } else if (attrName.equals("+")) { // This is a special placeholder indicating that all // operational attributes should be returned. deepCopy(operationalAttributes, operationalAttrsCopy, omitValues, true, omitReal, omitVirtual, true); continue; } String lowerName; HashSet<String> options; int semicolonPos = attrName.indexOf(';'); if (semicolonPos > 0) { String tmpName = attrName.substring(0, semicolonPos); lowerName = toLowerCase(tmpName); int nextPos = attrName.indexOf(';', semicolonPos+1); options = new HashSet<String>(); while (nextPos > 0) { options.add(attrName.substring(semicolonPos+1, nextPos)); semicolonPos = nextPos; nextPos = attrName.indexOf(';', semicolonPos+1); } options.add(attrName.substring(semicolonPos+1)); attrName = tmpName; } else { lowerName = toLowerCase(attrName); options = null; } AttributeType attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // Unrecognized attribute type - do best effort search. for (Map.Entry<AttributeType, List<Attribute>> e : userAttributes.entrySet()) { AttributeType t = e.getKey(); if (t.hasNameOrOID(lowerName)) { mergeAttributeLists(e.getValue(), userAttrsCopy, t, attrName, options, omitValues, omitReal, omitVirtual); continue; } } for (Map.Entry<AttributeType, List<Attribute>> e : operationalAttributes.entrySet()) { AttributeType t = e.getKey(); if (t.hasNameOrOID(lowerName)) { mergeAttributeLists(e.getValue(), operationalAttrsCopy, t, attrName, options, omitValues, omitReal, omitVirtual); continue; } } } else { // Recognized attribute type. if (attrType.isObjectClassType()) { if (!omitReal) { if (omitValues) { AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); List<Attribute> ocList = new ArrayList<Attribute>(1); ocList.add(Attributes.empty(ocType, attrName)); userAttrsCopy.put(ocType, ocList); } else { Attribute ocAttr = getObjectClassAttribute(); if (ocAttr != null) { AttributeType ocType = DirectoryServer.getObjectClassAttributeType(); if (!attrName.equals(ocAttr.getName())) { // User requested non-default object class type // name. AttributeBuilder builder = new AttributeBuilder(ocAttr); builder.setAttributeType(ocType, attrName); ocAttr = builder.toAttribute(); } List<Attribute> ocList = new ArrayList<Attribute>(1); ocList.add(ocAttr); userAttrsCopy.put(ocType, ocList); } } } } else { List<Attribute> attrList = getUserAttribute(attrType); if (attrList != null) { mergeAttributeLists(attrList, userAttrsCopy, attrType, attrName, options, omitValues, omitReal, omitVirtual); } else { attrList = getOperationalAttribute(attrType); if (attrList != null) { mergeAttributeLists(attrList, operationalAttrsCopy, attrType, attrName, options, omitValues, omitReal, omitVirtual); } } } } } } return new Entry(dn, objectClassesCopy, userAttrsCopy, operationalAttrsCopy); } /** * Copies the provided list of attributes into the destination * attribute map according to the provided criteria. * * @param sourceList * The list containing the attributes to be copied. * @param destMap * The map where the attributes should be copied to. * @param attrType * The attribute type. * @param attrName * The user-provided attribute name. * @param options * The user-provided attribute options. * @param omitValues * Indicates whether to exclude attribute values. * @param omitReal * Indicates whether to exclude real attributes. * @param omitVirtual * Indicates whether to exclude virtual attributes. */ private void mergeAttributeLists(List<Attribute> sourceList, HashMap<AttributeType, List<Attribute>> destMap, AttributeType attrType, String attrName, HashSet<String> options, boolean omitValues, boolean omitReal, boolean omitVirtual) { if (sourceList == null) { return; } for (Attribute attribute : sourceList) { if (attribute.isEmpty()) { continue; } else if (omitReal && !attribute.isVirtual()) { continue; } else if (omitVirtual && attribute.isVirtual()) { continue; } else if (!attribute.hasAllOptions(options)) { continue; } else { // If a non-default attribute name was provided or if the // attribute has options then we will need to rebuild the // attribute so that it contains the user-requested names and // options. AttributeType subAttrType = attribute.getAttributeType(); if ((attrName != null && !attrName.equals(attribute.getName())) || (options != null && !options.isEmpty())) { AttributeBuilder builder = new AttributeBuilder(); // We want to use the user-provided name only if this // attribute has the same type as the requested type. This // might not be the case for sub-types e.g. requesting // "name" and getting back "cn" - we don't want to rename // "name" to "cn". if (attrName == null || !subAttrType.equals(attrType)) { builder.setAttributeType(attribute.getAttributeType(), attribute.getName()); } else { builder.setAttributeType(attribute.getAttributeType(), attrName); } if (options != null) { builder.setOptions(options); } // Now add in remaining options from original attribute // (this will not overwrite options already present). builder.setOptions(attribute.getOptions()); if (!omitValues) { builder.addAll(attribute); } attribute = builder.toAttribute(); } else if (omitValues) { attribute = Attributes.empty(attribute); } // Now put the attribute into the destination map. // Be careful of duplicates. List<Attribute> attrList = destMap.get(subAttrType); if (attrList == null) { // Assume that they'll all go in the one list. This isn't // always the case, for example if the list contains // sub-types. attrList = new ArrayList<Attribute>(sourceList.size()); attrList.add(attribute); destMap.put(subAttrType, attrList); } else { // The attribute may have already been put in the list. // // This may occur in two cases: // // 1) The attribute is identified by more than one attribute // type description in the attribute list (e.g. in a // wildcard). // // 2) The attribute has both a real and virtual component. // boolean found = false; for (int i = 0; i < attrList.size(); i++) { Attribute otherAttribute = attrList.get(i); if (otherAttribute.optionsEqual(attribute.getOptions())) { // Assume that wildcards appear first in an attribute // list with more specific attribute names afterwards: // let the attribute name and options from the later // attribute take preference. attrList.set(i, Attributes.merge(attribute, otherAttribute)); found = true; } } if (!found) { attrList.add(attribute); } } } } } }