Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2007-2019 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright (C) 2008-2019 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.ldap.matchingrules.MatchingRule;
import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.ldif.LDIFException;
import com.unboundid.ldif.LDIFReader;
import com.unboundid.ldif.LDIFRecord;
import com.unboundid.ldif.LDIFWriter;
import com.unboundid.util.ByteStringBuffer;
import com.unboundid.util.Debug;
import com.unboundid.util.Mutable;
import com.unboundid.util.NotExtensible;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;
import static com.unboundid.ldap.sdk.LDAPMessages.*;
/**
* This class provides a data structure for holding information about an LDAP
* entry. An entry contains a distinguished name (DN) and a set of attributes.
* An entry can be created from these components, and it can also be created
* from its LDIF representation as described in
* RFC 2849. For example:
*
* This class also provides methods for retrieving the LDIF representation of
* an entry, either as a single string or as an array of strings that make up
* the LDIF lines.
*
* The {@link Entry#diff} method may be used to obtain the set of differences
* between two entries, and to retrieve a list of {@link Modification} objects
* that can be used to modify one entry so that it contains the same set of
* data as another. The {@link Entry#applyModifications} method may be used to
* apply a set of modifications to an entry.
*
* Entry objects are mutable, and the DN, set of attributes, and individual
* attribute values can be altered.
*/
@Mutable()
@NotExtensible()
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public class Entry
implements LDIFRecord
{
/**
* An empty octet string that will be used as the value for an attribute that
* doesn't have any values.
*/
private static final ASN1OctetString EMPTY_OCTET_STRING =
new ASN1OctetString();
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = -4438809025903729197L;
// The parsed DN for this entry.
private volatile DN parsedDN;
// The set of attributes for this entry.
private final LinkedHashMap attributes;
// The schema to use for this entry.
private final Schema schema;
// The DN for this entry.
private String dn;
/**
* Creates a new entry that wraps the provided entry.
*
* @param e The entry to be wrapped.
*/
protected Entry(final Entry e)
{
parsedDN = e.parsedDN;
attributes = e.attributes;
schema = e.schema;
dn = e.dn;
}
/**
* Creates a new entry with the provided DN and no attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
*/
public Entry(final String dn)
{
this(dn, (Schema) null);
}
/**
* Creates a new entry with the provided DN and no attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param schema The schema to use for operations involving this entry. It
* may be {@code null} if no schema is available.
*/
public Entry(final String dn, final Schema schema)
{
Validator.ensureNotNull(dn);
this.dn = dn;
this.schema = schema;
attributes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
}
/**
* Creates a new entry with the provided DN and no attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
*/
public Entry(final DN dn)
{
this(dn, (Schema) null);
}
/**
* Creates a new entry with the provided DN and no attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param schema The schema to use for operations involving this entry. It
* may be {@code null} if no schema is available.
*/
public Entry(final DN dn, final Schema schema)
{
Validator.ensureNotNull(dn);
parsedDN = dn;
this.dn = parsedDN.toString();
this.schema = schema;
attributes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
}
/**
* Creates a new entry with the provided DN and set of attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param attributes The set of attributes for this entry. It must not be
* {@code null}.
*/
public Entry(final String dn, final Attribute... attributes)
{
this(dn, null, attributes);
}
/**
* Creates a new entry with the provided DN and set of attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param schema The schema to use for operations involving this entry.
* It may be {@code null} if no schema is available.
* @param attributes The set of attributes for this entry. It must not be
* {@code null}.
*/
public Entry(final String dn, final Schema schema,
final Attribute... attributes)
{
Validator.ensureNotNull(dn, attributes);
this.dn = dn;
this.schema = schema;
this.attributes =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(attributes.length));
for (final Attribute a : attributes)
{
final String name = StaticUtils.toLowerCase(a.getName());
final Attribute attr = this.attributes.get(name);
if (attr == null)
{
this.attributes.put(name, a);
}
else
{
this.attributes.put(name, Attribute.mergeAttributes(attr, a));
}
}
}
/**
* Creates a new entry with the provided DN and set of attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param attributes The set of attributes for this entry. It must not be
* {@code null}.
*/
public Entry(final DN dn, final Attribute... attributes)
{
this(dn, null, attributes);
}
/**
* Creates a new entry with the provided DN and set of attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param schema The schema to use for operations involving this entry.
* It may be {@code null} if no schema is available.
* @param attributes The set of attributes for this entry. It must not be
* {@code null}.
*/
public Entry(final DN dn, final Schema schema, final Attribute... attributes)
{
Validator.ensureNotNull(dn, attributes);
parsedDN = dn;
this.dn = parsedDN.toString();
this.schema = schema;
this.attributes =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(attributes.length));
for (final Attribute a : attributes)
{
final String name = StaticUtils.toLowerCase(a.getName());
final Attribute attr = this.attributes.get(name);
if (attr == null)
{
this.attributes.put(name, a);
}
else
{
this.attributes.put(name, Attribute.mergeAttributes(attr, a));
}
}
}
/**
* Creates a new entry with the provided DN and set of attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param attributes The set of attributes for this entry. It must not be
* {@code null}.
*/
public Entry(final String dn, final Collection attributes)
{
this(dn, null, attributes);
}
/**
* Creates a new entry with the provided DN and set of attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param schema The schema to use for operations involving this entry.
* It may be {@code null} if no schema is available.
* @param attributes The set of attributes for this entry. It must not be
* {@code null}.
*/
public Entry(final String dn, final Schema schema,
final Collection attributes)
{
Validator.ensureNotNull(dn, attributes);
this.dn = dn;
this.schema = schema;
this.attributes =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(attributes.size()));
for (final Attribute a : attributes)
{
final String name = StaticUtils.toLowerCase(a.getName());
final Attribute attr = this.attributes.get(name);
if (attr == null)
{
this.attributes.put(name, a);
}
else
{
this.attributes.put(name, Attribute.mergeAttributes(attr, a));
}
}
}
/**
* Creates a new entry with the provided DN and set of attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param attributes The set of attributes for this entry. It must not be
* {@code null}.
*/
public Entry(final DN dn, final Collection attributes)
{
this(dn, null, attributes);
}
/**
* Creates a new entry with the provided DN and set of attributes.
*
* @param dn The DN for this entry. It must not be {@code null}.
* @param schema The schema to use for operations involving this entry.
* It may be {@code null} if no schema is available.
* @param attributes The set of attributes for this entry. It must not be
* {@code null}.
*/
public Entry(final DN dn, final Schema schema,
final Collection attributes)
{
Validator.ensureNotNull(dn, attributes);
parsedDN = dn;
this.dn = parsedDN.toString();
this.schema = schema;
this.attributes =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(attributes.size()));
for (final Attribute a : attributes)
{
final String name = StaticUtils.toLowerCase(a.getName());
final Attribute attr = this.attributes.get(name);
if (attr == null)
{
this.attributes.put(name, a);
}
else
{
this.attributes.put(name, Attribute.mergeAttributes(attr, a));
}
}
}
/**
* Creates a new entry from the provided LDIF representation.
*
* @param entryLines The set of lines that comprise an LDIF representation
* of the entry. It must not be {@code null} or empty.
*
* @throws LDIFException If the provided lines cannot be decoded as an entry
* in LDIF format.
*/
public Entry(final String... entryLines)
throws LDIFException
{
this(null, entryLines);
}
/**
* Creates a new entry from the provided LDIF representation.
*
* @param schema The schema to use for operations involving this entry.
* It may be {@code null} if no schema is available.
* @param entryLines The set of lines that comprise an LDIF representation
* of the entry. It must not be {@code null} or empty.
*
* @throws LDIFException If the provided lines cannot be decoded as an entry
* in LDIF format.
*/
public Entry(final Schema schema, final String... entryLines)
throws LDIFException
{
final Entry e = LDIFReader.decodeEntry(false, schema, entryLines);
this.schema = schema;
dn = e.dn;
parsedDN = e.parsedDN;
attributes = e.attributes;
}
/**
* Retrieves the DN for this entry.
*
* @return The DN for this entry.
*/
@Override()
public final String getDN()
{
return dn;
}
/**
* Specifies the DN for this entry.
*
* @param dn The DN for this entry. It must not be {@code null}.
*/
public void setDN(final String dn)
{
Validator.ensureNotNull(dn);
this.dn = dn;
parsedDN = null;
}
/**
* Specifies the DN for this entry.
*
* @param dn The DN for this entry. It must not be {@code null}.
*/
public void setDN(final DN dn)
{
Validator.ensureNotNull(dn);
parsedDN = dn;
this.dn = parsedDN.toString();
}
/**
* Retrieves the parsed DN for this entry.
*
* @return The parsed DN for this entry.
*
* @throws LDAPException If the DN string cannot be parsed as a valid DN.
*/
@Override()
public final DN getParsedDN()
throws LDAPException
{
if (parsedDN == null)
{
parsedDN = new DN(dn, schema);
}
return parsedDN;
}
/**
* Retrieves the RDN for this entry.
*
* @return The RDN for this entry, or {@code null} if the DN is the null DN.
*
* @throws LDAPException If the DN string cannot be parsed as a valid DN.
*/
public final RDN getRDN()
throws LDAPException
{
return getParsedDN().getRDN();
}
/**
* Retrieves the parent DN for this entry.
*
* @return The parent DN for this entry, or {@code null} if there is no
* parent.
*
* @throws LDAPException If the DN string cannot be parsed as a valid DN.
*/
public final DN getParentDN()
throws LDAPException
{
if (parsedDN == null)
{
parsedDN = new DN(dn, schema);
}
return parsedDN.getParent();
}
/**
* Retrieves the parent DN for this entry as a string.
*
* @return The parent DN for this entry as a string, or {@code null} if there
* is no parent.
*
* @throws LDAPException If the DN string cannot be parsed as a valid DN.
*/
public final String getParentDNString()
throws LDAPException
{
if (parsedDN == null)
{
parsedDN = new DN(dn, schema);
}
final DN parentDN = parsedDN.getParent();
if (parentDN == null)
{
return null;
}
else
{
return parentDN.toString();
}
}
/**
* Retrieves the schema that will be used for this entry, if any.
*
* @return The schema that will be used for this entry, or {@code null} if
* no schema was provided.
*/
protected Schema getSchema()
{
return schema;
}
/**
* Indicates whether this entry contains the specified attribute.
*
* @param attributeName The name of the attribute for which to make the
* determination. It must not be {@code null}.
*
* @return {@code true} if this entry contains the specified attribute, or
* {@code false} if not.
*/
public final boolean hasAttribute(final String attributeName)
{
return hasAttribute(attributeName, schema);
}
/**
* Indicates whether this entry contains the specified attribute.
*
* @param attributeName The name of the attribute for which to make the
* determination. It must not be {@code null}.
* @param schema The schema to use to determine whether there may be
* alternate names for the specified attribute. It may
* be {@code null} if no schema is available.
*
* @return {@code true} if this entry contains the specified attribute, or
* {@code false} if not.
*/
public final boolean hasAttribute(final String attributeName,
final Schema schema)
{
Validator.ensureNotNull(attributeName);
if (attributes.containsKey(StaticUtils.toLowerCase(attributeName)))
{
return true;
}
if (schema != null)
{
final String baseName;
final String options;
final int semicolonPos = attributeName.indexOf(';');
if (semicolonPos > 0)
{
baseName = attributeName.substring(0, semicolonPos);
options =
StaticUtils.toLowerCase(attributeName.substring(semicolonPos));
}
else
{
baseName = attributeName;
options = "";
}
final AttributeTypeDefinition at = schema.getAttributeType(baseName);
if (at != null)
{
if (attributes.containsKey(
StaticUtils.toLowerCase(at.getOID()) + options))
{
return true;
}
for (final String name : at.getNames())
{
if (attributes.containsKey(
StaticUtils.toLowerCase(name) + options))
{
return true;
}
}
}
}
return false;
}
/**
* Indicates whether this entry contains the specified attribute. It will
* only return {@code true} if this entry contains an attribute with the same
* name and exact set of values.
*
* @param attribute The attribute for which to make the determination. It
* must not be {@code null}.
*
* @return {@code true} if this entry contains the specified attribute, or
* {@code false} if not.
*/
public final boolean hasAttribute(final Attribute attribute)
{
Validator.ensureNotNull(attribute);
final String lowerName = StaticUtils.toLowerCase(attribute.getName());
final Attribute attr = attributes.get(lowerName);
return ((attr != null) && attr.equals(attribute));
}
/**
* Indicates whether this entry contains an attribute with the given name and
* value.
*
* @param attributeName The name of the attribute for which to make the
* determination. It must not be {@code null}.
* @param attributeValue The value for which to make the determination. It
* must not be {@code null}.
*
* @return {@code true} if this entry contains an attribute with the
* specified name and value, or {@code false} if not.
*/
public final boolean hasAttributeValue(final String attributeName,
final String attributeValue)
{
Validator.ensureNotNull(attributeName, attributeValue);
final Attribute attr =
attributes.get(StaticUtils.toLowerCase(attributeName));
return ((attr != null) && attr.hasValue(attributeValue));
}
/**
* Indicates whether this entry contains an attribute with the given name and
* value.
*
* @param attributeName The name of the attribute for which to make the
* determination. It must not be {@code null}.
* @param attributeValue The value for which to make the determination. It
* must not be {@code null}.
* @param matchingRule The matching rule to use to make the determination.
* It must not be {@code null}.
*
* @return {@code true} if this entry contains an attribute with the
* specified name and value, or {@code false} if not.
*/
public final boolean hasAttributeValue(final String attributeName,
final String attributeValue,
final MatchingRule matchingRule)
{
Validator.ensureNotNull(attributeName, attributeValue);
final Attribute attr =
attributes.get(StaticUtils.toLowerCase(attributeName));
return ((attr != null) && attr.hasValue(attributeValue, matchingRule));
}
/**
* Indicates whether this entry contains an attribute with the given name and
* value.
*
* @param attributeName The name of the attribute for which to make the
* determination. It must not be {@code null}.
* @param attributeValue The value for which to make the determination. It
* must not be {@code null}.
*
* @return {@code true} if this entry contains an attribute with the
* specified name and value, or {@code false} if not.
*/
public final boolean hasAttributeValue(final String attributeName,
final byte[] attributeValue)
{
Validator.ensureNotNull(attributeName, attributeValue);
final Attribute attr =
attributes.get(StaticUtils.toLowerCase(attributeName));
return ((attr != null) && attr.hasValue(attributeValue));
}
/**
* Indicates whether this entry contains an attribute with the given name and
* value.
*
* @param attributeName The name of the attribute for which to make the
* determination. It must not be {@code null}.
* @param attributeValue The value for which to make the determination. It
* must not be {@code null}.
* @param matchingRule The matching rule to use to make the determination.
* It must not be {@code null}.
*
* @return {@code true} if this entry contains an attribute with the
* specified name and value, or {@code false} if not.
*/
public final boolean hasAttributeValue(final String attributeName,
final byte[] attributeValue,
final MatchingRule matchingRule)
{
Validator.ensureNotNull(attributeName, attributeValue);
final Attribute attr =
attributes.get(StaticUtils.toLowerCase(attributeName));
return ((attr != null) && attr.hasValue(attributeValue, matchingRule));
}
/**
* Indicates whether this entry contains the specified object class.
*
* @param objectClassName The name of the object class for which to make the
* determination. It must not be {@code null}.
*
* @return {@code true} if this entry contains the specified object class, or
* {@code false} if not.
*/
public final boolean hasObjectClass(final String objectClassName)
{
return hasAttributeValue("objectClass", objectClassName);
}
/**
* Retrieves the set of attributes contained in this entry.
*
* @return The set of attributes contained in this entry.
*/
public final Collection getAttributes()
{
return Collections.unmodifiableCollection(attributes.values());
}
/**
* Retrieves the attribute with the specified name.
*
* @param attributeName The name of the attribute to retrieve. It must not
* be {@code null}.
*
* @return The requested attribute from this entry, or {@code null} if the
* specified attribute is not present in this entry.
*/
public final Attribute getAttribute(final String attributeName)
{
return getAttribute(attributeName, schema);
}
/**
* Retrieves the attribute with the specified name.
*
* @param attributeName The name of the attribute to retrieve. It must not
* be {@code null}.
* @param schema The schema to use to determine whether there may be
* alternate names for the specified attribute. It may
* be {@code null} if no schema is available.
*
* @return The requested attribute from this entry, or {@code null} if the
* specified attribute is not present in this entry.
*/
public final Attribute getAttribute(final String attributeName,
final Schema schema)
{
Validator.ensureNotNull(attributeName);
Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
if ((a == null) && (schema != null))
{
final String baseName;
final String options;
final int semicolonPos = attributeName.indexOf(';');
if (semicolonPos > 0)
{
baseName = attributeName.substring(0, semicolonPos);
options =
StaticUtils.toLowerCase(attributeName.substring(semicolonPos));
}
else
{
baseName = attributeName;
options = "";
}
final AttributeTypeDefinition at = schema.getAttributeType(baseName);
if (at == null)
{
return null;
}
a = attributes.get(StaticUtils.toLowerCase(at.getOID() + options));
if (a == null)
{
for (final String name : at.getNames())
{
a = attributes.get(StaticUtils.toLowerCase(name) + options);
if (a != null)
{
return a;
}
}
}
return a;
}
else
{
return a;
}
}
/**
* Retrieves the list of attributes with the given base name and all of the
* specified options.
*
* @param baseName The base name (without any options) for the attribute to
* retrieve. It must not be {@code null}.
* @param options The set of options that should be included in the
* attributes that are returned. It may be empty or
* {@code null} if all attributes with the specified base
* name should be returned, regardless of the options that
* they contain (if any).
*
* @return The list of attributes with the given base name and all of the
* specified options. It may be empty if there are no attributes
* with the specified base name and set of options.
*/
public final List getAttributesWithOptions(final String baseName,
final Set options)
{
Validator.ensureNotNull(baseName);
final ArrayList attrList = new ArrayList<>(10);
for (final Attribute a : attributes.values())
{
if (a.getBaseName().equalsIgnoreCase(baseName))
{
if ((options == null) || options.isEmpty())
{
attrList.add(a);
}
else
{
boolean allFound = true;
for (final String option : options)
{
if (! a.hasOption(option))
{
allFound = false;
break;
}
}
if (allFound)
{
attrList.add(a);
}
}
}
}
return Collections.unmodifiableList(attrList);
}
/**
* Retrieves the value for the specified attribute, if available. If the
* attribute has more than one value, then the first value will be returned.
*
* @param attributeName The name of the attribute for which to retrieve the
* value. It must not be {@code null}.
*
* @return The value for the specified attribute, or {@code null} if that
* attribute is not available.
*/
public String getAttributeValue(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a =
attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValue();
}
}
/**
* Retrieves the value for the specified attribute as a byte array, if
* available. If the attribute has more than one value, then the first value
* will be returned.
*
* @param attributeName The name of the attribute for which to retrieve the
* value. It must not be {@code null}.
*
* @return The value for the specified attribute as a byte array, or
* {@code null} if that attribute is not available.
*/
public byte[] getAttributeValueBytes(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a =
attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValueByteArray();
}
}
/**
* Retrieves the value for the specified attribute as a Boolean, if available.
* If the attribute has more than one value, then the first value will be
* returned. Values of "true", "t", "yes", "y", "on", and "1" will be
* interpreted as {@code TRUE}. Values of "false", "f", "no", "n", "off", and
* "0" will be interpreted as {@code FALSE}.
*
* @param attributeName The name of the attribute for which to retrieve the
* value. It must not be {@code null}.
*
* @return The Boolean value parsed from the specified attribute, or
* {@code null} if that attribute is not available or the value
* cannot be parsed as a Boolean.
*/
public Boolean getAttributeValueAsBoolean(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a =
attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValueAsBoolean();
}
}
/**
* Retrieves the value for the specified attribute as a Date, formatted using
* the generalized time syntax, if available. If the attribute has more than
* one value, then the first value will be returned.
*
* @param attributeName The name of the attribute for which to retrieve the
* value. It must not be {@code null}.
*
* @return The Date value parsed from the specified attribute, or
* {@code null} if that attribute is not available or the value
* cannot be parsed as a Date.
*/
public Date getAttributeValueAsDate(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValueAsDate();
}
}
/**
* Retrieves the value for the specified attribute as a DN, if available. If
* the attribute has more than one value, then the first value will be
* returned.
*
* @param attributeName The name of the attribute for which to retrieve the
* value. It must not be {@code null}.
*
* @return The DN value parsed from the specified attribute, or {@code null}
* if that attribute is not available or the value cannot be parsed
* as a DN.
*/
public DN getAttributeValueAsDN(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValueAsDN();
}
}
/**
* Retrieves the value for the specified attribute as an Integer, if
* available. If the attribute has more than one value, then the first value
* will be returned.
*
* @param attributeName The name of the attribute for which to retrieve the
* value. It must not be {@code null}.
*
* @return The Integer value parsed from the specified attribute, or
* {@code null} if that attribute is not available or the value
* cannot be parsed as an Integer.
*/
public Integer getAttributeValueAsInteger(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValueAsInteger();
}
}
/**
* Retrieves the value for the specified attribute as a Long, if available.
* If the attribute has more than one value, then the first value will be
* returned.
*
* @param attributeName The name of the attribute for which to retrieve the
* value. It must not be {@code null}.
*
* @return The Long value parsed from the specified attribute, or
* {@code null} if that attribute is not available or the value
* cannot be parsed as a Long.
*/
public Long getAttributeValueAsLong(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValueAsLong();
}
}
/**
* Retrieves the set of values for the specified attribute, if available.
*
* @param attributeName The name of the attribute for which to retrieve the
* values. It must not be {@code null}.
*
* @return The set of values for the specified attribute, or {@code null} if
* that attribute is not available.
*/
public String[] getAttributeValues(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValues();
}
}
/**
* Retrieves the set of values for the specified attribute as byte arrays, if
* available.
*
* @param attributeName The name of the attribute for which to retrieve the
* values. It must not be {@code null}.
*
* @return The set of values for the specified attribute as byte arrays, or
* {@code null} if that attribute is not available.
*/
public byte[][] getAttributeValueByteArrays(final String attributeName)
{
Validator.ensureNotNull(attributeName);
final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
if (a == null)
{
return null;
}
else
{
return a.getValueByteArrays();
}
}
/**
* Retrieves the "objectClass" attribute from the entry, if available.
*
* @return The "objectClass" attribute from the entry, or {@code null} if
* that attribute not available.
*/
public final Attribute getObjectClassAttribute()
{
return getAttribute("objectClass");
}
/**
* Retrieves the values of the "objectClass" attribute from the entry, if
* available.
*
* @return The values of the "objectClass" attribute from the entry, or
* {@code null} if that attribute is not available.
*/
public final String[] getObjectClassValues()
{
return getAttributeValues("objectClass");
}
/**
* Adds the provided attribute to this entry. If this entry already contains
* an attribute with the same name, then their values will be merged.
*
* @param attribute The attribute to be added. It must not be {@code null}.
*
* @return {@code true} if the entry was updated, or {@code false} because
* the specified attribute already existed with all provided values.
*/
public boolean addAttribute(final Attribute attribute)
{
Validator.ensureNotNull(attribute);
final String lowerName = StaticUtils.toLowerCase(attribute.getName());
final Attribute attr = attributes.get(lowerName);
if (attr == null)
{
attributes.put(lowerName, attribute);
return true;
}
else
{
final Attribute newAttr = Attribute.mergeAttributes(attr, attribute);
attributes.put(lowerName, newAttr);
return (attr.getRawValues().length != newAttr.getRawValues().length);
}
}
/**
* Adds the specified attribute value to this entry, if it is not already
* present.
*
* @param attributeName The name for the attribute to be added. It must
* not be {@code null}.
* @param attributeValue The value for the attribute to be added. It must
* not be {@code null}.
*
* @return {@code true} if the entry was updated, or {@code false} because
* the specified attribute already existed with the given value.
*/
public boolean addAttribute(final String attributeName,
final String attributeValue)
{
Validator.ensureNotNull(attributeName, attributeValue);
return addAttribute(new Attribute(attributeName, schema, attributeValue));
}
/**
* Adds the specified attribute value to this entry, if it is not already
* present.
*
* @param attributeName The name for the attribute to be added. It must
* not be {@code null}.
* @param attributeValue The value for the attribute to be added. It must
* not be {@code null}.
*
* @return {@code true} if the entry was updated, or {@code false} because
* the specified attribute already existed with the given value.
*/
public boolean addAttribute(final String attributeName,
final byte[] attributeValue)
{
Validator.ensureNotNull(attributeName, attributeValue);
return addAttribute(new Attribute(attributeName, schema, attributeValue));
}
/**
* Adds the provided attribute to this entry. If this entry already contains
* an attribute with the same name, then their values will be merged.
*
* @param attributeName The name for the attribute to be added. It must
* not be {@code null}.
* @param attributeValues The value for the attribute to be added. It must
* not be {@code null}.
*
* @return {@code true} if the entry was updated, or {@code false} because
* the specified attribute already existed with all provided values.
*/
public boolean addAttribute(final String attributeName,
final String... attributeValues)
{
Validator.ensureNotNull(attributeName, attributeValues);
return addAttribute(new Attribute(attributeName, schema, attributeValues));
}
/**
* Adds the provided attribute to this entry. If this entry already contains
* an attribute with the same name, then their values will be merged.
*
* @param attributeName The name for the attribute to be added. It must
* not be {@code null}.
* @param attributeValues The value for the attribute to be added. It must
* not be {@code null}.
*
* @return {@code true} if the entry was updated, or {@code false} because
* the specified attribute already existed with all provided values.
*/
public boolean addAttribute(final String attributeName,
final byte[]... attributeValues)
{
Validator.ensureNotNull(attributeName, attributeValues);
return addAttribute(new Attribute(attributeName, schema, attributeValues));
}
/**
* Adds the provided attribute to this entry. If this entry already contains
* an attribute with the same name, then their values will be merged.
*
* @param attributeName The name for the attribute to be added. It must
* not be {@code null}.
* @param attributeValues The value for the attribute to be added. It must
* not be {@code null}.
*
* @return {@code true} if the entry was updated, or {@code false} because
* the specified attribute already existed with all provided values.
*/
public boolean addAttribute(final String attributeName,
final Collection attributeValues)
{
Validator.ensureNotNull(attributeName, attributeValues);
return addAttribute(new Attribute(attributeName, schema, attributeValues));
}
/**
* Removes the specified attribute from this entry.
*
* @param attributeName The name of the attribute to remove. It must not be
* {@code null}.
*
* @return {@code true} if the attribute was removed from the entry, or
* {@code false} if it was not present.
*/
public boolean removeAttribute(final String attributeName)
{
Validator.ensureNotNull(attributeName);
if (schema == null)
{
return
(attributes.remove(StaticUtils.toLowerCase(attributeName)) != null);
}
else
{
final Attribute a = getAttribute(attributeName, schema);
if (a == null)
{
return false;
}
else
{
attributes.remove(StaticUtils.toLowerCase(a.getName()));
return true;
}
}
}
/**
* Removes the specified attribute value from this entry if it is present. If
* it is the last value for the attribute, then the entire attribute will be
* removed. If the specified value is not present, then no change will be
* made.
*
* @param attributeName The name of the attribute from which to remove the
* value. It must not be {@code null}.
* @param attributeValue The value to remove from the attribute. It must
* not be {@code null}.
*
* @return {@code true} if the attribute value was removed from the entry, or
* {@code false} if it was not present.
*/
public boolean removeAttributeValue(final String attributeName,
final String attributeValue)
{
return removeAttributeValue(attributeName, attributeValue, null);
}
/**
* Removes the specified attribute value from this entry if it is present. If
* it is the last value for the attribute, then the entire attribute will be
* removed. If the specified value is not present, then no change will be
* made.
*
* @param attributeName The name of the attribute from which to remove the
* value. It must not be {@code null}.
* @param attributeValue The value to remove from the attribute. It must
* not be {@code null}.
* @param matchingRule The matching rule to use for the attribute. It may
* be {@code null} to use the matching rule associated
* with the attribute.
*
* @return {@code true} if the attribute value was removed from the entry, or
* {@code false} if it was not present.
*/
public boolean removeAttributeValue(final String attributeName,
final String attributeValue,
final MatchingRule matchingRule)
{
Validator.ensureNotNull(attributeName, attributeValue);
final Attribute attr = getAttribute(attributeName, schema);
if (attr == null)
{
return false;
}
else
{
final String lowerName = StaticUtils.toLowerCase(attr.getName());
final Attribute newAttr = Attribute.removeValues(attr,
new Attribute(attributeName, attributeValue), matchingRule);
if (newAttr.hasValue())
{
attributes.put(lowerName, newAttr);
}
else
{
attributes.remove(lowerName);
}
return (attr.getRawValues().length != newAttr.getRawValues().length);
}
}
/**
* Removes the specified attribute value from this entry if it is present. If
* it is the last value for the attribute, then the entire attribute will be
* removed. If the specified value is not present, then no change will be
* made.
*
* @param attributeName The name of the attribute from which to remove the
* value. It must not be {@code null}.
* @param attributeValue The value to remove from the attribute. It must
* not be {@code null}.
*
* @return {@code true} if the attribute value was removed from the entry, or
* {@code false} if it was not present.
*/
public boolean removeAttributeValue(final String attributeName,
final byte[] attributeValue)
{
return removeAttributeValue(attributeName, attributeValue, null);
}
/**
* Removes the specified attribute value from this entry if it is present. If
* it is the last value for the attribute, then the entire attribute will be
* removed. If the specified value is not present, then no change will be
* made.
*
* @param attributeName The name of the attribute from which to remove the
* value. It must not be {@code null}.
* @param attributeValue The value to remove from the attribute. It must
* not be {@code null}.
* @param matchingRule The matching rule to use for the attribute. It may
* be {@code null} to use the matching rule associated
* with the attribute.
*
* @return {@code true} if the attribute value was removed from the entry, or
* {@code false} if it was not present.
*/
public boolean removeAttributeValue(final String attributeName,
final byte[] attributeValue,
final MatchingRule matchingRule)
{
Validator.ensureNotNull(attributeName, attributeValue);
final Attribute attr = getAttribute(attributeName, schema);
if (attr == null)
{
return false;
}
else
{
final String lowerName = StaticUtils.toLowerCase(attr.getName());
final Attribute newAttr = Attribute.removeValues(attr,
new Attribute(attributeName, attributeValue), matchingRule);
if (newAttr.hasValue())
{
attributes.put(lowerName, newAttr);
}
else
{
attributes.remove(lowerName);
}
return (attr.getRawValues().length != newAttr.getRawValues().length);
}
}
/**
* Removes the specified attribute values from this entry if they are present.
* If the attribute does not have any remaining values, then the entire
* attribute will be removed. If any of the provided values are not present,
* then they will be ignored.
*
* @param attributeName The name of the attribute from which to remove the
* values. It must not be {@code null}.
* @param attributeValues The set of values to remove from the attribute.
* It must not be {@code null}.
*
* @return {@code true} if any attribute values were removed from the entry,
* or {@code false} none of them were present.
*/
public boolean removeAttributeValues(final String attributeName,
final String... attributeValues)
{
Validator.ensureNotNull(attributeName, attributeValues);
final Attribute attr = getAttribute(attributeName, schema);
if (attr == null)
{
return false;
}
else
{
final String lowerName = StaticUtils.toLowerCase(attr.getName());
final Attribute newAttr = Attribute.removeValues(attr,
new Attribute(attributeName, attributeValues));
if (newAttr.hasValue())
{
attributes.put(lowerName, newAttr);
}
else
{
attributes.remove(lowerName);
}
return (attr.getRawValues().length != newAttr.getRawValues().length);
}
}
/**
* Removes the specified attribute values from this entry if they are present.
* If the attribute does not have any remaining values, then the entire
* attribute will be removed. If any of the provided values are not present,
* then they will be ignored.
*
* @param attributeName The name of the attribute from which to remove the
* values. It must not be {@code null}.
* @param attributeValues The set of values to remove from the attribute.
* It must not be {@code null}.
*
* @return {@code true} if any attribute values were removed from the entry,
* or {@code false} none of them were present.
*/
public boolean removeAttributeValues(final String attributeName,
final byte[]... attributeValues)
{
Validator.ensureNotNull(attributeName, attributeValues);
final Attribute attr = getAttribute(attributeName, schema);
if (attr == null)
{
return false;
}
else
{
final String lowerName = StaticUtils.toLowerCase(attr.getName());
final Attribute newAttr = Attribute.removeValues(attr,
new Attribute(attributeName, attributeValues));
if (newAttr.hasValue())
{
attributes.put(lowerName, newAttr);
}
else
{
attributes.remove(lowerName);
}
return (attr.getRawValues().length != newAttr.getRawValues().length);
}
}
/**
* Adds the provided attribute to this entry, replacing any existing set of
* values for the associated attribute.
*
* @param attribute The attribute to be included in this entry. It must not
* be {@code null}.
*/
public void setAttribute(final Attribute attribute)
{
Validator.ensureNotNull(attribute);
final String lowerName;
final Attribute a = getAttribute(attribute.getName(), schema);
if (a == null)
{
lowerName = StaticUtils.toLowerCase(attribute.getName());
}
else
{
lowerName = StaticUtils.toLowerCase(a.getName());
}
attributes.put(lowerName, attribute);
}
/**
* Adds the provided attribute to this entry, replacing any existing set of
* values for the associated attribute.
*
* @param attributeName The name to use for the attribute. It must not be
* {@code null}.
* @param attributeValue The value to use for the attribute. It must not be
* {@code null}.
*/
public void setAttribute(final String attributeName,
final String attributeValue)
{
Validator.ensureNotNull(attributeName, attributeValue);
setAttribute(new Attribute(attributeName, schema, attributeValue));
}
/**
* Adds the provided attribute to this entry, replacing any existing set of
* values for the associated attribute.
*
* @param attributeName The name to use for the attribute. It must not be
* {@code null}.
* @param attributeValue The value to use for the attribute. It must not be
* {@code null}.
*/
public void setAttribute(final String attributeName,
final byte[] attributeValue)
{
Validator.ensureNotNull(attributeName, attributeValue);
setAttribute(new Attribute(attributeName, schema, attributeValue));
}
/**
* Adds the provided attribute to this entry, replacing any existing set of
* values for the associated attribute.
*
* @param attributeName The name to use for the attribute. It must not be
* {@code null}.
* @param attributeValues The set of values to use for the attribute. It
* must not be {@code null}.
*/
public void setAttribute(final String attributeName,
final String... attributeValues)
{
Validator.ensureNotNull(attributeName, attributeValues);
setAttribute(new Attribute(attributeName, schema, attributeValues));
}
/**
* Adds the provided attribute to this entry, replacing any existing set of
* values for the associated attribute.
*
* @param attributeName The name to use for the attribute. It must not be
* {@code null}.
* @param attributeValues The set of values to use for the attribute. It
* must not be {@code null}.
*/
public void setAttribute(final String attributeName,
final byte[]... attributeValues)
{
Validator.ensureNotNull(attributeName, attributeValues);
setAttribute(new Attribute(attributeName, schema, attributeValues));
}
/**
* Adds the provided attribute to this entry, replacing any existing set of
* values for the associated attribute.
*
* @param attributeName The name to use for the attribute. It must not be
* {@code null}.
* @param attributeValues The set of values to use for the attribute. It
* must not be {@code null}.
*/
public void setAttribute(final String attributeName,
final Collection attributeValues)
{
Validator.ensureNotNull(attributeName, attributeValues);
setAttribute(new Attribute(attributeName, schema, attributeValues));
}
/**
* 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. It must
* not be {@code null}.
* @param scope The scope for which to make the determination. It must not
* be {@code null}.
*
* @return {@code true} if this entry is within the range of the provided
* base and scope, or {@code false} if not.
*
* @throws LDAPException If a problem occurs while making the determination.
*/
public boolean matchesBaseAndScope(final String baseDN,
final SearchScope scope)
throws LDAPException
{
return getParsedDN().matchesBaseAndScope(new DN(baseDN), scope);
}
/**
* 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. It must
* not be {@code null}.
* @param scope The scope for which to make the determination. It must not
* be {@code null}.
*
* @return {@code true} if this entry is within the range of the provided
* base and scope, or {@code false} if not.
*
* @throws LDAPException If a problem occurs while making the determination.
*/
public boolean matchesBaseAndScope(final DN baseDN, final SearchScope scope)
throws LDAPException
{
return getParsedDN().matchesBaseAndScope(baseDN, scope);
}
/**
* Retrieves a set of modifications that can be applied to the source entry in
* order to make it match the target entry. The diff will be generated in
* reversible form (i.e., the same as calling
* {@code diff(sourceEntry, targetEntry, ignoreRDN, true, attributes)}.
*
* @param sourceEntry The source entry for which the set of modifications
* should be generated.
* @param targetEntry The target entry, which is what the source entry
* should look like if the returned modifications are
* applied.
* @param ignoreRDN Indicates whether to ignore differences in the RDNs
* of the provided entries. If this is {@code false},
* then the resulting set of modifications may include
* changes to the RDN attribute. If it is {@code true},
* then differences in the entry DNs will be ignored.
* @param attributes The set of attributes to be compared. If this is
* {@code null} or empty, then all attributes will be
* compared. Note that if a list of attributes is
* specified, then matching will be performed only
* against the attribute base name and any differences in
* attribute options will be ignored.
*
* @return A set of modifications that can be applied to the source entry in
* order to make it match the target entry.
*/
public static List diff(final Entry sourceEntry,
final Entry targetEntry,
final boolean ignoreRDN,
final String... attributes)
{
return diff(sourceEntry, targetEntry, ignoreRDN, true, attributes);
}
/**
* Retrieves a set of modifications that can be applied to the source entry in
* order to make it match the target entry.
*
* @param sourceEntry The source entry for which the set of modifications
* should be generated.
* @param targetEntry The target entry, which is what the source entry
* should look like if the returned modifications are
* applied.
* @param ignoreRDN Indicates whether to ignore differences in the RDNs
* of the provided entries. If this is {@code false},
* then the resulting set of modifications may include
* changes to the RDN attribute. If it is {@code true},
* then differences in the entry DNs will be ignored.
* @param reversible Indicates whether to generate the diff in reversible
* form. In reversible form, only the ADD or DELETE
* modification types will be used so that source entry
* could be reconstructed from the target and the
* resulting modifications. In non-reversible form, only
* the REPLACE modification type will be used. Attempts
* to apply the modifications obtained when using
* reversible form are more likely to fail if the entry
* has been modified since the source and target forms
* were obtained.
* @param attributes The set of attributes to be compared. If this is
* {@code null} or empty, then all attributes will be
* compared. Note that if a list of attributes is
* specified, then matching will be performed only
* against the attribute base name and any differences in
* attribute options will be ignored.
*
* @return A set of modifications that can be applied to the source entry in
* order to make it match the target entry.
*/
public static List diff(final Entry sourceEntry,
final Entry targetEntry,
final boolean ignoreRDN,
final boolean reversible,
final String... attributes)
{
return diff(sourceEntry, targetEntry, ignoreRDN, reversible, false,
attributes);
}
/**
* Retrieves a set of modifications that can be applied to the source entry in
* order to make it match the target entry.
*
* @param sourceEntry The source entry for which the set of modifications
* should be generated.
* @param targetEntry The target entry, which is what the source entry
* should look like if the returned modifications are
* applied.
* @param ignoreRDN Indicates whether to ignore differences in the RDNs
* of the provided entries. If this is {@code false},
* then the resulting set of modifications may include
* changes to the RDN attribute. If it is {@code true},
* then differences in the entry DNs will be ignored.
* @param reversible Indicates whether to generate the diff in reversible
* form. In reversible form, only the ADD or DELETE
* modification types will be used so that source entry
* could be reconstructed from the target and the
* resulting modifications. In non-reversible form, only
* the REPLACE modification type will be used. Attempts
* to apply the modifications obtained when using
* reversible form are more likely to fail if the entry
* has been modified since the source and target forms
* were obtained.
* @param byteForByte Indicates whether to use a byte-for-byte comparison to
* identify which attribute values have changed. Using
* byte-for-byte comparison requires additional
* processing over using each attribute's associated
* matching rule, but it can detect changes that would
* otherwise be considered logically equivalent (e.g.,
* changing the capitalization of a value that uses a
* case-insensitive matching rule).
* @param attributes The set of attributes to be compared. If this is
* {@code null} or empty, then all attributes will be
* compared. Note that if a list of attributes is
* specified, then matching will be performed only
* against the attribute base name and any differences in
* attribute options will be ignored.
*
* @return A set of modifications that can be applied to the source entry in
* order to make it match the target entry.
*/
public static List diff(final Entry sourceEntry,
final Entry targetEntry,
final boolean ignoreRDN,
final boolean reversible,
final boolean byteForByte,
final String... attributes)
{
HashSet compareAttrs = null;
if ((attributes != null) && (attributes.length > 0))
{
compareAttrs =
new HashSet<>(StaticUtils.computeMapCapacity(attributes.length));
for (final String s : attributes)
{
compareAttrs.add(StaticUtils.toLowerCase(Attribute.getBaseName(s)));
}
}
final LinkedHashMap sourceOnlyAttrs =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
final LinkedHashMap targetOnlyAttrs =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
final LinkedHashMap commonAttrs =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
for (final Map.Entry e :
sourceEntry.attributes.entrySet())
{
final String lowerName = StaticUtils.toLowerCase(e.getKey());
if ((compareAttrs != null) &&
(! compareAttrs.contains(Attribute.getBaseName(lowerName))))
{
continue;
}
final Attribute attr;
if (byteForByte)
{
final Attribute a = e.getValue();
attr = new Attribute(a.getName(),
OctetStringMatchingRule.getInstance(), a.getRawValues());
}
else
{
attr = e.getValue();
}
sourceOnlyAttrs.put(lowerName, attr);
commonAttrs.put(lowerName, attr);
}
for (final Map.Entry e :
targetEntry.attributes.entrySet())
{
final String lowerName = StaticUtils.toLowerCase(e.getKey());
if ((compareAttrs != null) &&
(! compareAttrs.contains(Attribute.getBaseName(lowerName))))
{
continue;
}
if (sourceOnlyAttrs.remove(lowerName) == null)
{
// It wasn't in the set of source attributes, so it must be a
// target-only attribute.
final Attribute attr;
if (byteForByte)
{
final Attribute a = e.getValue();
attr = new Attribute(a.getName(),
OctetStringMatchingRule.getInstance(), a.getRawValues());
}
else
{
attr = e.getValue();
}
targetOnlyAttrs.put(lowerName, attr);
}
}
for (final String lowerName : sourceOnlyAttrs.keySet())
{
commonAttrs.remove(lowerName);
}
RDN sourceRDN = null;
RDN targetRDN = null;
if (ignoreRDN)
{
try
{
sourceRDN = sourceEntry.getRDN();
}
catch (final Exception e)
{
Debug.debugException(e);
}
try
{
targetRDN = targetEntry.getRDN();
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
final ArrayList mods = new ArrayList<>(10);
for (final Attribute a : sourceOnlyAttrs.values())
{
if (reversible)
{
ASN1OctetString[] values = a.getRawValues();
if ((sourceRDN != null) && (sourceRDN.hasAttribute(a.getName())))
{
final ArrayList newValues =
new ArrayList<>(values.length);
for (final ASN1OctetString value : values)
{
if (! sourceRDN.hasAttributeValue(a.getName(), value.getValue()))
{
newValues.add(value);
}
}
if (newValues.isEmpty())
{
continue;
}
else
{
values = new ASN1OctetString[newValues.size()];
newValues.toArray(values);
}
}
mods.add(new Modification(ModificationType.DELETE, a.getName(),
values));
}
else
{
mods.add(new Modification(ModificationType.REPLACE, a.getName()));
}
}
for (final Attribute a : targetOnlyAttrs.values())
{
ASN1OctetString[] values = a.getRawValues();
if ((targetRDN != null) && (targetRDN.hasAttribute(a.getName())))
{
final ArrayList newValues =
new ArrayList<>(values.length);
for (final ASN1OctetString value : values)
{
if (! targetRDN.hasAttributeValue(a.getName(), value.getValue()))
{
newValues.add(value);
}
}
if (newValues.isEmpty())
{
continue;
}
else
{
values = new ASN1OctetString[newValues.size()];
newValues.toArray(values);
}
}
if (reversible)
{
mods.add(new Modification(ModificationType.ADD, a.getName(), values));
}
else
{
mods.add(new Modification(ModificationType.REPLACE, a.getName(),
values));
}
}
for (final Attribute sourceAttr : commonAttrs.values())
{
Attribute targetAttr = targetEntry.getAttribute(sourceAttr.getName());
if ((byteForByte) && (targetAttr != null))
{
targetAttr = new Attribute(targetAttr.getName(),
OctetStringMatchingRule.getInstance(), targetAttr.getRawValues());
}
if (sourceAttr.equals(targetAttr))
{
continue;
}
if (reversible ||
((targetRDN != null) && targetRDN.hasAttribute(targetAttr.getName())))
{
final ASN1OctetString[] sourceValueArray = sourceAttr.getRawValues();
final LinkedHashMap sourceValues =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(
sourceValueArray.length));
for (final ASN1OctetString s : sourceValueArray)
{
try
{
sourceValues.put(sourceAttr.getMatchingRule().normalize(s), s);
}
catch (final Exception e)
{
Debug.debugException(e);
sourceValues.put(s, s);
}
}
final ASN1OctetString[] targetValueArray = targetAttr.getRawValues();
final LinkedHashMap targetValues =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(
targetValueArray.length));
for (final ASN1OctetString s : targetValueArray)
{
try
{
targetValues.put(sourceAttr.getMatchingRule().normalize(s), s);
}
catch (final Exception e)
{
Debug.debugException(e);
targetValues.put(s, s);
}
}
final Iterator>
sourceIterator = sourceValues.entrySet().iterator();
while (sourceIterator.hasNext())
{
final Map.Entry e =
sourceIterator.next();
if (targetValues.remove(e.getKey()) != null)
{
sourceIterator.remove();
}
else if ((sourceRDN != null) &&
sourceRDN.hasAttributeValue(sourceAttr.getName(),
e.getValue().getValue()))
{
sourceIterator.remove();
}
}
final Iterator>
targetIterator = targetValues.entrySet().iterator();
while (targetIterator.hasNext())
{
final Map.Entry e =
targetIterator.next();
if ((targetRDN != null) &&
targetRDN.hasAttributeValue(targetAttr.getName(),
e.getValue().getValue()))
{
targetIterator.remove();
}
}
final ArrayList delValues =
new ArrayList<>(sourceValues.values());
if (! delValues.isEmpty())
{
final ASN1OctetString[] delArray =
new ASN1OctetString[delValues.size()];
mods.add(new Modification(ModificationType.DELETE,
sourceAttr.getName(), delValues.toArray(delArray)));
}
final ArrayList addValues =
new ArrayList<>(targetValues.values());
if (! addValues.isEmpty())
{
final ASN1OctetString[] addArray =
new ASN1OctetString[addValues.size()];
mods.add(new Modification(ModificationType.ADD, targetAttr.getName(),
addValues.toArray(addArray)));
}
}
else
{
mods.add(new Modification(ModificationType.REPLACE,
targetAttr.getName(), targetAttr.getRawValues()));
}
}
return mods;
}
/**
* Merges the contents of all provided entries so that the resulting entry
* will contain all attribute values present in at least one of the entries.
*
* @param entries The set of entries to be merged. At least one entry must
* be provided.
*
* @return An entry containing all attribute values present in at least one
* of the entries.
*/
public static Entry mergeEntries(final Entry... entries)
{
Validator.ensureNotNull(entries);
Validator.ensureTrue(entries.length > 0);
final Entry newEntry = entries[0].duplicate();
for (int i=1; i < entries.length; i++)
{
for (final Attribute a : entries[i].attributes.values())
{
newEntry.addAttribute(a);
}
}
return newEntry;
}
/**
* Intersects the contents of all provided entries so that the resulting
* entry will contain only attribute values present in all of the provided
* entries.
*
* @param entries The set of entries to be intersected. At least one entry
* must be provided.
*
* @return An entry containing only attribute values contained in all of the
* provided entries.
*/
public static Entry intersectEntries(final Entry... entries)
{
Validator.ensureNotNull(entries);
Validator.ensureTrue(entries.length > 0);
final Entry newEntry = entries[0].duplicate();
for (final Attribute a : entries[0].attributes.values())
{
final String name = a.getName();
for (final byte[] v : a.getValueByteArrays())
{
for (int i=1; i < entries.length; i++)
{
if (! entries[i].hasAttributeValue(name, v))
{
newEntry.removeAttributeValue(name, v);
break;
}
}
}
}
return newEntry;
}
/**
* Creates a duplicate of the provided entry with the given set of
* modifications applied to it.
*
* @param entry The entry to be modified. It must not be
* {@code null}.
* @param lenient Indicates whether to exhibit a lenient behavior for
* the modifications, which will cause it to ignore
* problems like trying to add values that already
* exist or to remove nonexistent attributes or values.
* @param modifications The set of modifications to apply to the entry. It
* must not be {@code null} or empty.
*
* @return An updated version of the entry with the requested modifications
* applied.
*
* @throws LDAPException If a problem occurs while attempting to apply the
* modifications.
*/
public static Entry applyModifications(final Entry entry,
final boolean lenient,
final Modification... modifications)
throws LDAPException
{
Validator.ensureNotNull(entry, modifications);
Validator.ensureFalse(modifications.length == 0);
return applyModifications(entry, lenient, Arrays.asList(modifications));
}
/**
* Creates a duplicate of the provided entry with the given set of
* modifications applied to it.
*
* @param entry The entry to be modified. It must not be
* {@code null}.
* @param lenient Indicates whether to exhibit a lenient behavior for
* the modifications, which will cause it to ignore
* problems like trying to add values that already
* exist or to remove nonexistent attributes or values.
* @param modifications The set of modifications to apply to the entry. It
* must not be {@code null} or empty.
*
* @return An updated version of the entry with the requested modifications
* applied.
*
* @throws LDAPException If a problem occurs while attempting to apply the
* modifications.
*/
public static Entry applyModifications(final Entry entry,
final boolean lenient,
final List modifications)
throws LDAPException
{
Validator.ensureNotNull(entry, modifications);
Validator.ensureFalse(modifications.isEmpty());
final Entry e = entry.duplicate();
final ArrayList errors = new ArrayList<>(modifications.size());
ResultCode resultCode = null;
// Get the RDN for the entry to ensure that RDN modifications are not
// allowed.
RDN rdn = null;
try
{
rdn = entry.getRDN();
}
catch (final LDAPException le)
{
Debug.debugException(le);
}
for (final Modification m : modifications)
{
final String name = m.getAttributeName();
final byte[][] values = m.getValueByteArrays();
switch (m.getModificationType().intValue())
{
case ModificationType.ADD_INT_VALUE:
if (lenient)
{
e.addAttribute(m.getAttribute());
}
else
{
if (values.length == 0)
{
errors.add(ERR_ENTRY_APPLY_MODS_ADD_NO_VALUES.get(name));
}
for (int i=0; i < values.length; i++)
{
if (! e.addAttribute(name, values[i]))
{
if (resultCode == null)
{
resultCode = ResultCode.ATTRIBUTE_OR_VALUE_EXISTS;
}
errors.add(ERR_ENTRY_APPLY_MODS_ADD_EXISTING.get(
m.getValues()[i], name));
}
}
}
break;
case ModificationType.DELETE_INT_VALUE:
if (values.length == 0)
{
final boolean removed = e.removeAttribute(name);
if (! (lenient || removed))
{
if (resultCode == null)
{
resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
}
errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_ATTR.get(
name));
}
}
else
{
for (int i=0; i < values.length; i++)
{
final boolean removed = e.removeAttributeValue(name, values[i]);
if (! (lenient || removed))
{
if (resultCode == null)
{
resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
}
errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_VALUE.get(
m.getValues()[i], name));
}
}
}
break;
case ModificationType.REPLACE_INT_VALUE:
if (values.length == 0)
{
e.removeAttribute(name);
}
else
{
e.setAttribute(m.getAttribute());
}
break;
case ModificationType.INCREMENT_INT_VALUE:
final Attribute a = e.getAttribute(name);
if ((a == null) || (! a.hasValue()))
{
errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_SUCH_ATTR.get(name));
continue;
}
if (a.size() > 1)
{
errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NOT_SINGLE_VALUED.get(
name));
continue;
}
if ((rdn != null) && rdn.hasAttribute(name))
{
final String msg =
ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
if (! errors.contains(msg))
{
errors.add(msg);
}
if (resultCode == null)
{
resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
}
continue;
}
final BigInteger currentValue;
try
{
currentValue = new BigInteger(a.getValue());
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
errors.add(
ERR_ENTRY_APPLY_MODS_INCREMENT_ENTRY_VALUE_NOT_INTEGER.get(
name, a.getValue()));
continue;
}
if (values.length == 0)
{
errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_MOD_VALUES.get(name));
continue;
}
else if (values.length > 1)
{
errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MULTIPLE_MOD_VALUES.get(
name));
continue;
}
final BigInteger incrementValue;
final String incrementValueStr = m.getValues()[0];
try
{
incrementValue = new BigInteger(incrementValueStr);
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MOD_VALUE_NOT_INTEGER.get(
name, incrementValueStr));
continue;
}
final BigInteger newValue = currentValue.add(incrementValue);
e.setAttribute(name, newValue.toString());
break;
default:
errors.add(ERR_ENTRY_APPLY_MODS_UNKNOWN_TYPE.get(
String.valueOf(m.getModificationType())));
break;
}
}
// Make sure that the entry still has all of the RDN attribute values.
if (rdn != null)
{
final String[] rdnAttrs = rdn.getAttributeNames();
final byte[][] rdnValues = rdn.getByteArrayAttributeValues();
for (int i=0; i < rdnAttrs.length; i++)
{
if (! e.hasAttributeValue(rdnAttrs[i], rdnValues[i]))
{
errors.add(ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN()));
if (resultCode == null)
{
resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
}
break;
}
}
}
if (errors.isEmpty())
{
return e;
}
if (resultCode == null)
{
resultCode = ResultCode.CONSTRAINT_VIOLATION;
}
throw new LDAPException(resultCode,
ERR_ENTRY_APPLY_MODS_FAILURE.get(e.getDN(),
StaticUtils.concatenateStrings(errors)));
}
/**
* Creates a duplicate of the provided entry with the appropriate changes for
* a modify DN operation. Any corresponding changes to the set of attribute
* values (to ensure that the new RDN values are present in the entry, and
* optionally to remove the old RDN values from the entry) will also be
* applied.
*
* @param entry The entry to be renamed. It must not be
* {@code null}.
* @param newRDN The new RDN to use for the entry. It must not be
* {@code null}.
* @param deleteOldRDN Indicates whether attribute values that were present
* in the old RDN but are no longer present in the new
* DN should be removed from the entry.
*
* @return A new entry that is a duplicate of the provided entry, except with
* any necessary changes for the modify DN.
*
* @throws LDAPException If a problem is encountered during modify DN
* processing.
*/
public static Entry applyModifyDN(final Entry entry, final String newRDN,
final boolean deleteOldRDN)
throws LDAPException
{
return applyModifyDN(entry, newRDN, deleteOldRDN, null);
}
/**
* Creates a duplicate of the provided entry with the appropriate changes for
* a modify DN operation. Any corresponding changes to the set of attribute
* values (to ensure that the new RDN values are present in the entry, and
* optionally to remove the old RDN values from the entry) will also be
* applied.
*
* @param entry The entry to be renamed. It must not be
* {@code null}.
* @param newRDN The new RDN to use for the entry. It must not be
* {@code null}.
* @param deleteOldRDN Indicates whether attribute values that were present
* in the old RDN but are no longer present in the new
* DN should be removed from the entry.
* @param newSuperiorDN The new superior DN for the entry. If this is
* {@code null}, then the entry will remain below its
* existing parent. If it is non-{@code null}, then
* the resulting DN will be a concatenation of the new
* RDN and the new superior DN.
*
* @return A new entry that is a duplicate of the provided entry, except with
* any necessary changes for the modify DN.
*
* @throws LDAPException If a problem is encountered during modify DN
* processing.
*/
public static Entry applyModifyDN(final Entry entry, final String newRDN,
final boolean deleteOldRDN,
final String newSuperiorDN)
throws LDAPException
{
Validator.ensureNotNull(entry);
Validator.ensureNotNull(newRDN);
// Parse all of the necessary elements from the request.
final DN parsedOldDN = entry.getParsedDN();
final RDN parsedOldRDN = parsedOldDN.getRDN();
final DN parsedOldSuperiorDN = parsedOldDN.getParent();
final RDN parsedNewRDN = new RDN(newRDN);
final DN parsedNewSuperiorDN;
if (newSuperiorDN == null)
{
parsedNewSuperiorDN = parsedOldSuperiorDN;
}
else
{
parsedNewSuperiorDN = new DN(newSuperiorDN);
}
// Duplicate the provided entry and update it with the new DN.
final Entry newEntry = entry.duplicate();
if (parsedNewSuperiorDN == null)
{
// This should only happen if the provided entry has a zero-length DN.
// It's extremely unlikely that a directory server would permit this
// change, but we'll go ahead and process it.
newEntry.setDN(new DN(parsedNewRDN));
}
else
{
newEntry.setDN(new DN(parsedNewRDN, parsedNewSuperiorDN));
}
// If deleteOldRDN is true, then remove any values present in the old RDN
// that are not present in the new RDN.
if (deleteOldRDN && (parsedOldRDN != null))
{
final String[] oldNames = parsedOldRDN.getAttributeNames();
final byte[][] oldValues = parsedOldRDN.getByteArrayAttributeValues();
for (int i=0; i < oldNames.length; i++)
{
if (! parsedNewRDN.hasAttributeValue(oldNames[i], oldValues[i]))
{
newEntry.removeAttributeValue(oldNames[i], oldValues[i]);
}
}
}
// Add any values present in the new RDN that were not present in the old
// RDN.
final String[] newNames = parsedNewRDN.getAttributeNames();
final byte[][] newValues = parsedNewRDN.getByteArrayAttributeValues();
for (int i=0; i < newNames.length; i++)
{
if ((parsedOldRDN == null) ||
(! parsedOldRDN.hasAttributeValue(newNames[i], newValues[i])))
{
newEntry.addAttribute(newNames[i], newValues[i]);
}
}
return newEntry;
}
/**
* Generates a hash code for this entry.
*
* @return The generated hash code for this entry.
*/
@Override()
public int hashCode()
{
int hashCode = 0;
try
{
hashCode += getParsedDN().hashCode();
}
catch (final LDAPException le)
{
Debug.debugException(le);
hashCode += dn.hashCode();
}
for (final Attribute a : attributes.values())
{
hashCode += a.hashCode();
}
return hashCode;
}
/**
* Indicates whether the provided object is equal to this entry. The provided
* object will only be considered equal to this entry if it is an entry with
* the same DN and set of attributes.
*
* @param o The object for which to make the determination.
*
* @return {@code true} if the provided object is considered equal to this
* entry, or {@code false} if not.
*/
@Override()
public boolean equals(final Object o)
{
if (o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (! (o instanceof Entry))
{
return false;
}
final Entry e = (Entry) o;
try
{
final DN thisDN = getParsedDN();
final DN thatDN = e.getParsedDN();
if (! thisDN.equals(thatDN))
{
return false;
}
}
catch (final LDAPException le)
{
Debug.debugException(le);
if (! dn.equals(e.dn))
{
return false;
}
}
if (attributes.size() != e.attributes.size())
{
return false;
}
for (final Attribute a : attributes.values())
{
if (! e.hasAttribute(a))
{
return false;
}
}
return true;
}
/**
* Creates a new entry that is a duplicate of this entry.
*
* @return A new entry that is a duplicate of this entry.
*/
public Entry duplicate()
{
return new Entry(dn, schema, attributes.values());
}
/**
* Retrieves an LDIF representation of this entry, with each attribute value
* on a separate line. Long lines will not be wrapped.
*
* @return An LDIF representation of this entry.
*/
@Override()
public final String[] toLDIF()
{
return toLDIF(0);
}
/**
* Retrieves an LDIF representation of this entry, with each attribute value
* on a separate line. Long lines will be wrapped at the specified column.
*
* @param wrapColumn The column at which long lines should be wrapped. A
* value less than or equal to two indicates that no
* wrapping should be performed.
*
* @return An LDIF representation of this entry.
*/
@Override()
public final String[] toLDIF(final int wrapColumn)
{
List ldifLines = new ArrayList<>(2*attributes.size());
encodeNameAndValue("dn", new ASN1OctetString(dn), ldifLines);
for (final Attribute a : attributes.values())
{
final String name = a.getName();
if (a.hasValue())
{
for (final ASN1OctetString value : a.getRawValues())
{
encodeNameAndValue(name, value, ldifLines);
}
}
else
{
encodeNameAndValue(name, EMPTY_OCTET_STRING, ldifLines);
}
}
if (wrapColumn > 2)
{
ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines);
}
final String[] lineArray = new String[ldifLines.size()];
ldifLines.toArray(lineArray);
return lineArray;
}
/**
* Encodes the provided name and value and adds the result to the provided
* list of lines. This will handle the case in which the encoded name and
* value includes comments about the base64-decoded representation of the
* provided value.
*
* @param name The attribute name to be encoded.
* @param value The attribute value to be encoded.
* @param lines The list of lines to be updated.
*/
private static void encodeNameAndValue(final String name,
final ASN1OctetString value,
final List lines)
{
final String line = LDIFWriter.encodeNameAndValue(name, value);
if (LDIFWriter.commentAboutBase64EncodedValues() &&
line.startsWith(name + "::"))
{
final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
while (tokenizer.hasMoreTokens())
{
lines.add(tokenizer.nextToken());
}
}
else
{
lines.add(line);
}
}
/**
* Appends an LDIF representation of this entry to the provided buffer. Long
* lines will not be wrapped.
*
* @param buffer The buffer to which the LDIF representation of this entry
* should be written.
*/
@Override()
public final void toLDIF(final ByteStringBuffer buffer)
{
toLDIF(buffer, 0);
}
/**
* Appends an LDIF representation of this entry to the provided buffer.
*
* @param buffer The buffer to which the LDIF representation of this
* entry should be written.
* @param wrapColumn The column at which long lines should be wrapped. A
* value less than or equal to two indicates that no
* wrapping should be performed.
*/
@Override()
public final void toLDIF(final ByteStringBuffer buffer, final int wrapColumn)
{
LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
wrapColumn);
buffer.append(StaticUtils.EOL_BYTES);
for (final Attribute a : attributes.values())
{
final String name = a.getName();
if (a.hasValue())
{
for (final ASN1OctetString value : a.getRawValues())
{
LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
buffer.append(StaticUtils.EOL_BYTES);
}
}
else
{
LDIFWriter.encodeNameAndValue(name, EMPTY_OCTET_STRING, buffer,
wrapColumn);
buffer.append(StaticUtils.EOL_BYTES);
}
}
}
/**
* Retrieves an LDIF-formatted string representation of this entry. No
* wrapping will be performed, and no extra blank lines will be added.
*
* @return An LDIF-formatted string representation of this entry.
*/
@Override()
public final String toLDIFString()
{
final StringBuilder buffer = new StringBuilder();
toLDIFString(buffer, 0);
return buffer.toString();
}
/**
* Retrieves an LDIF-formatted string representation of this entry. No
* extra blank lines will be added.
*
* @param wrapColumn The column at which long lines should be wrapped. A
* value less than or equal to two indicates that no
* wrapping should be performed.
*
* @return An LDIF-formatted string representation of this entry.
*/
@Override()
public final String toLDIFString(final int wrapColumn)
{
final StringBuilder buffer = new StringBuilder();
toLDIFString(buffer, wrapColumn);
return buffer.toString();
}
/**
* Appends an LDIF-formatted string representation of this entry to the
* provided buffer. No wrapping will be performed, and no extra blank lines
* will be added.
*
* @param buffer The buffer to which to append the LDIF representation of
* this entry.
*/
@Override()
public final void toLDIFString(final StringBuilder buffer)
{
toLDIFString(buffer, 0);
}
/**
* Appends an LDIF-formatted string representation of this entry to the
* provided buffer. No extra blank lines will be added.
*
* @param buffer The buffer to which to append the LDIF representation
* of this entry.
* @param wrapColumn The column at which long lines should be wrapped. A
* value less than or equal to two indicates that no
* wrapping should be performed.
*/
@Override()
public final void toLDIFString(final StringBuilder buffer,
final int wrapColumn)
{
LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
wrapColumn);
buffer.append(StaticUtils.EOL);
for (final Attribute a : attributes.values())
{
final String name = a.getName();
if (a.hasValue())
{
for (final ASN1OctetString value : a.getRawValues())
{
LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
buffer.append(StaticUtils.EOL);
}
}
else
{
LDIFWriter.encodeNameAndValue(name, EMPTY_OCTET_STRING, buffer,
wrapColumn);
buffer.append(StaticUtils.EOL);
}
}
}
/**
* Retrieves a string representation of this entry.
*
* @return A string representation of this entry.
*/
@Override()
public final String toString()
{
final StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
/**
* Appends a string representation of this entry to the provided buffer.
*
* @param buffer The buffer to which to append the string representation of
* this entry.
*/
@Override()
public void toString(final StringBuilder buffer)
{
buffer.append("Entry(dn='");
buffer.append(dn);
buffer.append("', attributes={");
final Iterator iterator = attributes.values().iterator();
while (iterator.hasNext())
{
iterator.next().toString(buffer);
if (iterator.hasNext())
{
buffer.append(", ");
}
}
buffer.append("})");
}
}