0

I'm building a spring-mvc rest API application and I intend to use matrix variables for some of my endpoints. Unfortunately I'm not able to retrive more than one value per matrix variable used.

My spring-mvc version is spring-webmvc:4.3.12.RELEASE

I followed the steps shown in this example of implementation : http://www.baeldung.com/spring-mvc-matrix-variables.

I've enabled Spring MVC Matrix Variables :

package fr.compagny.project.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.util.UrlPathHelper;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

So I've created 2 test endpoints :

package fr.compagny.project.webservice;

import [...]

@Api
@RefreshScope
@RestController
@RequestMapping(value = "/my_awesome_project")
public class ProjectWS {

    //Services
    private ProjectService projectService;

    //Validator
    private ValidatorService validator;

    @ApiOperation(value = "Matrix Variable Test 1.")
    @GetMapping(value = "/matrix_test_one/{vars}", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(HttpStatus.OK)
    public String getMatrixTestOne (@MatrixVariable(pathVar = "vars", required = true) String v1,
                                    @MatrixVariable(pathVar = "vars", required = true) String v2,
                                    @MatrixVariable(pathVar = "vars", required = true) String v3) {
        return v1 + v2 + v3;
    }

    @ApiOperation(value = "Matrix Variable Test 2.")
    @GetMapping(value = "/matrix_test_two/{vars}", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(HttpStatus.OK)
    public Map<String, String> getMatrixTestTwo (@MatrixVariable Map<String, String> vars) {
        return vars;
    }

    @Autowired
    public void setProjectService(ProjectService projectService) {
        this.projectService = projectService;
    }

    @Autowired
    public void setValidatorService(ValidatorService validatorService) {
        this.validator = validatorService;
    }
}

When I call

GET http://[...]/my_awesome_project/matrix_test_one/v1=toto;v2=titi;v3=tata
OR
GET http://[...]/my_awesome_project/matrix_test_one/v1=toto

I have the same following error message :

There was an unexpected error (type=Bad Request, status=400). Missing matrix variable 'v2' for method parameter of type String

But when I call

GET http://[...]/my_awesome_project/matrix_test_one/v2=titi
OR
GET http://[...]/my_awesome_project/matrix_test_one/[anything except "v1=*"]

I have the same following error message :

There was an unexpected error (type=Bad Request, status=400). Missing matrix variable 'v1' for method parameter of type String

So Spring seems ok to get the first element of the matrix variable but stop then.

So I keep trying with the second test function :

GET http://[...]/my_awesome_project/matrix_test_two/v1=toto;v2=titi;v3=tata
OR
GET http://[...]/my_awesome_project/matrix_test_two/v1=toto

Return :

{
    "v1": "toto"
}

-

GET http://[...]/my_awesome_project/matrix_test_two/v2=titi;v1=toto;v3=tata

Return :

{
    "v2": "titi"
}

So this behavior seems to confirm my fears.

Did you see something I missed in order to enable matrix variable support (maybe related to semicolon) ?

Josef
  • 43
  • 1
  • 4

1 Answers1

0

The mentioned example is using Spring Boot. Launching the exmaple via Spring Boot works as expected. Without Spring Boot it doesn't work out of the box as it is explained in this Q&A. The reason is the UrlPathHelper injected from the @Configuration (point 2 in the exmple) isn't used to process the request. A default instance of UrlPathHelper is used and therefore urlPathHelper.shouldRemoveSemicolonContent() returns true. This removes the matrix variables from the request.

EDIT:

I debugged into it and it turned out that there are two beans of type RequestMappingHandlerMapping.

Two beans of type RequestMappingHandlerMapping

So I tried this configuration:

@Configuration
@ComponentScan(basePackageClasses = { WebMvcConfiguration.class })
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Bean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")
    @Qualifier("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")
    public RequestMappingHandlerMapping fullyQualifiedRequestMappingHandlerMapping() {
        return requestMappingHandlerMapping();
    }

    @Bean
    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();
        requestMappingHandlerMapping.getUrlPathHelper().setRemoveSemicolonContent(false);
        return requestMappingHandlerMapping;
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = configurer.getUrlPathHelper();
        if (urlPathHelper == null) {
            urlPathHelper = new UrlPathHelper();
        }
        urlPathHelper.setRemoveSemicolonContent(false);
    }

}

But the fully qualified bean wasn't created by the first method. This bean is processing the request. So the matrix variables were still removed.

As I was unable to provide a factory method for the bean I tried to modify the state of the bean:

@Component
public class Initializer {

    @Autowired
    private ApplicationContext appContext;

    @PostConstruct
    protected void init() {
        initUrlPathHelper("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
        initUrlPathHelper("requestMappingHandlerMapping");
    }

    protected void initUrlPathHelper(String beanName) {
        AbstractHandlerMapping b = (AbstractHandlerMapping) appContext.getBean(beanName);
        b.setUrlPathHelper(urlPathHelper());
    }

    public UrlPathHelper urlPathHelper() {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        return urlPathHelper;
    }

}

This did it for me. The matrix variables have been mapped.

LuCio
  • 5,055
  • 2
  • 18
  • 34
  • I've just modified my "matrix_test_one" endpoint in order to test this. I encounter exactly the same behavior described in my initial post. Sorry. – Josef May 31 '18 at 09:35
  • I have setup your controller and reproduced the case. I have found a similar [SO-Q&A](https://stackoverflow.com/questions/30539783/spring-mvc-missing-matrix-variable). But the guide you are using sets `urlPathHelper.setRemoveSemicolonContent(false)`. So I still have no solution. – LuCio May 31 '18 at 12:13
  • I went back to the guide of baeldung.com. They are using Spring Boot. So I changed the code to launch via Spring Boot and it worked. I update my answer. – LuCio Jun 01 '18 at 06:41