6.3 - Attribute

The Attribute class is used to store values associated with an AttributeType. An Entry can contain many Attribute{s, but only one of them is mandatory: the ObjectClass Attribute.

An Attribute can store zero, one or N values, accordingly to its associated AttributeType, which may allow null values, and which also can forbid muli-values.

The Attribute has a internal AttributeType which is usually defined using its name. This name is case insensitive, and we can also use the AttributeType OID.

Creating an Attribute

Creating an Attribute is not complex. Again, we split the API into two categories:

  • the schema agnostic Attributes
  • the schema aware Attributes

Schema agnostic Attribute

If we don’t inject a SchemaManager into the constructor, then the Attribute will have no way to control whether its AttributeType exists, nor that its Values are valid.

Let’s see how we can create Attributes. Basically, all what that needed is to provide the AttributeType as a String, and some Values (that are optional). Here is an example:

    Attribute attribute = new DefaultAttribute( " CN " );

Here, we created an empty Attribute. Note that cn does not allow empty values, but it’s not enforced. Also note that the AttributeType is lowercase and trimmed internally.

Let’s see another example, with some values:

    Attribute attribute = new DefaultAttribute( " CN ", "test", "Test", " test ", "test" );

Here, we create an Attribute with 3 values. We can see that the values are not duplicated (the “test” value is only inserted once) and that values are case sensitive (the “test” and “Test” values are both stored in the Attribute). The values aren’t trimmed either, so we can have a “test” and a ” test “ values stored.

This is why having a schema aware Attribute is really handy.

It’s possible to store binary values into an Attribute too:

    byte[] bytes1 = new byte[]{0x01, 0x02};
    byte[] bytes2 = new byte[]{0x03, 0x04};
    byte[] bytes3 = new byte[]{0x01, 0x02};

    Attribute attribute = new DefaultAttribute( " JpegPhoto ", bytes1, bytes2, bytes3 );

Same here: values are not duplicated.

Note that it’s not allowed to store a mix of binary and String values into an Attribute:

    byte[] bytes1 = new byte[]{0x01, 0x02};

    Attribute attribute = new DefaultAttribute( " JpegPhoto ", "test", bytes1 ); // Does not compile

Schema aware Attribute

We can inject a SchemaManager into the Attribute which will allow more control over the values stored within the Attribute. Let’s see an example of how this works:

    Attribute attribute = new DefaultAttribute( atCn );

Here, we created an Attribute with a specific AttributeType (‘atCn’) and no value. Let’s create a new Attribute with some values:

    Attribute attribute = new DefaultAttribute( atCn, "test", "Test", "  test  " );

The important point here is that the values are all considered equal. The contains method also uses the schema to compare its given value with the interned values. Here, with the cn AttributeType, the value is not case sensitive, so “TEST” is considered as an existing value, even if we injected “test”.

Modifying an Attribute

Now that we created an Attribute we would like to add or remove values from it. This is quite easy. We have a set of methods to add or remove values, and depending on whether the Attribute is schema aware or not, the added values will be checked.

Adding some value

Here is an example of a value addition into a schema agnostic Attribute, then the same operation into a schema aware Attribute:

    Attribute attribute = new DefaultAttribute( " CN " );
    Attribute attributeSA = new DefaultAttribute( atCn );
    
    // Add two values
    attribute.add( "test1", "" );
    
    // add two values
    attributeSA.add( "test1", "" );

We can see that the schema aware Attribute just contains only one value after the operation, as the cn attribute type does not allow empty strings.

There is one important point to understand – whenever a schema agnostic Attribute is created, it knows nothing about the type of values it will store. Once the first value is added, the Attribute will be typed accordingly to the first value added. Then we can no longer add values that are not of the same type.

If the Attribute is schema aware, it’s quite obvious. You won’t be able to add a binary value into a Human Readable Attribute. The following test shows that:

    Attribute attribute = new DefaultAttribute( " CN " );
    Attribute attributeSA = new DefaultAttribute( atCn );
    
    byte[] bytes = new byte[]{0x01, 0x02};
    
    // Add two values
    attribute.add( "test1" );
    attribute.add( bytes );

    // add two values
    attributeSA.add( "test1" );
    attributeSA.add( bytes );

Removing some values

Removing a value from an Attribute is a trivial operation. Of course, if the Attribute is schema aware, we will use the AttributeType comparator to check if the value is present in the Attribute or not (the comparator is associated with the attribute equality MatchingRule.

Here is an example:

    Attribute attribute = new DefaultAttribute( " CN " );
    
    // Add three values
    attribute.add( "test1", "test2", "test3" );
    
    // Remove 2 of them
    attribute.remove( "test2", "test3" );
    
    // Try to remove the last one, using wrong casing
    attribute.remove( "Test1" );

And the same example, on a schema aware Attribute. It demonstrates how convenient it is to manipulate such schema aware objects:

    Attribute attribute = new DefaultAttribute( atCn );
    
    // Add three values
    attribute.add( "test 1", "test 2", "test 3" );
    
    // Remove 2 of them
    attribute.remove( "TEST 2", "test   3" );
    
    // Try to remove the last one, using wrong casing
    attribute.remove( "Test 1" );

Attribute data access methods

We have a set of methods used to get some information about the Attribute. They are described in this chapter.

contains()

Checks if the given values are present in the Attribute. We can check for more than one value, but in this case, the method returns true only if all the values are present.

If the Attribute is schema aware, then the check uses the AttributeType to compare the given values with the interned values, otherwise, we do a strict comparison.

Here is an example:

    Attribute attribute1 = new DefaultAttribute( atCn );
    
    // Add three values
    attribute1.add( "test 1", "test 2", "test 3" );
    
    Attribute attribute2 = new DefaultAttribute( "cn" );
    
    // Add three values
    attribute2.add( "test 1", "test 2", "test 3" );

get()

Returns the first value from the Attribute. The first value is the one which has been added first. Note that it returns a Value instance.

getAttributeType()

Returns the internal AttributeType, if the Attribute is schema aware.

getBytes()

Returns the first value as a byte[], if the Attribute is not human readable. The user must know that the Attribute contains binary values, otherwise they will get an LdapInvalidAttributeValueException.

getString()

Returns the first value as a String, if the Attribute is human readable. The user must know that the Attribute contains String values, otherwise they will get an LdapInvalidAttributeValueException.

getId()

Returns the AttributeType normalized ID. If the Attribute is schema agnostic, it will be the trimmed and lower cased user provided ID, otherwise it will be the AttributeType OID (not the name).

getUpId()

Returns the attribute type user provided ID, if the user provided one. Typically, if the Attribute is schema aware, the user might not provide an ID, and in this case, this method will return the AttributeType name, or default to the OID.

isHumanReadable()

Tells if the Attribute contains String values or binary values.

Miscellaneous methods

We also have a set of miscellaneous methods, which are not frequently used. Here they are:

apply( AttributeType )

Inject an AttributeType into the Attribute, making it schema aware. It will check that the associated values are valid at the same time, and normalize the values.

Here is an example of the impact of such a method on an existing attribute :

    Attribute attribute = new DefaultAttribute( "CN", "  A    test" );
    
    attribute.apply( atCn );

It shows that we can now check that a value is not literately compared, the modified attribute uses the Equality MatchingRule to compare the values.

Here is another example, where we try to apply an attribute type to some attribute containing an invalid value : it generates an exception, as expected.

    byte[] bytes = new byte[]{0x01, 0x02};
    Attribute attribute = new DefaultAttribute( "CN", bytes );
    
    attribute.apply( atCn );

clear()

This method removes all the values from the attribute. The attribute type is not removed. Here is an example demonstrating how it works:

    Attribute attribute = new DefaultAttribute( atCn, "test1", "test2" );
    
    attribute.clear();

clone()

This method creates a new instance of an existing attribute. All of the attribute values along with its attribute type are cloned and distinct from the original attribute.

equals( Object )

Compares two Attributes. All of its values are compared and should be present in both attributes. If you compare a schema aware Attribute with a schema agnostic Attribute, they won’t be equal.

Here is a snippet of code demonstrating the equals method:

    Attribute attribute1 = new DefaultAttribute( atCn, "test 1", "test 2", "test 3" );
    Attribute attribute2 = new DefaultAttribute( atCn, "Test   3", "Test 2  ", "Test 1" );
    Attribute attribute3 = new DefaultAttribute( "cn", "test 1", "test 3", "test 2" );
    Attribute attribute4 = new DefaultAttribute( "cn", "test 1", "test 3", "test 2" );
    
    // Should be true
    boolean equals = attribute1.equals( attribute2 );
    equals = attribute3.equals( attribute4 );

    // Should be false
    equals = attribute1.equals( attribute3 );
    equals = attribute1.equals( attribute4 );
    equals = attribute2.equals( attribute4 );

    // Now, inject an AttrbuteType in one attribute    
    attribute4.apply( atCn );

    // The previously not equals attributes are now equal :
    equals = attribute1.equals( attribute4 );
    equals = attribute2.equals( attribute4 );

isInstanceOf( AttributeType )

Tells if an Attribute derives from a given AttributeType. It can be useful if you have a schema aware Attribute and want to know if its attribute type inherits from another one. Here is an example of usage:

    Attribute attribute = new DefaultAttribute( atCn, "test 1", "test 2", "test 3" );
    
    // Should be true
    boolean value = attribute.isInstanceOf( atCn );

isValid( AttributeType )

Checks if the Attribute contains valid data. It’s useful if one wants to apply an AttributeType to the Attribute, as the isValid method will tell if it’s possible to do so without throwing an exception.

Here is some code that test the different use cases:

    Attribute attribute1 = new DefaultAttribute( "cn", "\uFFFDtest" );
    
    // Should be false
    boolean isValid = attribute1.isValid( atCn ) );
    
    Attribute attribute2 = new DefaultAttribute( "cn" );

    // Should be false, 'cn' requires a value
    isValid = attribute2.isValid( atCn );
    
    Attribute attribute3 = new DefaultAttribute( "cn", "test" );

    // Should be true
    value = attribute3.isValid( atCn );
    
    Attribute attribute4 = new DefaultAttribute( "dc", "test", "test2" );

    // Should be false, we can't put more than one value in 'dc'
    value = attribute4.isValid( atDc );

iterator()

A convenient way to iterate over all of the Attribute values. It enables the for ( Value<?> value : attribute ) construct. Here is an example using the iterator:

    Attribute attribute = new DefaultAttribute( atCn, "test 1", "test 2", "test 3" );
    
    for ( Value<?> value : attribute )
    {
        System.out.println( "Value : " + value );
    }

This code produces the following output:

    Value : test 1
    Value : test 2
    Value : test 3

setUpId()

Sets the user provided identifier for the attribute type. If the Attribute is schema agnostic, then the normalized ID will be the given user provided ID lower cased and trimmed. If it’s schema aware, it will be used instead of the AttributeType ID.

size()

Returns the number of values stored in this Attribute