As mentioned in my previous blog entry on MQ and Weblogic interoperability titled "Oracle Weblogic 10.3 and IBM MQ - Can They Play Nicely?", the RefFSContextFactory JNDI classes provide the logical link between those two normally separate worlds. Having never delved into the inner workings of this part of the javax class hierarchy, I decided to see what was happening under the covers.

Let's assume we'd like to store a Greeting class in the RefFSContext JNDI implementation. To do so, first we define that POJO class:

package com.davidtiller.test;
 
public class Greeting {
 private String title;
 private String name;
 private String salutation;
 
 public Greeting() {
 } 
 public String getTitle() {
 return title;
 }
 public void setTitle(String title) {
 this.title = title;
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 public String getSalutation() {
 return salutation;
 }
 public void setSalutation(String salutation) {
 this.salutation = salutation;
 }
 public String getGreeting() {
 return salutation + ", " + title + " " + name;
 }
}

This Greeting has a salutation, a title, and a name. We can't store the Greeting directly since the RefFSContext implementation only stores javax.naming.Reference objects or objects that implement javax.naming.Referenceable. We must therefore first create a Reference to the Greeting and add the 'Address' attributes we want.

Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:BindingsDir");
 
try {
 Context ctx = new InitialContext(env);
 
 // Test 1 - create a Reference that describes a Greeting object
 Reference ref = new Reference(Greeting.class.getName(), GreetingFactory.class.getName(), null);
 
 // Add the RefAddrs we'd like to use when the object is created
 ref.add(new StringRefAddr("salutation", "Hello"));
 ref.add(new StringRefAddr("title", "Dr."));
 ref.add(new StringRefAddr("name", "Watson"));
 
 // Bind it to the name "Greeting"
 ctx.rebind("Greeting", ref);
}

Notice that we never actually created a Greeting class - we simply refer to it by name, and add the desired StringRefAddrs. When we bind it to the JNDI context, the file 'BindingsDir/.bindings' is created with the contents:

#This file is used by the JNDI FSContext.
#Wed Jun 23 09:20:35 EDT 2010
Greeting/ClassName=com.davidtiller.test.Greeting
Greeting/FactoryName=com.davidtiller.test.GreetingFactory
Greeting/RefAddr/0/Content=Hello
Greeting/RefAddr/0/Encoding=String
Greeting/RefAddr/0/Type=salutation
Greeting/RefAddr/1/Content=Dr.
Greeting/RefAddr/1/Encoding=String
Greeting/RefAddr/1/Type=title
Greeting/RefAddr/2/Content=Watson
Greeting/RefAddr/2/Encoding=String
Greeting/RefAddr/2/Type=name

In this example the code that generates the Reference object for the greeting has to know an awful lot about the innards of the Greeting itself. Wouldn't it be better if the Greeting object could somehow encapsulate the logic to generate its own Reference? Enter javax.naming.Referenceable - this interface defines one method - getReference - that does exactly that. The following class extends our Greeting class to add this functionality (I chose to do it this way to keep the two concepts separate and to show how little code is needed in the getReference method. I could have just as easily put that code in the Greeting class, but that would've been more confusing).

public class ReferenceableGreeting extends Greeting implements Referenceable {
 public Reference getReference() throws NamingException {
 Reference ref = new Reference(this.getClass().getName(), GreetingFactory.class.getName(), null);
 ref.add(new StringRefAddr("salutation", getSalutation()));
 ref.add(new StringRefAddr("title", getTitle()));
 ref.add(new StringRefAddr("name", getName()));
 
 return ref;
 }
}

This new method creates a Reference object for this class and factory (more on that in a minute), and adds the values of the current fields as StringRefAddrs. In order to store this new class, one must simply create a ReferenceableGreeting with the desired properties and bind the ReferenceableGreeting object itself - the bind method will notice that the object implements Referenceable and will call the getReference method on your behalf:

// Test 2 - create a ReferenceableGreeting and store it. Since
// we're using the RefFSContextFactory, it will call getReference()
// on the object and store that instead of the object itself.
 
ReferenceableGreeting rg = new ReferenceableGreeting();
rg.setSalutation("Good Evening");
rg.setTitle("Mr.");
rg.setName("Holmes");
 
// Bind the actual object. Behind the scenes this will result in
// a Reference to the object being bound.
ctx.rebind("ReferenceableGreeting", rg);

The results of this binding are shown below and will be added to the .bindings file defined when the context was created. As you can see, there is no difference between storing a hand-build Reference object and one generated via the getReference method called on your behalf.

