I have a DTO class with fields that either have javax or custom constraints put on them so everytime I hit this endpoint I am guaranteed to take in data that meets my requirements.
#PostMapping
public ResponseEntity<SomeResource> create(#Valid #RequestBody SomeDTO someDTO)
For reasons I do not want to go into, I am forced to validate this incoming data elsewhere (preferably in a separate service method) so I have tried doing:
#PostMapping
public ResponseEntity<SomeResource> create(#RequestBody SomeDTO someDTO) {
someService.validate(someDTO);
}
where the called method's signature is defined as
validate(#Valid SomeDTO someDTO)
Though I quickly figured out this does not actually do any other argument validation other than user input. With the Spring annotations not being particularly helpful, are there any other annotations out there that can validate an object passed in as a parameter to ensure the constraints are not violated?
Related
Problem
I have the following constraints on userUuid and itemUuid:
Both strings must not be null.
Both strings must be UUIDs (eg. f1aecbba-d454-40fd-83d6-a547ff6ff09e).
The composition (userUuid, itemUuid) must be unique.
I tried to implement the validation in my controller like:
#RestController
#Validated // (1)
public class CartItemController {
#PostMapping("/me/carts/{itemUuid}")
#ResponseStatus(HttpStatus.CREATED)
public void addItem(#PathVariable("itemUuid") String itemUuid,
Authentication auth) {
CartItemId id = getCartItemId(getUserUuidFrom(auth), itemUuid);
...
}
#Unique // (4)
public CartItemId getCartItemId(#NotNull #Uuid String userUuid, // (2)
#NotNull #Uuid String itemUuid) { // (3)
return new CartItemId(userUuid, itemUuid);
}
...
}
#Uuid and #Unique are custom constraints. Method validation is enabled in (1). (2) are the contraints for the user UUID. (3) are the constraints for the item UUID. The unique constraint is applied to the returned CartItemId in (4). However, the parameters and the return value are never validated. Neither for the standard #NotNull constraint nor for my custom constraints. I receive HTTP status 201 Created instead of 400 Bad Request.
What am I doing wrong?
Stuff that works
The following code works for the item UUID:
#RestController
#Validated
public class CartItemController {
#PostMapping("/me/{itemUuid}")
#ResponseStatus(HttpStatus.CREATED)
public void addItem(#PathVariable("itemUuid") #Uuid String itemUuid, // (1)
Authentication auth) {
...
}
}
Adding the #Uuid to the path variable parameter works. Values like anInvalidUuid are rejected. I also tested the #Unique constraint in other use cases and it worked perfectly.
What is the difference between addItem() and toId()?
Versions
I am using Java 1.8 and Spring Boot 2.0.0.RELEASE. org.hibernate.validator:hibernate-validator:6.0.7.Final is on my classpath.
Validation of method arguments is based on AOP: a validating proxy intercepts the method call and validates the argument before delegating (if everything is valid), to the actual method.
You're calling the getCartItemId() method from another method of the same class. So the method call doesn't go through the proxy. Only inter-bean calls can be intercepted.
So, in short, getCartItemId should be in a separate bean, injected into your controller.
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.
I'm trying to use Errai rest capabilities in my GWT app,
I took a look at the following guide:
http://errai-blog.blogspot.it/2011/10/jax-rs-in-gwt-with-errai.html
In particular, it says:
We simply put this interface somewhere in our client packages (e.g.
client.shared) where the GWT compiler can find it. To create a request
all that needs to be done is to invoke RestClient.create()
I think there's a plot hole here, how is Errai able to know how serialize/deserialize model classes ?
Can you help understanfing this ?
thanks
According to RestClient Class's create() method
public static <T, R> T create(final Class<T> remoteService, final RemoteCallback<R> callback, Integer... successCodes) {
return create(remoteService, null, callback, null, successCodes);
}
In example which you've provided; when using create() method Errai gets the CustomerService Class as remoteService, After many operations;
Errai parses and implements this CustomerService Interface with using their errai-codegen library which uses Java Reflection API.
When parsing simply;
First it looks for JAX-RS annotated methods and defines them as
JaxrsResourceMethod
Then it looks into parameters of that method if there is any parameter that annontated with JAX-RS annotations.
If it finds annontated parameters in JaxrsResourceMethod, it keeps that parameter with it's annotation type
If it finds not annontated parameter in JaxrsResourceMethod it defines as a entityParameter
Errai holds these annotated parameter and entityParameters in JaxrsResourceMethodParameters by their method. When building request it uses parameters by their rule.
Let me explain these rules with using an example you've provided.
Customer customer = new Customer("new name", "new last name", "new postal code");
RestClient.create(CustomerService.class, callback).updateCustomer(240193, customer);
Errai will create url like
example.com/cusomers/240193
Because #PathParam("id") annotation rule is adding parameter to the url and according to Errai's entityParameter rule customer will be marshalled when sending data with PUT.
#PUT
#Path("/{id}")
#Consumes("application/json")
#Produces("application/json")
public Customer updateCustomer(#PathParam("id") long id, Customer customer); //- See more at: http://errai-blog.blogspot.com.tr/2011/10/jax-rs-in-gwt-with-errai.html#sthash.2GTQtIg8.dpuf
One more additional note if you check here there is an exception in setEntityParameter method;
Only one non-annotated entity parameter allowed per method:
This means you cant define methods with more than 1 non-annotated parameter in Class which you sent in Errai.
In my Java Spring MVC 4 project, I have an AbstractRESTController with an update method:
#RequestMapping(
value="/{id}",
method=RequestMethod.PUT,
consumes={MediaType.APPLICATION_JSON_VALUE}
)
public #ResponseBody ResponseEntity<T> update(#PathVariable ID id,
#RequestParam String token, #RequestBody T json) {
[do fancy stuff]
}
and an extending class, let's call it MyController. Usually I want to use the method from the abstract class, but in MyController I have a special case (yay!), so I need to do further work.
My idea was to just override the #RequestMapping in the child class, do my additional fancy stuff and afterwards call the super class' update method from the MyController.update method. But this does not work, because I get an ambiguous mapping error during compilation.
Is there a way to make Spring override the parent class request mapping? I would like to avoid splitting the routes.
As you have noticed you can't do this because the ambiguous mapping.
If you want execute some additional code, you can use something like hook methods. So, define in your AbstractRESTController an empty method like this:
protected void doFancyStuff() {
}
Obs.: the empty method is a better choice here, and not an abstract one, to avoid the need to implement even with empty method body in all concrete controller.
Change the update method to call the hook method:
#RequestMapping(
value="/{id}",
method=RequestMethod.PUT,
consumes={MediaType.APPLICATION_JSON_VALUE}
)
public #ResponseBody ResponseEntity<T> update(#PathVariable ID id,
#RequestParam String token, #RequestBody T json) {
doFancyStuff();
}
And in MyController you will override and implement doFancyStuff method.
I was wondering if it is possible to chain #ModelAttribute methods by having an #ModelAttribute annotated, but not request mapped, method use another ModelAttribute in the method signature.
This would be in a controller.
ie
#ModelAttribute("attrOne")
public AttrOne getAttrOne() {
return service.getAttOne();
}
#ModelAttribute("attrTwo")
public AttrTwo getAttrTwo(#ModelAttribute("attrOne") AttrOne attrOne){
return anotherservice.getAttrTwo(attrOne);
}
Then if there was a request mapped method that did this:
#RequestMapping(method=RequestMethod.GET)
public String doSomething(#ModelAttribute("attrTwo") AttrTwo attrTwo )
would this work?
I seem to get a null object for AttrOne in the second annotated method... as the first annotated method is not called by the second one...
Cheers
I ran into the same situation by learning from the spring documention:
#ModelAttribute is also used at the method level [..]. For this usage the method signature can contain the same types as documented above for the #RequestMapping annotation.
I found SPR-6299 which faces this problem. In the comments you can find a workaround by providing only one #ModelAttribute annotated method which sets the attributes into the model:
#ModelAttribute
public void populateModel(Model model) {
model.addAttribute("attrOne", getAttrOne());
model.addAttribute("attrTwo", getAttrTwo());
}
According to SPR-6299, this will be possible in Spring 4.1 RC1 or later.