UnboundID LDAP SDK for Java 1.1.1

UnboundID has released version 1.1.1 of the UnboundID LDAP SDK for Java. This release adds a number of enhancements and bug fixes over the previous 1.1.0 release. The release notes provide a more complete overview of the changes in this release, but some of the most significant changes include:

  • We have found and corrected a bug that could cause the LDAP SDK to encounter an error and terminate the client connection if the server returned a response message in multiple parts, where the first part did not contain at least the complete BER type and length and there was a delay before the next packet(s) containing the remainder of the length.
  • There have been a number of significant updates to the searchrate, modrate, and authrate tools. The accuracy of the recent rates has been improved, and they have been updated to provide information about any non-success results returned by the server. The searchrate and modrate tools have been updated to support the use of the proxied authorization v2 control to request that operations be processed using an alternate authorization identity.
  • The ValuePattern class has been updated so that it supports obtaining data from files (either on the local filesystem or remotely via HTTP) rather than using numeric values within a specified range.
  • The behavior that the LDAP SDK exhibits when attempting to interact with a completely unresponsive server (e.g., for a system that is powered off, has the network cable disconnected, or has the server process forcibly stopped). Previously, the connect timeout may not have always been honored, and it was not possible to define operation timeouts for bind or extended operations. Further, the error message included in the exception resulting from a client-side timeout has been updated to include the type of operation and the length of time the client had waited for a response to the operation.
  • Client-side support for matching rule evaluation has been improved. It is now possible to select a matching rule by OID, and it is possible to make use of a matching rule ID in a sort key used for client-side sorting.
  • Schema parsing code has been updated to trim any leading or trailing spaces that may be contained in schema definitions. Even though leading or trailing spaces are technically not allowed, some servers don’t enforce this restriction, and the LDAP SDK has been updated to properly handle definitions containing them.

A Week with the Droid

At the beginning of the year, I got an Android Developer Phone 1 (basically an unlocked version of the T-Mobile G1) and was using it on T-Mobile’s network. For the most part, I loved the phone but hated T-Mobile’s network. If only I could have an Android phone on Verizon’s network, things would be perfect. So I waited anxiously for any news to come and eventually the rumors started to trickle in. Finally, Verizon launched the Droid last Friday and I was there early before the store opened, second in line to get one. I was in love. It’s a much faster phone than the G1, with a better display, better battery life, better camera, more memory, newer version of the operating system, better maps and navigation, better everything. Well, it didn’t have a better keyboard (the G1 keyboard has five rows of buttons with one row dedicated to numbers, while the Droid’s only has four and you have to use the alt key to hit the numbers which is kind of annoying, and also the G1 keys are more separated and feel better than those on the Droid).

My love for the Droid grew over the weekend. I saw four movies (two each on Friday and Saturday), and it kept me company while I was waiting for them to start. I downloaded and installed several apps from the Android market, most of them free but some of them purchased. However, that’s when the problems started. On Sunday evening, I purchased the Better Terminal Emulator Pro application and instantly the phone rebooted, and as soon as it came back up, it rebooted again, before I could do anything at all. And it kept rebooting. There was nothing that I could do to stop it. I found that if you hold down the “s” key or the menu key (on the keyboard, not the one below the screen), then the phone boots into safe mode, but even that wasn’t enough because it still rebooted. Trying to attach it to my computer over USB (from Linux or Windows) was unsuccessful, since it wasn’t getting far enough to allow me to see the device or attach to it.

Clearly, I couldn’t do anything at all with the phone in that state, so my only option was to take it back to the Verizon store on Monday morning, and they replaced it for me. Because it was tied to my Google account, all of my contacts and apps synced down without any problems, so it wasn’t too much of a hassle to get the second one configured like I had the first one. I did most of it in the car before leaving the Verizon store to head into work.

