Apache MyFaces 1.2

«»

Creating custom error messages


With ExtVal, the error messages shown if validation fails come from different
sources, as follows:



  • For validation that is derived from JPA annotations, ExtVal relies on the
    standard JSF validators. Hence, the error messages shown are the standard
    JSF error messages. The way in which standard JSF messages can be
    overridden is defined in the JSF standard. This is covered in the next section,
    Overriding standard JSF error messages.

  • The ExtVal annotations @Length, @DoubleRange, @LongRange, and
    @Required also rely on standard JSF mechanisms for implementing
    the validation. So these will lead to standard JSF error messages as well.

  • All other ExtVal annotations have their own default error messages.
    How to override these ExtVal messages is covered in the Overriding
    ExtVal default error messages
    section.


Overriding standard JSF error messages


Although overriding standard JSF messages is not a feature of ExtVal, we cover
it briefl y here for convenience. Standard JSF error messages can be overridden by
configuring a message bundle in the faces-config.xml file, and adding certain
key/value pairs to that message bundle. In our MIAS application, we’ve configured
a message bundle as follows:



<message-bundle>inc.monsters.mias.Messages</message-bundle>


This means that the JSF framework expects a file called Messages.properties to
be present in the inc/monsters/mias directory. In that file, we can configure our
custom messages. For example, to override the default message for required fields
that are left empty, we could add the following to the file:



javax.faces.component.UIInput.REQUIRED =
Hey dude, this field is required!


The important thing here is the key—javax.faces.component.UIInput.REQUIRED.
A list of all JSF error messages with their keys can be found in Appendix D. This
appendix also shows the placeholders that can be used in the message texts. The
placeholders will be replaced by the label of the input element that the message is
related to, and examples of good values or maximum and minimum values
where applicable.


Overriding ExtVal default error messages


ExtVal always looks in a fixed location for a message bundle; it doesn’t care about
the JSF message bundle configuration. To change a message, we can either put a
message bundle in that default location, or we can tell ExtVal to look for the message
bundle in another location. The default message bundle that ExtVal looks for is
validation_messages in the org.apache.myfaces.extensions.validator.
custom package. Of course, we could create that package within our application
and put a validation_messages.properties file there. But wouldn’t it be great if
we could just use our application-wide message bundle? That’s possible by telling
ExtVal to look somewhere else for a message bundle. This is done by setting a
context parameter in the web.xml file as follows:



<context-param>
<param-name>
org.apache.myfaces.extensions.validator.CUSTOM_MESSAGE_BUNDLE
</param-name>
<param-value>inc.monsters.mias.Messages</param-value>
</context-param>


Now we can put custom messages in our own Messages.properties file. To
override the default error message for the @Pattern annotation, we could add:



no_match = Pattern not matched


But in this case, “pattern not matched” might be a bit too generic as an error message
for end users. ExtVal lets us override the error message on a per-field basis, allowing
us to define more specific error messages. For example, we could have a firstName
field with a pattern that allows only letters. Now we would like to have a message
saying that only letters are allowed in names. In that case, we could write:



@Column(name = “FIRST_NAME”)
@Pattern(value=”[A-Za-z]*”
validationErrorMsgKey=”name_characters”)
private String firstName;


Now if we add a name_characters key to our message bundle, we can set our
customized, field-specific message:



name_characters = A name may only contain letters


In case we want to override the default ExtVal messages, a list of the default
messages and their keys can be found in Appendix E.



Alternative options for customized messages
The options that we just saw are not the only options that ExtVal offers
for creating custom error messages. However, the presented options are
very useful in most cases and allow us to use a single message bundle for
the whole application.
However, there may be reasons why we need other ways to customize
the messages. For example, if we were creating a standalone validation
module for reuse in more than one project, setting a context parameter
in the web.xml file is not an option. In that case, implementing our own
MessageResolver is probably a good idea. This is fairly simple to do
by just subclassing the DefaultValidationErrorMessageResolver
class from the org.apache.myfaces.extensions.validator.
core.validation.message.resolver package. Only the
getCustomBaseName() method needs to be overridden, and this
should return the fully-classified name of the message bundle.
ExtVal offers even more alternatives. Should you want to look for other
ways to provide custom messages, the ExtVal wikiis a good place to start:
http://wiki.apache.org/ myfaces/Extensions/Validator/
Getting_Started/Custom_Messages.


