LDAP SDK persistence framework part 1: Annotations

As I mentioned in my LDAPCon presentation, I’ve been working recently on updating the UnboundID LDAP SDK for Java to provide a framework that makes it easy to store and retrieve Java objects in an LDAP directory server. The majority of this framework is complete and has been committed into the SourceForge repository (although it’s currently in the branches/persist directory rather than in the trunk), so you can check it out and build it for yourself if you’d like to give it a shot. It provides support for add, delete, modify, get, and search operations, as well as for encoding and decoding LDAP entries to and from Java objects, and for validating the set of annotations contained in an object. The primary components that have not yet been implemented are the tools for generating LDAP schema from a properly-annotated Java object, or for generating a Java source file from LDAP schema, but that will hopefully be added in the near future.

At the heart of the persistence framework is a set of annotations that can be used to mark your source code with information about how the object should be represented in an LDAP directory server. In this post, I’ll describe those annotations and how they can be used.

The LDAPObject Annotation

The @LDAPObject annotation is used to mark the class for an object that is to be stored in an LDAP directory server. The class must include a constructor that takes zero arguments, although it may have any or no access modifier. This annotation may include the following elements:

  • structuralClass — The name of the LDAP structural object class to use for entries created from objects of this type. If this is not provided, then it will be assumed that the name of the LDAP structural object class is the same as the unqualified Java class name.
  • auxiliaryClass — The name(s) of any LDAP auxiliary object classes to use for entries created from objects of this type. If this is not provided, then no auxiliary object classes will be included in generated entries.
  • defaultParentDN — The DN of the entry below which objects of this type are typically located in the directory. This is optional, and even if it is provided, you can override this value on a per-operation basis.
  • postDecodeMethod — The name of a method in the associated Java class that should be invoked after all other processing has been performed when decoding an LDAP entry to a Java object. If this is provided, then the specified method must exist in the entry and must not take any arguments. That method can be used to perform any additional processing not directly provided by the LDAP SDK persistence framework, or to perform custom validation of the resulting object (optionally throwing an exception if a problem is found).
  • postEncodeMethod — The name of a method in the associated Java class that should be invoked after all other processing has been performed to encode the object to an LDAP entry. If this is provided, then the specified method must exist in the entry and must take exactly one argument with a type of com.unboundid.ldap.sdk.Entry. This method can be used to alter the resulting entry, or to perform further validation of that entry (optionally throwing an exception if a problem is found).

The LDAPField Annotation

The @LDAPField annotation is used to mark fields in a class containing the @LDAPObject annotation. Each field marked with this annotation will be associated with a different attribute in an LDAP entry. The field may be marked with any or no access modifier (i.e., it can be public, private, protected, or have package-level access), and must not be declared either static or final. Any fields included in the class which are not marked with the @LDAPField annotation will not be used when encoding or decoding objects.

This annotation may include the following elements:

  • attribute — The name of the LDAP attribute used to hold the value(s) for this field. If this is not provided, then it will be assumed that the name of the LDAP attribute is the same as the name of the Java field.
  • objectClass — The name(s) of the object class(es) in which this attribute is referenced. This will primarily be used in the process of generating LDAP schema that may be used to store the associated type of object. If this is not provided, then it will be assumed that the attribute is included in the structural object class for the object. Only object classes named as structural or auxiliary classes in the @LDAPObject annotation may be specified.
  • inRDN — Indicates whether this field should be included in the RDN for entries created from the object. If this is not provided, then it will be assigned a default value of false. At least one field or getter method in the class must be marked for inclusion in the entry RDN.
  • inAdd — Indicates whether this field should be included in the entry created from the object as part of an LDAP add operation. If this is not provided, then it will be assigned a default value of true. Note that if inRDN is true, then the field will always be included in add operations regardless of the value of the inAdd element.
  • inModify — Indicates whether this field should be included in the set of modifications when performing an LDAP modify operation for the object. If this is not provided, then it will be assigned a default value of true. Note that if inRDN is true, then the field will never be included in the modify operations regardless of the value of the inModify element.
  • inFilter — Indicates whether this field may be included in the filter that is generated when trying to search for objects matching the provided object. If this is not provided, then it will be assigned a default value of false.
  • encoderClass — The fully-qualified name of the Java class that provides the logic for encoding and decoding the value(s) of the field to and from an LDAP attribute. If this is not provided, then a default of com.unboundid.ldap.sdk.persist.DefaultLDAPFieldEncoder will be used (more information about this class and the field encoder API will be provided in an upcoming post).
  • defaultDecodeValue — A default value (or set of values) that will be assigned to the field when decoding an LDAP entry to a Java object if the entry does not contain the associated attribute. If this is not provided, then the field will be assigned a value of null (or a default value for primitives) if the attribute is missing from the entry being decoded.
  • defaultEncodeValue — A default value (or set of values) that will be used for the attribute generated from the associated field if it has a null value. If this is not provided, then no attribute will be included in the generated entry if the field has a null value.
  • requiredForDecode — Indicates whether the process of decoding an LDAP entry to a Java object should fail if the associated attribute is not included in the entry and no default decode values are defined. If this is not provided, then it will be assigned a default value of false.
  • requiredForEncode — Indicates whether the process of encoding the field to an LDAP attribute should fail if the field has a null value and no default encode values are defined. If this is not provided, then it will be assigned a default value of false.
  • failOnInvalidValue — Indicates whether the process of decoding an LDAP entry to a Java object should fail if the associated attribute contains a value that cannot be assigned to the field (e.g., because the value cannot be parsed in a way compatible with the type of the Java field). If this is not provided, then it will be assigned a default value of true.
  • failOnTooManyValues — Indicates whether the process of decoding an LDAP entry to a Java object should fail if the associated attribute has multiple values when only a single value can be assigned to the Java field. If this is not provided, then it will be assigned a default value of true.

The LDAPFieldGetter Annotation

The @LDAPFieldGetter annotation may be used to mark a method which may be used to generate an attribute to include in entries or modifications generated from the object. It can be used for cases in which a value should only be used when encoding and not decoding, or if custom processing is needed to prepare the value for storing in the directory. Any methods marked with this annotation must not take any arguments, and must not be declared static. They can have any or no access modifier, including public, private, or protected.

This annotation may include the following elements:

  • attribute — The name of the LDAP attribute in which the value(s) returned from the method should be stored. This is a required element.
  • objectClass — The name(s) of the LDAP object class(es) in which this attribute is referenced. It has the same use as the objectClass element of the @LDAPField annotation.
  • inRDN — Indicates whether the value returned from this method should be included in the RDN for entries created from the object. If this is not provided, then it will be assigned a default value of false. At least one field or getter method in the class must be marked for inclusion in the entry RDN.
  • inAdd — Indicates whether the value(s) of this method should be included in the entry created from the object as part of an LDAP add operation. It has the same behavior as the inAdd element of the @LDAPField annotation.
  • inModify — Indicates whether the value(s) of this method should be included in the set of modifications when performing an LDAP modify operation for the object. It has the same behavior as the inModify element of the @LDAPField annotation.
  • inFilter — Indicates whether this field may be included in the filter that is generated when trying to search for objects matching the provided object. It has the same behavior as the inFilter element of the @LDAPField annotation.
  • encoderClass — The fully-qualified name of the Java class that provides the logic for encoding the method value(s) to an LDAP attribute. It has the same behavior as the encoderClass element of the @LDAPField annotation.

The LDAPFieldSetter Annotation

The @LDAPFieldSetter annotation may be used to mark a method that can be used to update a Java object based with information from an attribute in an LDAP entry. It can be used for cases in which an attribute should be read from but not written to an entry, or for cases in which custom processing is required to update the object from an attribute. Any methods marked with this annotation must take exactly one argument and must not be declared static. The methods can have any or no access modifier.

This annotation may include the following elements:

  • attribute — The name of the LDAP attribute that should be used to provide the argument used when invoking the method. This is a required element.
  • encoderClass — The fully-qualified name of the Java class that provides the logic for decoding the attribute value(s) to the argument used to invoke the method. It has the same behavior as the encoderClass element of the @LDAPField annotation.
  • failOnInvalidValue — Indicates whether the process of decoding an LDAP entry to a Java object should fail if the associated attribute contains a value that cannot be converted to the argument used to invoke the method. It has the same behavior as the failOnInvalidValue element of the @LDAPField annotation.
  • failOnTooManyValues — Indicates whether the process of decoding an LDAP entry to a Java object should fail if the associated attribute has multiple values when only a single value can be held in the argument used to invoke the method. It has the same behavior as the failOnTooManyValues element of the @LDAPField annotation.

The LDAPDNField Annotation

The @LDAPDNField annotation may be used to mark at most one field in the class which will be assigned the DN of the entry with which the object is associated. If a field with this annotation is included in the object, then it must have a type of java.lang.String, and it must not be declared static or final. It may have any or no access modifier. This annotation must not be used to mark a field that is also marked with the @LDAPField annotation.

If a field with this annotation is present in the object, then it will be assigned in the course of initializing the object with information from an LDAP entry, or in the course of generating an LDAP entry from the object contents. If set, the value of this field may be used to obtain the appropriate entry DN for add, delete, or modify operations.

Neither the @LDAPDNField nor the @LDAPEntryField annotations are required to be used in an object marked with the @LDAPObject annotation, but it is strongly recommended that at least one of them be included so that the correct entry DN will be available for LDAP operations involving the object.

The LDAPEntryField Annotation

The @LDAPEntryField annotation may be used to mark at most one field in the class which will be assigned a read-only copy of the entry with which the object is associated. If a field with this annotation is included in the object, then it must have a type of com.unboundid.ldap.sdk.ReadOnlyEntry, and it must not be declared static or final. It may have any or no access modifier. This annotation must not be used to mark a field that is also marked with the @LDAPField annotation.

If a field with this annotation is present in the object, then it will be assigned in the course of initializing the object with information from an LDAP entry, or in the course of generating an LDAP entry from the object contents. If set, the value of this field may be used to obtain the appropriate entry DN for add, delete, or modify operations. It may also be used to refine the set of modifications generated from an object for inclusion in a modify operation, so that modifications do not include changes to attributes that have not changed since the object was retrieved.

As mentioned above, although neither is required, it is strongly recommended that either or both the @LDAPDNField or @LDAPEntryField annotations should be included in objects to be persisted in the directory.