Surely it was just a bad phone and everything would be fine with the new one. Unfortunately, that wasn’t the case. The battery in the new phone wasn’t fully charged, and as soon as I plugged it in when I got into the office, it rebooted. Fortunately, it only rebooted once and didn’t enter an infinite reboot loop like the last one, but I was nervous. One of the applications that had automatically synced was the Better Terminal Emulator Pro application that had seemingly triggered the problem on my first one, so I thought it would be safer to get it off the phone in case it did something to make things unstable. So I uninstalled and refunded the app, and instantly the phone rebooted, and it kept rebooting nonstop. Another trip to the Verizon store (a different store this time, mainly because it was closer to the office) over lunch and I got my third Droid. The problem app wasn’t ever on this phone, and things generally seemed to be OK., although I still noticed occasional reboots, especially if I got a text message.

Earlier today, I was working and signed up for an online service that needed to activated over the phone. I entered my phone number and waited for it to call, but nothing happened. That was when I noticed that my phone was again stuck in a reboot loop. Once again to the Verizon store. This time, I was determined to not let it happen again. I made sure that it was completely new hardware (e.g., so they didn’t move the memory card from my previous one to the new one) and I even used a completely separate Google account. I proceeded with extreme caution, and things were looking positive for a while. However, again since it was a new battery it wasn’t fully charged so I plugged it in to charge it, and fortunately it didn’t reboot. I sent it a text message and it didn’t reboot then either. So things were finally fixed, right? Nope. A while later I looked at the phone and noticed that it was once again stuck in a reboot loop. Fortunately, when I unplugged it, it stopped rebooting, and it started back up when I plugged it in again. So I left it unplugged and let it come back up, and then I sent it a text message and it rebooted right away.

At this point, I’m stuck. I really want to love the Droid, and when it’s not rebooting it’s an extremely nice device. But if it crashes when I plug it in to charge it, or when I receive a text message or a phone call, then it’s really not of any use to me. At this point, I think that my only recourse is to take it back and swap it out for the HTC Droid Eris (which is also on Verizon’s network and also launched last Friday). It’s not as nice a phone as the Motorola Droid pretty much all the way around (smaller screen with a lower resolution, less memory, no physical keyboard, not running Android 2.0), but HTC has more experience building Android devices than Motorola so hopefully it will be stable. So I’m off to the Verizon store again tomorrow, hopefully for the last time in quite a while.

LDAP SDK persistence framework part 2: Supported data types

Another very important aspect of the persistence framework being developed for the UnboundID LDAP SDK for Java is the set of data types that it supports. In order to be able to correctly store and retrieve Java objects in a directory server, the LDAP SDK has to now how to encode Java values to LDAP attributes, and then decode the values of those attributes back to their Java representations. The LDAP SDK provides an abstract LDAPFieldEncoder class which defines the API needed to achieve this, as well as other things like generating LDAP attribute type definitions for a particular field or method. The @LDAPField, @LDAPFieldGetter, and @LDAPFieldSetter annotation types all include elements that allow you to specify the type of encoder that should be used when interacting with the associated field or method.

If the encoderClass element isn’t provided in the annotation, then the LDAP SDK will use an instance of the DefaultLDAPFieldEncoder class. This class supports a pretty broad range of data types, including:

  • boolean primitives
  • double primitives
  • float primitives
  • int primitives
  • long primitives
  • short primitives
  • byte[] objects
  • char[] objects
  • java.lang.Boolean objects
  • java.lang.Double objects
  • java.lang.Float objects
  • java.lang.Integer objects
  • java.lang.Long objects
  • java.lang.Short objects
  • java.lang.String objects
  • java.lang.StringBuffer objects
  • java.lang.StringBuilder objects
  • java.math.BigDecimal objects
  • java.math.BigInteger objects
  • java.util.Date objects
  • java.util.UUID objects
  • java.util.concurrent.atomic.AtomicInteger objects
  • java.util.concurrent.atomic.AtomicLong objects
  • com.unboundid.ldap.sdk.DN objects
  • com.unboundid.ldap.sdk.Filter objects
  • com.unboundid.ldap.sdk.LDAPURL objects
  • com.unboundid.ldap.sdk.RDN objects

