RESTful servlet URLs - servlet-mapping in web.xml - java

I feel like this is a common problem but nothing I've researched has worked yet...
In my web.xml I have a mapping for all REST calls -
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
This works well if the URL is -
GET /rest/people
but fails if it is
GET /rest/people/1
I get a 400 Bad Request error saying The request sent by the client was syntactically incorrect (). I'm not sure it even made it to the Spring servlet to get routed...
How can I wildcard anything that starts with /rest so that it can be handled appropriately?
In other words, I'd like for all of the following to be valid -
GET /rest/people
GET /rest/people/1
GET /rest/people/1/phones
GET /rest/people/1/phones/23
Edit - Controller code as requested
#Controller
#RequestMapping("/people")
public class PeopleController {
#RequestMapping(method=RequestMethod.GET)
public #ResponseBody String getPeople() {
return GsonFactory.getInstance().toJson(LookupDao.getInstance().getPeople());
}
#RequestMapping(value="{id}", method=RequestMethod.GET)
public #ResponseBody String getPerson(#PathVariable String id) {
return GsonFactory.getInstance().toJson(LookupDao.getInstance().getPerson(id));
}
}
Answer
#matsev It didn't seem to matter if I had the / there or not.
While I was transposing the variable names for public view I changed a couple things to make it work.
Original
#RequestMapping(value="{id}", method=RequestMethod.GET)
public #ResponseBody String getPerson(#PathVariable String userId) {
return GsonFactory.getInstance().toJson(LookupDao.getInstance().getPerson(userId));
}
What I posted
#RequestMapping(value="{id}", method=RequestMethod.GET)
public #ResponseBody String getPerson(#PathVariable String id) {
return GsonFactory.getInstance().toJson(LookupDao.getInstance().getPerson(id));
}
The variable name mismatch did me in... I leave this here as a warning to all... match your variable names!

Try add a /before the {id}:
#RequestMapping(value="/{id}", method=RequestMethod.GET)
Without it, the id will be appended directly to the people url, e.g /rest/people1 as opposed to /rest/people/1.

Related

UrlMapping Wildcard for #Pathvariable

I am trying to Define #PathVariable with wildcard in Web.xml for url-mapping.
Seems it does not work unless I give the full path in the mapping.
Here is my code.
TrackingController .java - PSEUDO Code
#Controller
#RequestMapping(value = "/tracking")
public class JobController implements Runnable {
#RequestMapping(value = "/{countrycode}/{urlID}", method = RequestMethod.GET)
#ResponseBody
public RedirectView refreshcache(#PathVariable("countrycode") String countrycode, #PathVariable("urlID") String urlID){
String Oauthurl="";
System.out.println(countrycode);
System.out.println(urlID);
if (countrycode.equals("India"))
{
Oauthurl ="https://www.google.co.in/";
}
else
{
Oauthurl ="https://www.google.com/";
}
RedirectView redirectView = new RedirectView();
redirectView.setUrl(Oauthurl);
return redirectView;
}
What I have already Tried is putting the full path and the path with wildcard
in the web.xml
Full path- Works
<servlet-name>appServlet</servlet-name>
<url-pattern>/tracking/India/1</url-pattern>
</servlet-mapping>
Wildcard - Does not work
<servlet-name>appServlet</servlet-name>
<url-pattern>/tracking/*</url-pattern>
</servlet-mapping>
Expected Result with wild card is it would redirect to the Url based on the #Pathvariable provided
However it throws 404 Error
You need to specify double (*) in the path url to match any string.
here is the example.
<servlet-name>appServlet</servlet-name>
<url-pattern>/tracking/**</url-pattern>
</servlet-mapping>
Don't use mapping via web.xml. It's already done by #RequestMapping.
Following code should work:
#Controller
#RequestMapping(value = "/tracking")
public class JobController {
#GetMapping("/{countryCode}/{urlID}")//removed #ResponseBody
public RedirectView refreshCache(#PathVariable String countryCode, #PathVariable String urlID) {
String oauthUrl;
System.out.println(countryCode);
System.out.println(urlID);
if ("India".equalsIgnoreCase(countryCode)) {//Avoid NPE
oauthUrl = "https://www.google.co.in/";
} else {
oauthUrl = "https://www.google.com/";
}
return new RedirectView(oauthUrl);
}
}
If don't - check configuration. Spring can't find your controllers. Take a look

Spring restful API, is there a method being used like router to get other method's end points or URL?

#RequestMapping("/accounts")
public class controller {
#GetMapping("/get/{id}")
public final ResponseEntity<?> getHandler(){
}
#PostMapping(value = "/create")
public final ResponseEntity<?> createHandler(){
/*
trying to use some spring library methods to get the url string of
'/accounts/get/{id}' instead of manually hard coding it
*/
}
}
This is the mock code, now I am in createHandler, after finishing creating something, then I want to return a header including an URL string, but I don't want to manually concat this URL string ('/accounts/get/{id}') which is the end point of method getHandler(), so I am wondering if there is a method to use to achieve that? I know request.getRequestURI(), but that is only for the URI in the current context.
More explanation: if there is some library or framework with the implementation of route:
Routes.Accounts.get(1234)
which return the URL for the accounts get
/api/accounts/1234
The idea is, that you don't need to specify get or create (verbs are a big no-no in REST).
Imagine this:
#RequestMapping("/accounts")
public class controller {
#GetMapping("/{id}")
public final ResponseEntity<?> getHandler(#PathVariable("id") String id) {
//just to illustrate
return complicatedHandlerCalculation(id).asResponse();
}
#PostMapping
public final ResponseEntity<?> createHandler() {
//return a 204 Response, containing the URI from getHandler, with {id} resolved to the id from your database (or wherever).
}
}
This would be accessible like HTTP-GET: /api/accounts/1 and HTTP-POST: /api/accounts, the latter would return an URI for /api/accounts/2 (what can be gotten with HTTP-GET or updated/modified with HTTP-PUT)
To resolve this URI, you could use reflection and evaluate the annotations on the corresponding class/methods like Jersey does.
A Spring equivalent could be:
// Controller requestMapping
String controllerMapping = this.getClass().getAnnotation(RequestMapping.class).value()[0];
and
//Method requestMapping
String methodMapping = new Object(){}.getClass().getEnclosingMethod().getAnnotation(GetMapping.class).value()[0];
taken from How do i get the requestmapping value in the controller?

Why is my request hitting two controllers?

I have the following Spring controller:
#Controller
public class TypoWorkflowController {
#RequestMapping(value = "/workflow/typo-workflow/moreInfo", method = RequestMethod.GET)
public String serveMoreInfo(#RequestParam(value = "taskId", required=false) String taskId, ModelMap modelMap) {
return "typo-workflow-more-info";
}
}
My tiles-def file contains:
<definition name="typo-workflow-more-info" template="/WEB_INF/jsp/workflow/typo-workflow/moreInfo.jsp"/>
My JSP is plain old HTML.
When I hit the url /workflow/typo-workflow/moreInfo, Tomcat throws a StackOverflowError.
When I step through in debug mode, I see that I'm hitting my controller first, as I would expect, but then I hit another controller, at the method:
#Controller
#Order(value = Ordered.LOWEST_PRECEDENCE)
public class ContentServingController {
/* ... */
#RequestMapping({"/*", "/**/*"})
public ModelAndView serveContent(HttpServletResponse response, ModelMap model) {
/* ... */
}
}
As I poked around, it seeeeeeemed like we were in there to respond to a request for /WEB_INF/jsp/workflow/typo-workflow/moreInfo.jsp, but this doesn't happen for other controllers that operate in the same way (returning a View name).
So, can anyone provide me with some pointers for debugging this. Why would I be hitting a controller for a JSP anyway? Isn't a JSP supposed to be a little servlet itself?
Your tiles def is pointing to the WEB_INF folder when it should be pointing to the WEB-INF folder (dash instead of underscore) so spring doesn't know where to look within the app and is just making a normal http request, which is getting caught by the wildcard match.

#ResponseBody unable to return XML

I have a REST Webservice returning an int via #responseBody and I want this response to be in XML, and I don't know how to achieve that despite many tries.
My controller is as follow:
#RequestMapping(value = "/UserByAppli", method = RequestMethod.GET)
#ResponseBody
public List<Application> getNbUserByAppli()
{
return this.DAO.getNbUserByAppli();
}
And my application Object:
#Component
#XmlRootElement(name="Application")
#XmlAccessorType(XmlAccessType.FIELD)
public class Application
{
#XmlElement(name="Nom")
private String name;
#XmlElement(name="NbUtilisateurs")
private int nbUsers;
public Application()
{
}
...
}
It always returns application/json, and when I specify the header "Accept=application/xml" I get a 406 response code with org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation in Spring logs.
An explanation or a search direction would be appreciated...
Make sure you have JAXB2 in your classpath and have registered the appropriate message converter and pass the Accept: application/xml header. Also, like M. Deinum suggested, for the marshalling to work, you also need to wrap the <Application /> elements in another element <Applications />.

Spring MVC RequestMapping is being forwarded

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?

Categories