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

vineri, 17 iulie 2015

Introducing the OmniFaces Runner Scope

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())
- 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)

// 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.
      @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

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors