Servlet url pattern with wildcard in the middle - java

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

Related

Spring, middle score in request parameter

Is there a way to map a query parameter with a middle score using requests in spring?
I have no problem binding single worded parameters doing this:
Uri example: http://localhost:8080/test/?product=hotels
public class CitiesRequest{
private ProductType product;
public ProductType getProduct() {
return this.product;
}
public void setProduct(String product) {
this.product = product;
}
}
But I'd like to be able to receive parameters like this:
http://localhost:8080/test/?product-type=hotels
As Misha stated it is syntactically incorrect to have a variable name with a hyphen in Java. But Spring is fine with that and allows you to specify a parameter name (in the request) different from the variable name (in java code). For exemple, when using RequestMapping driven controller, one can write :
#RequestMapping("/test")
public ModelAndView getProduct(
#RequestParam("product-type") String productType) {
...
}
That way, getProduct will be called for a url like http://localhost/test?product-type=hotels and the parameter productTypewill receive the value hotels. And all is still purely declarative.
By default, Spring maps the query parameter key to the name of the Java variable. However, it's syntactically incorrect to have a variable name with a hyphen in Java, which explains why you're finding it particularly difficult to get Spring to set the parameter's value for you.
One workaround that might work is to just have a Map<String, String[]> parameter to represent all of the parameters. Then Spring doesn't have to map any query parameters to variable names, so the hyphenated name might end up in that map of all parameters. It may not be as comfortable as pre-split parameter objects, but it might get the hyphenated keys.
Another solution might be to configure the WebDataBinder, which controls how data from HTTP requests are mapped onto your controller's request parameters. But that's a whole can of worms, especially if you're just starting out with Spring. You can read more about it in the documentation under "data binding".

Can I use JSON data in RequestMapping in Spring MVC

I'm thinking the answer here is probably no, but just in case.
I'm doing something like this:
#RequestMapping(value="data.json", params="query=overview")
public String getOverview(#RequestBody MyRequest myRequest) {
[...]
return "overview";
}
#RequestMapping(value="data.json", params="query=detail")
public String getDetail(#RequestBody MyRequest myRequest) {
[...]
return "detail";
}
and the client is POSTing JSON data, which is deserialized by Jackson on the way in and bound to the MyRequest parameter, all working nicely.
However, I don't like the query type having to be specified in the URL of the requests. What I would like is to include the query parameter in the JSON object and use that to drive the #RequestMapping. Is this possible?
If not, I guess I will implement a single mapping and have that method delegate to others based on the incoming data, but that feels like a step in the wrong direction.
What you are trying to do does not work out of the box.
If you don't like the param why don't you add the qualifier to the URL like so:
#RequestMapping("/overview/data.json")
#RequestMapping("/detail/data.json")
If you absolutely need the functionality you mention, you could implement a custom RequestMappingHandlerMapping that would do what you want by extending that class as is done here.
It's not possible if you remove the params. You have to have something distinct between the two mappings. If you are intent on getting rid of the params, best you could do is have a single method/mapping and call your services or whatever other logic you have according to what the value of query is in your MyRequest object.
#RequestMapping(value="data.json")
public String getOverviewOrDetail(#RequestBody MyRequest myRequest) {
if (myRequest.getQuery().equalsIgnoreCase("overview")) {
[...]
return "overview"
} else if(myRequest.getQuery().equalsIgnoreCase("detail")) {
[...]
return "detail"
}
}
Since both methods are unmarshalling to the same object, you don't really need two separate methods/mappings.

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.

Is it possible to configure JAX-RS method with variable number of URI parameters?

is it possible to configure GET method to read variable number of URI parameters and interpret them either as variable argument (array) or collection? I know query parameters can be read as list/set but I can't go for them in my case.
E.g.:
#GET
#Produces("text/xml")
#Path("list/{taskId}")
public String getTaskCheckLists(#PathParam("taskId") int... taskId) {
return Arrays.toString(taskId);
}
Thanks in advance
If I understand your question correctly, the #Path annotation can take a regular expression to specify a list of path components. For example, something like:
#GET
#Path("/list/{taskid:.+}")
public String getTaskCheckLists(#PathParam("taskid") List<PathSegment> taskIdList) {
......
}
There's a more extensive example here.
I am not submitting this as an answer as it is merely an edge case on the currently accepted answer which is what I've also used.
In my case (Jersey 1.19) /list/{taskid:.+} would not work for the edge case of zero variable parameters. Changing the RegEx to /list/{taskid:.*} took care of that. See also this article (which seems to be applicable).
Moreover, upon changing the regexp to cardinality indicator to * (instead of +) I also had to deal programmatically with the case of empty strings as I would translate the List<PathSegment> into a List<String> (to pass it into my DB-access code).
The reason I am translating from PathSegment to String is that I didn't want a class from the javax.ws.rs.core package to pollute my Data Access Layer code.
Here's a complete example:
#Path("/listDirs/{dirs:.*}")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response listDirs(#PathParam("dirs") List<PathSegment> pathSegments) {
List<String> dirs = new ArrayList<>();
for (PathSegment pathSegment: pathSegments) {
String path = pathSegment.getPath();
if ((path!=null) && (!path.trim().equals("")))
dirs.add(pathSegment.getPath());
}
List<String> valueFromDB = db.doSomeQuery(dirs);
// construct JSON response object ...
}

Not repeating a path twice in Spring 3.0 MVC Restful mappings

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

Categories