Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
spring-projects-issues opened this issue Mar 31, 2016 · 4 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Mar 31, 2016

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

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Thanks for the suggestion! I went with a slightly refined template method, customizing the entire MessageSourceResolvable for the field:

protected MessageSourceResolvable getResolvableField(String objectName, String field)

Internally building the field codes but also determining the default message (and choosing the MessageSourceResolvable implementation).

@spring-projects-issues
Copy link
Collaborator Author

Bertrand Guay-Paquet commented

That was fast! Your changes look great, thanks!

@spring-projects-issues spring-projects-issues added type: enhancement A general enhancement in: core Issues in core modules (aop, beans, core, context, expression) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 4.3 RC1 milestone Jan 11, 2019
@pedropdv
Copy link

Is this available to use? I tried this naming pattern on message.properties, but it didn't work :(

@sbrannen
Copy link
Member

Is this available to use? I tried this naming pattern on message.properties, but it didn't work :(

Yes, this feature is available since Spring Framework 4.3. See 696dcb7 for details on the method that was introduced to support this. You would need to override that method in order to support a custom prefix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants