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

Introduction

In the last two posts about using JNDI, I have shown examples of how one can get and add information in a number of ways. The first post discussed LDAP servers and showed a quick program one could use to retrieve entries stored in the server.  The entry was represented as a collection of attributes.  The second post demonstrated a number of ways to store and retrieve Java objects on the server.  The two examples demonstrated how objects could be saved in base-64 or as a reference to another object.  This post will demonstrate how a set of attributes can be turned into a Java object and how to store that object as a set of attributes.

Setup

To make a Java object get saved as a set of entry attributes, I have to refactor the User class.  By implementing the DirContext, the class will organize itself into a set of attributes. Here is the new version of User, AttrUser.

public class AttrUser extends DirContextAdapter {

public static final String INIT_VALUE = “none”;

public static final String NAME_ATTR = “cn”;

public static final String UID_ATTR = “uid”;

public static final String EMAIL_ATTR = “mail”;

public static final String SN_ATTR = “sn”;

private Attributes attrs;


public AttrUser() {

attrs = new BasicAttributes(true);

Attribute oc = new BasicAttribute(“objectclass”);

oc.add(“inetOrgPerson”);

oc.add(“top”);


attrs.put(oc);

attrs.put(NAME_ATTR, INIT_VALUE);

attrs.put(UID_ATTR, INIT_VALUE);

attrs.put(EMAIL_ATTR, INIT_VALUE);

attrs.put(SN_ATTR, INIT_VALUE);

}

public String getName() {

String name = null;

try {

Attribute a = attrs.get(NAME_ATTR);

name = a.get().toString();

} catch (NamingException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

return name;

}


public void setName(String name) {

Attribute a = attrs.get(NAME_ATTR);

a.clear();

a.add(name);

}


public String getUserid() {

String userid = null;

try {

Attribute a = attrs.get(UID_ATTR);

userid = a.get().toString();

} catch(NamingException ne) {

ne.printStackTrace();

}

return userid;

}


public void setUserid(String userid) {

Attribute a = attrs.get(UID_ATTR);

a.clear();

a.add(userid);

}


public String getEmail() {

String email = null;

try {

Attribute a = attrs.get(EMAIL_ATTR);

email = a.get().toString();

} catch(NamingException ne) {

ne.printStackTrace();

}

return email;

}

public void setEmail(String email) {

Attribute a = attrs.get(EMAIL_ATTR);

a.clear();

a.add(email);

}


public void setLastname(String lastname) {

Attribute a = attrs.get(SN_ATTR);

a.clear();

a.add(lastname);

}


public String getLastname() {

String lastname = null;

try {

Attribute a = attrs.get(SN_ATTR);

lastname = a.get().toString();

} catch(NamingException ne) {

ne.printStackTrace();

}

return lastname;

}

public Attributes getAttributes(Name arg0) throws NamingException {

return getAttributes(arg0.toString());

}


public Attributes getAttributes(String arg0) throws NamingException {

if(arg0 != null && arg0.length() != 0) {

throw new NameNotFoundException();

}

return (Attributes)attrs.clone();

}


@Override

public Attributes getAttributes(Name arg0, String[] arg1) throws NamingException {

return getAttributes(arg0.toString(), arg1);

}


@Override

public Attributes getAttributes(String arg0, String[] arg1) throws NamingException {

if (arg0 != null && arg0.length() > 0) {

throw new NameNotFoundException();

}

Attributes ret = new BasicAttributes(true);


for (String id: arg1) {

ret.put(attrs.get(id));

}

return ret;

}

}

The class looks a lot different now. Notice that backed the whole class with an instance of Attributes.  I thought the code looked cleaner that way.  Also notice that I extended a DirContextAdapter instead of implementing DirContext.  To keep clean looking code, created an adapter.   Another reason is because the context provider looks to store java objects in a certain order. The provider will look to store a java object as a reference first, serialized second and a context third.  So If I had extended User.java, the example would have not worked.  Another small difference is I now added lastname as one of the attributes to store.  Lastname is a required attribute of InetOrgPerson, and since I want AttrUser to be accepted like an InetOrgPerson, it needs to keep track like one. The other class needed is the factory that will turn the context attributes back into an AttrUser.

public class AttrUserFactory implements DirObjectFactory {

@Override

public Object getObjectInstance(Object arg0, Name arg1, Context arg2,

Hashtable arg3) throws Exception {

// because this is for an attributes based object, this factory method simply returns null.

return null;

}

@Override

public Object getObjectInstance(Object arg0, Name arg1, Context arg2, Hashtable arg3, Attributes arg4) throws Exception {

AttrUser u = null;

if (checkObject(arg0, arg4)) {

u = new AttrUser();

Attribute a = arg4.get(AttrUser.NAME_ATTR);

u.setName(a.get().toString());


a = arg4.get(AttrUser.UID_ATTR);

u.setUserid(a.get().toString());


a = arg4.get(AttrUser.EMAIL_ATTR);

u.setEmail(a.get().toString());


a = arg4.get(AttrUser.SN_ATTR);

u.setLastname(a.get().toString());

}

return u;

}


private boolean checkObject(Object o, Attributes att) {

boolean isAttrUser = false;


if (o instanceof DirContext) {

if(att != null && att.size() >= 4) {

boolean areAttrThere = true;

areAttrThere &= (att.get(AttrUser.NAME_ATTR) != null);

areAttrThere &= (att.get(AttrUser.SN_ATTR) != null);

areAttrThere &= (att.get(AttrUser.EMAIL_ATTR) != null);

areAttrThere &= (att.get(AttrUser.UID_ATTR) != null);

isAttrUser = areAttrThere;

}

}

return isAttrUser;

}

}

This class implements the same DirObjectFactory as the last time but fills out the attribute based function. According to the DirObjectFactory contract, the function needs to return null if it is not a match for that data. Context providers can handle a range of factories.  It goes to each one and runs the factory call.  If the returned value is non-null or an exception, the traversal ends and it returns the value or allows the exception to be caught.  The setup is done, lets put it all together.

The Lookup

public class AttributeLookup {

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=Users,dc=example,dc=com”);

env.put(Context.OBJECT_FACTORIES, AttrUserFactory.class.getName());


return env;

}

