Java, JEE, Spring, JSF, Struts, Maven

Thursday, May 8, 2008

Using shale commons-validator with facelets

This entry describes how to integrate the Shale commons-validator tags with facelets. These tags include:
  • commonsValidator
  • validatorVar
  • validatorScript

For example:

...
<h:form onsubmit="return validateForm(this);">
...
<label for="firstName"
value="#{bundle['person.firstName']}*" /><br />
<h:inputText id="firstName" value="#{person.firstName}"
size="22" maxlength="20">
<val:commonsValidator type="required"
arg="#{textBundle['person.firstName']}" client="true" server="true" />
<val:commonsValidator type="maxlength" maxlength="20"
arg="#{textBundle['person.firstName']}" client="true" server="true" />
<val:commonsValidator type="mask"
arg="#{textBundle['person.firstName']}" client="true" server="true">
<val:validatorVar name="mask" value="#{validationBundle['regexp.personName']}" />
</val:commonsValidator>
</h:inputText>
...
<val:validatorScript functionName="validateForm" />
</h:form>
...

First, you must create a facelets taglib file to tell facelets how to interpret these tags. Within the taglib file, you will refer to specific facelet TagHandler implementation classes that you will provide.



shale-validator-taglib.xml

<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>

<namespace>http://shale.apache.org/validator</namespace>

<tag>
<tag-name>validatorScript</tag-name>
<component>
<component-type>org.apache.shale.ValidatorScript</component-type>
</component>
</tag>

<tag>
<tag-name>commonsValidator</tag-name>
<handler-class>net.genschaw.jsf.facelets.CommonsValidatorHandler</handler-class>
</tag>

<tag>
<tag-name>validatorVar</tag-name>
<handler-class>net.genschaw.jsf.facelets.CommonsValidatorVarHandler</handler-class>
</tag>

</facelet-taglib>

Save this file in your webapp, commonly under /WEB-INF. The <namespace> element is defining the namespace you will use for prefixing in your facelets templates.
For example:

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:val="http://shale.apache.org/validator">
...
...
</html>

The first <tag> element tells facelets to create a ValidatorScript component when it encounters a validatorScript tag. This component is defined in Shale's faces-config.xml.

The second <tag> element tells facelets to invoke a TagHandler when it encounters a commonsValidator tag. The tag handler is a class you need to implement (see below) to create a CommonsValidator instance and attach it to the associated component.

Finally, the third <tag> element tells facelets to also invoke a TagHandler when it encounters a validatorVar tag. This handler needs to expose the name/value pair in the current FaceletContext. The CommonsValidatorHandler will subsequently apply the variable(s) to the CommonsValidator it creates.

CommonsValidatorHandler

package net.genschaw.jsf.facelets;

import java.io.IOException;
import java.util.Map;

import javax.el.ValueExpression;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.validator.Validator;

import org.apache.shale.validator.CommonsValidator;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.tag.MetaRuleset;
import com.sun.facelets.tag.MetaTagHandler;
import com.sun.facelets.tag.Metadata;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagException;

/**
* Facelets TagHandler for "commonsValidator" tag.
*
* @author Shane Genschaw
*/
public class CommonsValidatorHandler extends MetaTagHandler {

/**
* Set validatorVar tag variables on commons validator.
*
* @see com.sun.facelets.tag.Metadata
*/
public static class ValidatorVarMetadata extends Metadata {

/**
* @see com.sun.facelets.tag.Metadata#applyMetadata(com.sun.facelets.FaceletContext, java.lang.Object)
*/
@SuppressWarnings("unchecked")
public void applyMetadata(FaceletContext ctx, Object instance) {
Map validatorVars = (Map) ctx.getAttribute(CommonsValidatorVarHandler.KEY);
if (null != validatorVars) {
CommonsValidator v = (CommonsValidator) instance;
v.getVars().putAll(validatorVars);
ctx.setAttribute(CommonsValidatorVarHandler.KEY, null);
}
}
}

private final TagAttribute binding;

private String validatorId = "org.apache.shale.CommonsValidator";

/**
* @param config
*/
public CommonsValidatorHandler(TagConfig config) {
super(config);
this.binding = this.getAttribute("binding");
}

/**
* @see com.sun.facelets.FaceletHandler#apply(com.sun.facelets.FaceletContext,
* javax.faces.component.UIComponent)
*/
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException {
if (parent == null || !(parent instanceof EditableValueHolder)) {
throw new TagException(this.tag,
"Parent not an instance of EditableValueHolder: " + parent);
}

// process validatorVar tag(s), if present
if (nextHandler != null) {
nextHandler.apply(ctx, parent);
}

// only process if it's been created
if (parent.getParent() == null) {
// cast to a ValueHolder
EditableValueHolder evh = (EditableValueHolder) parent;
ValueExpression ve = null;
Validator v = null;
if (this.binding != null) {
ve = this.binding.getValueExpression(ctx, Validator.class);
v = (Validator) ve.getValue(ctx);
}
if (v == null) {
v = this.createValidator(ctx);
if (ve != null) {
ve.setValue(ctx, v);
}
}
if (v == null) {
throw new TagException(this.tag, "No Validator was created");
}
this.setAttributes(ctx, v);
evh.addValidator(v);
}
}

protected Validator createValidator(FaceletContext ctx) {
if (this.validatorId == null) {
throw new TagException(
this.tag,
"Default behavior invoked of requiring a validator-id "
+ "passed in the constructor, must override ValidateHandler(ValidatorConfig)");
}
return ctx.getFacesContext().getApplication().createValidator(
this.validatorId);
}

// alias the minlength and maxlength attributes
@SuppressWarnings("unchecked")
protected MetaRuleset createMetaRuleset(Class type) {
MetaRuleset metaRuleset = super.createMetaRuleset(type).ignore(
"binding").alias("minlength", "minLength").alias("maxlength",
"maxLength").add(new ValidatorVarMetadata());

return metaRuleset;
}

}

CommonsValidatorVarHandler

package net.genschaw.jsf.facelets;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.el.ValueExpression;
import javax.faces.component.UIComponent;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;

/**
* Facelets TagHandler for "validatorVar" tag.
*
* @author Shane Genschaw
*/
public class CommonsValidatorVarHandler extends TagHandler {

/** Key under which variable map is stored in FaceletContext. */
public static final String KEY = "validatorVars";

// possible attributes
private final TagAttribute name;
private final TagAttribute value;

/**
* @param config
*/
public CommonsValidatorVarHandler(TagConfig config) {
super(config);
this.name = getRequiredAttribute("name");
this.value = getRequiredAttribute("value");
}

/**
* @see com.sun.facelets.FaceletHandler#apply(com.sun.facelets.FaceletContext,
* javax.faces.component.UIComponent)
*/
@SuppressWarnings("unchecked")
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException {
String nameStr = this.name.getValue(ctx);
ValueExpression veObj = this.value
.getValueExpression(ctx, Object.class);
if (null != veObj) {
Map validatorVars = (Map) ctx.getAttribute(KEY);
if (null == validatorVars) {
validatorVars = new HashMap();
ctx.setAttribute(KEY, validatorVars);
}
validatorVars.put(nameStr, veObj.getExpressionString());
}
}

}

The last step is to add a configuration parameter in your web.xml file to tell facelets to load you're new taglib.

<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/WEB-INF/shale-validator.taglib.xml</param-value>
</context-param>

Additional Resources