I am trying to create a RESTful controller using Spring 3.0. The controller is for a management API for a portal application. The operations I want to perform are:
GET /api/portals to list all the portals
POST /api/portals to create a new portal
GET /api/portals/{id} to retrieve an existing portal
PUT /api/portals/{id} to update an existing portal
DELETE /api/portal/{id} to delete an existing portal
After annotating the controller as illustrated below I find the the operations to list all the portals or create a new portal do not get mapped.
So my questions are:
Have I annotated the class correctly?
Am I following the correct conventions for implementing a RESTful web service?
Might there be something broken in Spring?
The code extract below shows how I have annotated my class:
#Controller
#RequestMapping("/api/portals")
public final class PortalAPIController
{
private final static Logger LOGGER = LoggerFactory.getLogger(PortalAPIController.class);
#RequestMapping(value = "/", method = RequestMethod.GET)
public String listPortals(final Model model)
{
PortalAPIController.LOGGER.debug("Portal API: listPortals()");
.
.
return "portals";
}
#RequestMapping(value = "/", method = RequestMethod.POST)
public String createPortal(#RequestBody final MultiValueMap<String, String> portalData, final Model model)
{
PortalAPIController.LOGGER.debug("Portal API: createPortal()");
.
.
return "portal";
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String getPortal(#PathVariable("id") final String portalId, final Model model, final HttpServletResponse response)
throws IOException
{
PortalAPIController.LOGGER.debug("Portal API: getPortal()");
.
.
return "portal";
}
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public String updatePortal(#PathVariable("id") final String portalId,
#RequestBody final MultiValueMap<String, String> portalData, final Model model, final HttpServletResponse response)
throws IOException
{
PortalAPIController.LOGGER.debug("Portal API: updatePortal()");
.
.
return "portal";
}
#RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String deletePortal(#PathVariable("id") final String portalId, final Model model, final HttpServletResponse response)
throws IOException
{
PortalAPIController.LOGGER.debug("Portal API: deletePortal()");
.
.
return "portal";
}
.
.
}
During start-up I am seeing that Spring things it has registered the end-points:
2010-02-19 01:18:41,733 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals/] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController#141717f]
2010-02-19 01:18:41,734 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals/{id}] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController#141717f]
2010-02-19 01:18:41,734 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals/{id}.*] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController#141717f]
2010-02-19 01:18:41,735 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals/{id}/] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController#141717f]
2010-02-19 01:18:41,735 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController#141717f]
2010-02-19 01:18:41,735 INFO [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] - Mapped URL path [/api/portals.*] onto handler [com.btmatthews.mars.portal.web.controller.PortalAPIController#141717f]
But when I try to invoke my API using cURL
curl http://localhost:8080/com.btmatthews.minerva.portal/api/portals/
or
curl http://localhost:8080/com.btmatthews.minerva.portal/api/portals
I get the following errors:
2010-02-19 01:19:20,199 WARN [org.springframework.web.servlet.PageNotFound] - No mapping found for HTTP request with URI [/com.btmatthews.minerva.portal/api/portals] in DispatcherServlet with name 'portal'
2010-02-19 01:19:32,360 WARN [org.springframework.web.servlet.PageNotFound] - No mapping found for HTTP request with URI [/com.btmatthews.minerva.portal/api/portals/] in DispatcherServlet with name 'portal'
I get the same problem when I try to do a create:
curl -F ...... --request POST http://localhost:8080/com.btmatthtews.minerva/api/portals
But if try to operate on an existing resource (retrieve, update or delete) it works okay.
Update: The solution was provided in a comment by #axtavt. I was using <url-pattern>/api/*</url-pattern> in my web.xml servlet mapping. It needed to be changed to <url-pattern>/</url-pattern>
Double check the url-pattern in your web.xml and compare it to your curl argument.
Here is an example I wrote which walks you through the whole Spring MVC process.
The URL you post in your curl excerpt
http://localhost:8080/portal/api/portals
does not match the URL in the Spring warning
/com.btmatthews.minerva.portal/api/portals
Without knowing how your webapp is set up, what context path it's at, what the Spring context looks like, etc., it's pretty hard to diagnose but this sounds like a big clue to me.
You Have done few Mistakes.
#RequestMapping(value = "/", method = RequestMethod.GET)
public String listPortals(final Model model)
{
}
Over here the method must accept some parameter Model, but that's not passed.
you must write that inside method. or if u want to pass that, u must sent it as path variable or as a request Param.
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String getPortal(#PathVariable("id")
here the correct syntax is #PathVariable(value="id")
And if this doesn't work
then try this:
#RequestMapping(value = "/SOMETHING", method = RequestMethod.GET)
In Spring 3, the Rest Controller is no more than a regular controller (#Component) and the only difference is that a Rest controller returns JSON/XML instead of "Views". So I think you are doing ok with respect to annotations. However, two missing things I can't see:
1- Every method needs to have a "produce" value (for content negotiation):
#RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = "application/json")
2- You need an ObjectMapper of some type (ex: Marshaller and Unmarshaller) to be able to map objects to JSON/XML and vice-versa. Those two should be in your servlet config, instead of configuring a view resolver.
And that should be it.
Related
I see Spring MVC multiple url mapping to the same controller method
So now I have a method defined as
#RequestMapping(value = {"/aaa", "/bbb", "/ccc/xxx"}, method = RequestMethod.POST)
public String foo() {
// was it called from /aaa or /bbb
}
At run time, I want to know if the controller was called from /aaa or /bbb
You can use HttpServletRequest#getServletPath which:
Returns the part of this request's URL that calls the servlet. This
path starts with a "/" character and includes either the servlet name
or a path to the servlet, but does not include any extra path
information or a query string.
As follow:
#RequestMapping(value = {"/aaa", "/bbb", "/ccc/xxx"}, method = RequestMethod.POST)
public String foo(HttpServletRequest request) {
String path = request.getServletPath(); // -> gives "/aaa", "/bbb" or "/ccc/xxx"
}
I am trying to get the request URL in a RestController. The RestController has multiple methods annotated with #RequestMapping for different URIs and I am wondering how I can get the absolute URL from the #RequestMapping annotations.
#RestController
#RequestMapping(value = "/my/absolute/url/{urlid}/tests"
public class Test {
#ResponseBody
#RequestMapping(value "/",produces = "application/json")
public String getURLValue(){
//get URL value here which should be in this case, for instance if urlid
//is 1 in request then "/my/absolute/url/1/tests"
String test = getURL ?
return test;
}
}
You may try adding an additional argument of type HttpServletRequest to the getUrlValue() method:
#RequestMapping(value ="/",produces = "application/json")
public String getURLValue(HttpServletRequest request){
String test = request.getRequestURI();
return test;
}
If you don't want any dependency on Spring's HATEOAS or javax.* namespace, use ServletUriComponentsBuilder to get URI of current request:
import org.springframework.web.util.UriComponentsBuilder;
ServletUriComponentsBuilder.fromCurrentRequest();
ServletUriComponentsBuilder.fromCurrentRequestUri();
Allows getting any URL on your system, not just a current one.
import org.springframework.hateoas.mvc.ControllerLinkBuilder
...
ControllerLinkBuilder linkBuilder = ControllerLinkBuilder.linkTo(methodOn(YourController.class).getSomeEntityMethod(parameterId, parameterTwoId))
URI methodUri = linkBuilder.Uri()
String methodUrl = methodUri.getPath()
Add a parameter of type UriComponentsBuilder to your controller method. Spring will give you an instance that's preconfigured with the URI for the current request, and you can then customize it (such as by using MvcUriComponentsBuilder.relativeTo to point at a different controller using the same prefix).
I want to know why spring mvc transform [""] to [null] when I use PostMan to test my API.
here is my controller:
#RequestMapping(value = "", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
public #ResponseBody ResponseEntity<Object> participateRstActivities(
HttpServletRequest request, #RequestBody RstActivityFrom rstForm)
throws ServiceException {
log.info("list size:{}, frist object:{}",rstForm.getRestaurant_ids().size(), rstForm.getRestaurant_ids().get(0));
}
here is my java bean:
public class RstActivityFrom {
private List<Integer> restaurant_ids;
private int activity_id;
// omit getter & setter
}
here is my request body when I use postman to test my api:
{
"restaurant_ids":[""],
"activity_id":119129
}
and the log in controller print :
list size:1, frist object:null.
this problem makes me feel confuse, I want to know why. Thanks
Since restaurant_ids is a List and not String, Change your JSON for restaurant_ids:
{
"restaurant_ids":[],
"activity_id":119129
}
If you don't want to allow an empty String value for objects mapped from your JSON, you can look into setting the Jackson's ObjectMapper Features as described here:
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html
The Java API for Jackson's DeserializationConfig.Feature(s) can be found here:
http://fasterxml.github.io/jackson-core/javadoc/1.9/org/codehaus/jackson/map/DeserializationConfig.Feature.html
i want to call REST API Controller from my REST API Controller.
"http://localhost:8080/function/1?filter={"id":1435263}"
Since we cannot send directly ({"id":1435263})JSON query String along with url because of spring mvc cannot read "{",i am sending query string(Search variables) in the form of map .
Object response = restTemplate.getForObject(crudControllerURL,
Object.class, map);
where map contains the values .
On Server side i am unable to retrieve this map. I tried #RequestParam Object obj but it did not work . I am clueless how can i get these values there?
Do i need to convert it into POST?
EDIT
when i try to use whole url with query String then i recieve
java.lang.IllegalArgumentException: Not enough variable values available to expand '"id"'
Adding Server side contoller code snippet(not whole) and please note i need to access map in Server REST API Controller .
Server side controller
#RequestMapping(value = "/{function}/{type}", method = RequestMethod.GET)
public List<Order> performFetchAll(#PathVariable String function,
HttpServletRequest request) throws JsonParseException,
JsonMappingException, IOException, InvalidAttributesException {
String requestQueryString = request.getQueryString();
if (requestQueryString == null
|| requestQueryString.equalsIgnoreCase(""))
return orderService.findAll();
Please provide your feedback. Thanks.
You should do this probably than complicating the request:
URL can be changed like this : http://localhost:8080/function/1?idFilter=1435263
#RequestMapping(value = "/{function}/{type}", method = RequestMethod.GET)
public List<Order> performFetchAll(#PathVariable String function, #RequestParam("idFilter") String id, HttpServletRequest request) throws JsonParseException,
JsonMappingException, IOException, InvalidAttributesException {
//DO something
}
If your filter request is going to be big and complex like a json, change your method to POST and take the JSON and do your logic.
My method is annotated as
#RequestMapping(value = "/keepAlive", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE,consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ModelMap test(HttpServletRequest req, final ModelMap model) {
model.addAttribute("keepAlive", true);
return model;
}
when I call is using ajax using JQuery
it returns 500 server error and at server log I can see that it is looking for KeepAlive.jsp, I am using spring 3.2.10 and have jackson 2 at class path. When I debugged source code request is passed to ModelAndViewMethodReturnValueHandler rather than RequestResponseBodyMethodProcessor , It seems Model and view handler is registered before req res handler. How to solve this. Same code worked for spring 3.1.2.
Thanks