Not repeating a path twice in Spring 3.0 MVC Restful mappings - java

I'm mapping the url /modules/tips/SOME_ID/small to access the tip with id SOME_ID and to render it using the view small.jsp. The following code works great for this, however I am forced to repeat the string modules/tips in two places. Spring MVC doesn't seem to have a convention for this that I can determine. Other than using a constant, is there a better way to reduce this repetition?
#Controller
public class TipsController{
#RequestMapping(value="/modules/tips/{tipId}/{viewName}",method=RequestMethod.GET)
public ModelAndView get(
#PathVariable String tipId,
#PathVariable String viewName) {
Tip tip = findTip(tipId);
return new ModelAndView("modules/tips/" + viewName,"tip",tip);
}
}

You view name mapping logic looks too "custom", so Spring hardly can offer some build-in support for it.
Hovewer, as a theoretical possibility, you can implement a custom ModelAndViewResolver and register it in the AnnotationMethodHandlerAdapter

You can do what you're trying to do by simply omitting the view name, as long as your views match up with your URLs. If you don't provide a view name, Spring will use a RequestToViewNameTranslator to try to figure out a view name. You can look at the source for that class to see exactly how it work. Here's a good quote from the docs:
"... a request URL of 'http://localhost/registration.html' will result in a logical view name of 'registration' being generated by the DefaultRequestToViewNameTranslator. This logical view name will then be resolved into the '/WEB-INF/jsp/registration.jsp' view by the InternalResourceViewResolver bean."
So it could, for example, make it so that a controller method handling "/modules/tips" would by default try to use a view named "modules/tips", presumably you would have a JSP at "/WEB-INF/jsp/modules/tips.jsp".
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-coc-r2vnt
EDIT: I just noticed that you said you tried omitting the view name, and it didn't seem like that worked. You could always write your own implementation of the RequestToViewNameTranslator and replace the DefaultRequestToViewNameTranslator with your own custom implentation. Check the docs for how to inject your own translator.

If you change your <url-pattern> element in web.xml to include modules/tips, you can effectively omit it from all of your Spring binding configuration:
<servlet-mapping>
<servlet-name>spring-mvc</servlet-name>
<url-pattern>/myapp/modules/tips/*</url-pattern>
</servlet-mapping>
#RequestMapping(value="/{tipId}/{viewName}",method=RequestMethod.GET)
public ModelAndView get(
#PathVariable String tipId,
#PathVariable String viewName) {
Tip tip = findTip(tipId);
return new ModelAndView(viewName,"tip",tip);
}

You can put a #requestMappings annotation on your class. The #requestMapping annotations on methods are then relative to this:
#Controller
#RequestMapping(value="/modules/tips")
public class TipsController{
#RequestMapping(value="{tipId}/{viewName}",method=RequestMethod.GET)
public ModelAndView get(
#PathVariable String tipId,
#PathVariable String viewName) {
Tip tip = findTip(tipId);
return new ModelAndView("modules/tips/" + viewName,"tip",tip);
}
}

Related

Sharing RequestMapping spring

I try to build a rest api with spring and face some issues. My original api is built with express on node and saw that some stuff I am pretty used to seem more complicated in spring.
For example, I have the following case, I could borrow the "controller" for /tasks even from the UserController.
/users
/users/:id
/users/:id/tasks
/tasks
Or I can easily inherit routes, my delegating them down. Spring doesn't seem to have something like that, where I could reference an already existing controller. It even seems to me that the RequestMapping value becomes long.
Is there something similar in Spring like what express can? Because I couldn't find any large spring mvc rest projects to illustrate that
You could add multiple values for #RequestMapping and also use Path Variables that fits your needs. For example,
#RequestMapping(value = "/users/{id}/tasks", method = GET)
public String getUserTaksFromIdPathVariable(#PathVariable("id") long id) {
return "Get all tasks from user with id=" + id;
}
And as I said, you can have multiple values:
#RequestMapping({"/tasks", "/users/{id}/tasks"}, method = GET)
public String getTasks(#PathVariable("id") Optional<long> id) {
return "whatever";
}

Servlet url pattern with wildcard in the middle

I'm working on an HttpServlet and trying to define a url-pattern with a wildcard, but not finding much documentation.
The path I want to capture is "resource/{id}/action"
I've tried my annotation as:
#WebServlet("/resource/*/action")
but this doesn't match, though the more basic "resource/*" works okay.
Also, is there any way I can automatically pull out my {id} wildcard, rather than having to parse the url manually?
I'm think you try to solve wrong task. It's really unusual decision to map servlet on wildcard like this. Take a look on Spring MVC framework there you can write methods like this
#RequestMapping("/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(#PathVariable String ownerId, #PathVariable String petId, Model model) {
Owner owner = ownerService.findOwner(ownderId);
Pet pet = owner.getPet(petId);
model.addAttribute("pet", pet);
return "displayPet";
}

Can converted #PathVariables reference each other?