In addition, it also supports arrays of all of the above types for dealing with multi-valued attributes. In the future, I intend to add add support for using java.util.Lists and java.util.Sets for all of the above types as well, but that’s not in the current implementation, and generics make that a bit tricky.

For most of the above data types, the value stored in the directory server is simply the string representation of the associated primitive or object. However, there are a few special cases:

  • boolean primitives and java.lang.Boolean objects are always stored using values of “TRUE” or “FALSE” (in all uppercase), as per the LDAP Boolean syntax.
  • byte[] objects are used to represent the raw bytes that comprise the attribute value
  • char[] objects are used to represent the raw characters that comprise the attribute value
  • java.util.Date objects are encoded using the LDAP generalized time syntax

Even though the default field encoder supports a number of primitive types (although it doesn’t support either byte or char in order to avoid confusion), I would generally recommend that you use the corresponding objects in the java.lang package instead. The reason for this is that primitives can’t have a null value, so it’s not possible to distinguish between cases in which there is no value and cases where there is a value that happens to be the same as the default value for that primitive (zero for numeric types and false for boolean). If you use the corresponding object types instead, then they can have null values, and you can use that to determine if a value has been assigned.

If you need to store some other kind of object in the directory that isn’t in this list, then you’ll need to create your own subclass of LDAPFieldEncoder with the appropriate logic for encoding and decoding values. If this is necessary, then I’d strongly recommend using DefaultLDAPFieldEncoder as a starting point.

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.

LDAPCon recorded presentation posted

I have now updated the UnboundID website to include a recorded version of the presentation that I gave at LDAPCon. The total presentation is right at 45 minutes, but I’ve broken it up into a few pieces so that you don’t have to try to digest it all at once.

You can find the recorded presentation, as well as a copy of the slides, at http://www.unboundid.com/products/ldapsdk/LDAPCon-2009-presentation/index.php.

LDAPCon slides posted

The LDAP SDK presentation that I gave Monday at LDAPCon went pretty smoothly and seemed to be pretty well-received. When I got home yesterday afternoon, I recorded a version of the presentation and we’ll hopefully be making that available online in the near future. However, a version of the slides that I used for the presentation is now available online at http://www.symas.com/ldapcon2009/papers/ely1.shtml.

Slides and/or papers for some of the other presentations are also available online. You can see them at http://www.symas.com/ldapcon2009/papers.shtml.

LDAP SDK now in Maven Central Repository

Just in time for my talk at LDAPCon on Monday, it looks like the UnboundID LDAP SDK for Java is now available in the Maven Central Repository.

You can get it with the following information

  • groupId — com.unboundid
  • artifactId — unboundid-ldapsdk
  • version — 1.1.0

The link to the POM file for this release is at http://repo1.maven.org/maven2/com/unboundid/unboundid-ldapsdk/1.1.0/unboundid-ldapsdk-1.1.0.pom.

UnboundID LDAP SDK for Java 1.1.0

