I am working on a Spring-MVC application in which there are 2 parts. It is basically a note taking application with 2 modes, one is groupMode and other is personalMode. Now they both have their different dao's, serviceImpl in the backend, but they are in one controller view.
I have a boolean value to set, to know which mode is the user currently in, to perform CRUD operations in the specific database table. Now As both group modes are personal modes are in the same view, I have to make my methods so they can accept objects from either of the mode. Does spring support accepting only one object even if I declare 2 in model attribute. Here is the example of what I want to achieve :
#RequestMapping(value = "/section/add", method = RequestMethod.POST)
public
#ResponseBody
boolean addSection(#ModelAttribute("section") Section section, #ModelAttribute("groupsection") GroupSection groupSection,Model model) {
if(boolean == true){
this.groupSectionService.addGroupSection(groupSection);
model.addAttribute("groupsection", new GroupSection());
} else{
this.sectionService.addSection(section);
model.addAttribute("section", new Section());
}
return true;
}
Is this possible, I will always be sending one object from the front-end. Thanks a lot. Any pointers or suggestions are welcome.
Whenever there is such a if-statement that "split" the complete controller method, like yours, I have the feeling that one controller method should been replaced by two methods, one for each case.
The easiest, and most straight forward solution would been using two different URLs.
But maybe you have some reason for using the same URL, then I would have two different controller methods with the same URL but a different params Attribute in #RequestMapping
#RequestMapping(value = "/section/add",
method = RequestMethod.POST
params="createGroupSection=false")
#ResponseBody
public boolean addSection(#ModelAttribute("section") Section section) {...}
#RequestMapping(value = "/section/add",
method = RequestMethod.POST
params="createGroupSection=true")
#ResponseBody
public boolean addGroupSection(#ModelAttribute("section") Section section) {...}
Related
I have the following two API-methods:
#PatchMapping("/{id}")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<Project> updateProjectInactivity(#PathVariable long id, #RequestBody InactivityDTO inactivityDTO)
throws ProjectNotFoundException {
return projectService.updateProjectInactivity(id, inactivityDTO);
}
#PatchMapping("/{id}")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<Project> updateProjectStatus(#PathVariable long id, #RequestBody StatusDTO statusDTO)
throws ProjectNotFoundException {
return projectService.updateProjectStatus(id, statusDTO);
}
The two methods have a different #RequestBody, but im currently getting an error because both of them have the same mapping.
Is there a way to have the same mapping for different methods with different RequestBodies? If not, whats the best workaround solution to achieve what i want? I could think of giving them a different #RequestParameter, but that would be ugly, because i dont need that parameter. It would be only used to achieve different mapping.
It is because of the #PatchMapping("/{id}") in both methods.
You can have the same url with different request mappings. like below
#DeleteMapping("/{id}")
#PatchMapping("/{id}")
You should start using different request path since both the methods are responsible for different actions or changing different sub domain. (As per rest convention)
for editing the status
#PostMapping({"{id}/status"}) - It represents that you are editing the status of an Object.
similarly, you should use different request path for inactivity
This is my first try at writting service from scratch. I'm using RestController and Java Spring to create a service which generates pdf based on parameters which are passed when calling the service. Service is called with one parameter, but can be called with two different variables (one is registry number and the other is identificator) and depending on which one of those two is passed, service generates the same JSON but different service is called in background of my program (one call works with IDN and one works with regNum).
So far I have this:
#RequestMapping(value = "/generatePdf/{idn}", method = RequestMethod.GET, produces = "application/pdf")
public String generatePdf(#PathVariable String idn) {
//logic
}
My question is this. What is the best solution for this problem?
Do I make separate method with different name and mapping?
Should I create a flag which checks which type od data is sent? Or, something third, feel free to suggest.
I would recommend you to create separate method instead of adding the additional flag:
API will be more readable and understandable, eg: GET /pdfByIdn/{idn} and GET /pdfByRN/{rn}
Easy to add additional cases, without modification of existing methods
Its make more sense to use separate service classes to different approaches to generate PDF's
#RequestMapping(value = "/generatePdf/{idn}/{rgn}", method =
RequestMethod.GET,
produces = "application/pdf")
public String generatePdf(#PathVariable(required = false) String idn,
#PathVariable(required = false) String rgn)
{
if(idn.equals(null){
//logic
}else {
//logic
}
}
I have some legacy controller which puts some amount of data into the Model object (needed for Thymeleaf template).
Now I have to return the same data as JSON in REST service.
For these purposes I have wrapped the block of data preparation into separate method to use in two places: in the old method which is used for thymeleaf template and in the new one:
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model) {
prepareIndexModel(model);
return "index";
}
#RequestMapping(value = "/index/model", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public Map<String, Object> indexModel(Model model) {
prepareIndexModel(model);
return model.asMap();
}
private void prepareIndexModel(Model model) {
model.addAttribute("prop1", ...);
...
}
However, when I try access via GET /index/model I receive the following error:
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "/WEB-INF/templates/index/model.html")
so it simply considers my method not as REST method. I guess, that it is because method is actually returns instance of ExtendedModelMap class which implements both interfaces: Model and Map.
Therefore, after chaning /index/model method to:
#RequestMapping(value = "/index/model", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public Map<String, Object> indexModel(Model model) {
prepareIndexModel(model);
return new LinkedHashMap<>(model.asMap());
}
everything started working expected: GET /index/model returns me desired JSON. So basically I just wrapped the model into LinkedHashMap.
My question is: is this a specified behaviour or simply a bug? I expected that by annotating a method with #ResponseBody annotation, Spring should ignore the fact that returning object is instance of Model. Shouldn't it?
UPDATE: #Sotirios Delimanolis provided the link to the very similar question, which was about version 3.2. But my question is not about why, but is it a bug of Spring or is it specified some where in the documentation?
UPDATE-2: Please, also note, that in the linked question a method has Model returning type, and its behaviour is described. In my case I have Map<...> returning type, what in my opinion makes this behaviour strange and inconsistent!
is this a specified behaviour or simply a bug?
I would say it's undefined behavior.
The Spring Framework Reference defines that a return type of Model means a View with a Model. It also defines that a return type of Map means a View with the Map as the Model. These are both well-defined.
It also specifies that for a method annotated with #ResponseBody, the returned value is written to the response HTTP body. Again, this is well-defined.
What it doesn't specify, is what happens when a #ResponseBody returns a Model or a Map.
It is very common to return a Map to be encoded as JSON, and that works correctly, i.e. #ResponseBody takes precedence over return type Map. This makes sense.
However, a Model is specifically for the purpose of model attributes for a View, so the fact that return type Model takes precedence over #ResponseBody makes some sense. But as I said, it's not specified behavior, but neither is it a bug. It is undefined.
If you asked me for what it should do, I'd leave it undefined, since Model as #ResponseBody doesn't make any sense to me.
Also, the documentation doesn't make the distinction between declared return type and actual return type, so it is undefined which is used.
The implementation uses the actual return type, which has the advantage that your handler method can return Object and then return e.g. a ModelAndView or an HttpEntity, depending on conditions. This flexibility makes sense, but it isn't explicitly defined, as far as I can see.
So, result of the combination of #ResponseBody, declared return type of Map, but actual return type of Model, is undefined.
If you ask me (and you kind of did), I'd say that your code is bugged. If you want to return a Map to be sent as the response body, why not just create the Map yourself. Asking the Spring framework for a view Model makes no sense at all to me.
Even reading the code, I'd be unsure what you actually intended, given the mixed signals of using a Model and specifying #ResponseBody.
Conclusion: Don't be lazy, and create the Map yourself.
My question is about the capabilities of Spring MVC.
#RequestMapping("SOME_VALUE")
RETURN_TYPE NAME_OF_FUNCTION (PARAMS){
STATEMENTS;
}
Is there a way where RETURN_TYPE is dependent on the statements?
For example: When a user clicks on a link, if a certain condition is true, I want the user to download a file (i.e. return a byte[]) but if it's false, I want to show the user a webpage with possible options (i.e. a ModelAndView object).
Now I understand that it can be simply implemented using javascript within the page but coming back to my originial question, is there a way in Spring MVC which does that for us with better handling?
Spring 3.2+ (I'm not sure about previous versions) works as follows. The handler method, #RequestMapping annotated, is invoked through reflection regardless of its return type. Spring receives its return value and dispatches a registered HandlerMethodReturnValueHandler to handle the return value if it supports it.
There are many types of HandlerMethodReturnValueHandler, for #ResponseBody, for ResponseEntity, for String, for ModelAndView, etc. You can see most of them in the javadoc.
So one, ugly way, to do this would be to define your method as
RequestMapping("SOME_VALUE")
public Object NAME_OF_FUNCTION (PARAMS){
if (something) {
return "a string";
} else if (somethingElse) {
return new ResponseEntity(new byte[] {1,2,3});
} else
return new OtherType();
}
Spring will use the first HandlerMethodReturnValueHandler for which supportsReturnType returns true.
In my opinion, you shouldn't do this. As much as possible, you should make the condition be external, ie come from the request. This way, you could map the condition in the #RequestMapping and have multiple #RequestMapping methods, one for each possible condition.
Instead of returning Object you can simply return String and set it to name of your JSP file (i.e. "myjsppage") when you want to return a page, or set it to redirect (i.e. "redirect:/mydownloadablefile") when you want to start downloading. For latter case you will also need to add another controller method with request mapping for "/mydownloadablefile" URL.
I'm working on converting a legacy project to Spring (trying to adjust little as possible for now) and I'm running into a small issue with mapping/translating legacy parameters to a model attribute object. I may be completely wrong in thinking about this problem but it appears to me that to translate a parameter to a specific model attribute setter is to pass in the request parameter through a method for creating a model attribute and manually call the correct setter:
#ModelAttribute("form")
public MyForm createMyForm(#RequestParameter("legacy-param") legacy) {
MyForm myForm = new MyForm();
myForm.setNewParam(legacy);
return myForm;
}
I don't necessarily want to change the request parameter name yet since some javascript and JSPs are depending on it being named that way but is there any way to do something like this? Or is there a different way to map/translate request parameters to model attributes?
public class MyForm {
#ParameterName("legacy-param")
private String newParam;
public void setNewParam(String value) { ... }
public String getNewParam() { ... }
}
#Controller
public class MyController {
#RequestMapping("/a/url")
public String myMethod(#ModelAttribute("form") MyForm myForm, BindingResult result) { ... }
}
The way you've written that model attribute method is indeed odd. I'm not entirely clear what you're actually trying to do.Assuming there are many parameters, you're going to end up with an awful lot of instances of MyForm in your ModelMap. A more 'normal' way to create model attribute would be like this:
#ModelAttribute("legacyParamNotCamel")
public MyForm createMyForm(#RequestParameter("legacy-param-not-camel") String legacy) {
return legacy;
}
Then in the JSP you can refer to it directly in expression language. e.g.,
<c:out value="${legacyParamNotCamel}"/>
If you want to put them onto a form backing object, you need to do it all in a single method that creates the object, not make new copies of it in each method. (assuming your form has more than a single parameter associated with it.)
--
It seems like what you're really trying to do though is translate the parameter names in the request before the web data binder gets ahold of it, so that you can bind oddly named parameters onto a java bean? For that you'll need to use an interceptor that translates the names before the binding process begins, or make your own subclass of the databinder than can take a property name translation map.
You placed the #ModelAttribute at the Method Level but the intention seems to be more of a formBackingObject hence we should be dealing at the Method Parameter Level
There's a difference.
I put up an explanation here on my blog along examples at Spring 3 MVC: Using #ModelAttribute in Your JSPs at http://krams915.blogspot.com/2010/12/spring-3-mvc-using-modelattribute-in.html