Spring 3.1.RC1 and PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE - spring

Posted in spring forum with no response.
I have the following code snippet (from here), which is part of my pet project.
#Controller
#RequestMapping("/browse")
public class MediaBrowser {
...
#RequestMapping("/**")
public final ModelAndView listContents(final HttpServletRequest request) {
String folder = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
...
}
I access the following url:
http://localhost:8080/myapp/browse
In spring 3.0.6.RELEASE, I got the folder variable as null, which is the expected value.
In spring 3.1.RC1, the folder variable is /browse.
Is this a bug or has something changed in spring-3.1?

As skaffman said, you probably shouldn't use PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE. Take a look at How to match a Spring #RequestMapping having a #pathVariable containing "/"? for an example of using AntPathMatcher to accomplish what you are trying

This looks very much like an internal implementation detail of the framework, one that you should not be relying on.
The javadoc for PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE says:
Note: This attribute is not required to be supported by all HandlerMapping implementations. URL-based HandlerMappings will typically support it, but handlers should not necessarily expect this request attribute to be present in all scenarios.
I wouldn't be surprised if the behaviour changed slightly between 3.0 and 3.1.

Related

How can I map URL containing colon to my Spring controller?

I want to map a URL (for example, http://example.com/v1/books:search) containing colons to my Spring MVC controller, but I can't make it work.
#RequestMapping("/v1/books")
public class BooksController {
#GetMapping(":search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
When I test this API, Spring returns 404 NOT_FOUND to me. It seems that Spring doesn't support colons in URL mapping.
Is there any method to make it work? Thanks.
I hit this attempting to do similar so I thought I'd share my findings.
With using most defaults and your code, the search method will be mapped to /v1/books/:search which is obviously not quite what you want. There are two places that I've found so far that get in the way of changing this. The first is the AntPathMatcher's combine method. This method will attempt to put a path separator (/) between segments. The second place is within the RequestMappingInfo's path parsing code. The former can be replaced easily. The latter not so much.
As the methods that tend to be problematic involve combining multiple #RequestMapping annotations, what I've found to work is to simply side-step combinations. On my controller class, I have a #Controller annotation and any defaults for #RequestMapping, but not a path attribute. On each method, the full path is then added. This isn't great, but it does get collection-level special "methods" to function properly. In your example, this would look like:
#Controller
#RequestMapping
public class BooksController {
#GetMapping("/v1/books:search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
Long story short: Do not do this - use / as a separator for the method.
A bit more detail: Have a look at Spring Framework issue #24771 that suggests that the team actually moves away from various ways to handle non-standard URL mappings in favor of simpler logic of URL processing, after entangling in a series of various issues with similar concepts. This "custom method" thing is unlikely to get a first class support in Spring, as a result.
Therefore, despite what Google does, just do this as a normal person and use /v1/books/search path:
#RequestMapping("v1/books")
public class BooksController {
#GetMapping("search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
}

How do I test form submission with Spring MVC test?

Most of my experience with creating controllers with Spring are for REST controllers that consume JSON formatted requests. I've been searching for documentation on how to do testing for form submission, and so far this is how I understand it should go using MockMvc:
MvcResult result = mockMvc.perform(post("/submit")
.param('title', 'test title')
.param('description', 'test description'))
.andReturn()
However, I'm not sure how to map the form parameters to a model object. I've seen the #ModelAttribute annotation pop up in my searches but I can't figure out how it should be used for mapping. In addition, this quick start guide from the official documentation does not elaborate on how things like th:object and th:field translate to HTML and subsequently to URL encoded form.
I have my controller code similar to the following:
#PostMapping('/submit')
def submit(#ModelAttribute WriteUp writeUp) {
//do something with writeUp object
'result'
}
I discovered through trial and error that my specific problem might have been Groovy specific. There test code and the controller code, it turns out, have no issues. To reiterate, for testing form submission, use the param method through perform method of MockMvcRequestBuilders. Another thing to note is that this doesn't seem to work if content type is not specified. Here's a sample test code that works for me:
MvcResult result = webApp.perform(post("/submit")
.contentType(APPLICATION_FORM_URLENCODED) //from MediaType
.param('title', 'test title')
.param('description', 'test description'))
.andReturn()
As you can see, it's not much different from what I posted originally. The controller code is pretty much just the same, with #ModelAttribute working just fine.
The problem with my setup though was that since I was using Groovy, I assumed that getters and setters were automatically generated in my WriteUp class. Here's how the WriteUp class looked originally:
class WriteUp {
private String title
private String description
}
I haven't written code in Groovy for a while, and the last time I did, classes like the one above can be assumed to have getters and setters implicitly. However, it turns out that is not the case. To solve my specific issue, I updated the access modifier in the fields to be default (package level):
class WriteUp {
String title
String description
}
I've seen the #ModelAttribute annotation pop up in my searches but I
can't figure out how it should be used for mapping.
When you mark your writeUp object with #ModelAttribute, then the Spring container populates the parameters (like title, description, etc..) from HttpServletRequest object & injects the object to the controller method, when the request comes to the server from the client (could be a Browser or MockMvc unit test client or anything else).
Also, few other basic points for your quick understanding:
(1) Controller methods are mapped to an URI and RequestMethod (like POST/GET/DELETE/PUT et..) like shown below:
#RequestMapping(value="/submit", method=RequestMethod.POST)
public String submit(#ModelAttribute WriteUp writeUp) {
//Call the service and Save the details
model.addAttribute("Writeup details added successfully");
return "writeUpResult"; //Returns to the View (JSP)
}
(2) #ModelAttribute will be mapped to an object (like your writeUp) for http POST/PUT requests where the html formd data is part of http body.
(3) #RequestParam or #PathParam will be used for http GET requests where the parameters are part of URL (i.e., not part of http body).
You can look here for understanding the DispatcherServlet request handling & Spring MVC basic web flow.

Is there a way to link JAX-RS resource to another resource like in Spring HATEOAS?

In Spring we've got #ExposesResourceFor annotation which can link our resource with other resources. Thanks to this our Value objects (representations) can know nothing of the actual resources.
Is there a way to do it in JAX-RS? I'm using Dropwizard with Jersey and Jackson and all I see is #InjectLinks annotation which I can use in a value object like this:
public class UserGroup {
#JsonProperty
public String name;
#InjectLinks(GroupsResource.class)
public URI myResource;
public UserGroup(String name){
this.name = name;
}
}
But unfortunatelly my Value Objects should know nothing about Resources, so I'm asking can I do such linking on the level of resources - link in spring-hateoas in controllers, as mentioned above.
With #InjectLinks, you don't have to declare the links in your model class. You can create a "wrapper" representation class, as shown in declarative-linking from the Jersey examples (though this solution is not really on the resource class level as you wish).
Another possible solution (rather than declarative linking) is to use the JAX-RS 2.0 Link class, and do the linking programmatically (with no ties to the Jersey implementation/annotations). You can either add the links to your response headers, as see here, or add Links to you model classes, as seen here (or use the wrapper class for this also, so as to not to invade your model classes)
Some Resources
Declarative Hyperlinking
Using Link for headers

Handling of pre-slash in #RequestMapping

Imagine that I have a Spring MVC controller something like this:
#Controller
#RequestMapping("/base-url")
public class MyController{
//..snip
#RequestMapping(method=RequestMethod.GET, value="/edit/{id}")
public String edit(Model model, HttpServletRequest request, Authentication authentication){
//..snip
}
}
My question is regarding the inner value parameter to the #RequestMapping annotation at the function level. Is the pre-slash on /edit/{id} required, or does edit/{id} do the job just as well? I would have imagined that the pre-slash would set the request to be absolute, regardless of the class level mapping, but it seems to be ignored.
Is one or the other considered better practice?
In the Spring documentation, they seem to always use the pre-slash. Are there any practical benefits to doing that?
Thanks,
idb.
According to the spring documentation, having a class level #RequestMapping annotation implies that all method level #RequestMappings will be relative to that of the class'.
It might be nice however, to have the ability to override the relative mappings in some rare cases.
I personally prefer to add pre-slash in value of #RequestMapping. In code level you can see: If the value does not start with an / then Spring (DefaultAnnotationHandlerMapping) will add it. Details answer you can visit: Use or not leading slash in value for #RequestMapping. Need official docs or point to Spring source?

Spring 3, ReST, #ResponseBody and #ExceptionHandler

I have been trying to get exception handling working in my simple Spring 3 based ReST web services. Based on everything I have seen, there is a bug that prevents this from working automatically with the #ResponseBody and #ExceptionHandler annotations
https://jira.springsource.org/browse/SPR-6902
So given that it isn't supported until Spring 3.1 or 3.0.6, what is the current best method for doing exception handling? I have seen numerous posts but haven't found a clear answer that has worked for me. An ideal solution would be one that automatically provides support for both xml and json
Do I have to manually define the entire marshalling setup? Won't this remove the need for the annotations that make using Spring 3 rest support worth it?
Seems in order to manually define marshalling (i.e. Jaxb2Marshaller) I need to add a new dependency on spring-ws which is a bit of a pain
Is it easier to just define a 'Response' object that all my methods return and wrap all functions in try/catch blocks?
You can redirect on error and then return something in #ResponseBody:
#ExceptionHandler(Exception.class)
public ModelAndView handleMyException(Exception exception) {
return new ModelAndView("redirect:errorMessage?error="+exception.getMessage());
}
#RequestMapping(value="/errorMessage", method=RequestMethod.GET)
#Responsebody
public String handleMyExceptionOnRedirect(#RequestParameter("error") String error) {
return error;
}
Little ugly, but this is just work around till the fix will be available.
This is a good workaround, but with one addition. The #ExceptionHandler(Exception.class)
should be #ExceptionHandler(MyException.class, YourException.class) as you can get into a loop using the general Exception class.
You can then test for (ex instanceof Myexception) to determine the message to display if need be.

Resources