The next section discusses how we can extend ExtVal by creating our own custom
validation strategy.


Creating our own validation strategy


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.

  • The other alternative way to configure ExtVal is to use Java in a way that
    is inspired by the way Google Guice does this sort of things. In this case, a
    custom startup listener has to be created in which the Google Guice
    style code can be executed. Basic usage information can be found at

    http://os890.blogspot.com/2009/09/myfaces-extval-java-configextension.

    html. See the Extending ExtVal with add-ons section for the
    download location and installation instructions.



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.


Extending ExtVal in many other ways


Implementing a custom Validation Strategy is just one example of the many concepts
in ExtVal that can be overridden by implementing a custom subclass, albeit one of
the most useful ones. Here’s a list of other concepts in ExtVal that can be overridden:



  1. StartupListener can be used to perform various actions at startup, such as
    registering any overridden ExtVal class. See the example in the Configuring
    ExtVal to use a custom Validation Strategy
    section.

  2. ValidationStrategy can be used to customize the validation behavior, as
    discussed in the previous section. The easiest way to implement this interface
    is to subclass one of the abstract classes provided by ExtVal.

  3. MessageResolver can be used to customize the error messages.

  4. ComponentInitializer allows the initialization of components before
    they are rendered. This can be used, for example, to add special client-side
    validation behavior to components.

  5. MetaDataTransformer transforms constraints to an independent format
    so a component initializer doesn’t have any knowledge about the annotation
    used. A detailed explanation of this mechanism can be found in the
    Empower the Client section of an article about ExtVal on JSF Central,
    at http://jsfcentral.com/articles/myfaces_extval_3.html.

  6. MetaDataExtractionInterceptor allows on-the-fl y manipulation
    of metadata.

  7. InformationProviderBean makes it possible to customize name
    conventions.

  8. ProcessedInformationRecorder can be used to capture values after they
    are converted by JSF. For example, the ExtVals implementation of cross
    validation is based on this mechanism.

  9. RendererInterceptor is one of the base mechanisms of ExtVal that is used
    to intercept renderers. All methods of javax.faces.render.Renderer can
    be intercepted.

  10. NameMapper is used extensively throughout the ExtVal framework, in order
    to map sources to targets. In most cases, names are mapped; for example,
    annotation names are mapped to validation strategy names.

  11. ExtVal makes extensive use of the Factory design pattern, and comes with
    a lot of factories that can be used when extending ExtVal. It is also possible
    to override the default factories as a way of changing ExtVal’s behavior. An
    overview of all of the factories can be found in the org.apache.myfaces.
    extensions.validator.core.factory.FactoryNames class in the
    ExtVal sources.


This list can be used as a starting point for exploring the extension opportunities in
ExtVal. Some more information can be found on the ExtVal wikiat http://wiki.
apache.org/myfaces/Extensions/Validator/DevDoc and http://wiki.apache.
org/myfaces/Extensions/Validator/ConceptOverview.


Extending ExtVal with add-ons


ExtVal is a very fl exible framework that was built with the possibility to extend it
in mind. As we saw in the previous section, the ExtVal framework is full of hooks
that can be a starting point for extending it. Of course, because it is an open source
framework, anyone has the opportunity to extend and modify the framework to fit
his needs. This can be a challenging job, even for advanced programmers. Everyone
who has the time and knowledge should be encouraged to do so, as they can help
in improving and expanding the ExtVal framework, or any other part of MyFaces.
However, this section will focus on an easier way to expand the possibilities of the
ExtVal framework: by using add-ons.


Getting add-ons for ExtVal


As ExtVal is a relatively new project and is not yet widely used, there are currently
no “third party” open source add-ons for ExtVal. However, the lead developer of
the ExtVal project has created some very useful add-ons. The following tables give
an overview of the ExtVal add-ons that are available at the time of writing of this
chapter. Keep an eye on the weblog of ExtVal’s lead developer for the latest news
about add-ons—http://os890.blogspot.com/.






Note that all of the add-ons mentioned here are hosted on the “os890” Maven
repository. Two different root paths are used for the add-ons. The path starting
with http://os890-m2-repository.googlecode.com/svn/trunk/os890 is the
location for add-ons that work only with snapshot releases of ExtVal. Once a new
stable version of ExtVal is released, we can expect these plugins to move to the other
path in the Maven repository where all plugins that work with the stable versions of
ExtVal are located, which is the URL starting with http://os890-m2-repository.
googlecode.com/svn/tags/os890. If we were using Maven, we could have used
these two URLs as Maven repositories and let Maven download the add-ons and
install them.



More cutting-edge add-ons for adventurous people
As stated above, the add-ons listed here are all available via the “os890”
Maven repository. Should you want to look for even more add-ons,
and if you are prepared to compile them from the source code yourself,
the source code repository of the “os890” project is an interesting place
to visit. You can find it at http://code.google.com/p/os890/
source/browse/#svn/trunk/java/web/jsf/extval.


Installing ExtVal add-ons


Installing an ExtVal add-on is simple. We only have to add the downloaded JAR to
the shared lib directory in which all other ExtVal JARs are located, in our EAR file.
If we have an application that is deployed as a single WAR file, we could simply add
the JARs to the deployed libraries of that WAR file. This is all there is to do to install
an ExtVal add-on. Note that each add-on has its specific usage instructions. Refer to
the documentation that is linked to in the tables in the previous section.


Using Bean Validation


Using annotations in JavaBeans is an elegant way of defining validation rules in a
declarative way. Apart from MyFaces ExtVal there are other projects that introduced
declarative validation, such as the Hibernate Validator and the Bean Validation
Framework for Spring. Some framework developers realized that it would be a
good idea to standardize this type of validation. This led to the Bean Validation
specification that was developed as JSR 303 in the Java Community Process.
Accordingly, Bean Validation will be a standard part of Java EE 6, but it can be used
in Java EE 5 by manually including a Bean Validation implementation.


One of the benefits of having an official standard for validation is that various user
interface frameworks can implement support for this type of validation. For example,
JavaServer Faces 2.0 will have support for Bean Validation embedded in it, and other
UI frameworks will probably follow.


But at the moment, we’re still building Java EE 5 and JSF 1.2 applications. And
although we can use Bean Validation in Java EE 5, JSF 1.2 doesn’t have Bean
Validation support. And that’s where ExtVal comes in. We can use ExtVal to
integrate JSR 303 Bean Validation into JSF 1.2 (and even JSF 1.1) projects. This section
will discuss some Bean Validation basics and show how to use Bean Validation with
ExtVal.



Note that we can only cover some basics of Bean Validation here. As Bean
Validation is a new standard, there is not much reference documentation
available yet. However, some decent documentation comes bundled
with Hibernate Validator—the reference implementation of JSR 303. That
documentation is also available online at http://docs.jboss.org/
hibernate/stable/validator/reference/. As an alternative,
the official specification of JSR 303 can be used as documentation. The
official specification can be found at http://jcp.org/en/jsr/
summary?id=303.


Setting up Bean Validation and ExtVal


To use Bean Validation, we need a JSR 303 implementation, unless we’re using
a Java EE 6 compliant application server. Currently, the only available JSR 303
implementation is the reference implementation, which is Hibernate Validator 4.0.
Hibernate Validator can be downloaded from https://www.hibernate.org/30.
html. We should make sure we download a 4.0 version, as versions before 4.0 do not
implement the JSR 303 standard. At the time of writing this chapter, the latest release
is 4.0.2 GA.