public static void main(String[] args) {

DirContext ctx = null;

try {

ctx = new InitialDirContext(getEnv());


AttrUser user = new AttrUser();

user.setEmail(“joey@example.com”);

user.setLastname(“Bourne”);

user.setName(“Joey”);

user.setUserid(“joey”);


ctx.rebind(“uid=” + user.getUserid(), user);

// read

AttrUser newUser = (AttrUser)ctx.lookup(“uid=” + user.getUserid());

System.out.println(“email is “ + newUser.getEmail());


} catch (NamingException e) {

e.printStackTrace();

}

finally{

try {

ctx.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

This looks a lot like the other examples that I have covered. The one exception is where I defined my object factory. If I did not include the object factory, the lookup would have returned a set of Attributes like on the first example. The data stored in the server shows this to be true.

dn: uid=joey,ou=Users,dc=example,dc=com

objectClass: organizationalPerson

objectClass: person

objectClass: inetOrgPerson

objectClass: top

cn: Joey

sn: Bourne

mail: joey@example.com

uid: joey

In fact, I could have just used the object factory and read any user entry from the server and have the entries turned into AttrUser objects and not have to worry about implementing DirContext.

That was Fun

That is the last demonstration I have right now for JNDI.  I have shown a simple entry lookup, how to store a Java Object directly and indirectly on the server to objects that create entries and those entries can be read back into a Java object.  If you have anymore JNDI knowledge to share, leave a comment.  The source code can be found at https://github.com/darylmathison/jndi-example.

Advertisement