spring-hateoas icon indicating copy to clipboard operation
spring-hateoas copied to clipboard

MultiValueMap RequestParam not supported by ControllerLinkBuilder

Open dschulten opened this issue 11 years ago • 4 comments
trafficstars

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.

dschulten avatar Sep 04 '14 18:09 dschulten

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);
}

marceloverdijk avatar Nov 14 '14 21:11 marceloverdijk

me to running in the same issue. any fix? or workaround it?

ArekCzarnik avatar Jan 19 '15 10:01 ArekCzarnik

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;
    }

schvanhugten avatar Feb 19 '15 15:02 schvanhugten

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

rohanagarwal36 avatar Jul 14 '17 06:07 rohanagarwal36