After downloading Hibernate Validator, we have to add the Bean Validation
libraries to our project. As described in the Setting up ExtVal section at the beginning
of this chapter, all libraries have to be in the shared lib directory of our EAR. We
also have to add the libraries that Hibernate Validator depends on. The following
table shows a list of libraries that have to be added to our project in order to be able
to use the Hibernate Validator. If we had used Maven, these libraries would have
been downloaded and added to our project automatically by Maven.



Once we have added the Bean Validation libraries to our project, we have to make
sure that we have also added ExtVal’s Bean Validation module to our project. The
Bean Validation module is only available from ExtVal version 1.2.3 onwards. See the
Setting up ExtVal section for more details.


Using Bean Validation annotations


The basic usage of Bean Validation is very similar to the use of ExtVal’s Property
Validation annotations. There are some differences in the annotations, though. The
following table lists all of the annotations that are defined in the Bean Validation
specification:





All annotations are defined in the javax.validation.constraints package. Apart
from the attributes mentioned in the previous table, all annotations (except the @
Valid annotation) have the following common attributes:



  • message: This attribute can be used to set a custom error message that will be
    displayed if the constraint defined by the annotation is not met. If we want to
    set a message bundle key instead of a literal message, we should surround it
    with braces. So we can set message to either “This value is not valid” or
    “{inc.monsters.mias.not_valid}”.

  • groups: This attribute can be used to associate a constraint with one or
    more validation processing groups. Validation processing groups can
    be used to infl uence the order in which constraints get validated, or to
    validate a bean only partially. (See http://docs.jboss.org/hibernate/
    stable/validator/reference/en/html/validator-usingvalidator.
    html#validator-usingvalidator-validationgroups for more on
    validation groups.)

  • payload: This attribute can be used to attach extra meta information to
    a constraint. The Bean Validation standard does not define any standard
    metadata that can be used, but specific libraries can define their own
    metadata. This mechanism can be used with ExtVal to add severity
    information to constraints, enabling the JSF pages to show certain constraint
    violations as warnings instead of errors. See the Using payloads to set severity
    levels
    section for an example of this.


OK, now we know which annotations can be used. Let’s see how we can use Bean
Validation annotations on our Employee class:



// Package declaration and imports omitted for brevity
public class Employee implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Temporal(TemporalType.DATE)
@Column(name=”BIRTH_DATE”)
@Past
private Date birthDate;
@Column(name=”FIRST_NAME”)
private String firstName;
@Temporal(TemporalType.DATE)
@Column(name=”HIRE_DATE”)
@Past
private Date hireDate;
@Column(name=”JOB_TITLE”)
@NotNull
@Size(min=1)
private String jobTitle;
@Column(name=”LAST_NAME”)
private String lastName;
@Min(value=100)
private int salary;
@Column(name=”KIDS_SCARED”)
private int kidsScared;
@OneToMany(mappedBy=”employee”)
private List<Kid> kids;
// Getters and setters and other code omitted.
}


The Bean Validation annotations are highlighted in the code example. Note that the
annotations are applied to the member variables here. Alternatively, we could have
applied them to the getter methods. The JPA annotations that we added in Chapter 8
are still present. In this example, the birthDate and hireDate are annotated with
@Past so that only dates in the past can be set. The jobTitle is set to have a
minimum length of one character by the @Size annotation. The salary must have a
minimum value of 100, as set by the @Min annotation.


Reusing validation


Bean Validation does not have a solution like the @JoinValidation annotation of
ExtVal’s Property Validation module. However, Bean Validation offers other ways to
avoid repetitive code and help us reusing validation. This section describes some of
the possibilities.


Inheriting validation


Constraints defined on (the properties of) super classes are inherited. This means
that if we have a super class called Person, like the following example, our Employee
class can inherit the properties—including the annotated constraints—as follows:



public class Person {
@Size(min=1)
private String firstName;
@Size(min=1)
private String lastName;
@Past
private Date birthDate;
// Getters and setters omitted.
}


No special actions have to be taken to inherit annotated validation constraints.


Using recursive validation


