JSFValidator.java
package org.andromda.cartridges.jsf.validator;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.Form;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorResources;
/**
* A JSF validator that uses the apache commons-validator to perform either
* client or server side validation.
*/
public class JSFValidator
implements Validator,
Serializable
{
private static final Log logger = LogFactory.getLog(JSFValidator.class);
/**
* Constructs a new instance of this class with the given <code>form</code> and
* <code>validatorAction</code>.
*
* @param formIdIn
* @param validatorActionIn
*/
public JSFValidator(final String formIdIn, final ValidatorAction validatorActionIn)
{
this.formId = formIdIn;
this.validatorAction = validatorActionIn;
}
private String formId;
/**
* Default constructor for faces-config.xml
*/
public JSFValidator()
{
// - default constructor for faces-config.xml
}
/**
* Validator type.
*/
private String type;
/**
* The setter method for the <code>type</code> property. This property is
* passed through to the commons-validator.
*
* @param typeIn The new value for the <code>type</code> property.
*/
public void setType(final String typeIn)
{
this.type = typeIn;
}
/**
* The getter method for the <code>type</code> property. This property is
* passed through to the commons-validator.
* @return this.type
*/
public String getType()
{
return this.type;
}
/**
* Parameter for the error message. This parameter is passed through to the
* commons-validator.
*/
private String[] args;
/**
* The setter method for the <code>args</code> property. This property is
* passed through to the commons-validator..
*
* @param argsIn The new value for the <code>args</code> property.
*/
public void setArgs(final String[] argsIn)
{
this.args = argsIn;
}
/**
* The parameters for this validator.
*/
private final Map parameters = new HashMap();
/**
* Gets the parameters for this validator (keyed by name).
*
* @return a map containing all parameters.
*/
public Map getParameters()
{
return this.parameters;
}
/**
* Adds the parameter with the given <code>name</code> and the given
* <code>value</code>.
*
* @param name the name of the parameter.
* @param value the parameter's value
*/
public void addParameter(
final String name,
final Object value)
{
this.parameters.put(
name,
value);
}
/**
* Returns the commons-validator action that's appropriate for the validator
* with the given <code>name</code>. This method lazily configures
* validator resources by reading <code>/WEB-INF/validator-rules.xml</code>
* and <code>/WEB-INF/validation.xml</code>.
*
* @param name The name of the validator
* @return getValidatorResources().getValidatorAction(name)
*/
public static ValidatorAction getValidatorAction(final String name)
{
return getValidatorResources().getValidatorAction(name);
}
/**
* The commons-validator action, that carries out the actual validation.
*/
private ValidatorAction validatorAction;
/**
* The location of the validator rules.
*/
public static final String RULES_LOCATION = "/WEB-INF/validator-rules.xml";
/**
* The key that stores the validator resources
*/
private static final String VALIDATOR_RESOURCES_KEY = "org.andromda.jsf.validator.resources";
/**
* Returns the commons-validator resources. This method lazily configures
* validator resources by reading <code>/WEB-INF/validator-rules.xml</code>
* and <code>/WEB-INF/validation.xml</code>.
*
* @return the commons-validator resources
*/
public static ValidatorResources getValidatorResources()
{
final FacesContext context = FacesContext.getCurrentInstance();
final ExternalContext external = context.getExternalContext();
final Map applicationMap = external.getApplicationMap();
ValidatorResources validatorResources = (ValidatorResources)applicationMap.get(VALIDATOR_RESOURCES_KEY);
if (validatorResources == null)
{
final String rulesResource = RULES_LOCATION;
final String validationResource = "/WEB-INF/validation.xml";
final InputStream rulesInput = external.getResourceAsStream(rulesResource);
if (rulesInput == null)
{
throw new JSFValidatorException("Could not find rules file '" + rulesResource + '\'');
}
final InputStream validationInput = external.getResourceAsStream(validationResource);
if (validationInput != null)
{
final InputStream[] inputs = new InputStream[] {rulesInput, validationInput};
try
{
validatorResources = new ValidatorResources(inputs);
applicationMap.put(
VALIDATOR_RESOURCES_KEY,
validatorResources);
}
catch (final Throwable throwable)
{
throw new JSFValidatorException(throwable);
}
}
else
{
logger.info(
"No validation rules could be loaded from --> '" + validationResource +
", validation not configured");
}
}
return validatorResources;
}
/**
* This <code>validate</code> method is called by JSF to verify the
* component to which the validator is attached.
*
* @see javax.faces.validator.Validator#validate(javax.faces.context.FacesContext, javax.faces.component.UIComponent, Object)
*/
public void validate(
final FacesContext context,
final UIComponent component,
final Object value)
{
if (this.formId != null)
{
try
{
final Form validatorForm = getValidatorResources().getForm(
Locale.getDefault(),
this.formId);
if (validatorForm != null)
{
final Field field = this.getFormField(validatorForm, component.getId());
if (field != null)
{
final Collection errors = new ArrayList();
this.getValidatorMethod().invoke(
this.getValidatorClass(),
context, value, this.getParameters(), errors, this.validatorAction,
field);
if (!errors.isEmpty())
{
throw new ValidatorException(new FacesMessage(
FacesMessage.SEVERITY_ERROR,
(String)errors.iterator().next(),
null));
}
}
else
{
logger.error("No field with id '" + component.getId() + "' found on form '" + this.formId + '\'');
}
}
else
{
logger.error("No validator form could be found with id '" + this.formId + '\'');
}
}
catch (final ValidatorException exception)
{
throw exception;
}
catch (final Exception exception)
{
logger.error(
exception.getMessage(),
exception);
}
}
}
/**
* Gets the validator class from the underlying <code>validatorAction</code>.
* @return the validator class
* @throws ClassNotFoundException
*/
private Class getValidatorClass()
throws ClassNotFoundException
{
final FacesContext context = FacesContext.getCurrentInstance();
final ExternalContext external = context.getExternalContext();
final Map applicationMap = external.getApplicationMap();
final String validatorClassName = this.validatorAction.getClassname();
Class validatorClass = (Class)applicationMap.get(validatorClassName);
if (validatorClass == null)
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null)
{
classLoader = getClass().getClassLoader();
}
validatorClass = classLoader.loadClass(validatorClassName);
applicationMap.put(
validatorClassName,
validatorClass);
}
return validatorClass;
}
/**
* Gets the validator method for the underlying <code>validatorAction</code>.
*
* @return the validator method.
* @throws ClassNotFoundException
* @throws NoSuchMethodException
*/
private Method getValidatorMethod()
throws ClassNotFoundException, NoSuchMethodException
{
Class[] parameterTypes =
new Class[]
{
javax.faces.context.FacesContext.class, Object.class, java.util.Map.class,
java.util.Collection.class, org.apache.commons.validator.ValidatorAction.class,
org.apache.commons.validator.Field.class
};
return this.getValidatorClass().getMethod(
this.validatorAction.getMethod(),
parameterTypes);
}
/**
* Attempts to retrieve the form field from the form with the given <code>formName</code>
* and the field with the given <code>fieldName</code>. If it can't be retrieved, null
* is returned.
* @param form the form to validate.
* @param fieldName the name of the field.
* @return the found field or null if it could not be found.
*/
private Field getFormField(
final Form form,
final String fieldName)
{
Field field = null;
if (form != null)
{
field = form.getField(fieldName);
}
return field;
}
/**
* Retrieves an error message, using the validator's message combined with
* the errant value.
* @param context
* @return ValidatorMessages.getMessage
*/
public String getErrorMessage(final FacesContext context)
{
return ValidatorMessages.getMessage(
this.validatorAction,
this.args,
context);
}
/**
* @see Object#toString()
*/
@Override
public String toString()
{
return super.toString() + ":formId=" + this.formId + ", validatorAction="
+ (this.validatorAction != null ? this.validatorAction.getName() : null);
}
private static final long serialVersionUID = 1;
}