Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

The sample page

Code Block
xml
xml
titleThe sample pagexml
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
  <title>Hello World!</title>
</head>
<body>
<f:view>
  <h:form id="mainForm">
    <h:panelGrid columns="3">
      <h:outputLabel for="name" value="Please enter your name" />
      <h:inputText id="name" value="#{helloWorld.name}" />
      <h:message for="name" showSummary="true" showDetail="false" />

      <h:commandButton value="Press me" action="#{helloWorld.send}" />
      <h:panelGroup />
      <h:panelGroup />
    </h:panelGrid>
  </h:form>
</f:view>
</body>
</html>

...

The sample bean

Code Block
java
java
titleFragment of the HelloWorldController beanjava
public class HelloWorldController {
    
    @Required
    private String name;

    // getters and setters omitted for brevity
}

...

Cross validation

Code Block
java
java
titleUsage of cross validationjava
public class Person {

    @NotEquals("lastName")
    private String firstName;

    private String lastName;

    // Getters and setters omitted for brevity
}

...

Note
titleHint

If you don't like the default conventions, you can provide a custom name mapper, or you provide a mapping between annotations and the validation strategies (via properties file or ExtVal Java API), or ...

Code Block
java
java
titleMinimal custom constraintjava
package my.custom.package

@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface CustomConstraint {
}

Note that this is just a minimalistic annotation definition. There's nothing ExtVal-specific here.

Code Block
java
java
titleMinimal validation strategy implementationjava
package my.custom.package

public class CustomConstraintValidationStrategy implements ValidationStrategy {

    public void validate(FacesContext facesContext, UIComponent uiComponent,
                         MetaDataEntry metaDataEntry, Object convertedObject) {

        //custom validation logic
    }
}

...

The class below implements the validation strategy for the @Equals annotation. Note that the class can be relatively simple, since the hard work is done for us in the AbstractCompareStrategy.

Code Block
java
java
titleCustom cross-validation strategiesjava
@SkipValidationSupport
public class EqualsStrategy extends AbstractCompareStrategy {

    public boolean useTargetComponentToDisplayErrorMsg(CrossValidationStorageEntry crossValidationStorageEntry) {
        return true;
    }

    protected String getValidationErrorMsgKey(Annotation annotation, boolean isTargetComponent) {
        return ((Equals) annotation).validationErrorMsgKey();
    }

    public boolean isViolation(Object object1, Object object2, Annotation annotation) {
        return object1 != null && !object1.equals(object2);
    }

    public String[] getValidationTargets(Annotation annotation) {
        return ((Equals) annotation).value();
    }
}

...

As we've seen before, we can easily reference a property within the same bean by using the property name.

Code Block
java
java
titleReferencing a local propertyjava
public class Person
{
    @NotEquals("lastName")
    private String firstName;

    private String lastName;
...
}

We can also refer to properties of related beans, as is shown in the example below:

Code Block
java
java
titleReferencing a property of a local referencejava
public class RegistrationPage
{
    private Person person = new Person();

    @Equals("person.password")
    private String oldPassword;
...
}

It is even possible to use EL-like expressions to refer to properties of beans that will be available at runtime.

Code Block
java
java
titleReferencing a property of a beanjava
public class RegistrationPage
{
    @Equals("#{person.password}")
    private String oldPassword;
...
}

...

Solution: The validation strategy optionally provides a meaningful validation error message. It's called a reverse validation error message.

Code Block
java
java
titleExamplejava
...
    @Override
    protected String getReverseErrorMessageSummary(Annotation annotation)
    {
        return "meaningful validation error message summary";
    }
...
    @Override
    protected String getReverseErrorMessageDetail(Annotation annotation)
    {
        return "meaningful validation error message details";
    }
...

...

It's possible since the first version of ExtVal. Due to new features introduced in the 3rd release of ExtVal you will see additional things in the example you might not have seen so far. However, they aren't required since the approach just uses the mechanism of cross-validation.:

Code Block
java
java
titleCustom cross-validation annotationjava
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Documented
@UsageInformation(UsageCategory.API)
public @interface ValidateLengthIf
{
    String[] validateIf();

    int minimum() default 0;

    int maximum() default Integer.MAX_VALUE;

    Class<? extends ValidationParameter>[] parameters() default ViolationSeverity.Error.class;
}
Code Block
java
java
titleCustom cross-validation strategyjava
@SkipValidationSupport
public class ValidateLengthIfValidationStrategy extends AbstractCompareStrategy<ValidateLengthIf>
{
    private UIComponent component;
    private FacesMessage facesMessage;

    @Override
    protected void initCrossValidation(CrossValidationStorageEntry crossValidationStorageEntry)
    {
        this.component = crossValidationStorageEntry.getComponent();
    }

    public boolean isViolation(Object source, Object target, ValidateLengthIf annotation)
    {
        if (Boolean.TRUE.equals(target))
        {
            try
            {
                LengthValidator lengthValidator = resolveLengthValidator();
                lengthValidator.setMinimum(annotation.minimum());
                lengthValidator.setMaximum(annotation.maximum());
                lengthValidator.validate(FacesContext.getCurrentInstance(), this.component, source);
            }
            catch (ValidatorException e)
            {
                this.facesMessage = e.getFacesMessage();
                return true;
            }
        }
        return false;
    }

    private LengthValidator resolveLengthValidator()
    {
        return (LengthValidator)FacesContext.getCurrentInstance()
                .getApplication().createValidator("javax.faces.Length");
    }

    public String[] getValidationTargets(ValidateLengthIf annotation)
    {
        return annotation.validateIf();
    }

    @Override
    public boolean useTargetComponentToDisplayErrorMsg(CrossValidationStorageEntry crossValidationStorageEntry)
    {
        return false;
    }

    @Override
    protected String getErrorMessageSummary(ValidateLengthIf annotation, boolean isTargetComponent)
    {
        return this.facesMessage.getSummary();
    }

    @Override
    protected String getErrorMessageDetail(ValidateLengthIf annotation, boolean isTargetComponent)
    {
        return this.facesMessage.getDetail();
    }

    protected String getValidationErrorMsgKey(ValidateLengthIf annotation, boolean isTargetComponent)
    {
        //for using the message of the std. validator instead of a cross-validation-key for extval message resolving
        return null;
    }
}

...

Register a resource-bundle file which contains an annotation/validation strategy mapping:

Code Block
java
java
titleAlternative config approach via a property filejava
StaticConfiguration<String, String> staticConfig = new StaticResourceBundleConfiguration();
staticConfig.setSourceOfMapping("[custom package + name of the properties file.]");
ExtValContext.getContext().addStaticConfiguration(StaticConfigurationNames.META_DATA_TO_VALIDATION_STRATEGY_CONFIG, staticConfig);

...

This approach is more typesafe - a simple example:

Code Block
java
java
titleAlternative config approach which manual mappingjava
StaticInMemoryConfiguration staticConfig = new StaticInMemoryConfiguration();
staticConfig.addMapping(CustomConstraint.class.getName(), CustomValidator.class.getName());
ExtValContext.getContext().addStaticConfiguration(StaticConfigurationNames.META_DATA_TO_VALIDATION_STRATEGY_CONFIG, staticConfig);

...

  • Inject a Spring bean into the validation strategy
  • AOP exception handling
  • ...
Code Block
xml
xml
titleSpring based dependency injection support for validation strategiesxml
<!-- The name of annotation: @CustomRequired -->

<!-- Part of the Spring configuration: -->

<bean id="customRequiredValidationStrategy" class="..." lazy-init="true">
    <property name="messageResolver" ref="customMsgResolver"/>
    <property name="requiredValidationService" ref="demoRequiredValidationService"/>
</bean>

<bean id="customMsgResolver" 
      class="org.apache.myfaces.extensions.validator.core.validation.message.resolver.DefaultValidationErrorMessageResolver"
      lazy-init="true">

    <!-- With JSF 1.2 you can use the var name of resource-bundle see faces-config.xml -->
    <property name="messageBundleVarName" value="messages"/>
</bean>

<bean id="demoRequiredValidationService" class="..."/>

...