We can use the @Valid annotation to use recursive validation (or graph validation
as it is called in the JSR 303 specification). The @Valid annotation can be used on
single member objects as well as on Collections. If applied to a Collection,
all objects in the collection are validated, but null values in the Collection are
ignored. For example, we could use this to validate the List of scared Kids that is
part of our Employee class, as follows:



public class Employee implements Serializable {
// Other member variables are left out here.
@OneToMany(mappedBy=”employee”)
@Valid
private List<Kid> kids;
// Getters and setters are omitted.
}


Now the List of Kids that is referenced by the kids variable can only contain valid
Kid objects. This means that all Bean Validation constraints that are defined on the
Kid class will be checked on all Kid objects in the List.


Composing custom constraints


A third way to prevent the repetition of code is to define constraint compositions.
Compared to ExtVal’s @JoinValidation, defining a custom constraint can be used
in at least as many situations as @JoinValidation. Defining custom constraints
with Bean Validation gives a more structural solution, compared to using
@JoinValidation. However, defining a custom constraint might involve a bit more
work than using @JoinValidation.



Another difference between constraint compositions and
@JoinValidation is that constraint compositions can only be used to
compose constraints out of other JSR 303 constraints, whereas
@JoinValidation can also be used to combine constraints from
different types, such as combining JPA constraints with ExtVal Property
Valdiation constraints. So all-in-all, @JoinValidation and constraint
composition have some overlap, but both also have their own unique
features.


Defining a custom constraint involves creating a new annotation. This may look a
bit complicated at first, but it is less complicated than it seems. Let’s see how we can
create an @Name annotation that we can use on all names in our project:



package inc.monsters.mias.data.validation;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.OverridesAttribute;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@NotNull
@Size(min = 2)

@Constraint(validatedBy = {})
@Retention(RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE})
public @interface Name {
String message() default
“{inc.monsters.mias.data.validation.Name.invalid_name}”;

Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@OverridesAttribute(constraint = Size.class, name = “max”)
int maxLength() default 20;

}


In this example, the following interesting things can be observed:



  • public @interface Name: This defines a new annotation—@Name.

  • @Target: This defines on what elements the new @Name annotation can be
    used. In this case, it can be used on methods, fields, and other annotations.
    Most of the time, this is fine for new constraints. It is also possible to create
    constraints that validate a class; in that case, TYPE should be used as the
    target. (See http://docs.jboss.org/hibernate/stable/validator/
    reference/en/html/validator-usingvalidator.html#d0e328 for more
    information on class-level constraints.)

  • @Retention: This defines that this annotation should be executed at runtime.
    For validation constraints, this should always be set to RUNTIME.

  • @Constraint: This identifies this annotation as being a validation constraint.
    It should always be used for custom constraints.

  • @NotNull and @Size(min=2) (right above the @Constraint annotation,
    highlighted): These are the constraints that the @Name constraint is based on.
    In other words, any element annotated with @Name must not be null and
    must have a size of at least 2.

  • int maxLength() default 20: This defines an attribute maxLength for the
    @Name annotation, with a default value of 20. So if no maxLength is specified,
    the maxLength will be 20.

  • @OverridesAttribute(constraint = Size.class, name = “max”):
    This causes the maxLength attribute to override the max attribute of the
    Size annotation.

  • String message() default “{…}”: This sets the default value of the
    message attribute to a message bundle key.


Other code that is not mentioned in the bulleted list is needed for every constraint
definition. We now have an @Name annotation that can be used on any name field in
our project. The annotated field cannot be empty, and should have a size of at least
2, and at most 20. The maximum size can be overridden by the maxLength attribute.
We can use it, for example, in our Employee class, as follows:



public class Employee implements Serializable {
// Other member variable are omitted.
@Column(name=”FIRST_NAME”)
@Name
private String firstName;
@Column(name=”LAST_NAME”)
@Name(maxLength = 40)
private String lastName;
// Getters and setters are omitted.
}


