Dashboard > JSF Spring Hibernate JSF Facelets Tutorial > Home > Doing field comparison at the Java level
JSF Spring Hibernate JSF Facelets Tutorial Log In   View a printable version of the current page.
Doing field comparison at the Java level
Added by Rick Hightower, last edited by Rick Hightower on Feb 09, 2007

Objectives

We are going to cover how we implemented the field comparison support at the Java level.

There were some tricky parts here because we have to compare the value the property is going to be instead of the value the property is. Imagine you have two fields called start and stop. You want to write a comparison that start is less than stop. Well in JSF for example, the actual values of start and stop do not get applied to the model object until the UPDATE MODEL VALUES phase (Spring MVC has a similar but different concept). This means we need the value that stop could be rather than the value that it is.

One utility method that was particularly hard to write but seems very useful is the findInput which finds the input field based on a property name. We use this method to find the input component that contains the property that we want to compare the field value to. We actually have to use the JSF component value as we need to do the comparison before the component values are actually passed to the domain object. Pretty psycho low-level JSF code... a useful utility for sure.

Background

To get his most out of this you may want to read the series on developing an extensible validation mechanism using Spring style extension points.

Writing field validation rules that compare properties.

First let's define an annotation as follows (remember we can use properties files or annotations).

Equals.java
package org.crank.annotations.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Equals {
    
    String compareToProperty();
    String detailMessage() default "";
    String summaryMessage() default "";


}

Remember in our world, annotation are just dataholders.

Notice the use of the @Equals annotation below.

User.java
package com.arcmind.jsfquickstart.model;

import java.io.Serializable;
import java.util.Date;

import org.crank.annotations.validation.Equals;
...

public class User implements Serializable {
    
    ...
    private String password;
    private String password2;
    
    public User () {
    }
    
    public int getAge() {
        return age;
    }
    

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword2() {
        return password2;
    }

    @Equals (compareToProperty="password")
    public void setPassword2(String password2) {
        this.password2 = password2;
    }

}

Everything else is as it was before.

The tricky part. Defining a Validation Context.

The tricky part is now that we are comparing two fields we need to pass some sort of context (a validation context) to our FieldValidators since they need more context (information) like who there parent object is and such. How do we pass additional information about the context that we are validating? The answer is we define a thread local variable that holds the additional information. This is better than passing 20 arguments to every FieldValidator as most field validators don't give a hoot about this additional context. This idea is borrowed from WebWork where I saw it used first and JSF where it is used as well. In fact, we will define an Abstract base class that knows how to get information from the context.

Now this context is going to vary depending on which framework we are using, i.e., JSF or Spring MVC. (The eventual plan is to have this validation framework work with both.) Normally I would define a interface to define this context however since this Context is going to be used in one area and is fairly specific with a finite number of extensions (one per framework), I opted for an abstract class that could both define the contract for the Validation Context and manager the Thread Local Variable that holds it.

Here is the ValidationContext.

ValidationContext.java
package org.crank.validation;

import java.util.Map;

/** This is the validator context. 
 *  It holds thread local state that is important to validation.
 *  It is an interface into underlying component and/or MVC/Model 2 architectures
 *  to abstract and simplify access to the context of things we need to perform field validations.
 *  @author Rick Hightower
 * */
public abstract class ValidationContext {
    
    /** Holds the parent object of the field. A parent object is an
     * object that contains fields.*/
    private Object parentObject;
    
    /** Context for validaiton rule variables. */
    private Map<String, Object> params;
    
    /** Holds the data(context) for the current thread. */
    private static ThreadLocal holder = new ThreadLocal();
    
    /** Provides access to the ValidationContext. */
    public static ValidationContext getCurrentInstance() {
        return (ValidationContext) holder.get();
    }
    
    /**Allows the subclass to register an instance of itself 
     * as the context. The subclass will either be JSF, Struts 2 (WebWork) or
     * Spring MVC aware.
     * 
     * @param context
     */
    protected void register(ValidationContext context) {
        holder.set((ValidationContext)context);
    }

