I would like to find a a way to validate a rest method based on all parameters outside the Controller.
First Question: Is there already a way to do it?
Second Question: If not - how can I hook the validation into spring mvc binding prozess.
A way how it could look like. It would be nice to mark the method with a new #MethodValidation Annotation:
#Validate
#MethodValidation(MyValidator.class)
public Response doSomthing(String param1, Integer param2, Something param3){}
Annotation
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface MethodValidation{
Class<? extends MethodValidator<?, ?>>[] value();
}
Implement a Validator
public class MyValidator implements MethodValidator{
public void validate(Object[] params, Errors errors){
String param1 = (String ) params[0];
Integer param2 = (Integer) params[1];
Something param3 = (Something)params[3];
// .... do some validations
if(error)
errors.reject("Some.error.done");
}
}
what kind of parameters exactly? a lot of spring stuff is actually available in ThreadLocals, if you dare to dig into it.
you CAN inject stuff into the binding process:
#ControllerAdvice
public class FooControllerAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(
Date.class,
new CustomFooEditor()
);
}
}
and the actual editor:
public class CustomFooEditor extends PropertyEditorSupport {
}
but this doesn't give you that much of an edge over regular validation.
or you can use spring aop triggered by an annotation, then annotate your methods, with the config:
#EnableAspectJAutoProxy(proxyTargetClass=true)
an aspect:
#Aspect
#Component
public class ValidationAspect {
#Pointcut("execution(public * * (..))")
private void anyPublicMethod() {}
#Around("anyPublicMethod() && #annotation(foo)")
public Object all(
ProceedingJoinPoint proceedingJoinPoint,
Foo ann) throws Throwable {
}
[...]
}
an annotation:
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Foo {
}
public String value();
and then annotating your method:
#RequestMapping...
#Foo(value="foo.bar.ValidatorClassname")
public Response x() {
}
... so you see, there's a lot of ways you can go. i'd really like to know what keeps you from using standard validation?
.rm
thanx for the answer.
I hope I am right: The standard validation outside the controller just allows me to to validate each method parameter separately.
I actually get into problems when the validation depends on 2 or more method parameter. This could be in following situation: Some thing is a part of an Object hierarchy:
public class Parent{
private Integer id;
private List<Something> childs;
...
}
public class Something{
private Integer id;
private String name;
...
}
The Constrain: it is not allowed that a Parent has 2 somethings in the list with the same name. For saving a new some thing I am calling the method.
#RequestMapping(
value = "/chargingstation/{parentId}",
method = RequestMethod.Post)
public Response doSomthing(
#PathVariable("parentId") Integer parentId,
Something param3)
Add the parentId to the Something-ModelOject was not an option.
So is there a way to handle this situation with the standard validation?
Related
I have different configurations all inheriting from a base configuration that are customized in forms. I want all of these to be handled by a single action result.
[HttpPost]
public IActionResult Register(AbstractBaseConfig config)
{
...do some logic...
return View("../Home/Index");
}
However, this is not possible because you cannot base in abstract classes as a parameter to an action result. Is there any work around for this so I don't need a seperate action result for each configuration? (I still want each configuration to be it's own class, I only need access to the base class methods in the action result logic).
Basically you can't, and the reason is that MVC will try to do new AbstractBaseConfig() as part of the Data Binding process (which parses the URL or the Form Post and puts the results in a concrete object). And by definition, doing new AbstractBaseConfig() is impossible for an abstract class.
It also makes sense for other reasons, I will explain why.
You seem to expect that MVC can determine the class from the parameters that are being passed in. That is not how it works, in fact the opposite is true: the Action Method has to specify the exact class, and then the Binder will instantiate that exact class and try to bind its properties.
Suppose you had this:
public abstract class Thing { public int ID { get;set; } }
public class NamedThing : Thing { public string Name { get;set; } }
public class OtherThing : Thing { public string Name { get;set; } }
and suppose it would be allowed to use:
public IActionResult Register(Thing thing)
then what would you expect to be in thing after Data Binding: a Thing object with only the ID set? Or one of the other object types, with Name set, but how would MVC ever be able to know which class you meant?
So for all these reasons, this is not possible.
You could have a base class inherit the abstract class and all your classes inherit from that base class whilst having that base class as your parameter
Take for example
public abstract class ABase
{
public void stuff()
{
var stuff = string.Empty;
stuff = "hello";
}
public virtual void otherstuff()
{
var stuff = string.Empty;
stuff = "hello";
}
}
public class Base : ABase
{
//empty
}
public class Derived : Base
{
public void mystuff()
{
this.stuff();
}
public override void otherstuff()
{
// Custom code
}
}
public ActionResult Register(Base config)
{
}
I've implemented custom annotation in my project, but implemented method argument resolver was never invoked.
Can someone help me with this issue?
My implementation:
Annotation
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface MyAnnotation {
String value();
}
Resolver
public class MyAnnotationResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
System.out.println("supportsParameter invoked!");
return parameter.getParameterAnnotation(MyAnnotation.class) != null;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
...
return "someString";
}
}
Configuaration
#Configuration
#EnableWebMvc
public class Config extends WebMvcConfigurerAdapter {
...
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new MyAnnotationResolver());
System.out.println("Resolver added!");
}
...
}
Controller
#Controller
#RequestMapping("/project")
public class ProjectController{
#RequestMapping(method = RequestMethod.GET, value= "/{hashCode}")
#ResponseBody
public String index(#MyAnnotation("hashCode") String hashCode, Model model) {
...
System.out.println(hashCode == null ? "HashCode=null" : "HashCode=" + hashCode);
}
As output I get:
Resolver added!
HashCode=null
Why supportsParameter(...) was never invoked?
It is not reproducible. supportsParameter gets called with the given code.
Since 3 years have passed after you asked, I checked the code that was used back then when you posted this question. However, the relevant code didn't change that much.
One possible explanation is that there was an argument resolver that supported the parameter, since it doesn't check another when it finds one.
In order to retrieve a list in a Spring MVC application I would like to write something like:
public String myMethod(#RequestParam("foo") List<FooUi> foos)
But the only solution I've found so far is the following :
public String myMethod(FooListWrapperUi fooListWrapperUi)
I don't like this solution because I have to write a wrapper each time I need to retrieve a list. In this example, the wrapper is the following :
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class FooListWrapperUi
{
private ArrayList<FooUi> fooList;
}
So my question is, is it possible to use something like the first solution or is it impossible and I need to write a wrapper?
Thanks.
You can accommodate your use case by creating your own HandlerMethodArgumentResolver:
public class FooUiResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return (methodParameter.getParameterType().equals(FooUi.class) ||
(methodParameter instanceof Collection<?> && ((ParameterizedType) methodParameter.getParameterType().getGenericSuperclass()).getActualTypeArguments()[0] == FooUi.class));
}
#Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
// Create instances of FooUi by accessing requests parameters in nativeWebRequest.getParameterMap()
}
}
The actual implementation will depend on how you would create one or more FooUi instances from the request parameters or body. You then need to register FooUiResolver in your servlet config:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers){
argumentResolvers.add(new FooUiResolver());
super.addArgumentResolvers(argumentResolvers);
}
Once registered, you can use FooUi in your controller method arguments without RequestParam or any other annotation:
#RequestMapping(value = "/foo")
public String myMethod(List<FooUi> foos){}
I have a 2 domain classes one with reference to another like this:
#Document
public class Dummy {
#Id
private UUID id;
private String name;
#Reference
private DummyAttribute dummyAttribute;
// getters and setters omitted.
}
#Document
public class DummyAttribute {
#Id
private UUID id;
private String name;
// getters and setters omitted.
}
I also have 2 repositories corresponding to Dummy and DummyAttribute.
public interface DummyRepository extends CrudRepository<Dummy, UUID> {
}
public interface DummyAttributeRepository extends
CrudRepository<DummyAttribute, UUID> {
}
I want to create a Dummy with a DummyAttribute. So, I create a dummyAttribute by posting to /dummyattributes. I get the response body with a self link to dummyAttribute back. This self link that I get back is used during the creation of Dummy. My JSON payload to the /dummies looks like :
{
"name" : "dummy",
"dummyAttribute" : <self link of dummyAttribute generated during POST>
}
When I do a GET on the association URL generated after POST, I correctly get
the dummyAttribute that was used. So far works well in Spring Data REST.
I want to do the same using my custom controllers. So, I created controllers
for both Dummy and DummyAttribute.
#RestController
public class DummyController {
#Autowired
private DummyRepository dummyRepository;
#Autowired
private DummyResourceProcessor processor;
#RequestMapping(value = "/dummies", method = RequestMethod.POST)
public HttpEntity<Resource<Dummy>> createTenant(#RequestBody Dummy dummy)
{
Dummy save = dummyRepository.save(dummy);
Resource<Dummy> dummyr = new Resource<Dummy>(save);
return new ResponseEntity<Resource<Dummy>>(processor.process(dummyr),
HttpStatus.CREATED);
}
#RequestMapping(value = "/dummies/{id}", method = RequestMethod.GET)
public HttpEntity<Resource<Dummy>> getDummy(#PathVariable("id") Dummy
dummy) {
Resource<Dummy> dummyr = new Resource<Dummy>(dummy);
return new ResponseEntity<Resource<Dummy>>(processor.process(dummyr),
HttpStatus.OK);
}
#RestController
public class DummyAttributeController {
#Autowired
private DummyRepository dummyRepository;
#Autowired
private DummyAttributeRepository dummyAttributeRepository;
#Autowired
private DummyAttributeResourceProcessor processor;
#RequestMapping(value = "/dummyAttributes", method =
RequestMethod.POST)
public HttpEntity<Resource<DummyAttribute>> createDummyAttribute(
#RequestBody DummyAttribute dummyAttribute) {
DummyAttribute save = dummyAttributeRepository.save(dummyAttribute);
Resource<DummyAttribute> dummyr = new Resource<DummyAttribute>(save);
return new ResponseEntity<Resource<DummyAttribute>>
(processor.process(dummyr),createHeaders(request,save.getId()),
HttpStatus.CREATED);
}
#RequestMapping(value = "/dummyAttributes/{id}", method =
RequestMethod.GET)
public HttpEntity<Resource<DummyAttribute>> getDummyAttribute(
#PathVariable("id") DummyAttribute dummyAttribute) {
Resource<DummyAttribute> dummyr = new Resource<DummyAttribute>
(dummyAttribute);
return new ResponseEntity<Resource<DummyAttribute>>
(processor.process(dummyr), HttpStatus.OK);
}
I followed the same sequence of step as above. I did a POST todummyAttribute.
Using this self link , I tried to create a dummy.
This time things are not so smooth. I get this exception back.
Can not instantiate value of type [simple type,
class com.sudo.DummyAttribute] from String value
('http://localhost:8080/dummyAttributes/3fa67f88-f3f9-4efa-a502-
bbeffd3f6025'); no single-String constructor/factory method at
[Source: java.io.PushbackInputStream#224c018a; line: 2, column: 19]
(through reference chain: com.sudo.Dummy["dummyAttribute"])
When I create a constructor inside DummyAttribute, and I parse the id from the url and assign it to the id.
public DummyAttribute(String url) {
String attrId = // parse the URL to get the id;
this.id = attrId;
}
Now things are work as expected.The dummyAttribute is assigned to dummy.
What I would like to know is why are things different when I write my custom-controller ? What am I missing ? How is it that when I use Spring Data REST, the reference URL to the dummyAttribute was automatically resolved to the corresponding dummyAttribute object and in the custom controller, I had to parse it manually and assign the id value explicitly to domainAttribute id?
Also, in the constructor I believe, the dummyAttribute is not resolved by finding it from repository by doing a findOne but a new dummyAttribute is being created. Is this correct?
How do I make my POSTs to my custom controller work exactly like how it works in Spring Data REST ? Do I need a custom serializer/deserializer for this ? Do I need to register some components manually and invoked it ?
I found that when I have customer controllers and #EnableWebMvc is used, the associated resource does not get resolved. That results in the error above. If no #EnableWebMvc is present, then the associated resource gets resolved properly. Not sure how #EnableWebMvc gets in between....
The spring versions that I use are : spring-boot-starter-1.2.5, spring-boot-starter-data-rest-1.2.5, spring-data-commons-1.9.3. spring-hateoas-0.16.0, spring-data-rest-core-2.2.3, spring-data-rest-webmvc-2.2.3.
spring mvc
#ModelAttribute("classname"),
How to make the argument "classname" a dynamic one ?
Whatever comes from view can get appended there.
Instantiation of the command object is the only place where Spring needs to know a command class. However, you can override it with #ModelAttribute annotated method:
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request,
#ModelAttribute("objectToShow") Object objectToShow)
{
...
}
#ModelAttribute("objectToShow")
public Object createCommandObject() {
return getCommandClass().newInstance();
}
By the way, Spring also works fine with the real generics:
public abstract class GenericController<T> {
#RequestMapping("/edit")
public ModelAndView edit(#ModelAttribute("t") T t) { ... }
}
#Controller #RequestMapping("/foo")
public class FooController extends GenericController<Foo> { ... }