Now, the firstName can’t be null. It must have at least 2 characters and at most 20
characters. The lastName has the same constraints, but can be up to 40 characters
long. Note how we have reached the same level of reuse as we did when we used
@JoinValidation in our Kid class earlier in this chapter. Creating our own custom
constraint may be a little more work, but it gives us a more structural way of reuse.
And we don’t get referencing problems, as we did with @JoinValidation. As a
bonus, we can reuse custom constraints over different projects. We can even create a
library of custom constraints to be used in several projects.


As an example of the fl exibility and extendability of ExtVal, the next section will
show us how we can set severity levels on certain constraints that give the users the
possibility to ignore certain warnings.


Using payloads to set severity levels


As mentioned, we can use the payload attribute on every Bean Validation
annotation to pass on meta information about the constraint. With ExtVal, we can
use this to create warning messages for certain constraints. These warning messages
will appear the first time the user submits a value that violates the constraint. The
user can either change the value or ignore the warning by submitting the value for
the second time. This section describes how we can implement this for the salary
field of our Employee class.


Setting up the Continue with warnings add-on


Let’s start by downloading and installing the Continue with warnings add-on, as
described in the Extending ExtVal with add-ons section. Once we’ve downloaded the
JAR file and added it to our project, we can start preparing our project to allow the
users to ignore warnings. The first thing we’ll have to do is to add a hidden input
component to all pages where we expect warnings to be shown that the user should
be able to ignore. In our example, we only have to add this hidden component to our
EditEmployee.xhtml page, as we will only be adding a warning-level constraint
to our Employee entity. The following code snippet shows the hidden component
added to the EditEmployee.xhtml page:



<ui:composition template=”templates/template.xhtml”>
<ui:define name=”title”>Edit employee</ui:define>
<ui:define name=”content”>
<h:inputHidden id=”extValWarnState”
value=”#{extValWarnState.continueWithWarnings}”/>

<tr:panelFormLayout>
<!– Form contents left out to save space. –>
</tr:panelFormLayout>
</ui:define>
</ui:composition>


The <h:inputHidden> component provides the Continue with warnings add-on
with a way to remember if the warning has already been shown to the user, and if
the user has already clicked on the submit button. It is important to not change the
id and value of the component, as the add-on expects them to have the values that
we just saw.



In our example, we add the hidden input component to only one
page. In a real-world application, the chances are that we need this
component on (nearly) every page. It may seem a good idea to add
the hidden component to our page template. However, this will
cause the “warning state” to be shared by all pages, which will cause
undesired behavior. So, unfortunately, we’ll have to add the hidden
component to every page.
On a side note, the requirement to use a hidden input is a design
decision that was made when the add-on was created. Of course, the
open source nature of the project allows us to alter this behavior of
the add-on if we like.


Setting the severity level of a constraint


Once we have added the hidden input component to the pages where warnings can
be shown, we can set the severity level of one of our constraints to “warning”. Let’s
see how we can do this for the salary variable in the Employee class:



package inc.monsters.mias.data;
import org.apache.myfaces.extensions.validator
.beanval.payload.ViolationSeverity;

// Other imports are hidden for brevity.
public class Employee implements Serializable {
@Min(value=100, payload=ViolationSeverity.Warn.class)
private int salary;
// All other variables and methods are omitted.
}


The only thing we added here is the payload=ViolationSeverity.Warn.class
attribute of the @Min annotation. And, of course, we had to add an import for the
ViolotionSeverity class.


Now, if we run our application and put a value below 100 in the salary input field
on the “Edit employee” page, a warning will be shown on submitting the form.
However, if we submit the form with the same value for the second time, the
warning will disappear and the data will be saved regardless of the violation of the
@Min constraint. This allows the manager that uses the “Edit Employee” page to
override the “no salaries below 100” policy.



Setting the severity level is not limited to Bean Validation constraints
Although setting a severity level is introduced in the Using Bean Validation
section, setting a severity level is also possible with ExtVal’s own
Property Validation annotations, and even with other constraints such
as constraints based on JPA annotations. This will be discussed in the
following two sections.

email

«»

Comments

comments