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, 9 februarie 2015

Use OmniFaces approach to attach converters to custom components

Let's suppose that you want to write a custom component that should support a JSF converter (custom converter or built-in converter). In this post, you will see the OmniFaces technique for accomplishing this task, but, first you have to know the main steps that a custom component should respect in order to support the converter attribute, <f:converter> tag or <f:convertNumber>, etc:

·         implement the ValueHolder interface
·         override the ValueHolder.setConverter() method
·         override the ValueHolder.getConverter() method
·         override the rest of methods (not discussed in this post)

So, we can have a quick skeleton of such a custom component:

import javax.faces.component.FacesComponent;
import javax.faces.component.ValueHolder;
import javax.faces.convert.Converter;
...
@FacesComponent(...)
public class MyComponent implements ValueHolder {

 private enum PropertyKeys {
  // Cannot be uppercased. They have to exactly match the attribute names.
  converter; // converter attribute name
 }

 private Converter converter; // converter object

 @Override
 public Converter getConverter() {
  // return the converter
 }

 @Override
 public void setConverter(Converter converter) {
  // set the converter
 }

 // override rest of methods           
}

So, via the converter object we can call the well-known getAsObject() and getAsString() methods, but, before that we need to ensure that it points a Converter instance. Based on a simple intuition, you may think that getConverter() and setConverter() implementations can be like below, and, since we are implementing the ValueHolder, JSF will call these methods accordingly:

...
@Override
 public Converter getConverter() {
  return converter;
 }

 @Override
 public void setConverter(Converter converter) {
  this.converter = converter;
 }
...

Well, is true that in some cases this implementation is ok, but, let's focus on the setConverter() method, and let's analyze when JSF calls it:

·          built-in converter indicated via <f:convertNumber>

<t:myComponent>
 <f:convertNumber type="percent"/>
</t:myComponent>

This will call the setConverter() method. In Mojarra, the method will be called via ConverterTagHandlerDelegateImpl#applyAttachedObject() method.

·         built-in converter indicated via <f:converter> as string

<t:myComponent>
 <f:converter converterId="javax.faces.BigInteger"/>
</t:myComponent>

This will call the setConverter() method. In Mojarra, the method will be called via ConverterTagHandlerDelegateImpl#applyAttachedObject() method.

·         built-in converter indicated via <f:converter> as ValueExpression

private String bigConverter= "javax.faces.BigInteger";
...
<t:myComponent>
 <f:converter converterId="#{myBean.bigConverter}"/>
</t:myComponent>

This will call the setConverter() method. In Mojarra, the method will be called via ConverterTagHandlerDelegateImpl#applyAttachedObject() method.

·         custom converter indicated via <f:converter> as string

<t:myComponent>
 <f:converter converterId="myConverter"/>
</t:myComponent>

This will call the setConverter() method. In Mojarra, the method will be called via ConverterTagHandlerDelegateImpl#applyAttachedObject() method.

·         custom converter indicated via <f:converter> as ValueExpression

private String myConverter= "myConverter";
...
<t:myComponent>
 <f:converter converterId="#{myBean.myConverter}"/>
</t:myComponent> 

This will call the setConverter() method. In Mojarra, the method will be called via ConverterTagHandlerDelegateImpl#applyAttachedObject() method.

·         built-in converter indicated via converter attribute as string

<t:myComponent converter="javax.faces.BigInteger" />   

This will call the setConverter() method. In Mojarra, the method will be called via ValueHolderRule$LiteralConverterMetadata#applyMetadata() method.

·         built-in converter indicated via converter attribute as ValueExpression

private Converter bigConverter= new BigIntegerConverter();
...
<t:myComponent converter="#{myBean.bigConverter}" /> 

This will NOT call the setConverter() method. In Mojarra, this case is resolved by the ValueHolderRule$DynamicConverterMetadata2#applyMetadata() method.

·         custom converter indicated via converter attribute as string

<t:myComponent converter="myConverter" />   

This will call the setConverter() method. In Mojarra, the method will be called via ValueHolderRule$LiteralConverterMetadata#applyMetadata() method.

·         custom converter indicated via converter attribute as ValueExpression

private Converter myConverter= new MyConverter();
...
<t:myComponent converter="#{myBean.myConverter}" />  

This will NOT call the setConverter() method. In Mojarra, this case is resolved by the ValueHolderRule$DynamicConverterMetadata2#applyMetadata() method.