I've got a Spring #RequestMapping with a couple of #PathVariables, and the first one is necessary to narrow down to the second one - you can see in the example below that I need to get the Department in order to get the Module. Using plain String #PathVariables I can do it like this:
#RequestMapping("/admin/{dept}/{mod}/")
public String showModule(#PathVariable String dept, #PathVariable String mod) {
Department department = dao.findDepartment(dept);
Module module = department.findModule(mod);
return "view";
}
But I'm keen to use Spring's Converter API to be able to specify the Department directly as the #PathVariable. So this works after I've registered a custom Converter class:
#RequestMapping("/admin/{dept}/")
public String showDept(#PathVariable Department dept) {
return "view";
}
But the Converter API doesn't give access outside of the single argument being converted, so it's not possible to implement the Converter for Module. Is there another API I can use? I'm eyeing up HandlerMethodArgumentResolver - has anyone solved a problem like this, or are you sticking to String #PathVariables?
I'm using Spring 3.1.
I haven't done it like this but one way I thought of was to make a separate converter for the both of them:
#RequestMapping("/admin/{deptAndModule}/")
public String showDept(#PathVariable DepartmentAndModule deptAndModule) {
return "view";
}
And have the converter able to take an input of the form "deptid-modid" e.g. "ch-c104". It wouldn't be possible to separate them with a slash as the request wouldn't match the RequestMapping pattern of /admin/*/.
In my case, the requirements have changed slightly so that module codes are fully unique and don't need to be scoped to department. So I don't need to do this any more. If I did, I would probably eschew the automatic Module conversion and do it manually in the method.

SpringMVC & #ModelAttribute peculiarities

Using Spring/SpringMVC 3.0.5 I've defined a method in my controller like this:
#RequestMapping(params = { "save","!delete" }, method = RequestMethod.POST)
public ModelAndView saveFoo(...
#ModelAttribute("vo") #Valid DescriptionBuilderVO vo, BindingResult result) {
...
result.rejectValue("foo.sequenceNumber", "foo.builder", new Object[]{vo.getFoo().getSequenceNumber()}, "Sequence Number too high"); vo.getFoo().setSequenceNumber(originalNumber);
return new ModelAndView(WebConstants.VIEW_BUILDER, "vo", vo);
Notice that I'm attempting to set a value in the VO object inside the controller. The funny thing is that if I do this with #ModelAttribute the new value doesn't show up. If I remove #ModelAttribute from the method contract, the new value appears exactly as you would think. The problem comes when there are errors, the only way to get the errors is to have the #ModelAttribute in the contract.
BTW my HTML looks like:
HTML
<form:input path="foo.sequenceNumber" id="sequenceNumber" size="4" maxlength="4"/>
<form:errors path="foo.sequenceNumber" cssClass="ui-state-error" />
foo.sequenceNumber = the value the user typed in; when I use #ModelAttribute
foo.sequenceNumber = the value I set in the controller; but I lose any errors
It seems to me that SpringMVC is putting the ModelAttribute VO into a "special" place and passing it back to the jsp but not in an obvious location. Does anyone know how I can get at the VO object in this situation?
wierd. The same thing works for me. The only difference i see is the order of Valid and ModelAttribute
can you try reversing the order of Valid and ModelAttribute?
public ModelAndView saveFoo(...
#Valid #ModelAttribute("vo") DescriptionBuilderVO vo, BindingResult result) {
}
BTW, which version of spring are you using?
I've tried many different things including the reordering the Valid and ModelAttribute annotations and it doesn't seem to make a difference.
In reading the documentation suggested by Pat in the comments it does refer to a special context where the VO is stored. Anyway, if you're trying anything similar to this I suggest you go about it in a different way perhaps building a completely new VO and passing it back to the view, which is what I did.
Try naming your object "descriptionBuilderVO", like the following:
#Valid #ModelAttribute("descriptionBuilderVO") DescriptionBuilderVO descriptionBuilderVO,
BindingResult result)
I know it shouldn't be this way, but I've found problems when the name of the object is different than the class name.
Note that you'll also have to change the name of the object in your jsp:
<form:form commandName="descriptionBuilderVO"
...etc...

Spring Web MVC: Use same request mapping for request parameter and path variable

Is there a way to express that my Spring Web MVC controller method should be matched either by a request handing in a ID as part of the URI path ...
#RequestMapping(method=RequestMethod.GET, value="campaigns/{id}")
public String getCampaignDetails(Model model, #PathVariable("id") Long id) {
... or if the client sends in the ID as a HTTP request parameter in the style ...
#RequestMapping(method=RequestMethod.GET, value="campaigns")
public String getCampaignDetails(Model model, #RequestParam("id") Long id) {
This seems to me a quite common real-world URL scheme where I don't want to add duplicate code, but I wasn't able to find an answer yet. Any advice highly welcome.
EDIT: It turns out that there seems currently (with Spring MVC <= 3.0) no way to achieve this, see discussion inside Javi's answer.
You can set both mapping url for the same function and setting id as optional.
#RequestMapping(method=RequestMethod.GET, value={"/campaigns","/campaigns/{id}"})
public String getCampaignDetails(Model model,
#RequestParam(value="id", required=false) Long id,
#PathVariable("id") Long id2)
{
}
though it would map as well when id is not sent, but you can control this inside the method.
EDIT: The previous solution doesn't work because #PathVariable is not set to null when there isn't {null} and it cannot map the URL (thanks ngeek). I think then that the only possible solution is to create two methods each one mapped with its #MappingRequest and inside one of them call the other function or redirect to the other URL (redirect: or forward: Spring prefixes). I know this solution is not what you're looking for but think it's the best you can do. Indeed you're not duplicating code but you're creating another function to handle another URL.
If you still want to stick to PathVariable approach and if you are getting 400 syntactically incorrect error then follow this approach-
#RequestMapping(method=RequestMethod.GET, value={"campaigns/{id}","campaigns"})
public String getCampaignDetails(Model model,
#PathVariable Map<String, String> pathVariables)
{
System.out.println(pathVariables.get("id"));
}
The #RequestMapping annotation now supports setting the path attribute instead of name or value. With path, you can achieve the mapping desired by this question:
#RequestMapping(method=RequestMethod.GET, path="campaigns/{id}")
public String getCampaignDetails(Model model, #PathVariable("id") Long id) {
#RequestMapping(method=RequestMethod.GET, value="campaigns")
public String getCampaignDetails(Model model, #RequestParam("id") Long id) {

Categories