Using the Java Naming and Directory Interface (JNDI) (Part 1)

Standard

Been learning a lot at my new job at St. Mary’s University. The university has been able to serve the students, faculty and staff via the “Gateway.” The Gateway is a portal site using a Luminis system from SunGuard. SunGuard based their system on uPortal. One of the central servers is a LDAP server. It contains course information, user information, department information, information about the current term and permission groups. With this much importance on one server I thought it would be a good idea to learn about it and how Java could interact with it.

First some background into LDAP.   LDAP stands for Lightwieght DirectoryAccess Protocol. LDAP servers are a type of Directory Server witch hold collections of information(well like a directory). Good examples are phone directories. The difference between a phone directory and a directory server is that a directory server can hold a directory of any data rather than contact information. They are not replacements for relational databases. Directory servers are not designed to handle generalized data relationships like relational databases. One needs to think more one-to-one or one-to-many data sets for LDAP servers. Another difference is the speed of reading vs. writing. Most directory servers are very fast on reading data but slow on writing.  Each LDAP entry has a distinguished name or dn.  This is the key of the entry.  It is normally composed of multiple parts of attributes found in the entry.  The other part of an entry is attributes.  Attributes have a name and a value.  For example, an entry with a dn of “uid=mborn, ou=Users, dc=example, dc=com” has an attribute named common name or cn that has the value “Max Born.”  There can be multiple values for each attribute.  In the last example, there are multiple objectClass attributes, each one with a unique value.  ObjectClasses are important for LDAP entries because they define what attributes an entry can contain or have to contain.  ObjectClasses are defined in schemas.  Normally, these files are separate from the normal configuration files.  Many of the schemas used in LDAP servers are standardized so as long as the LDAP server supports a schema.  The schemas that the examples use are InetOrgPerson and Java.

To run the examples, I suggest downloading and installing both sub projects from The Apache Directory Project. The Apache Directory Studio alone makes the effort worth it. The studio is LDAPv3 compliant so any LDAPv3 server can be used. I started using it on OpenLDAP because I bought “Mastering OpenLDAP” researching this subject. I switched over to Apache’s Directory because of how easy it was to use with the studio in a Microsoft Windows environment. I used the example.ldif file contained the directory server’s configuration directory. The only modification to the file was removing the users that used references. I also added an organzational unit named java to save the java objects into. I also suggest downloading and installing a JDKv1.3 or later. Java 1.3 and newer already contain the JNDI libraries that will be used throughout. I developed these examples using Java 1.6.

Here is the first example, all this does is connect to a server anonymously and find the user associated with a uid of mborn.

package org.mathison.example.jndi;

import javax.naming.Context;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.DirContext;
import javax.naming.directory.Attributes;
import javax.naming.NamingException;

import java.util.Hashtable;

public class JndiClient {

static Hashtable getEnv() {
Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY, “com.sun.jndi.ldap.LdapCtxFactory”);
env.put(Context.PROVIDER_URL, “ldap://localhost:10389/dc=example,dc=com”);

return env;

}

/**
* @param args

*/
public static void main(String[] args) {

try {

DirContext ctx = new InitialDirContext(getEnv());
Attributes attr = ctx.getAttributes(“uid=mborn,ou=Users”);
System.out.println(“Mborn’s name is ” + attr.get(“cn”).get());
ctx.close();

} catch (NamingException e) {

e.printStackTrace();

}

}

}

First thing that happens is creating an DirContext.  For this an instance of Hashtable is passed to the InitialDirContext.  The hashtable sets up the Context that is used and where it will connect.  The URL has two parts, the address to the server and the directory in the server.  All of the entries that are written to or read from are assumed to be in that directory.  The next line returns the attributes for the entry that has a user id of mborn and is in the users organizational unit.  The System.out line retrieves the “cn” or common name from the entry and prints it out to the screen.  All of this is wrapped in a try-catch block in case a NamingException is thrown.  This is the basic query and retrieve from a LDAP server and is most of what happens.

While JNDI is excellent at pulling attributes from a LDAP server, its real power comes from when one starts storing Java objects on the server. That will be discussed on the next entry.

Links to resources used for this article:
JNDI Tutorial
Mastering OpenLDAP
The Apache Directory Project

Advertisements

Writing Fail-Fast Code

Standard

With projects as complex as they are now, it can be hard to modify all of the code needed when a change is made.  First is to identify all the places needed for modification. The next step is to write code that handles the new requirement. The former is typically harder than the latter. Inspect the following code.

static void processIfQuiet(Data d) {

if (d.getType() == DataType.XML) {

System.out.println(“XML Data”);

}

else if (d.getType() == DataType.LEGACY) {

System.out.println(“Legacy Data”);

}

}

This code is fine if no more data types are added.  When a new DataType is created, this code could be skipped and no one would know.  The code could be pushed to production incomplete and there will be errors that the customer will find or not depending where this snippet is located.  If this is for a data loader, the new data type could be falling through and not be stored at all.  Three months down the road when the customer is generating a report relying on the new data type, the report code blows up and it takes awhile to find the error.  Here is an implementation trick that I had to learn the hard way.

static void processIfLoud(Data d) {

if (d.getType() == DataType.XML) {

System.out.println(“XML Data”);

}

else if (d.getType() == DataType.LEGACY) {

System.out.println(“Legacy Data”);

}

else { // new part

assert false: “Unknown Data Type”;

throw new RuntimeException(“Unknown Data Type”);

}

}

The else clause will catch any unknown type either throw an AssertionError or a RuntimeException depending whether or not assertions are enabled. This message could be logged and not have an ugly Error or Exception popping up. However, I would call this a “Developer’s error” meaning that it should not have got past the developer in the first place. Making it ugly will make sure it will not be missed and can be fixed before the customer gets to see it. This can be used for select statements too. See the loud version of a select statement:

import static blog.mathison.example1.DataType.XML;

import static blog.mathison.example1.DataType.LEGACY;

static void processCaseLoud(Data d) {

DataType dt = d.getType();

switch(dt) {

case XML:

System.out.println(“XML Data”);

break;

case LEGACY:

System.out.println(“Legacy Data”);

break;

default:

assert false: “Unknown Data Type”;

throw new RuntimeException(“Unknown Data Type”);

}

}

Just adding a default will make it fail where the problem is rather than three months down the road.  Do you think this not a good idea?  Are there better ways?  Leave me a comment.