LDAP: Replace a value of a multi-valued attribute


  1. create an entry
  2. add attributes
  3. delete all values of a multi-valued attribute
  4. example using Java

This article describes how to replace values of a multi-valued attribute in a directory database using the ldapmodify tool. For more general information about ldapmodify see the blog post “Using ldapmodify”. For newer versions of this post, see “LDAP: Replace a value of a multi-valued attribute“.

In order to replace a value of a multi-valued attribute, it is necessary to provide the value to be replaced with the value that replaces it. The procedure is: delete the old value and add the new value (these two operations can be accomplished in one connection).

Create an entry

Create the following entry:


#
# This is a test user entry
# in which one value of the 
# description attribute
# will be replaced with another value.
#
dn: uid=user.0,ou=People,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
postalAddress: Aaren Atp$91327 Broadway Street$Las Vegas, UT  08103
postalCode: 08103
uid: user.0
employeeNumber: 0
initials: AWA
givenName: Aaren
pager: +1 214 214 4195
mobile: +1 947 007 3231
cn: Aaren Atp
sn: Atp
telephoneNumber: +1 089 907 9947
street: 91327 Broadway Street
homePhone: +1 457 787 9183
l: Las Vegas
mail: user.0@example.com
st: UT
userPassword: password

Note that there is no description attribute in the entry LDIF. Add the entry using ldapmodify, in this case using the legacy OpenLDAP ldapmodify tool and specifying the post-read control:


/usr/bin/ldapmodify -h localhost -p 1389 \
 -D 'cn=directory manager' -w password \
 -c -a -f user.0.LDIF -e postread=cn,sn,mail
adding new entry "uid=user.0,ou=People,dc=example,dc=com"
control: 1.3.6.1.1.13.2 false ZGgEJnVpZD11c2VyLjAsb3U9UGVvcGxlLGRjPWV4YW1wbGUs
 ZGM9Y29tMD4wEQQCY24xCwQJQWFyZW4gQXRwMAsEAnNuMQUEA0F0cDAcBARtYWlsMRQEEnVzZXIuM
 EBleGFtcGxlLmNvbQ==
# ==> postread
dn: uid=user.0,ou=People,dc=example,dc=com
cn: Aaren Atp
sn: Atp
mail: user.0@example.com
# <== postread

This is the same command using the modern ldapmodify tool:


ldapmodify --hostname localhost --port 1389 \
 --bindDn 'cn=directory manager' --bindPassword password \
 -c -a -f user.0.LDIF --postReadAttributes cn,sn,mail
# Processing ADD request for uid=user.0,ou=People,dc=example,dc=com
# ADD operation successful for DN uid=user.0,ou=People,dc=example,dc=com
# Target entry after the operation:
# dn: uid=user.0,ou=People,dc=example,dc=com
# cn: Aaren Atp
# sn: Atp
# mail: user.0@example.com

There is no need to do an old-fashioned ldapsearch for the entry. In fact, it is a terrible idea to add or modify an entry and then search for it immediately on a different connection because the eventual consistency model of replication does not guarantee that the changes would be propagated yet. The post-read request control will retrieve the attributes and send them back in the post-read response control. The post-read control verified that the entry exists – this is my test directory server so I know that no one will remove the entry but me.

Add two description attributes

Create the following file to add a description attribute with two values:

>
#
# Adds two values to the
# description attribute
# 
dn: uid=user.0,ou=people,dc=example,dc=com
changetype: modify
add: description
description: description 1
-
add: description
description: description 2

alternatively:


#
# Adds two values to the
# description attribute
# 
dn: uid=user.0,ou=people,dc=example,dc=com
changetype: modify
add: description
description: description 1
description: description 2

Modify the entry using the ldapmodify tool:


ldapmodify --hostname localhost --port 1389 \
 --bindDn 'cn=directory manager' --bindPassword password \
 -c -a -f replace-mv.LDIF --preReadAttributes description \
 --postReadAttributes description
# Processing MODIFY request for uid=user.0,ou=people,dc=example,dc=com
# MODIFY operation successful for DN uid=user.0,ou=people,dc=example,dc=com
# Target entry before the operation:
# dn: uid=user.0,ou=People,dc=example,dc=com

# Target entry after the operation:
# dn: uid=user.0,ou=People,dc=example,dc=com
# description: description 1
# description: description 2

Before the modification, there was no description attribute, after the modification, description had two values.

Replace one of the values of the multi-valued attribute

Create the following LDIF to replace description 1 with description 3:


dn: uid=user.0,ou=people,dc=example,dc=com
changetype: modify
delete: description
description: description 1
-
add: description
description: description 3

There are two operations, delete value description 1, and then add description 3.


ldapmodify --hostname localhost --port 1389 \
 --bindDn 'cn=directory manager' --bindPassword password \
--preReadAttributes description \
--postReadAttributes description -c -a -f change-description.LDIF 
# Processing MODIFY request for uid=user.0,ou=people,dc=example,dc=com
# MODIFY operation successful for DN uid=user.0,ou=people,dc=example,dc=com
# Target entry before the operation:
# dn: uid=user.0,ou=People,dc=example,dc=com
# description: description 1
# description: description 2

# Target entry after the operation:
# dn: uid=user.0,ou=People,dc=example,dc=com
# description: description 2
# description: description 3

Delete All Values of a Multi-valued Attribute


#
# This LDIF input deletes all values
# of the description attribute.
#
dn: uid=user.0,ou=people,dc=example,dc=com
changetype: modify
delete: description

