Skip to content

Cannot use LocaleAware specs with date format config #301

@cschierle

Description

@cschierle

There are some specs supporting date fields that are also LocaleAware. When providing a config with a custom date format string (e.g. @Spec(config = {"dd.MM.yyyy"}), these specs cannot be initialized for incoming requests running into this error:

jakarta.servlet.ServletException: Request processing failed: java.lang.IllegalArgumentException: Invalid locale format: dd.MM.yyyy

	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:892)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:633)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:874)
	at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:723)
	at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:160)
	at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:127)
	at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:199)
	at net.kaczmarzyk.EqualIgnoreCaseE2eTest.findsByFormattedDateValueIgnoringCase(EqualIgnoreCaseE2eTest.java:122)
Caused by: java.lang.IllegalArgumentException: Invalid locale format: dd.MM.yyyy
	at org.apache.commons.lang3.LocaleUtils.parseLocale(LocaleUtils.java:294)
	at org.apache.commons.lang3.LocaleUtils.toLocale(LocaleUtils.java:376)
	at net.kaczmarzyk.spring.data.jpa.web.SimpleSpecificationResolver.resolveConverter(SimpleSpecificationResolver.java:157)
	at net.kaczmarzyk.spring.data.jpa.web.SimpleSpecificationResolver.newSpecification(SimpleSpecificationResolver.java:102)
	at net.kaczmarzyk.spring.data.jpa.web.SimpleSpecificationResolver.buildSpecification(SimpleSpecificationResolver.java:77)
	at net.kaczmarzyk.spring.data.jpa.web.SimpleSpecificationResolver.buildSpecification(SimpleSpecificationResolver.java:47)
	at net.kaczmarzyk.spring.data.jpa.web.SpecificationFactory.buildSpecification(SpecificationFactory.java:139)
	at net.kaczmarzyk.spring.data.jpa.web.SpecificationFactory.lambda$resolveSpecFromParameterAnnotations$1(SpecificationFactory.java:105)
	at net.kaczmarzyk.spring.data.jpa.web.SpecificationFactory.forEachSupportedSpecificationDefinition(SpecificationFactory.java:146)
	at net.kaczmarzyk.spring.data.jpa.web.SpecificationFactory.resolveSpecFromParameterAnnotations(SpecificationFactory.java:102)
	at net.kaczmarzyk.spring.data.jpa.web.SpecificationFactory.resolveSpec(SpecificationFactory.java:95)
	at net.kaczmarzyk.spring.data.jpa.web.SpecificationFactory.createSpecificationDependingOn(SpecificationFactory.java:76)
	at net.kaczmarzyk.spring.data.jpa.web.SpecificationArgumentResolver.resolveArgument(SpecificationArgumentResolver.java:105)
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
	at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:230)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:180)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:934)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:853)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:86)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:866)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003)
	... 9 more

if (def.config().length == 1) {
if (LocaleAware.class.isAssignableFrom(def.spec())) { // if specification is locale-aware, then we assume that config contains locale
String localeConfig = def.config()[0];
Locale customlocale = LocaleUtils.toLocale(localeConfig);
return Converter.withTypeMismatchBehaviour(def.onTypeMismatch(), conversionService, customlocale);
} else { // otherwise we assume that config contains date format
String dateFormat = def.config()[0];
return Converter.withDateFormat(dateFormat, def.onTypeMismatch(), conversionService);
}
}
throw new IllegalStateException("config should contain only one value -- a date format"); // TODO support other config values as well

SimpleSpecificationResolver.resolveConverter always evaluates the config value as Locale for LocaleAware Specs. I'm unsure how this could be properly resolved. Maybe the config evaluation as Locale should move somewhere near CaseConversionHelper.applyCaseConversion, since it is only relevant when using IgnoreCaseStrategy.APPLICATION.

if (effectiveStrategy == IgnoreCaseStrategy.APPLICATION) {
Locale effectiveLocale = locale != null ? locale : Locale.getDefault();
String convertedValue = value.toUpperCase(effectiveLocale);
return new ConvertedExpressions(
cb.upper(columnExpression),
cb.literal(convertedValue)
);
}
return new ConvertedExpressions(
applyDatabaseCaseConversion(cb, columnExpression, effectiveStrategy),
applyDatabaseCaseConversion(cb, cb.literal(value), effectiveStrategy)
);
}
/**
* Applies case conversion to both column expression and value according to the strategy.
*/
@SuppressWarnings("deprecation")
public static ConvertedExpressionsList applyCaseConversion(
CriteriaBuilder cb,
Expression<String> columnExpression,
String[] values,
IgnoreCaseStrategy strategy,
Locale locale) {
IgnoreCaseStrategy effectiveStrategy = strategy != null ? strategy : IgnoreCaseStrategy.DATABASE_UPPER;
if (effectiveStrategy == IgnoreCaseStrategy.APPLICATION) {

I added some test cases in my fork at cschierle@fab944b showcasing the IgnoreCase Specs supporting date fields running into this issue, while their exact match counterparts work just fine.

Any ideas how this issue could be resolved?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions