I have tested this on two different application servers now: Tomcat 8.0.15 and JBoss 6.2 EAP. The link is generated correctly on Tomcat, while it is generated incorrectly on JBoss. I have also created a small sample project to demonstrate the problem:
https://github.com/Vile2539/hateoas-link-test/tree/master
I've recently been changing some of my Spring REST services to use matrix variables instead of path variables. This, however, has led to some incorrect HATEOAS links being generated by the ControllerLinkBuilder.
What I currently have:
Controller Method
There's an overall @RequestMapping
of /test-items
on this. Also, from what I understand and have tested, the @PathVariable
is required, as a URL template alone isn't enough. That's why the {fullString}
exists.
@RequestMapping(value = "/{fullString}/test", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"})
@ResponseBody
public PagedCollectionResource test(@PathVariable String fullString, @MatrixVariable String testVariable, Pageable pageable) {
Link link = linkTo(methodOn(TestControllerImpl.class).test(fullString, testVariable, pageable)).withSelfRel();
Page<TestItem> testItems = testService.getPagedTestItems(testVariable, pageable);
return testService.getPagedCollectionResource(testItems, link);
}
Spring Config
<mvc:annotation-driven enable-matrix-variables="true">
<mvc:path-matching path-helper="pathHelper"/>
<mvc:argument-resolvers>
<bean class="org.springframework.data.web.PageableHandlerMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
<bean id="pathHelper" class="org.springframework.web.util.UrlPathHelper">
<property name="alwaysUseFullPath" value="true"/>
<property name="urlDecode" value="false"/>
<property name="removeSemicolonContent" value="false"/>
</bean>
Service Call
http://localhost:8080/testItem/test-items/stringhere;testVariable=036/test
Result
"links":[{"rel":"self","href":"http://localhost:8080/testItem/test-items/stringhere/test-items/stringhere;testVariable=036/test{?page,size,sort}"}]
As you can see, there's an additional /test-items/stringhere
for some reason. I'm almost certain that it's caused by the matrix varible, and it seems to be during the ControllerLinkBuilder
methodOn
call, but I cannot figure out why.
Would anyone have any suggestions on how to fix this issue, or (ideally) fix it and eliminate the need for the {fullString}
path variable?
Additional Question
Additionally, the reason for using the matrix variables is to get around a URL encoding issue. I was previously separating the path variables with a hyphen -
, so something like:
{variable1}-{variable2}
Unfortunately, both can contain special characters, such as /
, -
, &
, etc. Using the alwaysUseFullPath
and urlDecode
properties seen above, I was able to pass in the majority of special characters encoded - so calling the above with forward slashes would be:
/test-items/%2Fab-v%2Fc/test
variable1 = /ab
variable2 = v/c
This didn't, however, work for hyphens - encoded hyphens would be unencoded and result in the wrong path variables:
/test-items/a%2db-vc/test
variable1 = a
variable2 = b-vc
Double encoding the hyphens, however, does work - but is obviously a horrible hack.
If anyone has any suggestions on getting around this while maintaining the GET call, I'd be very appreciative. No assumptions can be made about the order or type of characters in the URL (so the path variables can't simply be separated with 2 hyphens).
It's a bit of a late update, but was swamped with work.
I solved this issue by extending the LinkBuilderSupport
for my own LinkBuilder class:
public class RootContextLinkBuilder extends LinkBuilderSupport<RootContextLinkBuilder> {
...
}
The problem with the initial implementation was in the getBuilder()
method. The ControllerLinkBuilder
used ServletUriComponentsBuilder.fromServletMapping(request);
, which I changed to ServletUriComponentsBuilder.fromContextPath(request);
. This now generates the correct href, and is called in the same manner as the ControllerLinkBuilder
.
For my additional question, we stuck with the matrix variables, and so didn't solve this.