All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.Attribute Maven / Gradle / Ivy

/*
 * 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.io.Serializable;
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.LinkedHashSet;
import java.util.Set;

import com.unboundid.asn1.ASN1Buffer;
import com.unboundid.asn1.ASN1BufferSequence;
import com.unboundid.asn1.ASN1BufferSet;
import com.unboundid.asn1.ASN1Element;
import com.unboundid.asn1.ASN1Exception;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.asn1.ASN1Sequence;
import com.unboundid.asn1.ASN1Set;
import com.unboundid.asn1.ASN1StreamReader;
import com.unboundid.asn1.ASN1StreamReaderSet;
import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
import com.unboundid.ldap.matchingrules.MatchingRule;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.util.Base64;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
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
 * attribute, which includes an attribute name (which may include a set of
 * attribute options) and zero or more values.  Attribute objects are immutable
 * and cannot be altered.  However, if an attribute is included in an
 * {@link Entry} object, then it is possible to add and remove attribute values
 * from the entry (which will actually create new Attribute object instances),
 * although this is not allowed for instances of {@link ReadOnlyEntry} and its
 * subclasses.
 * 

* This class uses the term "attribute name" as an equivalent of what the LDAP * specification refers to as an "attribute description". An attribute * description consists of an attribute type name or object identifier (which * this class refers to as the "base name") followed by zero or more attribute * options, each of which should be prefixed by a semicolon. Attribute options * may be used to provide additional metadata for the attribute and/or its * values, or to indicate special handling for the values. For example, * RFC 3866 describes the use * of attribute options to indicate that a value may be associated with a * particular language (e.g., "cn;lang-en-US" indicates that the values of that * cn attribute should be treated as U.S. English values), and * RFC 4522 describes a binary * encoding option that indicates that the server should only attempt to * interact with the values as binary data (e.g., "userCertificate;binary") and * should not treat them as strings. An attribute name (which is technically * referred to as an "attribute description" in the protocol specification) may * have zero, one, or multiple attribute options. If there are any attribute * options, then a semicolon is used to separate the first option from the base * attribute name, and to separate each subsequent attribute option from the * previous option. *

* Attribute values can be treated as either strings or byte arrays. In LDAP, * they are always transferred using a binary encoding, but applications * frequently treat them as strings and it is often more convenient to do so. * However, for some kinds of data (e.g., certificates, images, audio clips, and * other "blobs") it may be desirable to only treat them as binary data and only * interact with the values as byte arrays. If you do intend to interact with * string values as byte arrays, then it is important to ensure that you use a * UTF-8 representation for those values unless you are confident that the * directory server will not attempt to treat the value as a string. */ @NotMutable() @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class Attribute implements Serializable { /** * The array to use as the set of values when there are no values. */ private static final ASN1OctetString[] NO_VALUES = new ASN1OctetString[0]; /** * The array to use as the set of byte array values when there are no values. */ private static final byte[][] NO_BYTE_VALUES = new byte[0][]; /** * The serial version UID for this serializable class. */ private static final long serialVersionUID = 5867076498293567612L; // The set of values for this attribute. private final ASN1OctetString[] values; // The hash code for this attribute. private int hashCode = -1; // The matching rule that should be used for equality determinations. private final MatchingRule matchingRule; // The attribute description for this attribute. private final String name; /** * Creates a new LDAP attribute with the specified name and no values. * * @param name The name for this attribute. It must not be {@code null}. */ public Attribute(final String name) { Validator.ensureNotNull(name); this.name = name; values = NO_VALUES; matchingRule = CaseIgnoreStringMatchingRule.getInstance(); } /** * Creates a new LDAP attribute with the specified name and value. * * @param name The name for this attribute. It must not be {@code null}. * @param value The value for this attribute. It must not be {@code null}. */ public Attribute(final String name, final String value) { Validator.ensureNotNull(name, value); this.name = name; values = new ASN1OctetString[] { new ASN1OctetString(value) }; matchingRule = CaseIgnoreStringMatchingRule.getInstance(); } /** * Creates a new LDAP attribute with the specified name and value. * * @param name The name for this attribute. It must not be {@code null}. * @param value The value for this attribute. It must not be {@code null}. */ public Attribute(final String name, final byte[] value) { Validator.ensureNotNull(name, value); this.name = name; values = new ASN1OctetString[] { new ASN1OctetString(value) }; matchingRule = CaseIgnoreStringMatchingRule.getInstance(); } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be {@code null}. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final String... values) { Validator.ensureNotNull(name, values); this.name = name; this.values = new ASN1OctetString[values.length]; for (int i=0; i < values.length; i++) { this.values[i] = new ASN1OctetString(values[i]); } matchingRule = CaseIgnoreStringMatchingRule.getInstance(); } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be {@code null}. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final byte[]... values) { Validator.ensureNotNull(name, values); this.name = name; this.values = new ASN1OctetString[values.length]; for (int i=0; i < values.length; i++) { this.values[i] = new ASN1OctetString(values[i]); } matchingRule = CaseIgnoreStringMatchingRule.getInstance(); } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be {@code null}. * @param values The set of raw values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final ASN1OctetString... values) { Validator.ensureNotNull(name, values); this.name = name; this.values = values; matchingRule = CaseIgnoreStringMatchingRule.getInstance(); } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be {@code null}. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final Collection values) { Validator.ensureNotNull(name, values); this.name = name; this.values = new ASN1OctetString[values.size()]; int i=0; for (final String s : values) { this.values[i++] = new ASN1OctetString(s); } matchingRule = CaseIgnoreStringMatchingRule.getInstance(); } /** * Creates a new LDAP attribute with the specified name and no values. * * @param name The name for this attribute. It must not be * {@code null}. * @param matchingRule The matching rule to use when comparing values. It * must not be {@code null}. */ public Attribute(final String name, final MatchingRule matchingRule) { Validator.ensureNotNull(name, matchingRule); this.name = name; this.matchingRule = matchingRule; values = NO_VALUES; } /** * Creates a new LDAP attribute with the specified name and value. * * @param name The name for this attribute. It must not be * {@code null}. * @param matchingRule The matching rule to use when comparing values. It * must not be {@code null}. * @param value The value for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final MatchingRule matchingRule, final String value) { Validator.ensureNotNull(name, matchingRule, value); this.name = name; this.matchingRule = matchingRule; values = new ASN1OctetString[] { new ASN1OctetString(value) }; } /** * Creates a new LDAP attribute with the specified name and value. * * @param name The name for this attribute. It must not be * {@code null}. * @param matchingRule The matching rule to use when comparing values. It * must not be {@code null}. * @param value The value for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final MatchingRule matchingRule, final byte[] value) { Validator.ensureNotNull(name, matchingRule, value); this.name = name; this.matchingRule = matchingRule; values = new ASN1OctetString[] { new ASN1OctetString(value) }; } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be * {@code null}. * @param matchingRule The matching rule to use when comparing values. It * must not be {@code null}. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final MatchingRule matchingRule, final String... values) { Validator.ensureNotNull(name, matchingRule, values); this.name = name; this.matchingRule = matchingRule; this.values = new ASN1OctetString[values.length]; for (int i=0; i < values.length; i++) { this.values[i] = new ASN1OctetString(values[i]); } } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be * {@code null}. * @param matchingRule The matching rule to use when comparing values. It * must not be {@code null}. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final MatchingRule matchingRule, final byte[]... values) { Validator.ensureNotNull(name, matchingRule, values); this.name = name; this.matchingRule = matchingRule; this.values = new ASN1OctetString[values.length]; for (int i=0; i < values.length; i++) { this.values[i] = new ASN1OctetString(values[i]); } } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be * {@code null}. * @param matchingRule The matching rule to use when comparing values. It * must not be {@code null}. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final MatchingRule matchingRule, final Collection values) { Validator.ensureNotNull(name, matchingRule, values); this.name = name; this.matchingRule = matchingRule; this.values = new ASN1OctetString[values.size()]; int i=0; for (final String s : values) { this.values[i++] = new ASN1OctetString(s); } } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. * @param matchingRule The matching rule for this attribute. * @param values The set of values for this attribute. */ public Attribute(final String name, final MatchingRule matchingRule, final ASN1OctetString[] values) { this.name = name; this.matchingRule = matchingRule; this.values = values; } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be {@code null}. * @param schema The schema to use to select the matching rule for this * attribute. It may be {@code null} if the default matching * rule should be used. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final Schema schema, final String... values) { this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values); } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be {@code null}. * @param schema The schema to use to select the matching rule for this * attribute. It may be {@code null} if the default matching * rule should be used. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final Schema schema, final byte[]... values) { this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values); } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be {@code null}. * @param schema The schema to use to select the matching rule for this * attribute. It may be {@code null} if the default matching * rule should be used. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final Schema schema, final Collection values) { this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values); } /** * Creates a new LDAP attribute with the specified name and set of values. * * @param name The name for this attribute. It must not be {@code null}. * @param schema The schema to use to select the matching rule for this * attribute. It may be {@code null} if the default matching * rule should be used. * @param values The set of values for this attribute. It must not be * {@code null}. */ public Attribute(final String name, final Schema schema, final ASN1OctetString[] values) { this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values); } /** * Creates a new attribute containing the merged values of the provided * attributes. Any duplicate values will only be present once in the * resulting attribute. The names of the provided attributes must be the * same. * * @param attr1 The first attribute containing the values to merge. It must * not be {@code null}. * @param attr2 The second attribute containing the values to merge. It * must not be {@code null}. * * @return The new attribute containing the values of both of the * provided attributes. */ public static Attribute mergeAttributes(final Attribute attr1, final Attribute attr2) { return mergeAttributes(attr1, attr2, attr1.matchingRule); } /** * Creates a new attribute containing the merged values of the provided * attributes. Any duplicate values will only be present once in the * resulting attribute. The names of the provided attributes must be the * same. * * @param attr1 The first attribute containing the values to merge. * It must not be {@code null}. * @param attr2 The second attribute containing the values to merge. * It must not be {@code null}. * @param matchingRule The matching rule to use to locate matching values. * It may be {@code null} if the matching rule * associated with the first attribute should be used. * * @return The new attribute containing the values of both of the * provided attributes. */ public static Attribute mergeAttributes(final Attribute attr1, final Attribute attr2, final MatchingRule matchingRule) { Validator.ensureNotNull(attr1, attr2); final String name = attr1.name; Validator.ensureTrue(name.equalsIgnoreCase(attr2.name)); final MatchingRule mr; if (matchingRule == null) { mr = attr1.matchingRule; } else { mr = matchingRule; } ASN1OctetString[] mergedValues = new ASN1OctetString[attr1.values.length + attr2.values.length]; System.arraycopy(attr1.values, 0, mergedValues, 0, attr1.values.length); int pos = attr1.values.length; for (final ASN1OctetString attr2Value : attr2.values) { if (! attr1.hasValue(attr2Value, mr)) { mergedValues[pos++] = attr2Value; } } if (pos != mergedValues.length) { // This indicates that there were duplicate values. final ASN1OctetString[] newMergedValues = new ASN1OctetString[pos]; System.arraycopy(mergedValues, 0, newMergedValues, 0, pos); mergedValues = newMergedValues; } return new Attribute(name, mr, mergedValues); } /** * Creates a new attribute containing all of the values of the first attribute * that are not contained in the second attribute. Any values contained in * the second attribute that are not contained in the first will be ignored. * The names of the provided attributes must be the same. * * @param attr1 The attribute from which to remove the values. It must not * be {@code null}. * @param attr2 The attribute containing the values to remove. It must not * be {@code null}. * * @return A new attribute containing all of the values of the first * attribute not contained in the second. It may contain zero values * if all the values of the first attribute were also contained in * the second. */ public static Attribute removeValues(final Attribute attr1, final Attribute attr2) { return removeValues(attr1, attr2, attr1.matchingRule); } /** * Creates a new attribute containing all of the values of the first attribute * that are not contained in the second attribute. Any values contained in * the second attribute that are not contained in the first will be ignored. * The names of the provided attributes must be the same. * * @param attr1 The attribute from which to remove the values. It * must not be {@code null}. * @param attr2 The attribute containing the values to remove. It * must not be {@code null}. * @param matchingRule The matching rule to use to locate matching values. * It may be {@code null} if the matching rule * associated with the first attribute should be used. * * @return A new attribute containing all of the values of the first * attribute not contained in the second. It may contain zero values * if all the values of the first attribute were also contained in * the second. */ public static Attribute removeValues(final Attribute attr1, final Attribute attr2, final MatchingRule matchingRule) { Validator.ensureNotNull(attr1, attr2); final String name = attr1.name; Validator.ensureTrue(name.equalsIgnoreCase(attr2.name)); final MatchingRule mr; if (matchingRule == null) { mr = attr1.matchingRule; } else { mr = matchingRule; } final ArrayList newValues = new ArrayList<>(Arrays.asList(attr1.values)); final Iterator iterator = newValues.iterator(); while (iterator.hasNext()) { if (attr2.hasValue(iterator.next(), mr)) { iterator.remove(); } } final ASN1OctetString[] newValueArray = new ASN1OctetString[newValues.size()]; newValues.toArray(newValueArray); return new Attribute(name, mr, newValueArray); } /** * Retrieves the name for this attribute (i.e., the attribute description), * which may include zero or more attribute options. * * @return The name for this attribute. */ public String getName() { return name; } /** * Retrieves the base name for this attribute, which is the name or OID of the * attribute type, without any attribute options. For an attribute without * any options, the value returned by this method will be identical the value * returned by the {@link #getName} method. * * @return The base name for this attribute. */ public String getBaseName() { return getBaseName(name); } /** * Retrieves the base name for an attribute with the given name, which will be * the provided name without any attribute options. If the given name does * not include any attribute options, then it will be returned unaltered. If * it does contain one or more attribute options, then the name will be * returned without those options. * * @param name The name to be processed. * * @return The base name determined from the provided attribute name. */ public static String getBaseName(final String name) { final int semicolonPos = name.indexOf(';'); if (semicolonPos > 0) { return name.substring(0, semicolonPos); } else { return name; } } /** * Indicates whether the name of this attribute is valid as per RFC 4512. The * name will be considered valid only if it starts with an ASCII alphabetic * character ('a' through 'z', or 'A' through 'Z'), and contains only ASCII * alphabetic characters, ASCII numeric digits ('0' through '9'), and the * ASCII hyphen character ('-'). It will also be allowed to include zero or * more attribute options, in which the option must be separate from the base * name by a semicolon and has the same naming constraints as the base name. * * @return {@code true} if this attribute has a valid name, or {@code false} * if not. */ public boolean nameIsValid() { return nameIsValid(name, true); } /** * Indicates whether the provided string represents a valid attribute name as * per RFC 4512. It will be considered valid only if it starts with an ASCII * alphabetic character ('a' through 'z', or 'A' through 'Z'), and contains * only ASCII alphabetic characters, ASCII numeric digits ('0' through '9'), * and the ASCII hyphen character ('-'). It will also be allowed to include * zero or more attribute options, in which the option must be separate from * the base name by a semicolon and has the same naming constraints as the * base name. * * @param s The name for which to make the determination. * * @return {@code true} if this attribute has a valid name, or {@code false} * if not. */ public static boolean nameIsValid(final String s) { return nameIsValid(s, true); } /** * Indicates whether the provided string represents a valid attribute name as * per RFC 4512. It will be considered valid only if it starts with an ASCII * alphabetic character ('a' through 'z', or 'A' through 'Z'), and contains * only ASCII alphabetic characters, ASCII numeric digits ('0' through '9'), * and the ASCII hyphen character ('-'). It may optionally be allowed to * include zero or more attribute options, in which the option must be * separate from the base name by a semicolon and has the same naming * constraints as the base name. * * @param s The name for which to make the determination. * @param allowOptions Indicates whether the provided name will be allowed * to contain attribute options. * * @return {@code true} if this attribute has a valid name, or {@code false} * if not. */ public static boolean nameIsValid(final String s, final boolean allowOptions) { final int length; if ((s == null) || ((length = s.length()) == 0)) { return false; } final char firstChar = s.charAt(0); if (! (((firstChar >= 'a') && (firstChar <= 'z')) || ((firstChar >= 'A') && (firstChar <= 'Z')))) { return false; } boolean lastWasSemiColon = false; for (int i=1; i < length; i++) { final char c = s.charAt(i); if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))) { // This will always be acceptable. lastWasSemiColon = false; } else if (((c >= '0') && (c <= '9')) || (c == '-')) { // These will only be acceptable if the last character was not a // semicolon. if (lastWasSemiColon) { return false; } lastWasSemiColon = false; } else if (c == ';') { // This will only be acceptable if attribute options are allowed and the // last character was not a semicolon. if (lastWasSemiColon || (! allowOptions)) { return false; } lastWasSemiColon = true; } else { return false; } } return (! lastWasSemiColon); } /** * Indicates whether this attribute has any attribute options. * * @return {@code true} if this attribute has at least one attribute option, * or {@code false} if not. */ public boolean hasOptions() { return hasOptions(name); } /** * Indicates whether the provided attribute name contains any options. * * @param name The name for which to make the determination. * * @return {@code true} if the provided attribute name has at least one * attribute option, or {@code false} if not. */ public static boolean hasOptions(final String name) { return (name.indexOf(';') > 0); } /** * Indicates whether this attribute has the specified attribute option. * * @param option The attribute option for which to make the determination. * * @return {@code true} if this attribute has the specified attribute option, * or {@code false} if not. */ public boolean hasOption(final String option) { return hasOption(name, option); } /** * Indicates whether the provided attribute name has the specified attribute * option. * * @param name The name to be examined. * @param option The attribute option for which to make the determination. * * @return {@code true} if the provided attribute name has the specified * attribute option, or {@code false} if not. */ public static boolean hasOption(final String name, final String option) { final Set options = getOptions(name); for (final String s : options) { if (s.equalsIgnoreCase(option)) { return true; } } return false; } /** * Retrieves the set of options for this attribute. * * @return The set of options for this attribute, or an empty set if there * are none. */ public Set getOptions() { return getOptions(name); } /** * Retrieves the set of options for the provided attribute name. * * @param name The name to be examined. * * @return The set of options for the provided attribute name, or an empty * set if there are none. */ public static Set getOptions(final String name) { int semicolonPos = name.indexOf(';'); if (semicolonPos > 0) { final LinkedHashSet options = new LinkedHashSet<>(StaticUtils.computeMapCapacity(5)); while (true) { final int nextSemicolonPos = name.indexOf(';', semicolonPos+1); if (nextSemicolonPos > 0) { options.add(name.substring(semicolonPos+1, nextSemicolonPos)); semicolonPos = nextSemicolonPos; } else { options.add(name.substring(semicolonPos+1)); break; } } return Collections.unmodifiableSet(options); } else { return Collections.emptySet(); } } /** * Retrieves the matching rule instance used by this attribute. * * @return The matching rule instance used by this attribute. */ public MatchingRule getMatchingRule() { return matchingRule; } /** * Retrieves the value for this attribute as a string. If this attribute has * multiple values, then the first value will be returned. * * @return The value for this attribute, or {@code null} if this attribute * does not have any values. */ public String getValue() { if (values.length == 0) { return null; } return values[0].stringValue(); } /** * Retrieves the value for this attribute as a byte array. If this attribute * has multiple values, then the first value will be returned. The returned * array must not be altered by the caller. * * @return The value for this attribute, or {@code null} if this attribute * does not have any values. */ public byte[] getValueByteArray() { if (values.length == 0) { return null; } return values[0].getValue(); } /** * Retrieves the value for this attribute as a Boolean. If this attribute has * multiple values, then the first value will be examined. 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}. * * @return The Boolean value for this attribute, or {@code null} if this * attribute does not have any values or the value cannot be parsed * as a Boolean. */ public Boolean getValueAsBoolean() { if (values.length == 0) { return null; } final String lowerValue = StaticUtils.toLowerCase(values[0].stringValue()); if (lowerValue.equals("true") || lowerValue.equals("t") || lowerValue.equals("yes") || lowerValue.equals("y") || lowerValue.equals("on") || lowerValue.equals("1")) { return Boolean.TRUE; } else if (lowerValue.equals("false") || lowerValue.equals("f") || lowerValue.equals("no") || lowerValue.equals("n") || lowerValue.equals("off") || lowerValue.equals("0")) { return Boolean.FALSE; } else { return null; } } /** * Retrieves the value for this attribute as a Date, formatted using the * generalized time syntax. If this attribute has multiple values, then the * first value will be examined. * * @return The Date value for this attribute, or {@code null} if this * attribute does not have any values or the value cannot be parsed * as a Date. */ public Date getValueAsDate() { if (values.length == 0) { return null; } try { return StaticUtils.decodeGeneralizedTime(values[0].stringValue()); } catch (final Exception e) { Debug.debugException(e); return null; } } /** * Retrieves the value for this attribute as a DN. If this attribute has * multiple values, then the first value will be examined. * * @return The DN value for this attribute, or {@code null} if this attribute * does not have any values or the value cannot be parsed as a DN. */ public DN getValueAsDN() { if (values.length == 0) { return null; } try { return new DN(values[0].stringValue()); } catch (final Exception e) { Debug.debugException(e); return null; } } /** * Retrieves the value for this attribute as an Integer. If this attribute * has multiple values, then the first value will be examined. * * @return The Integer value for this attribute, or {@code null} if this * attribute does not have any values or the value cannot be parsed * as an Integer. */ public Integer getValueAsInteger() { if (values.length == 0) { return null; } try { return Integer.valueOf(values[0].stringValue()); } catch (final NumberFormatException nfe) { Debug.debugException(nfe); return null; } } /** * Retrieves the value for this attribute as a Long. If this attribute has * multiple values, then the first value will be examined. * * @return The Long value for this attribute, or {@code null} if this * attribute does not have any values or the value cannot be parsed * as a Long. */ public Long getValueAsLong() { if (values.length == 0) { return null; } try { return Long.valueOf(values[0].stringValue()); } catch (final NumberFormatException nfe) { Debug.debugException(nfe); return null; } } /** * Retrieves the set of values for this attribute as strings. The returned * array must not be altered by the caller. * * @return The set of values for this attribute, or an empty array if it does * not have any values. */ public String[] getValues() { if (values.length == 0) { return StaticUtils.NO_STRINGS; } final String[] stringValues = new String[values.length]; for (int i=0; i < values.length; i++) { stringValues[i] = values[i].stringValue(); } return stringValues; } /** * Retrieves the set of values for this attribute as byte arrays. The * returned array must not be altered by the caller. * * @return The set of values for this attribute, or an empty array if it does * not have any values. */ public byte[][] getValueByteArrays() { if (values.length == 0) { return NO_BYTE_VALUES; } final byte[][] byteValues = new byte[values.length][]; for (int i=0; i < values.length; i++) { byteValues[i] = values[i].getValue(); } return byteValues; } /** * Retrieves the set of values for this attribute as an array of ASN.1 octet * strings. The returned array must not be altered by the caller. * * @return The set of values for this attribute as an array of ASN.1 octet * strings. */ public ASN1OctetString[] getRawValues() { return values; } /** * Indicates whether this attribute contains at least one value. * * @return {@code true} if this attribute has at least one value, or * {@code false} if not. */ public boolean hasValue() { return (values.length > 0); } /** * Indicates whether this attribute contains the specified value. * * @param value The value for which to make the determination. It must not * be {@code null}. * * @return {@code true} if this attribute has the specified value, or * {@code false} if not. */ public boolean hasValue(final String value) { Validator.ensureNotNull(value); return hasValue(new ASN1OctetString(value), matchingRule); } /** * Indicates whether this attribute contains the specified value. * * @param value The value for which to make the determination. It * must not be {@code null}. * @param matchingRule The matching rule to use when making the * determination. It must not be {@code null}. * * @return {@code true} if this attribute has the specified value, or * {@code false} if not. */ public boolean hasValue(final String value, final MatchingRule matchingRule) { Validator.ensureNotNull(value); return hasValue(new ASN1OctetString(value), matchingRule); } /** * Indicates whether this attribute contains the specified value. * * @param value The value for which to make the determination. It must not * be {@code null}. * * @return {@code true} if this attribute has the specified value, or * {@code false} if not. */ public boolean hasValue(final byte[] value) { Validator.ensureNotNull(value); return hasValue(new ASN1OctetString(value), matchingRule); } /** * Indicates whether this attribute contains the specified value. * * @param value The value for which to make the determination. It * must not be {@code null}. * @param matchingRule The matching rule to use when making the * determination. It must not be {@code null}. * * @return {@code true} if this attribute has the specified value, or * {@code false} if not. */ public boolean hasValue(final byte[] value, final MatchingRule matchingRule) { Validator.ensureNotNull(value); return hasValue(new ASN1OctetString(value), matchingRule); } /** * Indicates whether this attribute contains the specified value. * * @param value The value for which to make the determination. * * @return {@code true} if this attribute has the specified value, or * {@code false} if not. */ boolean hasValue(final ASN1OctetString value) { return hasValue(value, matchingRule); } /** * Indicates whether this attribute contains the specified value. * * @param value The value for which to make the determination. It * must not be {@code null}. * @param matchingRule The matching rule to use when making the * determination. It must not be {@code null}. * * @return {@code true} if this attribute has the specified value, or * {@code false} if not. */ boolean hasValue(final ASN1OctetString value, final MatchingRule matchingRule) { try { return matchingRule.matchesAnyValue(value, values); } catch (final LDAPException le) { Debug.debugException(le); // This probably means that the provided value cannot be normalized. In // that case, we'll fall back to a byte-for-byte comparison of the values. for (final ASN1OctetString existingValue : values) { if (value.equalsIgnoreType(existingValue)) { return true; } } return false; } } /** * Retrieves the number of values for this attribute. * * @return The number of values for this attribute. */ public int size() { return values.length; } /** * Writes an ASN.1-encoded representation of this attribute to the provided * ASN.1 buffer. * * @param buffer The ASN.1 buffer to which the encoded representation should * be written. */ public void writeTo(final ASN1Buffer buffer) { final ASN1BufferSequence attrSequence = buffer.beginSequence(); buffer.addOctetString(name); final ASN1BufferSet valueSet = buffer.beginSet(); for (final ASN1OctetString value : values) { buffer.addElement(value); } valueSet.end(); attrSequence.end(); } /** * Encodes this attribute into a form suitable for use in the LDAP protocol. * It will be encoded as a sequence containing the attribute name (as an octet * string) and a set of values. * * @return An ASN.1 sequence containing the encoded attribute. */ public ASN1Sequence encode() { final ASN1Element[] elements = { new ASN1OctetString(name), new ASN1Set(values) }; return new ASN1Sequence(elements); } /** * Reads and decodes an attribute from the provided ASN.1 stream reader. * * @param reader The ASN.1 stream reader from which to read the attribute. * * @return The decoded attribute. * * @throws LDAPException If a problem occurs while trying to read or decode * the attribute. */ public static Attribute readFrom(final ASN1StreamReader reader) throws LDAPException { return readFrom(reader, null); } /** * Reads and decodes an attribute from the provided ASN.1 stream reader. * * @param reader The ASN.1 stream reader from which to read the attribute. * @param schema The schema to use to select the appropriate matching rule * for this attribute. It may be {@code null} if the default * matching rule should be selected. * * @return The decoded attribute. * * @throws LDAPException If a problem occurs while trying to read or decode * the attribute. */ public static Attribute readFrom(final ASN1StreamReader reader, final Schema schema) throws LDAPException { try { Validator.ensureNotNull(reader.beginSequence()); final String attrName = reader.readString(); Validator.ensureNotNull(attrName); final MatchingRule matchingRule = MatchingRule.selectEqualityMatchingRule(attrName, schema); final ArrayList valueList = new ArrayList<>(10); final ASN1StreamReaderSet valueSet = reader.beginSet(); while (valueSet.hasMoreElements()) { valueList.add(new ASN1OctetString(reader.readBytes())); } final ASN1OctetString[] values = new ASN1OctetString[valueList.size()]; valueList.toArray(values); return new Attribute(attrName, matchingRule, values); } catch (final Exception e) { Debug.debugException(e); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_ATTR_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), e); } } /** * Decodes the provided ASN.1 sequence as an LDAP attribute. * * @param encodedAttribute The ASN.1 sequence to be decoded as an LDAP * attribute. It must not be {@code null}. * * @return The decoded LDAP attribute. * * @throws LDAPException If a problem occurs while attempting to decode the * provided ASN.1 sequence as an LDAP attribute. */ public static Attribute decode(final ASN1Sequence encodedAttribute) throws LDAPException { Validator.ensureNotNull(encodedAttribute); final ASN1Element[] elements = encodedAttribute.elements(); if (elements.length != 2) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_ATTR_DECODE_INVALID_COUNT.get(elements.length)); } final String name = ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); final ASN1Set valueSet; try { valueSet = ASN1Set.decodeAsSet(elements[1]); } catch (final ASN1Exception ae) { Debug.debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_ATTR_DECODE_VALUE_SET.get(StaticUtils.getExceptionMessage(ae)), ae); } final ASN1OctetString[] values = new ASN1OctetString[valueSet.elements().length]; for (int i=0; i < values.length; i++) { values[i] = ASN1OctetString.decodeAsOctetString(valueSet.elements()[i]); } return new Attribute(name, CaseIgnoreStringMatchingRule.getInstance(), values); } /** * Indicates whether any of the values of this attribute need to be * base64-encoded when represented as LDIF. * * @return {@code true} if any of the values of this attribute need to be * base64-encoded when represented as LDIF, or {@code false} if not. */ public boolean needsBase64Encoding() { for (final ASN1OctetString v : values) { if (needsBase64Encoding(v.getValue())) { return true; } } return false; } /** * Indicates whether the provided value needs to be base64-encoded when * represented as LDIF. * * @param v The value for which to make the determination. It must not be * {@code null}. * * @return {@code true} if the provided value needs to be base64-encoded when * represented as LDIF, or {@code false} if not. */ public static boolean needsBase64Encoding(final String v) { return needsBase64Encoding(StaticUtils.getBytes(v)); } /** * Indicates whether the provided value needs to be base64-encoded when * represented as LDIF. * * @param v The value for which to make the determination. It must not be * {@code null}. * * @return {@code true} if the provided value needs to be base64-encoded when * represented as LDIF, or {@code false} if not. */ public static boolean needsBase64Encoding(final byte[] v) { if (v.length == 0) { return false; } switch (v[0] & 0xFF) { case 0x20: // Space case 0x3A: // Colon case 0x3C: // Less-than return true; } if ((v[v.length-1] & 0xFF) == 0x20) { return true; } for (final byte b : v) { switch (b & 0xFF) { case 0x00: // NULL case 0x0A: // LF case 0x0D: // CR return true; default: if ((b & 0x80) != 0x00) { return true; } break; } } return false; } /** * Generates a hash code for this LDAP attribute. It will be the sum of the * hash codes for the lowercase attribute name and the normalized values. * * @return The generated hash code for this LDAP attribute. */ @Override() public int hashCode() { if (hashCode == -1) { int c = StaticUtils.toLowerCase(name).hashCode(); for (final ASN1OctetString value : values) { try { c += matchingRule.normalize(value).hashCode(); } catch (final LDAPException le) { Debug.debugException(le); c += value.hashCode(); } } hashCode = c; } return hashCode; } /** * Indicates whether the provided object is equal to this LDAP attribute. The * object will be considered equal to this LDAP attribute only if it is an * LDAP attribute with the same name and set of values. * * @param o The object for which to make the determination. * * @return {@code true} if the provided object may be considered equal to * this LDAP attribute, 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 Attribute)) { return false; } final Attribute a = (Attribute) o; if (! name.equalsIgnoreCase(a.name)) { return false; } if (values.length != a.values.length) { return false; } // For a small set of values, we can just iterate through the values of one // and see if they are all present in the other. However, that can be very // expensive for a large set of values, so we'll try to go with a more // efficient approach. if (values.length > 10) { // First, create a hash set containing the un-normalized values of the // first attribute. final HashSet unNormalizedValues = StaticUtils.hashSetOf(values); // Next, iterate through the values of the second attribute. For any // values that exist in the un-normalized set, remove them from that // set. For any values that aren't in the un-normalized set, create a // new set with the normalized representations of those values. HashSet normalizedMissingValues = null; for (final ASN1OctetString value : a.values) { if (! unNormalizedValues.remove(value)) { if (normalizedMissingValues == null) { normalizedMissingValues = new HashSet<>(StaticUtils.computeMapCapacity(values.length)); } try { normalizedMissingValues.add(matchingRule.normalize(value)); } catch (final Exception e) { Debug.debugException(e); return false; } } } // If the un-normalized set is empty, then that means all the values // exactly match without the need to compare the normalized // representations. For any values that are left, then we will need to // compare their normalized representations. if (normalizedMissingValues != null) { for (final ASN1OctetString value : unNormalizedValues) { try { if (! normalizedMissingValues.contains( matchingRule.normalize(value))) { return false; } } catch (final Exception e) { Debug.debugException(e); return false; } } } } else { for (final ASN1OctetString value : values) { if (! a.hasValue(value)) { return false; } } } // If we've gotten here, then we can consider them equal. return true; } /** * Retrieves a string representation of this LDAP attribute. * * @return A string representation of this LDAP attribute. */ @Override() public String toString() { final StringBuilder buffer = new StringBuilder(); toString(buffer); return buffer.toString(); } /** * Appends a string representation of this LDAP attribute to the provided * buffer. * * @param buffer The buffer to which the string representation of this LDAP * attribute should be appended. */ public void toString(final StringBuilder buffer) { buffer.append("Attribute(name="); buffer.append(name); if (values.length == 0) { buffer.append(", values={"); } else if (needsBase64Encoding()) { buffer.append(", base64Values={'"); for (int i=0; i < values.length; i++) { if (i > 0) { buffer.append("', '"); } buffer.append(Base64.encode(values[i].getValue())); } buffer.append('\''); } else { buffer.append(", values={'"); for (int i=0; i < values.length; i++) { if (i > 0) { buffer.append("', '"); } buffer.append(values[i].stringValue()); } buffer.append('\''); } buffer.append("})"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy