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

duminică, 7 decembrie 2014

JSF Converters and Validators Demystified

(JSF validators/converters, Mojarra UIInput, immediate attr)

How many articles about JSF converters/validators have you read ? Many?
How many of them were good? A few!
Is this article good? I hope!

If you are not a novice, then there is no secret how the JSF converters and validators works. But, if you think that it will be nice to have a concise remainder, or is a topic like a tongue-twister nursery rhyme, then, as a JSF developer, is important to understand the below paragraphs.

The idea behind converters/validators consist in providing a layer between the user interface and the data model. More precisely, the same information is represented in a readable and modifiable manner to the user, and it is stored in the data model using data types, like int, String, Date, etc. Per example, we may have a java.util.Date object stored in the data model that is exposed to the user as a simple text. The user can read the date and modify it accordingly (he can't modify the java.util.Date object from a web interface, but it can modify the information extracted from the object). After he submits the form that contains the modified date, JSF should capture the text and convert it back to an java.util.Date object. Further, the java.util.Date object can participate in different kind of operations that involves dates (e.g. build a graph, make a schedule etc - for these tasks is much more useful to work with java.util.Date objects, instead of dates as strings).

Note When we are working with objects without using a converter, the end-user will see the result of calling the object.toString() method.

Moreover, JSF should validate the result of conversion to ensure that some internal requirements of the application are respected (e.g. accept only dates after 1 January 2000). Finally, the information is pushed back in data model.
But, what is actually happening ? When a form is submitted, the browser is responsible to send a request value to the server (you can easily inspect the request values using Firebug). Once the request value reach the server side, JSF will store it in a component object in the phase called Apply Request Values. At this moment the request value becomes the submitted string value. Further, in the Process Validation phase, the submitted string value is converted to a new value of the appropriate object type that is required by the application (e.g. java.util.Date). Next, JSF will apply the validators to the new value (if they are any).  If the new value is valid, it becomes the local value which can be used to update the model.
Of course, conversion and validation will not work smoothly all the time! When they failed (any of them) JSF takes a radical decision and "rejects" the user request by invoking the Render Response phase, which is responsible to redisplay the current page. Page authors are aware of this behavior, so they have to prepare the terrain by "infiltrating" in page tags as, <h:message> and <h:messages>. These tags will reveal the info, warning and error messages that has been fired by the converters/validators. In such way, the user is informed about what went wrong, and he can supply another request values. The process repeats until the Process Validation phase is successfully ended. Next, is time to update the data model using the converted/validated data. This take place in the Update Model phase.
In order to "disturb" a little the above sequence of events, we have to use important attributes that can be applied for input components. The first one is the required attribute - basically, this is a flag that specify whether a value must be supplied for a UIInput component or a component that extends UIInput (e.g. UIViewParameter). Per example, for an UIInput, the required value is a non-empty value (non-empty string, non-empty List or non-null), but the submitted null values tells JSF that the component was not submitted at all, which means that it doesn't apply the required validator. So, if the submitted value was not null, and it successfully passed the conversion, then the required validator will stop an empty value to be validated by other attached validators. When required="false", JSF will pass the empty value through the attached validators if the javax.faces.VALIDATE_EMPTY_FIELDS context parameter was set to true (default true, since JSF 2.0) and the submitted value was not null. But, keep in mind that setting this context parameter to false, will decrease the power of JSR 303 Bean Validation usability. The same logic is applicable in case of the UIViewParameter, only that in this case a submitted null value (not empty string) for a view parameter will suddenly stop the validation and the response is rendered. This is a decision specific to UIViewParameter, which applies the required validator earlier than UIInput. For a better understanding, think that if we have an attached converter, then the UIViewParameter applies required validator to submitted value, and to the converted value, while the UIInput applies the required validator only to the converted value. Well, you may think of required validator as " the guardian" of the validation process. Practically, if you can't pass successfully over its control, nothing happens further. The value is marked as invalid, an error message is placed in the queue, and if there are more validators waiting, they are not called and the page is redisplayed in Render Response phase. Obviously, if you don't use this validator, then you have to deal with empty values in a custom way.
The other important attribute is immediate. This is another flag  that indicates that the component's value must be converted and validated immediately, during the Apply Request Values phase, rather than waiting until Process Validations phase. Most probably, you will see this attribute used in command components (e.g. <h:commandButton>) to implement buttons like, "Cancel" and "Clear". This is needed for transferring the invocation of action in Apply Request Values phase.
Now, let's have a quick overview of this flow from the methods perspective. We can mark as the starting point the processDecodes() method. This method is implemented in the UIComponentBase, and is adjusted in specific input components, like UIInput and UIViewParameter. By default, in UIComponentBase, "this method perform the component tree processing required by the Apply Request Values phase of the request processing lifecycle for all facets of this component, all children of this component, and this component itself". Notice below the relevant part:
...
pushComponentToEL(context, null);

Iterator kids = getFacetsAndChildren();
while (kids.hasNext()) {
 UIComponent kid = (UIComponent) kids.next();
 kid.processDecodes(context);
}

try {
    decode(context);
} catch (RuntimeException e) {
         context.renderResponse();
         throw e;
} finally {
  popComponentFromEL(context);
}
...

In case of UIInput, this method is called in the Apply Request Values phase and it inspects the value of the immediate attribute. If this value is true, then it triggers the validation before the Process Validations Phase:
public void processDecodes(FacesContext context) {
  ...
  super.processDecodes(context);

  if (isImmediate()) {
      executeValidate(context);
   }
 }

By the other hand the UIViewParameter doesn't override this method (immediate is always false).
Further, if the immediate is false, then in Process Validations phase, the  processValidators() method is called. This method is implemented in the UIComponentBase, and is adjusted in specific input components, like UIInput and UIViewParameter. By default, in UIComponentBase, "this method perform the component tree processing required by the Process Validations phase of the request processing lifecycle for all facets of this component, all children of this component, and this component itself". Notice below the relevant part:
...
pushComponentToEL(context, null);

Application app = context.getApplication();
app.publishEvent(context, PreValidateEvent.class, this);

Iterator kids = getFacetsAndChildren();
while (kids.hasNext()) {
          UIComponent kid = (UIComponent) kids.next();
          kid.processValidators(context);
}
app.publishEvent(context, PostValidateEvent.class, this);
popComponentFromEL(context);
...

In UIInput, this behavior is adjusted to take into account the immediate attribute. Notice below the relevant part:
...
if (!isImmediate()) {
    Application application = context.getApplication();
    application.publishEvent(context, PreValidateEvent.class, this);
    executeValidate(context);
    application.publishEvent(context, PostValidateEvent.class, this);
 }
 for (Iterator<UIComponent> i = getFacetsAndChildren(); i.hasNext(); ) {
     i.next().processValidators(context);
 }
...

Further, in UIViewParameter the above implementation is adjusted to deal with null values in presence of required validator. Well, UIInput assumes that a null value means don't check, but UIViewParameter doesn't accept null values and required validator:
...
if (getSubmittedValue() == null && myIsRequired()) {
     String requiredMessageStr = getRequiredMessage();
     FacesMessage message;
     if (null != requiredMessageStr) {
         message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                                 requiredMessageStr,
                                 requiredMessageStr);
     } else {
        message = MessageFactory.getMessage(context, REQUIRED_MESSAGE_ID,
                          MessageFactory.getLabel(context, this));
     }
     context.addMessage(getClientId(context), message);
     setValid(false);
     context.validationFailed();
     context.renderResponse();
 } else {
     super.processValidators(context);
 }
...
The myIsRequired() method is a private method that checks the well-known isRequired() and another private method, named isRequiredViaNestedRequiredValidator() - its name is self explanatory:
private boolean myIsRequired() {
  return super.isRequired() || isRequiredViaNestedRequiredValidator();
}

So, when there are non-null submitted values or the required validator is not used, the UIViewParameter relies on UIInput implementation. It is easy to observe that the flow goes into the executeValidate() method. This is a private method defined in UIInput, which basically calls the validate() method, and afterwards, if the validation failed, it renders the response:
private void executeValidate(FacesContext context) {
  try {
        validate(context);
  } catch (RuntimeException e) {
     context.renderResponse();
     throw e;
  }

  if (!isValid()) {
      context.validationFailed();
      context.renderResponse();
  }
}

So, the validate() method is the "brain" of validation. This is the place where the magic happens. First, if the submitted value is null, the validation returns. JSF consider that  the component was not submitted  at all. In case of an UIViewParameter, at this point, we know that the required is false.
...
Object submittedValue = getSubmittedValue();
 if (submittedValue == null) {
      return;
}
...

Further, JSF deals the empty strings, depending on the context parameter, javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL, which is false by default:
...
if ((considerEmptyStringNull(context)
       && submittedValue instanceof String
          && ((String) submittedValue).length() == 0)) {
       setSubmittedValue(null);
       submittedValue = null;
 }
...

One step further, and the converter's getAsObject() method is called (via getConvertedValue() method). It will convert the submitted string value to the object type required by the application - if the component is bound to a bean property in the model , but there is no converter attached, then JSF will use the converter that has the same data type as the bean property. If conversion fails, the submitted value is marked as invalid and JSF adds an error message to the queue maintained by the FacesContext. Further, the flow reach the validateValue() method, which validates the converted value (newValue):
...
Object newValue = null;

try {
      newValue = getConvertedValue(context, submittedValue);
} catch (ConverterException ce) {
   addConversionErrorMessage(context, ce);
   setValid(false);
}

validateValue(context, newValue);
...

But, what's happening inside the validateValue() method ? Well, first, if the new value is valid (it successfully passed the converter) and it is an empty value (empty string, empty List or null) and the required validator is used, then the new value is marked as invalid and an error message is placed in the queue of FacesContext. But, if the new value is valid and it is not an empty value or is an empty value, but validate empty fields is enabled, then calls all validators, one by one.
If validation is successfully accomplished, then the new value is stored as the local value, and it is used later to update the model, the submitted value is deleted (set to null) and the ValueChangeEvent is emitted (if it has to):
...
if (isValid()) {
    Object previous = getValue();
    setValue(newValue);
    setSubmittedValue(null);
    if (compareValues(previous, newValue)) {
         queueEvent(new ValueChangeEvent(this, previous, newValue));
    }
 }
...

Finally, the flow reaches the Update Model Values phase and the local value is used to update the data model. JSF needs those local values to keep the data until the entire validation process is successfully done. The local values "gain" the access to the data model only if all validations has successfully passed.

Maybe, we pushed the things a little bit too far, but sometimes, in order to fit the puzzle pieces, is good to have the big picture in mind. So, here it is the picture of  how validators and converters works in Mojarra 2.2 for UIInput:


Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors