Optional path segments in Spring MVC - java

Reading this (Spring 3) article from 2010, it discusses using an extension to provide a convenient way to include optional path segments:
#RequestMapping("/houses/[preview/][small/]{id}")
public String handlePreview(#PathVariable long id, #PathVariable("preview/") boolean preview, #PathVariable("small/") boolean small) {
return "view";
}
I know I could just implement a number of request mappings to achieve the same effect:
#RequestMapping(value="/houses/preview/{id}")
...
#RequestMapping(value="/houses/{id}")
...
~~~ snip ~~~
But depending on the number of possible permutations, it seems like a very verbose option.
Does any later version of Spring (after 3) provide such a facility? Alternatively, is there any mechanism to chain portions of the request URL to feed a larger response method signature?
Update
This answer to a question relating to sharing path variables and request parameters suggests an approach like:
#RequestMapping(method=RequestMethod.GET, value={"/campaigns","/campaigns/{id}"})
#ResponseBody
public String getCampaignDetails(
#PathVariable("id") String id)
{
~~~ snip ~~~
But the path variable cannot be set to null. Just going to /campaigns will return a 400 response.

Why you don't use java.util.Optional if you are using Java 1.8. To take your later example, you can avert a 400 response with put a Optional<String> instead of String representing your eventualy path like this:
#RequestMapping(method=RequestMethod.GET, value={"/campaigns","/campaigns/{id}"})
#ResponseBody
public String getCampaignDetails(
#PathVariable("id") Optional<String> id)
{
if(id.isPresent()){
model.addAttribute("id", id.get());//id.get() return your String path
}
}

Related

POST or GET what should I use in case request parameters exceeds 15? [duplicate]

This question already has answers here:
Design RESTful query API with a long list of query parameters [closed]
(4 answers)
Closed 3 years ago.
I am designing a rest API, to get a resource based on some parameters but in some cases these parameters are between 15-20 in number.
I am thinking of using a POST request to get the resource based on these 15 parameters. I know that POST request should not be used in case of getting the resource.
I want to know if there is a better option to handle this then sending POST request?
You can use Get service by using Map. It will accept all param.
/test?A=ABC&B=123
#RequestMapping(value="/test",method = RequestMethod.GET)
public String testUrl(#RequestParam Map<String, String> parameters)
{
println(parameters.get("A"));
println(parameters.get("B"));
return parameters.get("A");
}
Out Put Will Be
ABC
123
GET doesn't restrict the number of parameters
the only restriction is the length of the URL (which contains these parameters)
So if you're expecting that the parameters and values would cause a long URL, you can use POST instead
Standard says that URL length should be not more than 2,083 characters
even if some browsers/servers allow more, it's better to stick on this value for a wide-range support for all browsers/servers/gateways
In order to make your #Controller code more concise (e.g. get rid of 15x #RequestParam) you can use #ModelAttribute annotation.
#GetMapping(value="/things")
public List<Thing> findAllThings(#ModelAttribute ThingDTO thing) {
// logic
}
and your ThingDTO class like that:
public class ThingDTO {
private String name;
private ThingType type;
[...] //getters, setters etc.
}
This is going to map your class attributes to the #RequestParams. The code looks a bit cleaner imho.
When it comes to the URL length you should check the topic here: What is the maximum length of a URL in different browsers? and decide if there's possibility of exceeding the limit.
What should you use? For data retrieval I'd in 99% cases go with GET so if the above is not a blocker for you, go with GET. If there's a chance of exceeding the limit, go with POST.
The parameter length shouldn't be a problem to handle for the server.
You should read about how to design a rest api. I would recommend you to build it like this:
/api/{version}/{entity}/{id}
If you are using Spring Boot this is very simple to build.
Just write a Controller like this
#RestController
#RequestMapping("/api/users")
public class UsersService {
#Autowired
UsersRepository userRepo;
#RequestMapping(value="/find-all", method=RequestMethod.GET)
public Page<User> getAllUsers(#RequestParam(value="page", defaultValue = "0", required=false)Integer page,
#RequestParam(value="size", defaultValue= "20", required=false)Integer size){
...
}
#RequestMapping(value="/find/{id}")
public ResponseEntity<User> getUserById(#PathVariable(name="id")Long id){
...
}
#RequestMapping(value="/save", method=RequestMethod.POST)
public ResponseEntity<User> createUser(#RequestBody User user){
...
}
#RequestMapping(value="/delete/{id}", method = RequestMethod.GET)
public ResponseEntity<Void> deleteUser(#PathVariable(name="id")Long id){
...
}
}
Hope this sample code helps you to build your api. Just pass your ID as a PathVariable like shown in deleteUser() or findUser().
If you need more Parameters, you can extend the list or use RequestParam like used in getAllUsers().
The parameters are optional but need a default value.

Regarding REST Path Conflict

I am creating two methods(GET) in a REST service in which the URL is for the first method is of the form
/a/b/{parameter}?start=1 &
end=100 & name="some value"
and for the second method it is
/a/b/{parameter}
When i run the code it gives a conflict.
Can anyone please suggest me how these can be configured for the methods and also to make the query paramters OPTIONAL?
Thanks
This should work fine:
#GET
#Path("/a/b/{parameter}")
public Response get(#PathParam("parameter") String parameter, #QueryParam("start") Integer start, #QueryParam("end") Integer end, #QueryParam("name") String name) {
// switch here according to the values received. All are optional and non-sent are nulls here
}
If in the future you will have default values you can add them inline like this (for the name query param for example):
#DefaultValue("some-name") #QueryParam("name") String name

HttpMediaTypeNotAcceptableException when specifying applicaiton/xml in Accept header

Apologies, I didn't see a tag for java-noob.
I working with an existing restful api in Spring. I was tasked with adding a new api with a couple utility methods, each of which return a string. So far so good. All is working well.
I added a simple wrapper to return the string as an object and I'm able to build/deploy/test and I get back my response which looks like
{ id: "12345" }
If I specify the Accept header = application/xml I get the following exception:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
Other methods in this package seem to serialize in both xml and json fine. I don't see any custom serialization code being used in the controller base and I don't know where it is handled (or if it is even related to serialization). Can someone help me figure out where to start looking?
Here is my [simplified] controller:
#Controller
public class UtilController extends ControllerBase
{
#XmlRootElement(name="util_response")
public static class UtilResponse extends APIResponseBase
{
private String id;
#XmlElement(name="id")
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
}
#RequestMapping(value = "/{pid}/util/output", method = RequestMethod.GET)
#ResponseBody
public UtilResponse output(HttpServletResponse httpResponse,
#PathVariable("pid") int pid,
#RequestParam(value = "id", required=true) String id) throws Exception
{
UtilResponse utilResponse = new UtilResponse();
utilResponse.setId(id);
return utilResponse;
}
}
I also tried updating #RequestMapping and added produces = "application/xml" (yes, obviously having no clue what this really does), but that had zero effect.
Seriously, I'm really new about how all this java stuff "works" (.net dev by trade) and would love to understand more. I don't know what Jackson is, but I don't see references to it in our project and everything else seems to work so please no responses that say "Why don't you just use Jackson?" I also have deadlines so sometimes just getting it working takes precedence.
please no responses that say "Why don't you just use Jackson?
Nobody would say that. Jackson is for JSON ;)
produces = "application/xml" ..., but that had zero effect
produces restricts the handler method to the provided types. So produces="application\xml" makes the method unavailable for requests that expect JSON, e.g. It does not change the method's outcome.
Your description of the problem indicates that Spring is not able finding a way to serialize UtilResponse to XML. The most likely cause is that JAXB2 is not present on the classpath. Spring uses that as default to create XML.

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 ...
}

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