My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

luni, 26 ianuarie 2015

Programmatically Evaluate a ValueExpression Using OmniFaces

This post was inspired by the source code of the OmniFaces GraphicImage component

So, in this post you can see how to programmatically evaluate a ValueExpression with two approaches shaped in the below figure:


For this, let's suppose that we have a managed bean, named PlayerBean. Here, we define two simple methods, named clearRanking() and changeRank() as below - the encapsulated business logic is not relevant:

@ManagedBean
public class PlayerBean ... {

 public boolean clearRanking() {
  //...
  return true;
 }

 public Rank changeRank(int old_pos, int new_pos) {
  //...
  return some_rank;
 }
...
}

The Rank class is a simple POJO:

public class Rank implements Serializable{
   
 //fields
 ...
 //getters and setters
 ...
}

Now, let's suppose that we want to programmatically evaluate the following ValueExpression:

... value="#{playerBean.clearRanking()}"...

Basically, you can do this:

import org.omnifaces.util.Faces;
...
ELContext elc = Faces.getELContext();
ValueExpression ve = getValueExpression("value");

Object content = ve.getValue(elc);
System.out.println("Content returned: " + content);

Output: Content returned: true

Now, if we repeat the same thing for the next ValueExpression:

... value="#{playerBean.changeRank(2, 4)}"...

And, the output is an instance of Rank: Content returned : atp.singles.Rank@3426d349

Further, for some reasons, we want to call the method pointed by the ValueExpression when the ValueExpression is not available anymore. For this, we can split the task in two parts:
·         inspect/store the ValueExpression using the OmniFaces ExpressionInspector API
·         use Java Reflection API to call the method

Note If you are not familiar with OmniFaces ExpressionInspector, please check Inspect a ValueExpression Using OmniFaces.

Based on OmniFaces ExpressionInspector API, we can inspect the first ValueExpression (#{playerBean.clearRanking()}) and store its MethodReference in a Map, like this:

...
Map<String, MethodReference> STORED_METHODS = new ConcurrentHashMap<>();

ELContext elc = Faces.getELContext();
ValueExpression ve = getValueExpression("value");

MethodReference methodReference = ExpressionInspector.getMethodReference(elc, ve);
String methodName = methodReference.getBase().getClass().getSimpleName() +
                    "_" + methodReference.getMethod().getName();

if (!STORED_METHODS.containsKey(methodName)) {
    STORED_METHODS.put(methodName, new MethodReference(methodReference.getBase(),
       methodReference.getMethod()));
}
...

Later, we can extract the MethodReference from this Map and use Java Reflection API to invoke the specified method, like this - when this is happening, the ValueExpression doesn't exist anymore:

...
MethodReference methodReference = STORED_METHODS.get(methodName);
if (methodReference != null) {
    Method method = methodReference.getMethod();
    Object content = null;
    try {
        content = method.invoke(methodReference.getBase(), (Object[]) null);
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
             //...
    }
    System.out.println("Content returned : " + content);
 }
...

Further, let's focus on the second ValueExpression, #{playerBean.changeRank(2, 4)}. Well, we can't apply the above code because the changeRank() method gets two parameters. But, we can slightly adapt the approach used for storing the MethodReference for the parameters also. For this we need the MethodReference#getActualParameters(), as below - the modification is highlighted:

...
Map<String, MethodReference> STORED_METHODS = new ConcurrentHashMap<>();

ELContext elc = Faces.getELContext();
ValueExpression ve = getValueExpression("value");

MethodReference methodReference = ExpressionInspector.getMethodReference(elc, ve);
String methodName = methodReference.getBase().getClass().getSimpleName() +
                    "_" + methodReference.getMethod().getName();

if (!STORED_METHODS.containsKey(methodName)) {
    STORED_METHODS.put(methodName, new MethodReference(methodReference.getBase(),
                       methodReference.getMethod(), methodReference.getActualParameters(),
                       methodReference.isFromMethod()));
        }
...

Now, we can call the changeRank() method. Notice that we cannot simply pass the actual parameters to the  method because we will get a java.lang.IllegalArgumentException: argument type mismatch exception. First, we need to convert them to int:

...
MethodReference methodReference = STORED_METHODS.get(methodName);
if (methodReference != null) {
    Method method = methodReference.getMethod();
    Object content = null;
    Object[] actualParams = methodReference.getActualParameters();
    Object[] convParams = new Object[actualParams.length];
    for(int i=0; i<actualParams.length;i++){
        convParams[i] = Integer.valueOf(String.valueOf(actualParams[i]));
    }
 
    try {
        content = method.invoke(methodReference.getBase(), convParams);
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
               //...
    }
    System.out.println("Content returned : " + content);
}
...

Now, let's add another method in the PlayerBean:

public Rank changeRankAndMarkDate(int old_pos, int new_pos, Date d) {
  //...
  return some_rank;
 }

Notice that this method gets three parameters, two integers (primitives) and one object (java.util.Date). The ValueExpression involved in this example passed two integers and a string that encapsulates the a date in a long number - practically the milliseconds returned by the Date.getTime():

... value="#{playerBean.changeRankAndMarkDate(2, 4, '1421830456775')}"...

So, beside converting the two integers, now we need to convert this string to java.util.Date also. Basically, we can adjust the above code for any kind of parameters, but it will be much more nicer to have a generic method useful in all cases. Actually, OmniFaces gave the solution out of the box. This solution can be found in the source code of the GraphicResource and it is encapsulated in a method named, convertToObjects(). The below snippet is a slight adaptation of the original (you should provide the SomeComponent - it can be any dummy component instance, or, if you are in a custom component, just pass an instance of it by calling its empty constructor):

private static Object[] convertToObjects(FacesContext context,
                                  Object[] objects, Class<?>[] types) {
 int i = 0;
 Object[] cobjects = new Object[objects.length];
 String[] values = new String[objects.length];
 for (Object t : objects) {
      values[i] = String.valueOf(t);
      i++;
 }

 Application application = context.getApplication();

 for (i = 0; i < values.length; i++) {
      String value = isEmpty(values[i]) ? null : values[i];
      Converter converter = application.createConverter(types[i]);
      cobjects[i] = (converter != null)
               ? converter.getAsObject(context, new SomeComponent(), value)
               : value;
 }

 return cobjects;
}

So, in case you need to supply a custom object as argument for some reason, you need to explicitly register a converter for it yourself via @FacesConverter(forClass). This converter will be used by the above code. In our example, we need a converter capable to convert the milliseconds time in a java.util.Date, so we can write one immediately, like this - we are especially interested by the getAsObject() method:

@FacesConverter(forClass=java.util.Date.class)
public class DateConverter implements Converter{

 @Override
 public Object getAsObject(FacesContext context, UIComponent component, String value) {     
  return new Date(Long.parseLong(value));
 }

 @Override
 public String getAsString(FacesContext context, UIComponent component, Object value) {      
  return String.valueOf(((Date)value).getTime());
 }   
}

So, having the convertToObjects() helper class and the above custom converter, we can store the value="#{playerBean.changeRankAndMarkDate(2, 4, '1421830456775')}" simply as before:

...
Map<String, MethodReference> STORED_METHODS = new ConcurrentHashMap<>();

ELContext elc = Faces.getELContext();
ValueExpression ve = getValueExpression("value");

MethodReference methodReference = ExpressionInspector.getMethodReference(elc, ve);
String methodName = methodReference.getBase().getClass().getSimpleName() +
                    "_" + methodReference.getMethod().getName();

if (!STORED_METHODS.containsKey(methodName)) {
    STORED_METHODS.put(methodName, new MethodReference(methodReference.getBase(),
                       methodReference.getMethod(), methodReference.getActualParameters(),
                       methodReference.isFromMethod()));
        }
...

And, we can call it by using the convertToObjects() helper class, which will call the custom converter:
...
MethodReference methodReference = STORED_METHODS.get(methodName);
if (methodReference != null) {
    Method method = methodReference.getMethod();
    Object content = null;      
    Object[] convParams = convertToObjects(Faces.getContext(), 
       methodReference.getActualParameters(), methodReference.getMethod().getParameterTypes());         
    try {
        content = method.invoke(methodReference.getBase(), convParams);
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
               //...
 }
 System.out.println("Content returned : " + content);
}
...

Well, mission accomplished!

If you check the OmniFaces GraphicResource source code, you will notice that there is a convertToStrings() method also. This works perfectly with the custom converter, getAsString() method.

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors