spring-hateoas
spring-hateoas copied to clipboard
MultiValueMap RequestParam not supported by ControllerLinkBuilder
In order to get a complex query with many possible keys, one can use a MultiValueMap as RequestParam rather than predefined param names.
@RequestMapping(value = { "/contracts/query" }, method = RequestMethod.GET)
public @ResponseBody
List<Resource<TContract>> queryContracts(@RequestParam MultiValueMap<String, String> query) { ...
ControllerLinkBuilder fails to build a proper link to method, the conversion service tries to stringify the MultiValueMap and says it has no suitable converter.
I'm running in the same issue. @dschulten did you find a way to workaround it?
For reference a part of the stacktrace:
2014-11-14 22:22:44.977 ERROR 10908 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type @org.springframework.web.bind.annotation.RequestParam org.springframework.util.MultiValueMap<java.lang.String, java.lang.String>
to type java.lang.String] with root cause
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type @org.springframework.web.bind.annotation.RequestParam org.springframework.util.MultiValueMap<java.lang.String, java.lang.String> to type java.lang.String
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:311)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192)
at org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor$BoundMethodParameter.asString(AnnotatedParametersParameterAccessor.java:172)
at org.springframework.hateoas.mvc.ControllerLinkBuilderFactory.linkTo(ControllerLinkBuilderFactory.java:141)
at org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo(ControllerLinkBuilder.java:135)
Where a controller looks like:
@RequestMapping(method = GET, produces = APPLICATION_JSON_VALUE)
@Transactional(readOnly = true)
public ResponseEntity list(@RequestParam MultiValueMap<String, String> params, @PageableDefault(sort = "code") Pageable pageable, PagedResourcesAssembler pagedResourcesAssembler) {
Page<Product> products;
PagedResources<Customer> pagedResources;
if (params.containsKey("q")) {
String q = params.getFirst("q");
products = productRepository.findByCodeContainingOrDescriptionContainingAllIgnoreCase(q, q, pageable);
// here we try to create a link with the given multi value map of request params; it fails
Link link = linkTo(methodOn(ProductController.class).list(params, pageable, pagedResourcesAssembler)).withSelfRel();
pagedResources = pagedResourcesAssembler.toResource(products, productResourceAssembler, link);
} else {
products = productRepository.findAll(pageable);
pagedResources = pagedResourcesAssembler.toResource(products, productResourceAssembler);
}
return ResponseEntity.ok().body(pagedResources);
}
me to running in the same issue. any fix? or workaround it?
My work-around is to simply use a String (`@RequestParam(value = "search", required = false)') and convert the value to a MultiValueMap with a utility. I ran into another issue as you can see in the JavaDoc.
/**
* We need this method because Spring doesn't allow matrix variables on the base URI of a service.
* This because Spring requires an URL template if variables are expected, but we can't map those without clashing with our sub-mappings.
* That's why we solve it with a request parameter that behaves as a matrix variable (?search={matrix variables})
*
* @param matrixVariables string formatted as <code>a=b;c=d</code>
* @return
*/
public static MultiValueMap<String, String> resolveMatrixVariables(String matrixVariables) {
MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>();
String[] variables = StringUtils.split(matrixVariables, ";");
for (String variable : variables) {
String[] keyValue = StringUtils.split(variable, "=");
result.add(keyValue[0], keyValue[1]);
}
return result;
}
A hackish workaround : You can set the required flag of @RequestParam annotation to false and this may start working. I tried it with @RequestHeaders(required = false) HttpHeaders headers and yeah it worked. Also you will have to pass null for that param when calling the method while creating the link.
P.S. HttpHeaders inherits from MultiValueMap