Introduction to Spring Validation

March 1, 2011

Spring Framework

«»

Attaching Custom Message Codes Resolver

In this section, we will see how to attach custom message codes resolver object while performing validation. Even before that, we wanted to see where and how messages codes will actually get resolved. When the caller invokes validation on an object, the validator class is expected to populate the error conditions in case of any errors. These errors are encapsulated through the interfaces ‘ObjectError’ and ‘FieldError’. Object Errors are for encapsulating error conditions when the state of the object itself is violated – e.g. that an object being null. Field errors are used to represent error scenarios wherein a error is at the field level – e.g. name is invalid, age is containing negative values.

The validator class owns the responsibility for attaching an error code for an error condition. For example, when found that a property ‘name’ is null, we may designate an error code ‘name.null’ while populating the error object. This error code will be resolved to a message code by Spring suitable for producing output error message. For example, for a property ‘p1′ defined on an object ‘o1′ that is of type ‘String’ having an error code ‘ec1′, Spring will produce multiple message codes for this scenario. Specifically the ‘DefaultMessageCodesResolver’ implementation will produce four message codes ‘ec1.o1.p1′, ‘ec1.p1′, ‘ec1.String’ and ‘ec1′ for the above example scenario.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Mobile {
 
	private String name;
	private Double cost;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Double getCost() {
		return cost;
	}
	public void setCost(Double cost) {
		this.cost = cost;
	}	
}

We have a simple model object called ‘Mobile’ have properties ‘name’ and ‘cost’. The code shown below is the corresponding validator class for the Mobile object. There is nothing new in this validator as it follows the traditional model of validating the properties ‘name’ and ‘cost’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package net.javabeat.articles.validator.simple;
 
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
 
public class MobileValidator implements Validator{
 
	@Override
	public boolean supports(Class<?> classObject) {
		return classObject == Mobile.class;
	}
 
	@Override
	public void validate(Object object, Errors errors) {
 
		Mobile mobile = (Mobile)object;
		if (mobile.getName() == null || mobile.getName().trim().length() == 0){			
			errors.rejectValue("name", "name.required", "Name field is missing");
		}
 
		Double cost = mobile.getCost();
		if (cost == null || cost.doubleValue() == 0){
			errors.rejectValue("cost", "cost.incorrect", "Cost of the mobile has incorrect value");
		}
	}
}

Now, have a look at the custom message codes resolver implementation. Remember that a ‘Message codes resolver’ provides a pattern for producing ‘message codes’ corresponding to the ‘error codes’ that a validator defines and it is always possible to produce multiple message codes for a single error code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package net.javabeat.articles.validator.simple;
 
import org.springframework.validation.MessageCodesResolver;
 
public class MobileMessageCodesResolver implements MessageCodesResolver {
 
	@Override
	public String[] resolveMessageCodes(String errorCode, String objectName) {
		System.out.println(errorCode + objectName);
 
		String customErrorCode = "mobile/" + objectName + "/" + errorCode;
		return new String[]{customErrorCode};
	}
 
	@SuppressWarnings("rawtypes")
	@Override
	public String[] resolveMessageCodes(String errorCode, String objectName, 
		String field, Class fieldType) {
 
		String customErrorCode = "mobile/" + objectName + "/" 
			+ fieldType.getName() + "/" + field + "/" + errorCode;
		return new String[]{customErrorCode};
	}
}

The overloaded methods ‘resolveMessageCodes()’ will be called by the framework for collecting the message codes. Note that the parameters passed on to this method wil be the name of the object, the name of the field, error code and the class type of the field. For illustration purpose we have constructed a customized message code with ‘/’ as a separator and have returned it to the caller.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package net.javabeat.articles.validator.simple;
 
import java.util.Arrays;
import java.util.List;
 
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.ValidationUtils;
 
public class MobileClient {
 
	public static void main(String[] args) {
 
		testMobile();
		testMobileWithCustomMessageCodesResolver();
	}
 
	private static void testMobile(){
 
		MobileValidator mobileValidator = new MobileValidator();		
		Mobile mobileObject = new Mobile();
		BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(mobileObject, "mobileObject");	
 
		validate(mobileObject, mobileValidator, bindingResult);
	}
 
	private static void testMobileWithCustomMessageCodesResolver(){
 
		MobileValidator mobileValidator = new MobileValidator();	
		Mobile mobileObject = new Mobile();
 
		BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(mobileObject, "mobileObject");
		MobileMessageCodesResolver mobileMessageCodesResolver = new MobileMessageCodesResolver();
		bindingResult.setMessageCodesResolver(mobileMessageCodesResolver);
 
		validate(mobileObject, mobileValidator, bindingResult);
	}
 
	private static void validate(Mobile mobileObject, MobileValidator mobileValidator, BeanPropertyBindingResult bindingResult){
 
		ValidationUtils.invokeValidator(mobileValidator, mobileObject, bindingResult);		
 
		System.out.println("Total error count is " + bindingResult.getErrorCount());
		System.out.println("-------------------------------------------------");
 
		List<ObjectError> allObjectErrors = bindingResult.getAllErrors();
		for (ObjectError objectError : allObjectErrors){
 
			if (objectError instanceof FieldError){
 
				FieldError fieldError = (FieldError)objectError;
				System.out.println("Field name is " + fieldError.getField());
			}
 
			System.out.println("Codes " + Arrays.asList(objectError.getCodes()).toString());
			System.out.println("Error Code is " + objectError.getCode());
			System.out.println("Default message is " + objectError.getDefaultMessage());
			System.out.println();
		}
		System.out.println("-------------------------------------------------");
	}
}

The above sample client has two methods, the first test method does validation on the mobile object with the Spring’s default ‘message codes resolver implementation’, whereas the second test method attaches a ‘custom message codes resolver’. Note that a ‘custom messages codes resolver’ implementation can be set on the ‘Errors’ object by calling the method ‘setMessageCodesResolver’. Watch out for the output from both the test methods, more specifically on the call getCodes() defined on the error object.

 

email

«»

Comments

comments

  • Prash

    Thank you.