Spring MVC: Request Scope, trying to update a Command Object with binder.setDisallowedFields - spring-mvc

I have this Object
public class Deportista implements Serializable {
private static final long serialVersionUID = 6229604242306465153L;
private String id;
...
#NotNull(message="{field.null}")
public String getId() {
return id;
}
...
}
I have the following Controller's methods
#InitBinder(value="deportistaRegistrar")
public void registrarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> registrarInitBinder >>>>>>>>>>>>>");
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.GET)
public String crearRegistrarFormulario(Model model){
logger.info("crearRegistrarFormulario GET");
Deportista deportista = new Deportista();
model.addAttribute("deportistaRegistrar", deportista);
return "deportista.formulario.registro";
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.POST)
public String registrarPerson(#Validated #ModelAttribute("deportistaRegistrar") Deportista deportista,
BindingResult result){
logger.info("registrarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.registro";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.insertDeportista(deportista);
return "redirect:/manolo.htm";
}
Until here the Controller is able to create a form (GET) and submit (POST) a new command object, Validation code works well.
The problem is with the update.
I have the following:
#InitBinder(value="deportistaActualizar")
public void actualizarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> actualizarInitBinder >>>>>>>>>>>>>");
binder.setDisallowedFields("id");
}
Observe I have binder.setDisallowedFields("id")
public String crearActualizarFormulario(#PathVariable("id") String id, Model model){
logger.info("crearActualizarFormulario GET");
Deportista deportista = this.fakeMultipleRepository.findDeportista(id);
model.addAttribute("deportistaActualizar", deportista);
return "deportista.formulario.actualizacion";
}
#RequestMapping(value="/{id}/actualizar.htm", method=RequestMethod.POST)
public String actualizarPerson(#Validated #ModelAttribute("deportistaActualizar") Deportista deportista,
BindingResult result){
logger.info("actualizarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.actualizacion";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.updateDeportista(deportista);
return "redirect:/manolo.htm";
}
The problem is:
when the form or command has any error, the controller re-render the view and the form appear showing the error messages how is expected, but without the ID value
or
if I try to update the object, of course keeping the id value, and without any error to simply proceed to update, it fails
The following appears in the Console:
- -------- createCollections ---------------
- >>>>>>>> actualizarInitBinder >>>>>>>>>>>>>
- Skipping URI variable 'id' since the request contains a bind value with the same name.
- actualizarPerson POST
- Deportista [id=null, nombre=Manuel, ...]
- There are errors!!!!
- Error Field error in object 'deportistaActualizar' on field 'id': rejected value [null]; codes [NotNull.deportistaActualizar.id,NotNull.id,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [deportistaActualizar.id,id]; arguments []; default message [id]]; default message [The field must be not empty]
The id is null. How I can around this problem keeping the Request Scope?
I have an alternate controller which is working with #SessionAttributes and all works perfect. But since is a huge risk if the user has many tabs open in the same web browser, one for create and other for updating, all is going to be very wrong. According with Spring MVC + Session attributes and multiple tabs, request scope should be used instead of session scope. It has sense.
Sadly seems Spring is not going to fix this:
#SessionAttributes doesn't work with tabbed browsing
Addition
According with your suggestion, I have the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#RequestParam(defaultValue="") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Observe the method uses #RequestParam, my problem is how update that method to work when the URL to update has the following style
http://localhost:8080/spring-utility/deportista/1/actualizar.htm. There is no param in the URL, therefore #RequestParam is useless now.
I already have read the Spring Reference documentation:
Using #ModelAttribute on a method
Second Addition
Yes, you was right, and I did that yesterday, but I forget to share the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#PathVariable(value="id") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Since a #ModelAttribute is called always before by any handler method, the following URL fails http://localhost:8080/spring-utility/deportista/registrar.htm, the following appears on the page
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect.
Of course because the URL does not contains the expected id. Therefore I can't create new records to later edit/see.
I can confirm, that for the following work:
http://localhost:8080/spring-utility/deportista/1/detalle.htm
http://localhost:8080/spring-utility/deportista/1/actualizar.htm
the id (1) is retrieved.
How I could resolve this?
Thank You

Related

How to accept GET argument in Spring boot Controller and return appropriate object

I am very new to spring boot and I can't figure out the #Controller class. What should I pass if I can't find a particular object in my DB in spring boot? Is it better if I declare my return type as Response Entity and send a null User object?
//Get single user
#GetMapping("/users/{id}")
public User getUser(#PathVariable String id){
try {
Long i = Long.parseLong(id);
} catch (NumberFormatException ex) {
return ???? //Can't figure out what to return here.
}
return userService.getUser(id);
}
I want the consumer to know that they've sent an invalid string.
2) Also, User's variable id is of Long type. So should I take the argument as Long in the getUser function or take a String and parse it? Taking a Long would crash my server if a string was sent in the link.
This is my typical code of a REST controller for 'get user by id' request:
#RestController
#RequestMapping("/users") // 1
public class UserController {
private final UserRepo userRepo;
public UserController(UserRepo userRepo) {
this.userRepo = userRepo;
}
#GetMapping("/{id}") // 2
public ResponseEntity getById(#PathVariable("id") Long id) { // 3
return userRepo.findById(id) // 4
.map(UserResource::new) // 5
.map(ResponseEntity::ok) // 6
.orElse(ResponseEntity.notFound().build()); // 7
}
}
Where:
1 - is a common starting path for all requests handled by this controller
2 - a path variable pattern of GET request (/users/{id}).
3 - provide the name of path variable which name is correspond to the parameter in GetMapping. The type of the parameter in getById method is corresponds to the type of User ID.
4 - I use findById method of my UserRepo that return Optional
5 - Here I transform User to some type of DTO - UserResource (it's optional step)
6 - return OK response if User was found
7 - or return Not Found response otherwise.
I'm also using the controller-service-repository pattern in a couple of projects, and this is how I lay it out:
Controller.java
#RestController // 1
#RequestMapping(value = "/users") // 2
public class UserController {
private final UserService userService;
#Autowired // 3
public UserController(UserService userService) {
this.userService = userService;
}
#RequestMapping(value = "/{user_id}", method = RequestMethod.GET) //4
#ResponseStatus(HttpStatus.OK) //5
public UserModel getUser(#PathVariable(value="user_id") long user_id) { //6
return userService.getUserById(user_id);
}
#RequestMapping(method = RequestMethod.POST) // 7
#ResponseStatus(HttpStatus.CREATED) // 8
public UserModel getUser(#ResponseBody UserModel userModel) { // 9
return userService.createUser(usermodel);
}
}
1) #RestController is a combination of #Controller and #ResponseBody, which essentially means every method in your class will have a response body.
2) Prefix #RequestMapping values in this class with /users
3) Autowiring in the Constructor is the safest approach to injecting beans.
4) This method will be accessible via a GET request to /users/{user_id}
5) This method will return HttpStatus.OK status code on success (200)
6) Extracts the path variable "user_id" from the Request. Use the same numeric type as your user id's here (i.e. int or long).
7) This method will be accessible via a POST request to /users
8) This method will return HttpStatus.CREATED status code on success (201)
9) Extracts a UserModel from the request body (should have the same structure as the json given later).
There are no real differences to Cepr0 and my approach, it's purely a style preference.
UserModel can be a class like this:
UserModel.java
public class UserModel {
private String username;
// Constructor, Getter, Setter...
}
And this will return a JSON object in the body of the response like this:
{
"username":"username"
}
If you would like to handle Exceptions within your controller (and even control the data returned by an exception, you can use #ExceptionHandler like so:
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponseWrapper> handleGenericException(Exception ex){
return ResponseEntity
.status(HttpStatus.I_AM_A_TEAPOT)
.body(new CustomExceptionWrapper(ex)));
}
Where CustomExceptionHandler converts exceptions thrown by your application into a format you decide (This can also be a POJO and Spring Boot will transform it into JSON for you!)
To answer your questions more specifically:
1) You should throw an exception if the user isn't found that will include the response status 404 (NOT FOUND). Returning null is typically a bad idea as it could mean a lot of things.
1.1?) If your user sends an invalid string, you can look up which exception it causes in your server, and use the exception handler to deal with it and return an appropriate response (BAD_REQUEST maybe?)
2) Yes use long if your use id's are longs.
Check out the baeldung site, would really recommend them for learning Spring Boot.

How to pass a generic collection Class object as an argument

I've RESTful service Spring MVC based.
The service has a RESTful resource method that returns the following response:
public class OperationalDataResponse<T> {
private String status;
private String statusMessage;
private T result;
//getters and setters
}
This response object encapsulates the result object of type T.
On the client side I use RestTemplate with GsonHttpMessageConverter added.
I get the response from service as a ResponseEntity
I handle the generic response with runtime Type as below:
public class OperationalDataRestClient<REQ,RESULT_TYPE> {
public OperationalDataResponse<RESULT_TYPE> getOperationalData(String resourcePath, Map<String, Object> urlVariables, Class<RESULT_TYPE> resultType) {
//code to invoke service and get data goes here
String responseString = responseEntity.getBody();
response = GsonHelper.getInstance().fromJson(responseString, getType(OperationalDataResponse.class, resultType));
}
Type getType(final Class<?> rawClass, final Class<?> parameter) {
return new ParameterizedType() {
#Override
public Type[] getActualTypeArguments() {
return new Type[] { parameter };
}
#Override
public Type getRawType() {
return rawClass;
}
#Override
public Type getOwnerType() {
return null;
}
};
}
}
This works like a charm as long as my resultType is a non-collection class.
So, this works great from caller code:
getOperationalData(someResourcePath, someUrlVariables, MyNonGenericClass.class)
However if my resultType is a collection (say, List<String> or List<MyNonGenericClass>)
then I don't know how to pass the resultType Class from the caller code.
For example, from caller code,
getOperationalData(someResourcePath, someUrlVariables, List.class)
or
getOperationalData(someResourcePath, someUrlVariables, List<MyNonGenericClass>.class)
throws compilation error.
I tried passing on ArrayList.class as well but that too doesn't work.
Any suggestion how can I pass a generic collection as a resultType from caller code (in other words, as an example, how can I pass the class object of a List<String> or List<MyNonGenericClass> from caller code ?)
If you know that ResultType is coming as a List, Then it will obvious fail like you said compilation issue.Why? because you are trying to send a List when you method only accepts a single value.In order to over come that issue you will have to change the method arguments to the following
public OperationalDataResponse<RESULT_TYPE> getOperationalData(String resourcePath, Map<String, Object> urlVariables, List<Class<RESULT_TYPE>> resultType){
....
}
and you will have to make some slight modification to getType() Method,loop it and then pass each class value to getType method like so
for(MyNonGenericClass myClass:mylist){
getType(OperationalDataResponse.class, myClass.getClass());
}

