Saturday, March 24, 2007

How to reduce coding by extending Managed Beans

After coding many backing/managed beans it became clear to me that there were a few things that I seem to be doing over and over, these were:
  • Getting values from the binding layer
  • Setting values in the binding layer
  • Executing operation bindings
  • Using a combination of the above in a backing bean method

The logical thing to do was be to but my code for doing this into a class and extend this class for all of my managed beans. The class is called JSFBean and uses the binding "#{bindings}" to access the binding container. The three methods in the class (so far) are: execute, getValue and setValue.

The bean which I have called JSFBean includes these three methods and some basic error handling. When creating a backing bean simply add "extends JSFBean" to the class definition.

JSFBean.java:
package com.delexian.ui.backing;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.binding.OperationBinding;

public class JSFBean {

public JSFBean() {
}

public DCBindingContainer getBindings() {
FacesContext fc = FacesContext.getCurrentInstance();
ValueBinding vb = fc.getApplication().createValueBinding("#{bindings}");
DCBindingContainer dc = (DCBindingContainer) vb.getValue(fc);

return dc;
}

public boolean execute(String operation){
DCBindingContainer bindings = getBindings();
OperationBinding operationBinding = bindings.getOperationBinding(operation);
if (operationBinding == null){
FacesContext fc = FacesContext.getCurrentInstance();
fc.addMessage("Invalid Operation", new FacesMessage(operation + " is not a valid operation for this page"));
return true;
}

operationBinding.execute();

return operationBinding.getErrors().isEmpty();
}

public Object getValue(String el){
FacesContext fc = FacesContext.getCurrentInstance();
ValueBinding expr = fc.getApplication().createValueBinding(el);
return expr.getValue(fc);
}

public void setValue(String el, Object value){
FacesContext fc = FacesContext.getCurrentInstance();
ValueBinding expr = fc.getApplication().createValueBinding(el);
expr.setValue(fc, value);

}

}



An example usage in a backing bean Employees.java, extends JSFBean:
package com.delexian.ui.backing;

public class Employees extends JSFBean {
public Employees() {
}

public String calculate_action() {
execute("CalculateCommission");
execute("Commit");
return null;
}
}


4 comments:

John Stegeman said...

Hi Brendan,

Nice post. I'm interested in your feedback on the approach you use to get the bindings vs injecting it into every bean?

John

Brenden Anstey said...

Hi John,
both methods are equally valid, however I did it this way in JSFBean because the managed property (injecting method) does not get set/called in the super class (JSFBean) only in the local class. This is why i used a ValueBinding to get a reference to the binding container.
JDev sometimes adds setBindings and getBindings to the local bean class. If it does so you can either wipe them out (and the managed property) and inherit the super class JSFBean setBindings and getBindings or let them override the super class methods.
Brenden

Ric Smith said...

Hi John,

I like your implementation here. However, I would advise adding a param to your execute method that allows you to passing a HashMap containing parameters for your operation binding. That will open this method up to more coding situations. Great work BTW!

Regards,
Ric

Brenden Anstey said...

Hi Ric,
I definately agree, a hashmap parameter is a great idea and would definately expand the beans capabilities. Another worthwhile enhancement is providing a meaningful return value which could be propagated to to a faces message.
cheers,
Brenden