    /** Get the parent object. Allows the FieldValidators to access
     * the parent object. 
     * @return
     */
    public Object getParentObject() {
        return parentObject;
    }
    
    /** Allows our integration piece for JSF or Spring MVC to set the 
     * parent object. The parent object is the form bean in Spring MVC speak.
     * @param parentObject
     */
    public void setParentObject(Object parentObject) {
        this.parentObject = parentObject;
    }

    /**
     * Proivde a list of parameters that we can access from field validators.
     * @return
     */
    public Map<String, Object> getParams() {
        return params;
    }

    public void setParams(Map<String, Object> params) {
        this.params = params;
    }
    
    /**
     * Gets the proposed property value.
     * This is the value before it gets applied to the actual bean.
     * @param propertyName
     * @return
     */
    public abstract Object getProposedPropertyValue(String propertyName);
}

Please note that some of the methods are marked abstract. The most interesting class is the class that subclasses this one.

The AbstractCompareValidator is the base class that all validators that do field comparison should subclass. It deals with the ValidationContext so classes that subclass it do not have to. This minimizes the affect of changing the ValidationContext (which is a real possibility especially when porting to other frameworks Struts 2, Spring MVC).

AbstractCompareValidator.java
package org.crank.validation.validators;


import org.crank.annotations.design.ExpectsInjection;
import org.crank.annotations.design.Implements;
import org.crank.validation.FieldValidator;
import org.crank.validation.ValidationContext;
import org.crank.validation.ValidatorMessage;
import org.crank.validation.ValidatorMessageHolder;


/**
 * <p>
 * <small>
 * AbstractCompareValidator is the base class for all validators that compare
 * two values to perform validation.
 * 
 * Future versions of this class will deal with other values besides 
 * simple properties like expressions.
 * 
 * AbstractCompareValidator
 * </small>
 * </p>
 * @author Rick Hightower
 */
public abstract class AbstractCompareValidator extends AbstractValidator {

    /** Holds the name of the variable we are getting out of the validation
     * context. This variable could be another property.
     */
    private String compareToProperty;

    /**
     * 
     */
    @Implements(interfaceClass=FieldValidator.class)
    public ValidatorMessageHolder validate(Object value, String fieldLabel) {
        
        /* Create the validator message. */
        ValidatorMessage message = new ValidatorMessage();
        
        /* Get the comparison value. */
        Object compareToPropertyValue = lookupCompareToPropertyValue();
        
        /* Check to see if this is valid. */
        boolean valid = checkValidity(value, compareToPropertyValue);
        
        /* If it is not valid, then populate the message and 
         * return it regardless.
         */
        if (!valid) {
            populateMessage(message, fieldLabel);
        }

        return message;
    }
    

    /**
     * Check Validity of the value compared to the property value.
     * This class uses the template design pattern. Subclasses are suppose
     * to override this method and this class uses IoC to delegate
     * validity checking to the subclass.
     * 
     * @param object
     * @param compareToPropertyValue
     * @return
     */
    protected abstract boolean checkValidity(Object object, 
            Object compareToPropertyValue);


    /** This method looks of the ValidationContext to get the
     *  compareToProperty.
     * @return
     */
    protected Object lookupCompareToPropertyValue() {
        return ValidationContext.getCurrentInstance()
            .getProposedPropertyValue(compareToProperty);
    }


    /**
     * 
     * @param compareToProperty
     */
    @ExpectsInjection
    public void setCompareToProperty(String compareToProperty) {
        this.compareToProperty = compareToProperty;
    }


    protected String getCompareToProperty() {
        return compareToProperty;
    }


}

Now since the base class does most of the heavy lifting the base class is nearly nude in comparison as follows:

EqualsCompareValidator.java
package org.crank.validation.validators;

/** This class checks to see if the values are Equal. */
public class EqualsCompareValidator extends AbstractCompareValidator {

    @Override
    protected boolean checkValidity(Object object, Object compareToPropertyValue) {
        return object.equals(compareToPropertyValue);
    }

}

Most of the real heavy lifting comes in the JSF integration which we will cover next. (Spring MVC integration is still waiting for a need. My next project will use Spring MVC for the main web app and JSF for the admin pages so maybe the need will arise soon. I am sure I will write the Spring MVC integration this year).