ReferenceableGreeting/ClassName=com.davidtiller.test.ReferenceableGreeting
ReferenceableGreeting/FactoryName=com.davidtiller.test.GreetingFactory
ReferenceableGreeting/RefAddr/0/Content=Good Evening
ReferenceableGreeting/RefAddr/0/Encoding=String
ReferenceableGreeting/RefAddr/0/Type=salutation
ReferenceableGreeting/RefAddr/1/Content=Mr.
ReferenceableGreeting/RefAddr/1/Encoding=String
ReferenceableGreeting/RefAddr/1/Type=title
ReferenceableGreeting/RefAddr/2/Content=Holmes
ReferenceableGreeting/RefAddr/2/Encoding=String
ReferenceableGreeting/RefAddr/2/Type=name

Notice that I slipped a GreetingFactory class in there. In order for the naming subsystem to 'reinflate' a stored Reference to an object, a factory that implements javax.naming.spi.ObjectFactory and is capable of interpreting the RefAddrs for the object must be provided. In this case, a simple factory will do.

public class GreetingFactory implements ObjectFactory {
 public GreetingFactory() {
 }
 
 @SuppressWarnings("unchecked")
 public Object getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable<>, ?> environment) throws Exception {
 if (refInfo instanceof Reference) {
 Reference ref = (Reference) refInfo;
 
 // Get instance of class using no-arg constructor. It must be a Greeting
 // or a subclass of Greeting. We don't check the className for validity.
 ClassGreeting> clazz = (ClassGreeting>) Class.forName(ref.getClassName());
 ConstructorGreeting> constructor = clazz.getConstructor((Class[]) null);
 Greeting greeting = constructor.newInstance((Object[]) null);
 
 // Set properties from the supplied RefAddrs. We could use reflection.
 greeting.setSalutation(getStringAddress(ref, "salutation"));
 greeting.setTitle(getStringAddress(ref, "title"));
 greeting.setName(getStringAddress(ref, "name"));
 
 return greeting;
 } else {
 return refInfo;
 }
 }
 
 private String getStringAddress(Reference ref, String addrType) {
 String value = null;
 
 RefAddr ra = ref.get(addrType);
 if (ra != null) {
 value = ra.getContent().toString();
 }
 
 return value;
 }
}

The contract of the getReference interface requires a no arg constructor for the factory class and that the getReference method return the reference info parameter if this factory can't handle the given Reference. As usual, strict input validation and error checking are not included in these examples to make the code clearer for the reader. Because we use reflection to create a class instance, this factory can handle any subclass of Greeting (like ReferenceableGreeting), as long as the attributes are limited to those in Greeting. If further abstraction is desired, one could write a fully reflection-based class that creates the desired object and sets the properties by reflection as well. This factory would suffice for any class that had simple, String or byte[] properties whose names were used as the RefAddrs in the Reference.

Pulling the objects back out of JNDI is straightforward, and is essentially the same regardless of how it was stored.

// Fetch a Greeting object out of JNDI. Remember, we bound a
// Reference object, NOT a Greeting object.
Greeting g = (Greeting) ctx.lookup("Greeting");
System.out.println(g.getGreeting());

This code snippet results in "Hello, Dr. Watson". For the ReferenceableGreeting, I added a few lines of code to prove that the retrieved object is not the same as the one stored.

// Note the object ID before binding it for comparison
 
System.out.println("ReferenceableGreeting being bound is " + rg.toString());
 
// Bind the actual object. Behind the scenes this will result in
// a Reference to the object being bound.
 
ctx.rebind("ReferenceableGreeting", rg);
 
// Yank it back out, and it's been recreated.
 
rg = (ReferenceableGreeting) ctx.lookup("ReferenceableGreeting");
 
// Note that this object ID is different from the bound one
 
System.out.println("ReferenceableGreeting retrieved is " + rg.toString());
System.out.println(rg.getGreeting());

This results in output like the following:

ReferenceableGreeting being bound is com.davidtiller.test.ReferenceableGreeting@55e83f9
ReferenceableGreeting retrieved is com.davidtiller.test.ReferenceableGreeting@2a5330
Good Evening, Mr. Holmes

I could've pulled the ReferenceableGreeting out as an instance of Greeting, naturally.

I hope this little introduction to the RefFSContext JNDI implementation has been helpful. The source of the demonstration classes has been included in the attached executable jar file. To run the test, simply create the directory "BindingsDir" in the same directory as the jar file and run java -jar RefFSTest-src.jar.zip.