This class helps in letting code run within
its own scope. Such scope is defined by specific variables being available to
EL within it. The request scope is used to store the variables - this is
the definition of the OmniFaces utility class, ScopedRunner.
Putting
the things as simple as possible, this class manages two maps, suggestively
named, scopedVariables and previousVariables. The below code is pretty straightforward:
public class
ScopedRunner {
private FacesContext context;
private Map<String, Object>
scopedVariables;
private Map<String, Object>
previousVariables = new HashMap<>();
public ScopedRunner(FacesContext context) {
this(context, new HashMap<String,
Object>());
}
public ScopedRunner(FacesContext context,
Map<String, Object> scopedVariables) {
this.context = context;
this.scopedVariables = scopedVariables;
}
...
The scopedVariables holds the
keys names of the variables and the values of the variables that should be
available in the JSF request scope at a specific moment and for a specific
period of time (let's name this period of time, the runner
scope). The spotlight is represented
by the Callback.Void#invoke() -
presented later. These variables can be
passed via the second constructor from above or added one by one using the ScopedRunner#with():
public ScopedRunner with(String key, Object
value) {
scopedVariables.put(key,
value);
return
this; // return this ScopedRunner, so adding variables and finally calling invoke can be chained
}
...
So,
basically we have a class which allows us, at instantiation moment or later, to
populate the scopedVariables map with some keys and values. These are
needed in the JSF request scope at a specific moment for a limited period of
time, and they will replace any exiting variables with the same name. The replaced
variables (if any) should be restored in the JSF request scope at the end of
this period of time (at the end of the runner scope).
For
storing the original variables (if any), ScopedRunner have
another map named, previousVariables. This map acts as a provisory storage
for the variables extracted from request scope until they are putted back
(actually, you don't even need to be aware of the existence of this map!).
Now,
remember that we just said above that the spotlight is represented by the Callback.Void#invoke(). Well, the
ScopedRunner#invoke() method is
used to delimitate and define the period of time for which this scope exist (specifies
the runner scope boundaries). Basically, until you explicitly call this method,
the runner scope doesn't exist, or, with other words, the scopedVariables
is
not "published" in the JSF request scope. When this method is called,
OmniFaces accomplished several main steps that actually demarcates the runner scope lifespan:
- the runner scope "is alive"
- it
"publishes" the scopedVariables in the JSF request scope (check setNewScope())
- it
stores the previously existing variables (if any) in the previousVariables (check setNewScope())
- it
provides control to the developer by calling his Callback.Void#invoke()
implementation - practically, your invoke() method
represents the moment in time when you need the passed in variables (scopedVariables) to be in
the request scope
- when the
developer gives control back to ScopedRunner#invoke() method,
the request scope content is restored via the previousVariables content (check restorePreviousScope())
- after all its content is transferred back in the request scope, this map is cleared, (check restorePreviousScope())
- after all its content is transferred back in the request scope, this map is cleared, (check restorePreviousScope())
- the runner scope "dies"
The ScopedRunner#invoke()and the corresponding
helpers are listed below:
public void invoke(Callback.Void callback) {
try {
setNewScope();
callback.invoke();
}
finally {
restorePreviousScope();
}
}
private void setNewScope() {
previousVariables.clear();
Map<String, Object> requestMap =
context.getExternalContext().getRequestMap();
for (Map.Entry<String, Object> entry :
scopedVariables.entrySet()) {
Object
previousVariable = requestMap.put(entry.getKey(), entry.getValue());
if (previousVariable != null) {
previousVariables.put(entry.getKey(),
previousVariable);
}
}
}
private void restorePreviousScope() {
try {
Map<String, Object> requestMap =
context.getExternalContext().getRequestMap();
for (Map.Entry<String, Object>
entry : scopedVariables.entrySet()) {
Object previousVariable = previousVariables.get(entry.getKey());
if (previousVariable != null) {
requestMap.put(entry.getKey(),
previousVariable);
} else {
requestMap.remove(entry.getKey());
}
}
} finally {
previousVariables.clear();
}
}
} // end of
ScopedRunner class
Here it is an use case flow diagram:
Now, that
you know what the OmniFaces ScopedRunner does, is time to think to an use
case. Let's suppose that we have an iterating components (UIData/UIRepeat/
UISelectItems) which
should be programmatically consulted on a per-iteration basis. For this, we are
using the var attribute, which in case of UISelectItems, exposes
the value from the value attribute under this request scoped key so
that it may be referred to in EL for the value of other attributes. This means
that at a certain moment, in request scope, we must have a key with the var attribute
value and a value with the object instance of that iteration.
We can
easily add this into the request map, but if the request map already contain
such a key then the corresponding entry will be lost. An workaround will be to store locally the
existing key-value pair, place our key-value in request scope, and restore it
at the end of our job. Basically, this is what ScopedRunner does in a
professional and generic approach. The main advantages of using ScopedRunner are:
- we don't
have to take care of adding/restoring the request scope
- we can easily manipulate
multiple variables
- we can easily
reuse the ScopedRunner in multiple places
- we
obtain a clean and detached code of this topic
- we can
extend the ScopedRunner for accomplish more specific tasks
- we
exploit OmniFaces capabilities as much as possible (it will be an waste of time
to have installed OmniFaces and to not use this ScopedRunner despite
other workarounds)
A skeleton
of usage was inspired from the org.omnifaces.util.selectitems.SelectItemsCollector class:
UISelectItems
uiSelectItems ...; // An UISelectItems instance.
Iterable<?>
items ...; // The value of the uiSelectItems value attribute as Iterable
// (we may suppose that these are not instances of SelectItem)
// (we may suppose that these are not instances of SelectItem)
// The UISelectItems
var attribute value.
Map<String, Object>
attributes = uiSelectItems.getAttributes();
String var = (String)
attributes.get("var");
// Helper
class that's used to set the item value in (EL) scope using the name set by
// "var"
during the iteration. If during each iteration the value of this is changed,
// any value
expressions in the attribute map referring it will resolve to that
// particular
instance.
ScopedRunner scopedRunner = new
ScopedRunner(facesContext);
for (final
Object item : items) {
if (!isEmpty(var)) {
scopedRunner.with(var, item);
}
// During each iteration, just resolve all
attributes
scopedRunner.invoke(new Callback.Void() {
// Before calling the below invoke(), the
ScopedRunner#setNewScope() has
// replaced in the request scope the entry that has a key equal with the var value (if
// any) with the one indicated above via the ScopedRunner#with() and stored it under
// the private previousVariables map.
// any) with the one indicated above via the ScopedRunner#with() and stored it under
// the private previousVariables map.
@Override
public
void invoke() {
// Just resolve all attributes for this item.
...
// Build a SelectItem instance to wrap this
item or do something else.
}
});
// The ScopedRunner#restorePreviousScope()
will restore the original entry.
}
Now, you
can exploit the ScopedRunner in different ways in your JSF projects.
Niciun comentariu :
Trimiteți un comentariu