JSF Integration

The bulk of the JSF integration is done in the JSFValidationContext which subclasses the ValidationContext that we mentioned earlier as follows:

(Please read the comments)

JSFValidationContext.java
package org.crank.web.validation.jsf.support;

import javax.faces.component.UIForm;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;

import org.crank.validation.ValidationContext;

/** This class implements the ValidationCotext for JSF. 
 *  It was a pain to write.
 * */
class JSFValidationContext extends ValidationContext {
    
    /** The form that holds the fields. */
    private UIForm form;
    
    /** 
     * Create the JSFValidation context passing a input field.
     * @param input
     */
    public JSFValidationContext (UIInput input) {
        /* We need the form that holds this field. */
        this.form = JSFComponentTreeUtils.findForm(input);
    }
    
    
    /**
     * Here is the magic of this class. We need to get the value
     * of a property in the validation phase before it gets applied to the
     * actual model object. Thus, we need to grab the value from the actual
     * component since at this point it is only a proposed value.
     * 
     * 
     */
    @Override
    public Object getProposedPropertyValue(String propertyName) {
        
        /* We have to find the component that holds the proposed value. */
        UIInput input = JSFComponentTreeUtils.findInput(form, propertyName);
        
        /* If we could not find it, complain since this means it is
         * likely missconfigured. */
        if (input==null) {
            throw new RuntimeException("Could not find the component" +
                    " for propertyName=" + propertyName + 
                    " for form=" + form.getId());
        }
    
        /* Extract the value from the input component 
         * force it to convert if needed. */
        return extractValueForceValidationIfNeeded(input);
    }

    
    /**
     * This method provides two bits of magic JSF programming.
     * 
     * This method extracts the value from the input component and 
     * forces the component to convert if needed. 
     * 
     * See, the problem is that the component may not be validated yet.
     * 
     * If it has not been validated yet, then the converter has not
     * been invoked yet and the value is null.
     * 
     * Thus, we need to force it to validate if the value is null.
     * 
     * @param input
     * @return
     */
    private Object extractValueForceValidationIfNeeded(UIInput input) {
        /* See if the value is null, if it is force conversion and validation. 
         * You have to understand the JSF lifecycle to understand why we
         * are doing this.
         * */
        if (input.getValue() == null){
            try {
                /* Force validation which will force conversion. */
                input.validate(FacesContext.getCurrentInstance());
            } catch (Exception ex) {
                /* We don't actually want the validation errors. 
                 * We just want the conversion to happen.
                 * JSF will call validate when it wants the actual
                 * error messages.
                 * */
                //ignore FacesExceptions
            }
        }
        return input.getValue();

    }

    /**
     * Register this bad boy so we can access it from our FieldValidators.
     */
    @Override
    protected void register(ValidationContext context) {
        super.register(context);
    }
    
    /**
     * Free. Set yourself free. If you love something set it free.
     * Free as in beer.
     * 
     * This method frees the context from the Thread local variable.
     */
    void free() {
        super.register(null);
    }

}

Okay... The really, really interesting code is in this utility class that the above uses as follows:

BTW One utility method that was particularly hard to write but seems very useful is the findInput which finds the input field based on a property name. We use this method to find the input component that contains the property that we want to compare the field value to. We actually have to use the JSF component value as we need to do the comparison before the component values are actually passed to the domain object. Pretty psycho low-level JSF code... a useful utility for sure.

JSFComponentTreeUtils.java
package org.crank.web.validation.jsf.support;

import java.util.Iterator;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

import org.crank.annotations.design.NeedsRefactoring;

/**
 * Misc. JSF component tree manipulation code.
 * 
 * @author Rick Hightower heavily based on code 
 * from Cagatay Civici writen with Apache license.
 */
public class JSFComponentTreeUtils {