UnboundID has released version 1.1.0 of the UnboundID LDAP SDK for Java. This is a significant update over the previous 1.0.0 release, both in terms of functionality and project policies. You can see the release notes for more detailed information about the changes, but some of the most significant updates include:

  • The LDAP SDK is now covered by the GNU Lesser General Public License (LGPL) version 2.1. This is an addition to the existing GPLv2 and UnboundID LDAP SDK Free Use Licenses that were already in use for the project. LGPLv2.1 is an OSI-approved open source license that is very friendly for use in both open source and proprietary applications.
  • As previously mentioned, the LDAP SDK is now available on SourceForge, including a public source repository, mailing lists, forums, and download files.
  • The LDAP SDK has been updated to make it easier for use with the Apache Maven build system. It should be included in the Central Repository in the near future, but in the meantime, you can use a repository URL of http://ldap-sdk.sourceforge.net/maven/ with a groupId of “com.unboundid”, an artifactId of “unboundid-ldapsdk”, and a version of “1.1.0”.
  • The LDAP SDK jar file has been updated so that its manifest includes the appropriate headers to make it easier to use with the OSGi module system.
  • We have added code to help make it easier to migrate applications written using the Netscape Directory SDK for Java or JNDI to the UnboundID LDAP SDK for Java. The LDAP SDK contains a number of analogs for classes in the Netscape Directory SDK for Java, and in many cases you can simply change import statements from “netscape.ldap” to “com.unboundid.ldap.sdk.migrate.ldapjdk” and recompile your code. If you’re currently using JNDI, then you can use classes in the “com.unboundid.ldap.sdk.migrate.jndi” package to aid in the conversion process.
  • The LDAP SDK has been updated to make it easier to have server schema information taken into account when reading responses. A new “use schema” connection option has been defined, and when it is enabled then the LDAP SDK will use the server schema to select appropriate matching rules for attributes in search result entries. A number of other schema-related improvements have also been made.
  • The searchrate, modrate, and authrate tools have been updated to provide the ability to load-balance requests across multiple servers, to include timestamps in the output, and to run for a specified number of warm-up intervals before beginning overall statistics collection.

LDAP SDK now on SourceForge

Earlier today, I created a SourceForge project for the UnboundID LDAP SDK for Java. You can find it at http://sourceforge.net/projects/ldap-sdk/. This project contains a subversion repository containing the source code for the Standard Edition, as well as a set of mailing lists and a forum and a download of the current 1.0.0 release. You can now easily check out and build the LDAP SDK for yourself.

This makes it easy to see what we’re working on without having to wait for the next release (which currently includes things like OSGi support and easier migration from other Java-based LDAP APIs), and if you do run into a problem of some kind that requires a code fix, you can get access to that fix without needing to wait for a new release and without requiring us to provide you with a private build.

UnboundID LDAP SDK for Java 1.0.0

UnboundID has released version 1.0.0 of the UnboundID LDAP SDK for Java. We have felt for a long time that it was more than stable enough for use in production applications (and in fact it is in use in production applications in several places), but had not yet declared it 1.0 just in case we received any feedback that might have caused us to consider altering some portion of the API in an incompatible way. Although we have gotten several comments and suggestions, and we have added a number of features in response to that feedback, there haven’t been any incompatible changes and we feel very confident that there won’t need to be any such changes in the future (and have an extensive test framework in place to ensure that doesn’t happen). We are therefore bumping the version number to 1.0.0 just in case the pre-1.0 version number caused anyone to shy away from using it.

A complete list of the changes since the 0.9.10 release can be found in the release notes, but some of the most significant changes include:

  • We have added support for the permissive modify control, which may be used to request that the directory server not reject attempts to add an attribute value that already exists or remove an attribute value that does not yet exist.
  • We have added a new LDAPThreadLocalConnectionPool class, which provides an alternate connection pool implementation that uses a separate connection per client thread that attempts to use the connection pool. This eliminates the need to explicitly configure the number of connections to maintain, and also eliminates contention between threads when trying to use the pool.
  • We have added the ability to create a Schema object by reading definitions from one or more LDIF files. It was previously only possible to create a Schema object by reading it from a directory server over LDAP.
  • We have updated the LDIF reader to provide the ability to reject entries that contain duplicate values for the same attribute in the same entry. We have also added the ability to use schema information when reading data from LDIF to help avoid incorrectly classifying values as duplicates (e.g., if two values differ only by capitalization, but are in an attribute type defined with a caseExactMatch equality matching rule).
  • We have updated the validate-ldif tool so that it has the ability to use schema definitions read from files (allowing it to be used in a completely offline manner), and to take advantage of the ability to reject entries with duplicate attribute values (although this can be disabled if desired).