spring MVC ambiguous mapping error

I am new to Spring MVC, I have a controller displaying a single user detail or
all the users as a list
// without passing any parameter the app should display all users
#RequestMapping("/user")
public String listUsers(Model m) {
List<User> users = userService.getUsers();
m.addAttribute(users);
return "userlist";
}
//same url but with userId parameter, the app displays a single user detail
#RequestMapping("/user")
public String userDetail(#RequestParam("userId") String userId, Model m)
throws IOException {
User user = userService.getUserById(userId);
m.addAttribute(user);
return "user_detail";
}
in fact I got a error "spring ambiguous mapping", my mapping syntax is definitely wrong, my question is my desired functionality can be achieved
in Spring or not.
I would say it would be correct to specify list request as
#RequestMapping("/user")
and specific user request as
#RequestMapping("/user/{userId}")
by passing userId as path parameter.
You can correct second method like
#RequestMapping(value={"/user/{userId}"})
public String userDetail(#PathVariable String userId, Model m)
throws IOException {
User user = userService.getUserById(userId);
m.addAttribute(user);
return "user_detail";
}

ValueProvider never being called

I'm working with MVC 4 Web API and I have this dummy ValueProvider:
DummyValueProvider.cs
class DummyValueProvider : IValueProvider
{
public DummyValueProvider()
{
}
public bool ContainsPrefix(string prefix)
{
return true;
}
public ValueProviderResult GetValue(string key)
{
return new ValueProviderResult("testing", "testing", System.Globalization.CultureInfo.InvariantCulture);
}
}
class DummyValueProviderFactory : System.Web.Http.ValueProviders.ValueProviderFactory
{
public override IValueProvider GetValueProvider(System.Web.Http.Controllers.HttpActionContext actionContext)
{
return new DummyValueProvider();
}
}
This ValueProvider should return true for any key asked, so it will always supply a value to the model binder when it needs. The ValueProvider is registered in the WebApiConfig like this:
WebApiConfig.cs
config.Services.Add(typeof(ValueProviderFactory), new DummyValueProviderFactory());
The code compiles and runs fine.
I also have this action in the Account API controller:
AccountController.cs
public HttpResponseMessage Register(string foo) { ... }
The action gets called fine when I call it like below:
/register?foo=bar
And foo is filled with bar as expected; but if I call:
/register
The server returns 404 with the message No HTTP resource was found that matches the request URI 'http://localhost:14459/register'.
Also, I put breakpoints inside methods ContainsPrefix() and GetValue(), but they never get triggered.
What am I doing wrong? Shouldn't DummyValueProvider be providing the value testing to parameter foo?
Try this
public HttpResponseMessage Get([ValueProvider(typeof(DummyValueProviderFactory))] string foo) {... }
I higly suggest you to read this recent article to customize Web Api Binding.
Update:
After reading the article the OP was able to discover the solution. It was that using the parameter attribute [ModelBinder] was required for it to work. This was because unless the parameter is annotated, [FromUri] is assumed. Once annotated with [ModelBinder] the registered handlers are executed.

Recommended way to display an error message without resorting to #ModelAttribute with Spring MVC

I have the following method skeleton in a Spring MVC application:
#RequestMapping(value = "/activateMember/{token}", method = RequestMethod.GET, produces = "text/html")
public String activateMember(#PathVariable("token") String token) {
...
}
I am trying to display an error message if the token is invalid for some reason. However I have no ModelAttribute in the method arguments and I don't really want one. But of course I can't use an Errors or BindingResults argument because of the absence of a ModelAttribute and its corresponding form.
So my question is:
what is the recommended way to display an error message given the above method signature and without introducing a ModelAttribute?
If the String you've returned from the method is a viewname (Spring default) then simply create a view for this case and do like:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
if(checkToken(token)){
doProcess();
return "userprofile";
} else {
return "badtoken"
}
}
In more complicated case you may have a hierarchy of exceptions, related to bad tokens. (Token is expired, token is just incorrect and so on). You can register an #ExceptionHandler in the same controller:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
return activate(token); // This method may throw TokenException and subclasses.
}
#ExceptionHandler(TokenException.class)
public ModelAndView tokenException(TokenException e){
// some code
return new ModelAndView("badtoken", "exception", e);
}

Resources