7 min read

The ExtVal framework is very extensible, and extending it is fairly simple. The framework uses the convention over configuration paradigm. This means that if we’re happy with the conventions of the framework, we don’t have to configure anything. As an example of the extensibility of ExtVal, in this section we’re going to change the default behavior of ExtVal’s @Pattern annotation.

The @Pattern annotation accepts an array of Strings for the value argument. This means that more than one regular expression can be used to validate the input. By default, all regular expressions have to be matched in order for an input string to be valid. For example, if the patterns [A-Z].S* and [A-Za-z]* are combined, this effectively means that only words starting with a capital letter and containing only the characters a through to z, which may or may not be in capitals, are allowed. Note that this can be achieved with one single expression too—[A-Z].[A-Za-z]*.

Although combining two regular expressions with an “and” relation might be useful sometimes, having multiple expressions where only one of them has to be matched can be quite powerful too. We can think of a list of patterns for various (international) phone number formats. The input would be valid if one of the patterns is matched. The same can be done for postal codes, social security codes, and so on. So let’s see how we can change the behavior of ExtVal to achieve this

Implementing a custom validation strategy

ExtVal uses the concept of Validation Strategy for every type of validation. So, if an @Pattern annotation is used, ExtVal will use a PatternStrategy to execute the validation. We can implement our own ValidationStrategy to override the functionality of ExtVal’s standard PatternStrategy. The easiest way to do this is to create a subclass of AbstractAnnotationValidationStrategy <Pattern>:

package inc.monsters.mias.extval;


import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;

import org.apache.myfaces.extensions.validator.baseval.annotation.Pattern;

import org.apache.myfaces.extensions.validator.core.metadata.MetaDataEntry;
import org.apache.myfaces.extensions.validator.core.validation.strategy.AbstractAnnotationValidationStrategy;

public class PatternOrValidationStrategy extends AbstractAnnotationValidationStrategy<Pattern> {

@Override
protected String getValidationErrorMsgKey(Pattern annotation) {
return annotation.validationErrorMsgKey();
}

@Override
protected void processValidation(FacesContext facesContext, UIComponent uiComponent, MetaDataEntry metaDataEntry, Object convertedObject) throws ValidatorException {


Pattern annotation = metaDataEntry.getValue(Pattern.class);
boolean matched = false;
String expressions = null;

for (String expression : annotation.value()) {
if (convertedObject != null && java.util.regex.Pattern.compile(expression).matcher(convertedObject.toString()).matches()) {

matched = true;
break;
} else {
 if (expressions == null) {
expressions = expression;
} else {
expressions += ", " + expression;

}
}
}
if(!matched) {
FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_ERROR, getErrorMessageSummary(annotation), getErrorMessageDetail(annotation).replace("{0}",expressions));

throw new ValidatorException(fm);
}
}
}

The most important part of this class is, of course, the processValidation() method. This uses the MetaDataEntry object to access the annotation that defines the validation. By calling annotation.value(), the array of Strings that was set in the @Pattern annotation’s value attribute is obtained. By iterating over that array, the user input (convertedObject.toString()) is matched against each of the patterns. If one of the patterns matches the input, the boolean variable matched is set to true and the iteration is stopped. A ValidatorException is thrown if none of the patterns matches the input. The else branch of the outer if statement is used to create a list of patterns that didn’t match. That list is appended to the error message if none of the patterns matches.

Now that we’ve created our own custom validation strategy, we will have to tell ExtVal to use that instead of the default strategy for the @Pattern annotation. The next section shows how to do that.

Configuring ExtVal to use a custom validation strategy

The most straightforward way to configure a custom Validation Strategy in ExtVal is to write a custom Startup Listener that will add our Validation Strategy to the ExtVal configuration. A Startup Listener is just a JSF PhaseListener with some specific ExtVal functionality—it deregisters itself after being executed, thus guaranteeing that it will be executed only once. We can simply subclass ExtVal’s AbstractStartupListener. That way, we don’t have to implement much ourselves:

package inc.monsters.mias.extval;

import org.apache.myfaces.extensions.validator.baseval.annotation.Pattern;

