Difference between two #RequestMapping annotations - java

I am pretty new in Spring MVC and I have the following doubt.
In a controller, I have a method annotated in this way:
#Controller
#RequestMapping(value = "/users")
public class UserController {
#RequestMapping(params = "register")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
So this method handle HTTP Request toward the URL /users?register where register is a parameter (because the entire class handle request toward /users resource).
Is it the same thing if, instead using the params = "register" I use the following syntaxt:
#Controller
public class UserController {
#RequestMapping("/users/{register}")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
I have deleted the mapping at class level and I use #RequestMapping("/users/{register}").
Is it the same meaning of the first example?

NO, they are completely different constructs:
Code 1
#Controller
#RequestMapping(value = "/users")
public class UserController {
#RequestMapping(params = "register")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
In this case, createForm method will be called when a HTTP request is made at URL /users?register. Quoting from Spring Javadoc, it means this method will be called whatever the value of the register HTTP parameter; it just has to be present.
"myParam" style expressions are also supported, with such parameters having to be present in the request (allowed to have any value).
Code 2
#Controller
public class UserController {
#RequestMapping("/users/{register}")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
In this case, #RequestMapping is declaring register as a PathVariable. The method createForm will be called if a HTTP request is made at URL /users/something, whatever the something. And you can actually retrieve this something like this:
#RequestMapping("/users/{register}")
public String createForm(#PathVariable("register") String register, Model model) {
// here "register" will have value "something".
model.addAttribute("user", new Customer());
return "user/register";
}

Related

Call controller from another controller with object as a parameter and retrieve the response

I have two restcontrollers. Both are postmapping endpoints.
After the first controller (Controller1) makes everything i need witn an object, i would like to call/redirect the second controller in order to proceed and then get a response from it.
#RestController
public class Controller1{
#PostMapping("/endpoint1")
public ReponseEntity<?> doWhatController1HasToDo(#RequestBody Object request){
//some processing
//here i would like to call second controller
}
}
#RestController
public class Controller2{
#PostMapping("/endpoint2")
public ReponseEntity<?> doWhatController2HasToDo(#RequestBody Object request){
//some processing
return new ResponseEntity<>(JSON, HttpStatus.OK);
}
}
I've tried using RestTemplate, but do get always 405 error. I've read somewhere, that it's because of the multipart
private ResponseEntity forwardUsingRestTemplate(HttpServletRequest httpServletRequest, Object object) throws MalformedURLException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity req = new HttpEntity(object, headers);
RestTemplate template = new RestTemplate();
ResponseEntity<TdmResponse> response = template.exchange(getBaseUrl(httpServletRequest) + "/endpoint2", HttpMethod.POST, req, TdmResponse.class);
}
The question is, how do i call second endpoint?
Why would you need to call another endpoint? Are these controllers in separate applications?
If not it's way more efficient to use a service on top of those 2 controllers:
public class Service {
public Object processController1(Object object) {
//some processing
return processController2(result of some processing);
}
public Object processController2(Object object) {
// processing
}
}
And then in your controllers use these 2 methods:
public class Controller1{
private Service service;
public ReponseEntity<?> doWhatController1HasToDo(#RequestBody Object request){
return new ResponseEntity<>(service.processController1(request), OK);
}
}
public class Controller2{
private Service service;
public ReponseEntity<?> doWhatController1HasToDo(#RequestBody Object request){
return new ResponseEntity<>(service.processController2(resultProcess1), OK);
}
}
If there are 2 different applications then the problem might be your CSRF settings. If you have CSRF enabled in the second application then it will reject your call because you are not passing the CSRF token with RestTemplate.
Later Edit:
You can use the facade pattern to add another layer of abstraction between Controller and Services:
public class Facade{
private Service1 service1;
private Service2 service2;
public ReponseEntity<?> doWhatController1HasToDo(#RequestBody Object request){
Object resultService1 = service1.process(request);
Object resultService2 = service2.process(resultService1);
return new ResponseEntity<>(resultService2, OK);
}
public ReponseEntity<?> doWhatController2HasToDo(#RequestBody Object request){
Object resultService2 = service2.process(request);
return new ResponseEntity<>(resultService2, OK);
}
}
You could also inject the second controller into the first controller and just call the method directly. This works if you don't want to change the endpoints dynamically.
Your controller is annotated with #RestController which means whatever is returned from controller methods are interpreted in the form of json or xml. In your case, if you return anything from doWhatController1HasToDo of Controller1, it will process this as json or xml. You should do this in following ways. It might help you.
#Controller
public class Controller1{
#PostMapping("/endpoint1")
public String doWhatController1HasToDo(#RequestBody Object request){
//some processing
return "redirect:/doWhatController2HasToDo";
} }
I am assuming, both your Controller are in same folder (i.e Controller1 and Controller2). This will call method doWhatController2HasToDo(#RequestBody Object request) of Controller2, and you can do anything into this method, like
#RestController
public class Controller2{
#PostMapping("/endpoint2")
public ReponseEntity<?> doWhatController2HasToDo(#RequestBody Object request){
//some processing
return new ResponseEntity<>(JSON, HttpStatus.OK);
}
}

Swagger Controller Mappings

With Swagger-UI I am trying to display methods that have the same base URL underneath one Swagger container regardless of which controller they fall under. Imagine this code.
// Current Swagger Config Class
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(getPaths())
.build()
.apiInfo(getApiInfo());
}
private Predicate<String> getPaths(){
return or(regex("/attributes.*"),
regex("/other.*"));
}
// Controller 1
#RestController
#RequestMapping("/attributes")
#Api(value = "User Attribute Services", description = "User Attributes API")
public class UserAttributesController {
#Autowired
private UserAttributesService userService;
#Autowired
private UserAttributeValidator userAttributeValidator;
#RequestMapping(value = "/user/search", method = RequestMethod.POST, produces = "application/json")
public List<User> searchUser(#RequestBody List<UserDTO> userDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/user/insert", method = RequestMethod.POST)
public boolean insertUser(#RequestBody List<UserDTO> userDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/user/update{id}", method = RequestMethod.PATCH)
public boolean updateUser(#PathVariable id, #RequestBody UserDTO updateDetails){
// meaningless implementation ...
}
#RequestMapping(value = "/user/delete/{id}", method = RequestMethod.DELETE)
public boolean deleteUser(#PathVariable id){
// meaningless implementation ...
}
}
// Controller 2
#RestController
#RequestMapping("/attributes")
#Api(value = "Tech Attribute Services", description = "Tech Attributes API")
public class TechAttributesController {
#Autowired
private TechAttributesService techService;
#Autowired
private TechAttributeValidator techAttributeValidator;
#RequestMapping(value = "/tech/search", method = RequestMethod.POST, produces = "application/json")
public List<Tech> searchTech(#RequestBody List<TechDTO> techDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/insert", method = RequestMethod.POST)
public boolean insertTech(#RequestBody List<TechDTO> techDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/update{id}", method = RequestMethod.PATCH)
public boolean updateTech(#PathVariable id, #RequestBody TechDTO updateDetails){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/delete/{id}", method = RequestMethod.DELETE)
public boolean deleteTech(#PathVariable id){
// meaningless implementation ...
}
}
My application swagger page is displaying the attributes underneath seperate containers. Eventhough I specified the regex path of '/attributes' in my Swagger Configuration the fact that these are in different controllers is overriding what I want. If I put all these methods into 1 controller I get the desired result, but this is only a subset of the attribute classes I have and I do not want a massive controller that spans over 1k lines.
Extra: If you noticed the services are almost identical for the attributes. I have been trying to find a clever way of declaring them once and simply overriding the method declations instead of redefining similiar services over again, but because the #RequestMapping annotation needs to be a constant value I could not find a way to change the annotation depending on the implementation rather than the declaration.
Swagger-UI Picture

Spring REST service to consume and produce both HTML form POST and AJAX/JSON in a single method

I'm trying to teach myself Spring by creating a very simple web application. I have a class to create "Note" objects:
#Controller
#RequestMapping(value = "/notes")
public class NoteRestController {
#Autowired
private MappingJackson2JsonView jsonView;
[...]
#ResponseStatus(HttpStatus.CREATED)
#RequestMapping(method = RequestMethod.POST, consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE })
public ModelAndView create(final Model model,
#Valid #ModelAttribute final Note note, final BindingResult result) {
ModelAndView mav;
// how can I test the request source?
if (<requesting from HTML FORM>) {
// return jsonView
mav = new ModelAndView(jsonView);
} else {
// return JSP view
mav = new ModelAndView("note", "model", model);
}
model.addAttribute("note", note);
if (result.hasErrors()) {
model.addAttribute("errors", result.getAllErrors());
// on error, redirect back to note page with form
// return new ModelAndView("note/note", "model", model);
return mav;
}
note.setId(daoService.createNote(note));
return mav;
}
}
I would like to be able to use a single method (like the above) to handle requests from both an AJAX post AND a HTML form post. If triggered by AJAX I would like to return JSON (with validation errors if present), and if it is triggered by a HTML form, I would like to return to the JSP using the form taglib
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
and show validation errors next to input fields using e.g.
<form:errors path="title" cssClass="errorMessage"></form:errors>
Is this possible, or should I be creating two controllers; one for the REST/JSON, and one for HTML/form? Maybe there is something I can pass into the method that can be interrogated to determibne the request source, but I can't see it right now.
What would be the "best practice" in this case?
EDIT 1:
Trying answer from #ring-bearer first as it allows for the same URL pattern, but having issues.
Using methods:
// used to handle JSON/XML
#RequestMapping(method = RequestMethod.POST, produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public #ResponseBody Note create(
#Valid #ModelAttribute final Note note, final BindingResult result) {
[...]
}
// used to handle form view
#RequestMapping(method = RequestMethod.POST)
public ModelAndView createForView(final Model model,
#Valid #ModelAttribute final Note note, final BindingResult result) {
[...]
}
Interestingly, the HTML form submission, still gets handled by create() and not createForView(). After looking at the form submission request headers, I see that this Accept header:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
By adding produces = "text/html" to #RequestMapping on createForView(), all 3 scenarios work (form, AJAX/JSON, AJAX/XML).
Is this normal, or am I still missing something?
This can be achieved using "content negotiation". Spring MVC needs to be enabled for content negotiation using a "contentNegotiationManager" definition. It can be set up using Java or XML configuration. The configuration will centrally manage media type mappings(json, xml etc). Once that is set up, a controller class can be built to cater to both JSON and View(HTML). Below is a generic example(uncompiled), it should be easy to refactor your class to similar structure to avoid violation of DRY.
#Controller
class ReportController{
//1- Method for JSON/marshalling types(XML)
#RequestMapping(value="/report", produces={"application/xml", "application/json"})
#ResponseStatus(HttpStatus.OK)
public #ResponseBody List<ReportPara> generateReport(Principal principal) {
return reportService.generateReport(principal);
}
//2- For View technologies ( Eg:JSP HTML)
#RequestMapping("/report")
public String generateReportForView(Model model, Principal principal) {
model.addAttribute( generateReport(principal) );
// Return the view to use for rendering the response
return ¨reports/main¨;
}
}
Which of the two #RequestMapping methods will execute? It is determined by content negotiation definition. Eg: URLs such as report.xml or report.json map to the first method, any other URLs ending in report.anything map to the second.
The following will be easier to maintain:
#Controller
class NoteController {
#Autowired NoteService service;
#RequestMapping(method = RequestMethod.POST, value = "/note")
public ModelAndView createFromForm(#ModelAttribute #Valid Note note, BindingResult result) {
return new ModelAndView("note", create(note));
}
#RequestMapping(method = RequestMethod.POST, value = "/api/note")
#ResponseBody
public Note createFromApi(#RequestBody Note note) {
return create(note);
}
private Note create(Note note) {
return service.create(note);
}
}

Spring #SessionAttributes returns null

Im trying to use #SessionAttributes in my controller class as follows:
#Controller
#SessionAttributes({"calMethods", "taxList"})
#RequestMapping("/secure")
public class reportController extends BaseController {
//..
#ModelAttribute("taxList")
public List<multiCalDto> getTaxList() {
return new ArrayList<multiCalDto>();
}
//....
#RequestMapping(value = "/confirmCal.html", method = RequestMethod.GET)
public ModelAndView launchconfirmCal(HttpServletRequest request, #RequestParam("seqNo") String seqNo) {
...........
ModelAndView modelAndView = new ModelAndView("confirmCalView");
modelAndView.addObject("taxList", calBean.getTaxList());
return modelAndView;
}
#RequestMapping(value = "/executeCalPay.html", method = RequestMethod.POST)
public ModelAndView executeCalPay(HttpServletRequest request, #ModelAttribute("taxList") List<multiCalDto> taxList) {
// ............
ModelAndView modelAndView = new ModelAndView("calView");
System.out.println("taxList -- "+taxList);
return modelAndView;
}
}
I added taxList in launchconfirmCal() and trying to access the same in executeCalPay().
I tried to print taxList before adding to modelAttribute and the size is 12 and when I retireve in executeCalPay() it shows null.
I am not changing its value in JSP.
Remove or comment out this method and retry
#ModelAttribute("taxList")
public List<multiCalDto> getTaxList() {
return new ArrayList<multiCalDto>();
}
#ModelAttribute annotated methods are called before ALL request mapping methods, so it resets your taxList before the post method is called

Spring MVC 3.0 Rest problem

I'm trying out Spring MVC 3.0 for the first time and like to make it RESTfull.
This is my controller:
#Controller
#RequestMapping(value = "/product")
#SessionAttributes("product")
public class ProductController {
#Autowired
private ProductService productService;
public void setProductValidator(ProductValidator productValidator, ProductService productService) {
this.productService = productService;
}
#RequestMapping(method = RequestMethod.GET)
public Product create() {
//model.addAttribute(new Product());
return new Product();
}
#RequestMapping(method = RequestMethod.POST)
public String create(#Valid Product product, BindingResult result) {
if (result.hasErrors()) {
return "product/create";
}
productService.add(product);
return "redirect:/product/show/" + product.getId();
}
#RequestMapping(value = "/show/{id}", method = RequestMethod.GET)
public Product show(#PathVariable int id) {
Product product = productService.getProductWithID(id);
if (product == null) {
//throw new ResourceNotFoundException(id);
}
return product;
}
#RequestMapping(method = RequestMethod.GET)
public List<Product> list()
{
return productService.getProducts();
}
}
I have 2 questions about this.
I'm a believer in Convention over Configuration and therefor my views are in jsp/product/ folder and are called create.jsp , list.jsp and show.jsp this works relatively well until I add the #PathVariable attribute. When I hit root/product/show/1 I get the following error:
../jsp/product/show/1.jsp" not found how do I tell this method to use the show.jsp view ?
If I don't add the RequestMapping on class level my show method will be mapped to root/show instead of root/owner/show how do I solve this ? I'd like to avoid using the class level RequestMapping.
add your 'product' to Model and return a String /product/show instead of Product. In your show.jsp, you can access the product object form pageContext
Check out the section in the manual about "Supported handler method arguments and return types".
Basically, when your #RequestMapping method returns just an object, then Spring uses this as a single model attribute, and, I'm guessing, attempts to use the request URL as the basis for the view name.
The easiest way to return the view and data you want from the same method is probably to just have the method return a ModelAndView, so you can explicitly specify the viewName and the model data.

Categories