    /** Find a component in a component subtree.. 
     * Here we are navigating down the tree. */
	public static UIComponent findComponent(UIComponent root, String id) {
        
        /* Hey maybe we are lucky and the component is the root. */
		if (id.equals(root.getId()))
			return root;

        /* Recursively iterate through the component tree and find
         * this id. Peek a boo... I see you.*/
		UIComponent child = null;
		UIComponent component = null;
		Iterator children = root.getFacetsAndChildren();
        
        /* Iterate through the component tree. */
		while (children.hasNext() && (component == null)) {
            
            /* Evaluate this child. */
			child = (UIComponent) children.next();
            
            /* Hey child are you the component I am looking for? */
			if (id.equals(child.getId())) {
				component = child;
				break;
			}
            /* If not, does the component I looking for exist
             * in your component tree?
             */
			component = findComponent(child, id);
			if (component != null) {
				break;
			}
		}
		return component;
	}

    /* This method starts at the root and finds the component. */
	public static UIComponent findComponentInView(String id) {
		UIComponent component = null;
		FacesContext context = FacesContext.getCurrentInstance();
		if (context != null) {
			UIComponent root = context.getViewRoot();
			component = findComponent(root, id);
		}
		return component;
	}

    /** Find a childs component form if it can be found. 
     *  Here we are navigating up the tree.
     * */
	public static UIForm findForm(UIComponent child) {
		UIComponent component = child;
		while (!((component=component.getParent())instanceof UIForm)){
			if (component instanceof UIViewRoot) break;
		}
		return (UIForm)component;
	}

    /** 
     * Find an input component given a property name it is bound to.
     * 
     * @param container
     * @param propertyName
     * @return
     */
    @NeedsRefactoring("See long comment at the end of the method.")
    public static UIInput findInput(UIComponent container, String propertyName) {
        
        /* Let's try this the easy way first. */
        UIInput input = (UIInput)container.findComponent(propertyName);
        
        if (input == null) {
            /* If that did not work, let's get a little rougher. */
            input = (UIInput) findComponent(container, propertyName);
        }
      
        /* Look for the property as part of the value expression. */
        if (input==null) {
            List<UIComponent> children = container.getChildren();
            for (Iterator iter = children.iterator(); iter.hasNext();) {
                UIComponent comp = (UIComponent) iter.next();
                if (!(comp instanceof UIInput)) {
                    return findInput(comp, propertyName);
                } else {
                    UIInput potentialInput = (UIInput) comp;
                    String expression = potentialInput.getValueExpression("value").getExpressionString();
                    if (expression.endsWith(propertyName+"}")
                            || 
                        expression.endsWith(propertyName+"']}")        
                    ){
                        input = potentialInput;
                    } 
                }
            }
        }

        /* The same thing but less strict. */
        if (input==null) {
            List<UIComponent> children = container.getChildren();
            for (Iterator iter = children.iterator(); iter.hasNext();) {
                UIComponent comp = (UIComponent) iter.next();
                if (!(comp instanceof UIInput)) {
                    return findInput(comp, propertyName);
                } else {
                    UIInput potentialInput = (UIInput) comp;
                    String expression = potentialInput.getValueExpression("value").getExpressionString();
                    if (expression.contains(propertyName)){
                        input = potentialInput;
                    } 
                }
            }
        }
        /* The only other thing left is to look for brackets ([]) that don't have
         * quotes (not ['...'] OR ["..."]) and then evaluate the object in 
         * the brackets and see if it is
         * a string. If it is a string, then see if it is equal to our
         * property value. We may have to do this to make this work in all
         * situations. The above should make this work Presto and 99% of
         * other JSF applications. The last bit I described should cover the remained
         * 1%. This is a lot of effort for the remaing 1% so I defer for now.
         */
        return input;
    }
    
}

Both the utility code and the JSF validation context code get used by the JSF meta-data validator defined as follows (please read comments... Please scan the class below and see how the JSF validation context is getting used):

JSFBridgeMetaDataDrivenValidator.java
package org.crank.web.validation.jsf.support;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;

import org.crank.annotations.design.DependsOnJSF;
import org.crank.annotations.design.ExpectsInjection;
import org.crank.core.CrankConstants;
import org.crank.core.CrankContext;
import org.crank.core.ObjectRegistry;
import org.crank.core.PropertiesUtil;
import org.crank.validation.FieldValidator;
import org.crank.validation.ValidationContext;
import org.crank.validation.ValidatorMetaData;
import org.crank.validation.ValidatorMetaDataReader;
import org.crank.validation.validators.CompositeValidator;

