Using the Java Naming and Directory Interface (JNDI) (Part 2)
In my last post, I discussed directory servers and a quick example of how to query them. To read part one click here. JNDI can be used to store whole objects into a LDAP server. In this entry, a java object will be stored on the server directly.
First example uses a mechanism that Java has had since the beginning, serialization. I need to define a user class. The following class is from User.java:
public class User implements Serializable {
private static final long serialVersionUID = 3999866113934116781L;
private String name;
private String userid;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
It is just a number of setters and getters for a name, user id and email. It does implement java.io.Serializable to make it work for storage. Here is the class that uses User.java.
public class JavaObjectLookup {
static Hashtable<String, String> getEnv() {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, “com.sun.jndi.ldap.LdapCtxFactory”);
env.put(Context.PROVIDER_URL, “ldap://localhost:10389/ou=java,dc=example,dc=com”);
return env;
}
public static void main(String[] args) {
DirContext ctx = null;
try {
ctx = new InitialDirContext(getEnv());
// first bind an object to the Directory
User user = new User();
user.setName(“Joey”);
user.setUserid(“joey”);
user.setEmail(“joey@example.com”);
ctx.bind(“cn=joey”, user);
User u = (User)ctx.lookup(“cn=joey”);
System.out.println( “User’s email is “ + u.getEmail());
} catch (NamingException e) {
e.printStackTrace();
}
finally {
try {
ctx.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Notice that the connection URL has been changed to “ou=java,dc=example,dc=com” so the object will be put into the java organizational unit. On the LDAP server, the entry looks like the following:
dn: cn=joey,ou=java,dc=example,dc=com
objectClass: javaSerializedObject
objectClass: javaObject
objectClass: javaContainer
objectClass: top
cn: joey
javaClassName: org.mathison.example.jndi.User
javaSerializedData:: rO0ABXNyAB5vcmcubWF0aGlzb24uZXhhbXBsZS5qbmRpLlVzZXI3gmE
J1ipHrQIAA0wABWVtYWlsdAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABTAAGdXNlcm
lkcQB+AAF4cHQAEGpvZXlAZXhhbXBsZS5jb210AARKb2V5dAAEam9leQ==
javaClassNames: org.mathison.example.jndi.User
javaClassNames: java.lang.Object
javaClassNames: java.io.Serializable
The instance is stored as a base-64 encoded string. This is good for storing an instance that a lot of different java processes will access such as a printer driver. But what if a service wants to register its services on a LDAP server? It is no good to store a copy of a service on a directory server because a service is only useful if it can serve. It would be better if the entry just “referred” at the service rather being a copy of it. This is the rational for using javax.naming.Reference. To store a reference, a developer can have the class implement java.naming.Referenceable and call bind on the object or create a reference and bind the reference. On retrieving the reference, a developer can create an object from the information in the reference instance. If an object factory is used, the lookup returns an instance of your class. The following example has ReferUser that implements Referencable and uses an object factory.
Example code:
public class JavaObjectRefLookup {
static Hashtable<String, String> getEnv() {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, “com.sun.jndi.ldap.LdapCtxFactory”);
env.put(Context.PROVIDER_URL, “ldap://localhost:10389/ou=java,dc=example,dc=com”);
return env;
}
/**
* @param args
*/
public static void main(String[] args) {
DirContext ctx = null;
try {
ctx = new InitialDirContext(getEnv());
// first bind an object to the Directory
ReferUser user = new ReferUser();
user.setName(“Joey”);
user.setUserid(“joey”);
user.setEmail(“joey@example.com”);
ctx.rebind(“cn=joey”, user);
ReferUser u = (ReferUser)ctx.lookup(“cn=joey”);
System.out.println( “User’s email is “ + u.getEmail());
} catch (NamingException e) {
e.printStackTrace();
}
finally{
try {
ctx.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
import org.mathison.jndi.example2.User;
public class ReferUser extends User implements Referenceable {
public Reference getReference() throws NamingException {
Reference ref = new Reference(this.getClass().getName(),
ReferUserFactory.class.getName(), null);
ref.add(new StringRefAddr(“name”, getName()));
ref.add(new StringRefAddr(“mail”, getEmail()));
ref.add(new StringRefAddr(“uid”, getUserid()));
return ref;
}
}
import javax.naming.directory.Attributes;
import javax.naming.spi.DirObjectFactory;
public class ReferUserFactory implements DirObjectFactory {
@Override
public Object getObjectInstance(Object arg0, Name arg1, Context arg2, Hashtable<?, ?> arg3, Attributes arg4) throws Exception {
return getObjectInstance(arg0, arg1, arg2, arg3);
}
@Override
public Object getObjectInstance(Object arg0, Name arg1, Context arg2, Hashtable<?, ?> arg3) throws Exception {
ReferUser user = null;
if (arg0 instanceof Reference){
Reference ref = (Reference)arg0;
if(ref.getClassName().equals(ReferUser.class.getName())) {
RefAddr name = ref.get(“name”);
RefAddr uid = ref.get(“uid”);
RefAddr email = ref.get(“mail”);
user = new ReferUser();
user.setName(name.getContent().toString());
user.setEmail(email.getContent().toString());
user.setUserid(uid.getContent().toString());
}
}
return user;
}
}
Here is what you will find if you take a look at the Directory Server:
dn: cn=joey,ou=java,dc=example,dc=com
objectClass: javaContainer
objectClass: javaNamingReference
objectClass: javaObject
objectClass: top
cn: joey
javaClassName: org.mathison.example.jndi.ReferUser
javaFactory: org.mathison.example.jndi.ReferUserFactory
javaReferenceAddress: #0#name#Joey
javaReferenceAddress: #1#mail#joey@example.com
javaReferenceAddress: #2#uid#joey
Notice that the object’s attributes are now stored in javaReferenceAddress attributes. Also notice how each of these techniques used special java storage attributes to the job done. Well, the next part of this blog series will tackle creating a object without special java structures. All of this code can be downloaded from http://darylmathisonblog.googlecode.com/svn/trunk/ via subversion or your browser.
Using the Java Naming and Directory Interface (JNDI) (Part 1)
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.
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
Writing Fail-Fast Code
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.
Welcome
Big Hello!!
First a little about myself. I have been developing some part of an enterprise system in Java since 1998. I have had experience with RMI, JDBC, Swing, Servlets, ADF, and OAF. I am currently studying for my Sun Certification Java Programmer 6. Here are some links I have found useful in Java developing:
http://java.sun.com – This is ground zero for Java Development news and links.
http://java.sun.com/javase/6/docs/api/ – API Specification for Java 6 SE. I have this page always open when I am programming.
http://java.sun.com/javase/downloads/index.jsp – Download JDKs, documentation and development bundles.
Those are what I can think of now. I will post more as I think of them.