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

Standard

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.

Advertisements

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

Standard

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 https://github.com/darylmathison/jndi-example via subversion or your browser.