Delete Selected Values of a Multi-valued Attribute

To delete selected values of a multi-valued attribute:


#
# This LDIF input deletes two values
# of the description attribute,
# namely, "value 1" and "value 2".
#
dn: uid=user.0,ou=people,dc=example,dc=com
changetype: modify
delete: description
description: value 1
description: value 2


These examples assume the directory server has a naming context or prefix dc=example,dc=com. To determine which naming contexts your directory server supports, execute the following query:

ldapsearch -h HOSTNAME -p PORT \
 -b "" -s base '(&)' namingContexts

For more information, see the entry “The Root DSE”.

Using Java

In Java using the UnboundID LDAP SDK:


package samplecode;

import java.util.ArrayList;
import java.util.List;

import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.LDAPConnectionPool;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ModificationType;
import com.unboundid.ldap.sdk.ModifyRequest;
import com.unboundid.ldap.sdk.ReadOnlyEntry;
import com.unboundid.ldap.sdk.controls.PostReadRequestControl;
import com.unboundid.ldap.sdk.controls.PostReadResponseControl;
import com.unboundid.ldap.sdk.controls.PreReadRequestControl;
import com.unboundid.ldap.sdk.controls.PreReadResponseControl;

/**
 * An example of how to replace values of multi-valued attributes.
 * 
 * @author Terry.Gardner@UnboundID.COM
 * @since 0.1
 */
public final class ReplaceValue {

  /**
   * @param ldapConnectionPool
   *          Provides services applicable to a pool of connections to a
   *          directory server. This object may not be {@code null}.
   * 
   * @param entry
   *          The entry that will be the target of modify requests.
   * 
   * @return A new and distinct ReplaceValue object.
   */
  public static ReplaceValue
      getReplaceValue(final LDAPConnectionPool ldapConnectionPool,
                      final String entry) {
    if(ldapConnectionPool == null) {
      throw new NullPointerException("A null ldap connection pool object "
          + " violates the contract of this class.");
    }
    return new ReplaceValue(ldapConnectionPool,entry);
  }

  private ReplaceValue(final LDAPConnectionPool ldapConnectionPool,
                       final String entry) {
    this.ldapConnectionPool = ldapConnectionPool;
    this.entry = entry;
  }

  void addDescriptionValues() {
    /*
     * Add a description attribute with two values.
     */
    final List<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.ADD,
                              "description",
                              "description 1"));
    mods.add(new Modification(ModificationType.ADD,
                              "description",
                              "description 2"));
    final ModifyRequest modifyRequest = new ModifyRequest(entry,mods);
    modifyRequest.addControl(new PreReadRequestControl("description"));
    modifyRequest.addControl(new PostReadRequestControl("description"));

    try {
      final LDAPResult result = ldapConnectionPool.modify(modifyRequest);
      if(result.hasResponseControl(PreReadResponseControl.PRE_READ_RESPONSE_OID)) {
        final Control c =
            result.getResponseControl(PreReadResponseControl.PRE_READ_RESPONSE_OID);
        final ReadOnlyEntry e = ((PreReadResponseControl)c).getEntry();
        System.out.println("the entry pre-modify:" + e);
      }
      if(result.hasResponseControl(PostReadResponseControl.POST_READ_RESPONSE_OID)) {
        final Control c =
            result.getResponseControl(PostReadResponseControl.POST_READ_RESPONSE_OID);
        final ReadOnlyEntry e = ((PostReadResponseControl)c).getEntry();
        System.out.println("the entry post-modify:" + e);
      }
    } catch (final LDAPException lex) {
      lex.printStackTrace();
    }
  }

  void replaceDescriptionValues() {
    /*
     * Replace 'description 1' with 'description 3'.
     */
    final List<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              "description",
                              "description 1",
                              "description 3"));
    final ModifyRequest modifyRequest = new ModifyRequest(entry,mods);
    modifyRequest.addControl(new PreReadRequestControl("description"));
    modifyRequest.addControl(new PostReadRequestControl("description"));

    try {
      final LDAPResult result = ldapConnectionPool.modify(modifyRequest);
      if(result.hasResponseControl(PreReadResponseControl.PRE_READ_RESPONSE_OID)) {
        final Control c =
            result.getResponseControl(PreReadResponseControl.PRE_READ_RESPONSE_OID);
        final ReadOnlyEntry e = ((PreReadResponseControl)c).getEntry();
        System.out.println("the entry pre-modify:" + e);
      }
      if(result.hasResponseControl(PostReadResponseControl.POST_READ_RESPONSE_OID)) {
        final Control c =
            result.getResponseControl(PostReadResponseControl.POST_READ_RESPONSE_OID);
        final ReadOnlyEntry e = ((PostReadResponseControl)c).getEntry();
        System.out.println("the entry post-modify:" + e);
      }
    } catch (final LDAPException lex) {
      lex.printStackTrace();
    }

  }

  private final String entry;

  private final LDAPConnectionPool ldapConnectionPool;

}

About Terry Gardner

Terry Gardner was a leading directory services architect with experience with many large scale directory services installations and messaging server installations, and was a Subject Matter Expert in the field of Directory Services and Solaris (operating system) performance. Mr. Gardner also participated in the open-source software community. Mr. Gardner passed away in December, 2013.
This entry was posted in computing, Java, LDAP, UnboundID LDAP SDK and tagged , , , , , , , . Bookmark the permalink.

One Response to LDAP: Replace a value of a multi-valued attribute

  1. Ray Cormier says:

    Great information, thanks Terry :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s