I have a REST service based on Spring MVC.
This is my code:
public class SitesController {
#RequestMapping(value="/rest/sites/{id}", method=RequestMethod.GET)
#ResponseBody
public SiteDTO getSite(#PathVariable String id) {
Integer siteId = Integer.parseInt(id);
Site site = cms.getSite(siteId);
SiteDTO siteResult = new SiteDTO(site);
return siteResult;
}
#RequestMapping(value="/rest/sites", method=RequestMethod.GET)
public SitesResult getSites(#RequestParam Integer companyId) {
Collection<Site> sites = cms.getSites(cms.getCompany(companyId));
SitesResult sitesResult = new SitesResult(sites);
return sitesResult;
}
}
(I skipped some code that doesn't apply to the problem)
When I go to the URL /rest/sites/1 it is returning the data that I expect, but when I go to /rest/sites?companyId=1 I get a 404 page: HTTP Status 404 - /rest/rest/sites.
The log is showing that the code in the getSitesfunction is run, but after that the log is showing the following: org.springframework.web.servlet.view.JstlView Forwarding to resource [rest/sites] in InternalResourceView 'rest/sites'
Why is it redirected instead of executed?
UPDATE
Found the problem. Because I didn't have #ResponseBody above my method, the dispatcher forwarded my request. More information here, the key thing was If the method is annotated with #ResponseBody, the return type is written to the response HTTP body. The return value will be converted to the declared method argument type using HttpMessageConverters.
Because your method return type SitesResult is not one of the supported return types, Spring will add the returned object to the Model using its class name and try to render a view named by the value of your request mapping, which is why it is trying to render /rest/sites. It's not actually doing an HTTP forward, but a dispatcher forward which is what servlets do to render a view (eg. jsp).
If you want to return a specific view, return a String containing its name.
Instead
#RequestMapping(value="/rest/sites", method=RequestMethod.GET)
public SitesResult getSites(#RequestParam Integer companyId) {
Collection<Site> sites = cms.getSites(cms.getCompany(companyId));
SitesResult sitesResult = new SitesResult(sites);
return sitesResult;
}
Do this
#RequestMapping(value="/rest/sites", method=RequestMethod.GET)
public String getSites(#RequestParam Integer companyId, Model model) {
Collection<Site> sites = cms.getSites(cms.getCompany(companyId));
SitesResult sitesResult = new SitesResult(sites);
model.addAttribute("sitesResult", sitesResult);
String myView = "myView";
return myView;
}
This is just a guess on my part...
Method getSites could be declared as :
public SitesResult getSites(#RequestParam("companyId") Integer companyId) {
I don't know if this would have any effect?
Or could it be that the logical view name specified does not map to a view?
Related
I'm using a bit of a personalized security back-end due to the nature of the app and was trying out how to implement a few simple error returns in my REST API controller. It's simple enough to do in a html page controller like I have in the following:
#Controller
public class HomeController {
#Autowired
private UserService userService;
#GetMapping("/home.html")
public String home(Model model) {
String redirect = "home";
if(!userService.getCurrentUser().isCanAccessService()) {
redirect = "unauthorized";
}
return redirect;
}
}
I can easily just redirect it to the unauthorized page that I made since I'm returning the string value here. However, when I go to a REST API it's not as simple:
#RestController
public class bagelController {
#Autowired
private bagelService bagelService;
#Autowired
private UserService userService;
#GetMapping("/rest/bagel/search")
public Bagel searchBagel (#RequestParam(value = "bagel", required = false) String bagel,
#RequestParam(value = "bagelInd", required = false, defaultValue = "1") int bagelInd) {
Bagel bagel;
if(!userService.getCurrentUser().isBagelEditAccess()) {
bagel = null;
// I want to return a 401 or direct to my unathorized page if I get an invalid user here.
}
else {
bagel = bagelService.getbagel(bagel, bagelInd);
// if my bagel object returns null, I want to return a 404 or direct to a 404 not
found page here.
}
return bagel;
}
You can have a ControllerAdvice which handles exceptions and their HTTP return code. Then you can annotate a method in it the following way for example:
#ExceptionHandler(NoSuchEntityException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
This will return a 404 code every time it encounters a NoSuchEntityException (custom exception). So you can throw such an exception when you check if an entity is null. You can use the same thing for 401 or any other HTTP code as well.
One way to do this.
#GetMapping("/rest/bagel/search")
public ResponseEntity<Bagel> searchBagel (#RequestParam(value = "bagel", required = false) String bagel,
#RequestParam(value = "bagelInd", required = false, defaultValue = "1") int bagelInd) {
Bagel bagel = null;
if(!userService.getCurrentUser().isBagelEditAccess()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
else {
bagel = bagelService.getbagel(bagel, bagelInd);
if(bagel == null) {
return ResponseEntity.notFound().build();
}
}
return ResponseEntity.ok(bagel);
}
You can create custom exceptions within your application for this scenario like BagelNotFoundException and UnauthorizedException. Both these custom exception classes can extend Exception class or more specific classes from java exception hierarchy. You can annotate these custom exception classes with #ResponseStatus annotation to provide the http status code that should be sent in the response.
Next, you need to throw the objects of these exceptions within your controller.
Once this exception is thrown, an exception handler should be present within your application to take care of these exceptions. The same can be defined using #ControllerAdvice and #ExceptionHandler within your custom exception handler classes.
This way you'll be able to send appropriate response to the client, and the client application needs to redirect the user to error pages based on the response code received.
Hope this helps!
I have the following problem:
I have a Rest controller that I want to configure in the following URLs:
/api/districts/1,2,3 - (list districts by array of ids)
/api/districts/1 - (list district by a single id)
These are the following mapping methods:
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public District getById(#PathVariable int id) {
// check input
return districtService.getById(id);
}
#RequestMapping(value = "/{districtIDs}", method = RequestMethod.GET)
public List<District> listByArray(#PathVariable Integer[] districtIDs) {
ArrayList<District> result = new ArrayList<>();
for (Integer id : districtIDs) {
result.add(districtService.getById(id));
}
return result;
}
This is the error I'm getting when I make a request to /api/districts/1,2,3
There was an unexpected error (type=Internal Server Error, status=500).
Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/api/districts/1,2,3': {public java.util.List com.groto.server.web.DistrictsController.listByArray(java.lang.Integer[]), public com.groto.server.models.hibernate.District com.groto.server.web.DistrictsController.getById(int)}
this is the error I'm getting when I make a request to /api/districts/1
There was an unexpected error (type=Internal Server Error, status=500).
Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/api/districts/1': {public java.util.List com.groto.server.web.DistrictsController.listByArray(java.lang.Integer[]), public com.groto.server.models.hibernate.District com.groto.server.web.DistrictsController.getById(int)}
In Spring MVC, overloading based on PathVariable type will not be possible, as both the APIs will be considered same. At runtime, two handlers will be found for any of the requests you have mentioned and hence the exception.
You can instead remove the getById() method and the second API will work for a single ID also. Only difference is the return type which will be a List and that can be handled at the client side easily.
I found a solution below url.
https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-path-variable.html
/**
* Using mutually exclusive regex, which can be used
* to avoid ambiguous mapping exception
*/
#Controller
#RequestMapping("/dept")
public class DeptController {
#RequestMapping("{id:[0-9]+}")
public String handleRequest(#PathVariable("id") String userId, Model model){
model.addAttribute("msg", "profile id: "+userId);
return "my-page";
}
#RequestMapping("{name:[a-zA-Z]+}")
public String handleRequest2 (#PathVariable("name") String deptName, Model model) {
model.addAttribute("msg", "dept name : " + deptName);
return "my-page";
}
}
I need to set a cookie with redirect in my login controller. I used code below to set cookie.
#RequestMapping("/fbresponse")
public String getToken(#RequestParam(required = false, value = "code") String code, HttpServletResponse sResponse) {
sResponse.addCookie(new Cookie("logged", "123"));
return "redirect:"+user.getLastPage();
}
In my index I try to retrive the cookie using following code:
#RequestMapping("/")
public String getIndex(#CookieValue(value="logged", required=false)String test){
user.setLastPage("/");
loginCheck();
System.out.println(test);
return "index";
}
But it always returns null. I tried returning new ModelAndView. It also did not work and since I need some components in model it does not suit my requirement.
How can set and retrieve a cookie? Is it possible to do it with redirect?
UPDATE
I have class level #RequestMapping in my login controller.
#Controller
#RequestMapping("/login")
public class LoginController {
#RequestMapping("/fbresponse")
public String getToken(#RequestParam(required = false, value = "code") String code, HttpServletResponse sResponse) {
sResponse.addCookie(new Cookie("logged", "123"));
return "redirect:"+user.getLastPage();
}
}
When I remove the class level request mapping add cookies works. How can I add a cookie correctly with class level request mapping?
You need to set the path of the cookie, otherwise it's valid only for the current path.
In my controller i have these ff methods
#RequestMapping("/countryList.html")
#ModelAttribute("countries")
public Collection<Country> getCountries() {
return worldService.getAllCountries();
}
#RequestMapping("/countryList.html")
public String getName() {
return viewers_name;
}
What I was trying to do is that in the countryList.html, it will return the countries and the name of the current user viewing it, however upon accessing the countryList.html it returned me an exception
Ambiguous handler methods mapped for HTTP path '/countryList.html': {public java.lang.String levelup.world.web.CountryController.getName(), public java.util.Collection levelup.world.web.CountryController.getCountries()}.
How would I resolve this issue?
#RequestMapping("/countryList.html") should be unique to mehod. How you gave this request mapping to two methods.
As Per your comments:-
#RequestMapping(value = "/countryList.html")
public Collection<Country> getCountries(ModelMap model) {
model.addAttribute("countries", countryObject);
return viewName;
}
Or define jsonView in config to return json object for ajax calls
#RequestMapping(value = "/countryList.html")
public Collection<Country> getCountries(ModelMap model) {
model.addAttribute("countries", countryObject);
return jsonView;
}
Because You have the same request mapping to different methods. The exception message is simple
I like to implement a REST-API into my SpringMVC application. At the moment, I have one method to handle POST-Requests, which "returns" a rendered ViewScript.
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
return "success";
}
It would be nice, to add a second method with the #ResponseBody Annotation for POST-Requests, e.g. to send a JSON-Response.
Furthermore, the old Method still has to exists, to handle "normal" Requests.
But a code like this doesn't work:
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
return "success";
}
#RequestMapping(method=RequestMethod.POST)
#ResponseBody
public Object add(User user, Model model)
{
// [...]
return myObject;
}
With this code, I'm getting a 405 (Method Not Allowed) Error from Tomcat. How can I fix this?
As it stands, Spring has no way to differentiate between these two requests: same URL, same request method.
You can further differentiate by mimetype:
#RequestMapping(method=RequestMethod.POST, headers="content-type=application/json")
Although there are several mimetypes associated with JSON :/ The headers value takes an array, however, so you can narrow/widen it as necessary.
See the headers docs.
Dont USE TWO ANNOTATION. It is a poor option. Just have one more method without annotation. But the method from the old method by checking the below condition.
JUST PASS ONE MORE ARGUMENT FROM UI by query parameter(request="JSON_Request").
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
if(request="JSON_Request") {
newMethod(user, model);
}
return "success";
}
private Object newMethod(User user, Model model)
{
// [...]
return myObject;
}