...
The sample page
Code Block |
---|
| xml |
---|
| xml |
---|
title | The 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 |
---|
title | Fragment of the HelloWorldController beanjava |
---|
|
public class HelloWorldController {
@Required
private String name;
// getters and setters omitted for brevity
}
|
...
Cross validation
Code Block |
---|
| java |
---|
| java |
---|
title | Usage of cross validationjava |
---|
|
public class Person {
@NotEquals("lastName")
private String firstName;
private String lastName;
// Getters and setters omitted for brevity
}
|
...
Note |
---|
|
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 |
---|
title | Minimal 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 |
---|
title | Minimal 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 |
---|
title | Custom cross-validation strategies | java |
---|
|
@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 |
---|
title | Referencing a local property | java |
---|
|
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 |
---|
title | Referencing 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 |
---|
title | Referencing 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 |
---|
|
...
@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 |
---|
title | Custom cross-validation annotation | java |
---|
|
@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 |
---|
title | Custom 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 |
---|
title | Alternative 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 |
---|
title | Alternative 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 |
---|
title | Spring 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="..."/>
|
...