import org.apache.myfaces.extensions.validator.core.ExtValContext;
import org.apache.myfaces.extensions.validator.core.initializer.configuration.StaticConfigurationNames;
import org.apache.myfaces.extensions.validator.core.initializer.configuration.StaticInMemoryConfiguration;

import org.apache.myfaces.extensions.validator.core.startup.AbstractStartupListener;

public class PatternOrStartupListener extends AbstractStartupListener {

@Override
protected void init() {


// 1.
StaticInMemoryConfiguration config = new StaticInMemoryConfiguration();

// 2.
config.addMapping(Pattern.class.getName(), PatternOrValidationStrategy.class.getName());


// 3.
ExtValContext.getContext().addStaticConfiguration(StaticConfigurationNames.META_DATA_TO_VALIDATION_STRATEGY_CONFIG, config);
}
}

There are three important steps here, which tie up with the numbers in the previous code:

  1. Create a new StaticInMemoryConfiguration object.
  2. Add a mapping to the StaticInMemoryConfiguration object. This maps the @Pattern annotation to our own PatternOrValidationStrategy validation strategy implementation. The addMapping() method expects two Strings, each containing a fully qualified class name. The safest way to get these class names is to use class.getName() because that will work even if the class is moved to another package. This step actually maps the @Pattern annotation to the PatternOrValidationStrategy.
  3. The created StaticInMemoryConfiguration object has to be added to the ExtValContext to become effective.

Note that we don’t implement the usual PhaseListener methods here. They are already implemented by the AbstractStartupListener. The last thing that we have to do is to add this Startup Listener to our faces-config.xml file as an ordinary PhaseListener, as follows:

<phase-listener>inc.monsters.mias.extval.PatternOrStartupListener</phase-listener>

Mapping an annotation to a validation strategy is one of the many name mappings that are performed inside the ExtVal framework. ExtVal implements its own NameMapper mechanism for all mappings that are performed inside the framework. As with nearly every part of ExtVal, this NameMapper mechanism can be overridden if desired. See also the Extending ExtVal in many other ways section.

Using alternative configuration add-ons

Although implementing a custom Startup Listener is fairly simple, it might not be the most ideal way to configure ExtVal—especially if a lot of configuration changes have to be made. The author of ExtVal has created two add-ons for ExtVal that provide alternative ways to configure ExtVal. Those add-ons are not apart of the ExtVal project. The author of ExtVal provides them as examples, but no support is available for them. If they fit your needs, you can use them. If not, you can use them as a starting point to implement your own configuration add-on.

  • One alternative is the annotation-based configuration. In this case, custom implementations can be annotated with special annotations, and should be put in a special base package. At application startup, the base package will be scanned for annotations, and the found annotations will be used to create the necessary configuration. See the Extending ExtVal with add-ons section for the download location, and installation instructions for this add-on. Some basic usage documentation is provided at http://os890.blogspot.com/2008/10/myfaces-extval-config-extension.html.
  • One alternative is the annotation-based configuration. In this case, custom implementations can be annotated with special annotations, and should be put in a special base package. At application startup, the base package will be scanned for annotations, and the found annotations will be used to create the necessary configuration. See the Extending ExtVal with add-ons section for the download location, and installation instructions for this add-on. Some basic usage documentation is provided at http://os890.blogspot.com/2008/10/myfaces-extval-config-extension.html.

Testing the custom validation strategy

Now that we’ve implemented our custom Validation Strategy, let’s do a simple test. For example, we could add the @Pattern annotation to the firstName property of the Kid class, as follows:

@Column(name = "FIRST_NAME")
@Pattern(value={"[A-Za-z]*", "[0-9]*"})
private String firstName;

In this case, “Shirley” would be valid input, as would be “4623”. But “Shirley7” wouldn’t be valid, as none of the regular expressions allow both letters and digits. If we had used the default PatternStrategy, no valid input for the firstName field would be possible, as the regular expressions in this example exclude each other

Of course this test case is not very useful. As mentioned before, having different patterns where only one of them has to be matched can be very useful for different (international) phone number formats, postal codes, social security codes, and so on. The example here is kept simple in order to make it easy to understand what input will match and what input won’t match.

LEAVE A REPLY

Please enter your comment!
Please enter your name here