We can easily conclude that the setConverter() method is not called when the converter is indicated via a ValueExpression that must evaluate to javax.faces.convert.Converter. This will make our getConverter() to return null! This is happening because JSF distinguish between static converters (indicated via converter ID) and dynamic converters (indicated via ValueExpression evaluated to javax.faces.convert.Converter). For the static converters JSF will invoke the setConverter() method, while for dynamic converters, JSF will only invoke the setValueExpression() method which ultimately stores the EL expression in state helper. The getConverter() then evaluates it and assigns the result as an instance variable as they are not necessarily serializable (and thus should not be stored in state helper!).

Let's check out the Mojarra code for the case of using <f:converter> tag. Look how the ConverterTagHandlerDelegateImpl#applyAttachedObject() method calls the setConverter():

// Mojarra 2.2.9 - com.sun.faces.facelets.tag.jsf. ConverterTagHandlerDelegateImpl
public void applyAttachedObject(FacesContext context, UIComponent parent) {
 FaceletContext ctx = (FaceletContext)
context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
 ValueHolder vh = (ValueHolder) parent;
 ValueExpression ve = null;
 Converter c = null;
 if (owner.getBinding() != null) {
     ve = owner.getBinding().getValueExpression(ctx, Converter.class);
     c = (Converter) ve.getValue(ctx);
 }
 if (c == null) {
     c = this.createConverter(ctx);
     if (ve != null) {
         ve.setValue(ctx, c);
     }
 }
 if (c == null) {
     throw new TagException(owner.getTag(), "No Converter was created");
 }
 owner.setAttributes(ctx, c);
 vh.setConverter(c);
 Object lv = vh.getLocalValue();
 FacesContext faces = ctx.getFacesContext();
 if (lv instanceof String) {
     vh.setValue(c.getAsObject(faces, parent, (String) lv));
 }
}

private Converter createConverter(FaceletContext ctx) {
 if (owner.getConverterId(ctx) == null) {
     throw new TagException(owner.getTag(),
      "Default behavior invoked of requiring a converter-id passed in the constructor, must override ConvertHandler(ConverterConfig)");
 }
 return ctx.getFacesContext().getApplication().createConverter(owner.getConverterId(ctx));
}

If you fallow the code line by line, is pretty clear how it works.
Now, let's see the case of using the converter attribute, which is more interesting for us. In Mojarra, the magic happens in ValueHolderRule class as below:

// Mojarra 2.2.9 - com.sun.faces.facelets.tag.jsf.ValueHolderRule
final class ValueHolderRule extends MetaRule {

 final static class LiteralConverterMetadata extends Metadata {

  private final String converterId;

  public LiteralConverterMetadata(String converterId) {
   this.converterId = converterId;
  }

  public void applyMetadata(FaceletContext ctx, Object instance) {
   ((ValueHolder) instance).setConverter(ctx.getFacesContext()
     .getApplication().createConverter(this.converterId));
  }
 }
 ...
 final static class DynamicConverterMetadata2 extends Metadata {

  private final TagAttribute attr;

  public DynamicConverterMetadata2(TagAttribute attr) {
   this.attr = attr;
  }

  public void applyMetadata(FaceletContext ctx, Object instance) {
   ((UIComponent) instance).setValueExpression("converter", attr
      .getValueExpression(ctx, Converter.class));
  }
 }
 ...
 public final static ValueHolderRule Instance = new ValueHolderRule();

  public Metadata applyRule(String name, TagAttribute attribute, MetadataTarget meta) {
   if (meta.isTargetInstanceOf(ValueHolder.class)) {

       if ("converter".equals(name)) {
           if (attribute.isLiteral()) {
               return new LiteralConverterMetadata(attribute.getValue());
           } else {
               return new DynamicConverterMetadata2(attribute);
           }
       }

       ...
   }
   return null;
 }
}

Finally, the UIComponent#setValueExpression() will access the state via getStateHelper().put() and will store the converter. Now, that we know how the things works, we can adjust the getConverter() accordingly. OmniFaces uses the follow getConverter() in its Param component:

private Converter localConverter;

@Override
public Converter getConverter() {
 return localConverter != null ? localConverter : (Converter) getStateHelper().eval(PropertyKeys.converter);
}

So, our custom component skeleton will become:

import javax.faces.component.FacesComponent;
import javax.faces.component.ValueHolder;
import javax.faces.convert.Converter;
...
@FacesComponent(...)
public class MyComponent implements ValueHolder {

 private enum PropertyKeys {
  // Cannot be uppercased. They have to exactly match the attribute names.
  converter; // converter attribute name
 }

 private Converter localConverter; // converter object

@Override
public void setConverter(Converter converter) {
 localConverter = converter;
}

@Override
public Converter getConverter() {
 return localConverter != null ? localConverter : (Converter) getStateHelper().eval(PropertyKeys.converter);
}

 // override rest of methods           
}

Done!

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Visitors Starting 4 September 2015

Locations of Site Visitors