This post was inspired by the source code of the OmniFaces GraphicImage component
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:
·
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