Skip to content

Make localized field names lookup extensible in JSR-303 validation messages [SPR-14104] #18676

@spring-projects-issues

Description

@spring-projects-issues

Bertrand Guay-Paquet opened SPR-14104 and commented

When using JSR-303 annotations for validation, Spring looks up error messages in message bundles and makes available the field name as well as the annotation attribute values in alphabetical order. These arguments are determined by LocalValidatorFactoryBean via its superclass SpringValidatorAdapter with this cose:

protected Object[] getArgumentsForConstraint(String objectName, String field, ConstraintDescriptor<?> descriptor) {
	List<Object> arguments = new LinkedList<Object>();
       // HERE are established the message keys for resolving the field name
	String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};
	arguments.add(new DefaultMessageSourceResolvable(codes, field));
	// Using a TreeMap for alphabetical ordering of attribute names
	Map<String, Object> attributesToExpose = new TreeMap<String, Object>();
	for (Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) {
		String attributeName = entry.getKey();
		Object attributeValue = entry.getValue();
		if (!internalAnnotationAttributes.contains(attributeName)) {
			attributesToExpose.put(attributeName, attributeValue);
		}
	}
	arguments.addAll(attributesToExpose.values());
	return arguments.toArray(new Object[arguments.size()]);
}

Currently, for a field named "fooField" of the class "barClass", the message keys would be ["barClass.fooField", "fooField"]. I believe this was implemented in #11073.

This can then be used in a message.properties file (for a @Size annotation) like so:

Size={0} must be between {1} and {2}

where {0} is either the localized field name (using the 2 keys described above) or the field name itself as a fallback.

This works great, but does not allow for using "namespaced" common field names in messages.properties like so:

labels.firstName=First Name
labels.lastName=Last Name
labels.etc=...

I coded this custom validator factory bean which achieves this:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

	@Override
	public Validator getValidator() {
		CustomValidator factory = new CustomValidator();
		return factory;
	}

	private static class CustomValidator extends OptionalValidatorFactoryBean {

		private static final String FIELD_NAME_PREFIX = "labels";
		
		@Override
		protected Object[] getArgumentsForConstraint(String objectName, String field,
				ConstraintDescriptor<?> descriptor) {
			Object[] arguments = super.getArgumentsForConstraint(objectName, field, descriptor);
			
			// Add a custom message code for the field name
			if (arguments.length > 0 && arguments[0] instanceof DefaultMessageSourceResolvable) {
				DefaultMessageSourceResolvable fieldArgument = (DefaultMessageSourceResolvable) arguments[0];
				String[] codes = fieldArgument.getCodes();
				String[] extendedCodes = new String[codes.length + 1];
				extendedCodes[0] = FIELD_NAME_PREFIX + Errors.NESTED_PATH_SEPARATOR + field;
				System.arraycopy(codes, 0, extendedCodes, 1, codes.length);
				arguments[0] = new DefaultMessageSourceResolvable(extendedCodes, field);
			}
			return arguments;
		}
	}
}

Now, for the feature request: I think it would be useful to externalize the choice of field name message codes to a protected method which could be overwritten by child classes. In this way, the implementation would be much cleaner and more robust.

To be clear, I'm proposing to change this line in SpringValidatorAdapter :

String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};

to something like :

String[] codes = getFieldCodes(objectName, field);

Affects: 4.2.5

Referenced from: commits 696dcb7

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions