Wednesday, February 20, 2013

Prefered way for referencing Beans from Java (updated 21-02)

When working in XPages SSJS (Server Side Javascript) it is very easy to reference your Managed Beans. Say you want to print the name of the user to the console and have a bean named User with a method getUsername(), you simply call:
print(User.getUsername());
In Java it is not quite that easy, you have to dig a lot deaper to reach the bean. Examples on how to to this are readily available on the internet, copy/paste one in a Utils class and you end up with something like this:
package nl.defrog;

import javax.faces.context.FacesContext;

public class Utils {
    public static Object getVariableValue(String varName) {
        FacesContext context = FacesContext.getCurrentInstance();
        return context.getApplication().getVariableResolver().resolveVariable(context, varName);
    }
}
Still want to print the name of the current user to the console? Call:
UserBean userBean = (UserBean) Utils.getVariableValue(UserBean.BEAN_NAME);
System.out.println(userBean.getUsername());
It’s quite a bit of code for just being able to get to the username.
PS: See what I did with the name of the bean? I stored it as the constant BEAN_NAME in the UserBean and made it publicly available. See below for the final version of the code.

Singleton pattern

To clean things up I’m following something common from the Singleton pattern.
A Singleton is a Java class of which only one instance will ever exist in the entire JVM. You cannot simply instantiate a new instance of the class by calling the constructor, you have to call the static method getInstance() on the class to get a handle to the object (that’s the relevant variant for this case, there are other ways).
This sounds a bit like a bean, where we don’t want to instantiate a new object, but want to retrieve the existing Managed Bean from either scope (application, session, view, request).

getInstance() on a Bean

To get the instance of the bean that’s already in a scope (or have it instantiated at the first access) we add a getInstance() method to the bean. This method will call the getVariableValue() method from the Utils class shown above and have it return the bean we’re looking for in the correct type.
See the complete UserBean below.
package nl.defrog.beans;

import nl.defrog.Utils;

import java.io.Serializable;

public class UserBean implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final String BEAN_NAME = "User";
    private String username;

    public UserBean() {

    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public static UserBean getInstance(String beanName) {
        return (UserBean) Utils.getVariableValue(beanName);
    }
 
    public static UserBean getInstance() {
        return getInstance(BEAN_NAME);
    }
}
Now if we want to print the name of the current user to the console, all it takes is a simple:
System.out.println(UserBean.getInstance().getUsername());
If you want to keep the bean around, you could call:
UserBean userBean = UserBean.getInstance();
Nice and clean, isn’t it? I thought so.
Happy hacking!

Updated 21-02-2013 12:30
As per Tim's suggestion (see comments) I've overloaded the getInstance() method so you can provide a bean name when calling the method. This is handy when you have multiple beans using the same Java class.

Now also available on XSnippets.

5 comments:

  1. One suggestion: overload the getInstance method to accept a String. In many cases, each bean class will behave like a singleton, but in some cases, you may wish to use the same class for several different managed beans in the same app. If getInstance is called with no arguments, it returns the "default" instance for the bean; if it is passed a bean name, then it instead returns a specific bean instance of that class.

    ReplyDelete
  2. I wonder how this will behave when you have a sessionbound variable. Isn't the singleton pattern jvm bound (aka application scope behaviour). and therefore it could happen that users's get the wrong username ? ( the name of the person who initiated the class in the first place?)

    ReplyDelete
  3. Jeroen, I'm not using the singleton pattern

    ReplyDelete
  4. Jeroen, to be sure, this is *not* the singleton pattern. I'm using the method name that's common (getInstance()), but the code behind that method is using the variable resolver to get the correct Managed Bean.

    ReplyDelete
  5. Thimo, indeed it isn't the singleton pattern. Sorry if my previous post looked a bit harsh. It wasn't mean to be so.

    ReplyDelete