/**
 * 
 * <p>
 * This class allows us to perform JSF validations based on meta-data stored in
 * our classes.
 * </p>
 * 
 * <p>
 * This class acts as a bridge from the Crank validator world to the JSF world.
 * </p>
 * 
 * <p>
 * This allows us to easily associated validators with model properties'
 * meta-data.
 * 
 * We then delegate validation to the crank validation system.
 * </p>
 * 
 * @author Rick Hightower
 */
@DependsOnJSF
public class JSFBridgeMetaDataDrivenValidator extends
		AbstractJSFBridgeValidator {
	private ValidatorMetaDataReader jsfBridgeValidatorMetaDataReader = null;

	private PropertiesUtil jsfBridgeValidatorPropertiesUtil = null;

    @Override
	/**
	 * Find the crank validator.
	 * 
	 * 
	 */
	@DependsOnJSF
	protected FieldValidator findValidatorAndFieldName(
			FacesContext facesContext, UIComponent component,
			String[] fieldNameHolder) {

		/*
		 * Get the value expression associated with the component that was
		 * passed.
		 */
		UIInput inputComponent = (UIInput) component;
		ValueExpression valueExpression = inputComponent
				.getValueExpression("value");
		String expressionString = valueExpression.getExpressionString();
        /*
         * Extract the parent object expression and the property name (field)
         * that we are validating.
         */
        ValidatorData validatorData = new ValidatorData(expressionString, facesContext);

        registerValidationContext(facesContext, inputComponent, validatorData);
		fieldNameHolder[0] = validatorData.getPropertyNameOfTheField();

		/* Read the metaDataList for this property of this parent object. */
		List<ValidatorMetaData> metaDataList = readMetaData(validatorData
				.getParentClassOfTheField(), validatorData.getPropertyNameOfTheField());
		CompositeValidator cv = createValidator(metaDataList);
		return cv;
	}
    
    /**
     * Clean up the Validation context.
     * If you love something set it free. If it does not come back to you,
     * hunt it down and really destroy it. Clean up the Thread local variable.
     */
    protected void cleanup() {
        JSFValidationContext context = (JSFValidationContext) 
            ValidationContext.getCurrentInstance();
        context.free();
    }

    /**
     * Register a new ValidationContext and initialize it.
     * 
     * @param facesContext
     * @param inputComponent
     * @param validatorData
     */
    private void registerValidationContext(FacesContext facesContext, UIInput inputComponent, ValidatorData validatorData) {
        JSFValidationContext context = new JSFValidationContext(inputComponent);
        context.setParentObject(validatorData.lookupParentObject(facesContext));
        context.setParams(inputComponent.getAttributes());
        context.register(context);
    }

	/**
	 * Create the validator by looking it up in the ObjectRegistry and then
	 * populating it with values from the meta-data list.
	 * 
	 * @param validationMetaDataList
	 *            Holds metadataInformation about validation.
	 * @return
	 */
	protected CompositeValidator createValidator(
			List<ValidatorMetaData> validationMetaDataList) {

		/*
		 * A field (property) can be associated with many validators so we use a
		 * CompositeValidator to hold all of the validators associated with this
		 * validator.
		 */
		CompositeValidator compositeValidator = new CompositeValidator(); // hold
		/*
		 * Lookup the list of validators for the current field and initialize
		 * them with validation meta-data properties.
		 */
		List<FieldValidator> validatorsList = 
			lookupTheListOfValidatorsAndInitializeThemWithMetaDataProperties(validationMetaDataList);

		compositeValidator.setList(validatorsList);

		return compositeValidator;
	}

	/**
	 * Lookup the list of validators for the current field and initialize them
	 * with validation meta-data properties.
	 */
	private List<FieldValidator> 
	   lookupTheListOfValidatorsAndInitializeThemWithMetaDataProperties(
			   List<ValidatorMetaData> validationMetaDataList) {
		
		List<FieldValidator> validatorsList = new ArrayList<FieldValidator>();

		/*
		 * Look up the crank validators and then apply the properties from the
		 * validationMetaData to them.
		 */
		for (ValidatorMetaData validationMetaData : validationMetaDataList) {
			/* Look up the FieldValidator. */
			FieldValidator validator = lookupValidatorInRegistry(
					validationMetaData
					.getName());
			/*
			 * Apply the properties from the validationMetaData to the
			 * validator.
			 */
			applyValidationMetaDataPropertiesToValidator(validationMetaData,
					validator);
			validatorsList.add(validator);
		}
		return validatorsList;
	}

	protected List<ValidatorMetaData> readMetaData(Class clazz,
			String propertyName) {
		initIfNeeded();
		return jsfBridgeValidatorMetaDataReader.readMetaData(clazz,
				propertyName);
	}

	/** For dependency injection of the ValidatorMetaDataReader. */
	@ExpectsInjection
	public void setJsfBridgeValidatorMetaDataReader(
			ValidatorMetaDataReader jsfBridgeValidatorMetaDataReader) {
		this.jsfBridgeValidatorMetaDataReader = 
			jsfBridgeValidatorMetaDataReader;
	}

	/**
	 * This method looks up the validator in the registry.
	 * 
	 * 
	 * @param validationMetaDataName
	 *            The name of the validator that we are looking up.
	 * 
	 * @return
	 */
	private FieldValidator lookupValidatorInRegistry(
			String validationMetaDataName) {
		ObjectRegistry applicationContext = CrankContext.getObjectRegistry();

		FieldValidator validator = (FieldValidator) applicationContext
				.getObject(CrankConstants.FRAMEWORK_PREFIX
						+ CrankConstants.FRAMEWORK_DELIM + "validator"
						+ CrankConstants.FRAMEWORK_DELIM
						+ validationMetaDataName, FieldValidator.class);
		return validator;
	}

	/**
	 * Look up dependencies in object registry.
	 * 
	 */
	private void initIfNeeded() {
		if (jsfBridgeValidatorMetaDataReader == null
				|| jsfBridgeValidatorPropertiesUtil == null) {
			CrankContext.getObjectRegistry().resolveCollaborators(this);
			assert jsfBridgeValidatorMetaDataReader != null : "The " +
					"jsfBridgeValidatorMetaDataReader was found in " +
					"the Spring application context file";
		}
	}

	/**
	 * This method applies the properties from the validationMetaData to the
	 * validator uses Spring's BeanWrapperImpl.
	 * 
	 * 
	 * @param validationMetaDataName
	 *            The name of the validator that we are looking up.
	 * 
	 * @return
	 */
	private void applyValidationMetaDataPropertiesToValidator(
			ValidatorMetaData metaData, FieldValidator validator) {
		initIfNeeded();
		Map<String, Object> properties = metaData.getProperties();
        ifPropertyBlankRemove(properties, "detailMessage");
        ifPropertyBlankRemove(properties, "summaryMessage");
		this.jsfBridgeValidatorPropertiesUtil.copyProperties(validator,
				properties);
	}

    /** Removes a property if it is null or an empty string. 
     *  This allows the property to have a null or emtpy string in the
     *  meta-data but we don't copy it to the validator if the property
     *  is not set.
     *  @author Rick Hightower 
     * */
	private void ifPropertyBlankRemove(Map<String, Object> properties, String property) {
	    Object object = properties.get(property);
        if (object == null) {
            properties.remove(property);
        } else if (object instanceof String) {
            String string = (String) object;
            if ("".equals(string.trim())){
                properties.remove(property);    
            }
        }
    }

    public void setJsfBridgeValidatorPropertiesUtil(
			PropertiesUtil jsfBridgeValidatorPropertiesUtil) {
		this.jsfBridgeValidatorPropertiesUtil = 
			jsfBridgeValidatorPropertiesUtil;
	}

}

That sums up how we did the field comparison in Java at a very low level.

Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 1.4.2 Build:#214 Jun 30, 2005) - Bug/feature request - Contact Administrators