Using SimpleFormController - spring

I have a controller which accepts a form post -
#Controller
public class RegistrationFormController extends SimpleFormController {
.....
.....
#RequestMapping(value="index", method=RequestMethod.POST)
protected ModelAndView onSubmit(#ModelAttribute Registration registration) throws Exception {
String uname=registration.getUsername();
.....
.......
ModelAndView mv = new ModelAndView("success");
.....
......
mv.addObject("addr",addr);
return mv;
}
Thsi would work just as well even if i do not extend SimpleFormController.
What then can I gain by extending?

Well, the setup seems not good. The #Controller annotation is a nice feature which is used for declaring stereotypes. It just says that it is another spring Component or Spring managed bean and can be detected in component scan.
Where as when you extend SimpleFormController you explicitly say that it is a Controller and it has to be used as a controller, it will be used to accept submitted form data and return a response in form of a view.
The two notations in the same class makes no sense at all, I feel that making a class SimpleFormController restricts it from using any method name and you are forced to use a onSubmit method. Whereas, if you use #Controller you leverage all the flexibility in Spring 3 and above.

Related

Automatically document #PathVariable annotated parameters within #ModelAttribute annotated methods

In our REST-API we need to be multi-tenant capable. For achiving this all rest controllers subclass a common REST controller which defines a request mapping prefix and exposes a model attribute as follows
#RequestMapping(path = "/{tenantKey}/api")
public class ApiController {
#ModelAttribute
public Tenant getTenant(#PathVariable("tenantKey") String tenantKey) {
return repository.findByTenantKey(tenantKey);
}
}
Derived controllers make use of the model attributes in their request mapping methods:
#RestController
public class FooController extends ApiController {
#RequestMapping(value = "/foo", method = GET)
public List<Foo> getFoo(#ApiIgnore #ModelAttribute Tenant tenant) {
return service.getFoos(tenant);
}
}
This endpoint gets well documented in the swagger-ui. I get an endpoint documented with a GET mapping for path /{tenantKey}/api/foo.
My issue is, that the {tenantKey} path variable is not documented in swagger-ui as parameter. The parameters section in swagger is not rendered at all. If I add a String parameter to controller method, annotating it with #PathVariable("tenantKey) everything is fine, but I don't want a tenantKey parameter in my controller method, since the resolved tenant is already available as model attribute.
So my question is: Is there a way do get the #PathVariable from the #ModelAttriute annotated method in ApiController documented within swagger-ui in this setup?
Project-Setup is
Spring-Boot (1.4.2)
springfox-swagger2 (2.6.1)
springfox-swagger-ui (2.6.1)
This is certainly possible. Model attributes on methods are not supported currently. Instead, you could take the following approach.
Mark the getTenant method with an #ApiIgnore (not sure if it gets treated as a request mapping.)
In your docket you can add tenantKey global path variable (to all end points). Since this is a multi-tenant app it's assuming this applies to all endpoints.

Can I specify that a controller level request mapping is not valid for a specific method into a Spring MVC controller class?

I am working on a Spring MVC application and I have the following problem into a controller class that is annotated with #RequestMapping("/profilo/") at class level.
So I have something like this:
#Controller
#RequestMapping("/profilo/")
public class ProfiloController extends BaseController {
..................................................................
..................................................................
SOME CONTROLLER METHOD THAT WORK CORRECTLY
..................................................................
..................................................................
#RequestMapping(value = "utenze/{username}/confermaEmail/{email:.+}", method = RequestMethod.GET)
public String confermaModificaEmail(#PathVariable String username, #PathVariable String email, Model model) {
logger.debug("INTO confermaModificaEmail(), indirizzo e-mail: " + email);
.......................................................................
.......................................................................
.......................................................................
}
}
So, as you can see in the previous code snippet, I have this ProfiloController class that is annotated with #RequestMapping("/profilo/") so it means that all the HTTP Request handled by the controller method of this class have to start with the /profilo/ in the URL.
It is true for all the method of this class except for the confermaModificaEmail() method that have to handle URL as:
http://localhost:8080/my-project/utenze/mario.rossi/confermaEmail/a.nobili#siatec.net
that don't tart with /profilo/.
So can I specify that for this specific method of the controller the #RequestMapping("/profilo/") controller level mapping is not valid and have to be not considered?
This is not possible.
Spring has maintained a proper #Controller structure which says for all endpoints related to ControllerName (i.e .Portfolio in your case) should be kept in this class.
Ideally, any other url not related to the functionality should be kept as part of a different Controller class.
If still you want to keep in same controller, try calling the endPoint url confermaModificaEmail() by redirecting the http call from "/portfolio/<"sample"> to the required url. But this is not recommended.

Custom #RequestMapping annotation

I have few methods in my spring controllers which are mapped on the same path, example.
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
protected ResourceDTO getById(#PathVariable int id) {
return super.getById(id);
}
I was wondering if there is a way to create an annotation that will automatically have set value and method, to have something like this:
#RequestMappingGetByID
protected ResourceDTO getById(#PathVariable int id) {
return super.getById(id);
}
Have a nice day everyone
Update
The goal of this is the following
all my controllers (eg. user, order, client) extends a parametrized BaseController that includes a base set of function (get by id, save, update, delete, etc) All the logic is on the BaseController, but in order to map the value I have to add the annotation on the specific controller.
Instead of writing all the time {id} and post I would like to annotate the methods with a custom interface that already includes those values
The following works for Spring 4.1.x that I tested:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
#interface RequestMappingGetByID {
}
Then you can use
#RequestMappingGetByID
protected ResourceDTO getById(#PathVariable int id) {
return super.getById(id);
}
like you mention.
This kind of annotation is was Spring calls a meta-annotation. Check out this part of the documentation
I am not sure if this meta-annotation would work in versions of Spring prior to 4.x, but it's definitely possible since Spring had some meta-annotation handling capabilities in the 3.x line
If you where using Groovy, you could also take advantage of the #AnnotationCollector AST, which in effect would keep the duplication out of your source code, but would push the regular #RequestMapping annotation into the produced bytecode. Check out this for more details.
The benefit in this case would be that Spring need not have to be equipped with the meta-annotation reading capabilities, and there for the solution possibly works on older Spring versions

How to redirect from a controller to another controller on Spring

Trying to do something that should be really easy...
Im using Spring MVC 3 and I want to redirect a call to a controller from inside another controller... ex:
#Controller
#SessionAttributes
public class UserFormController {
#Autowired
private UserService userService;
#RequestMapping(value = "/method1", method = RequestMethod.GET)
public ModelAndView redirectFormPage() {
//redirect to controller2 here,
//pointing to a method inside it, without going to any url first
}
Tried to do that with modelandview but it seems to me that all the solutions have to go through an url first and just then i can redirect to the controller.
thanks for all the help!
=============== more info ===========
The flow is like that: methodA on controller1 is called... do some stuff... and then wants to redirect the user to a listPage... this page has a list of objects, which a methodB on controller2 is able to load and send it to the this listPage. What im trying to achieve is : always that someone needs to get this page, i will call this method onn this controller2 and load it.
You can do
public String redirectFormPage() {
return "redirect:/url/controller2";
}
I don't know why you want to do this but this may work:
#Controller
#SessionAttributes
public class UserFormController {
#Autowired
private UserService userService;
#Autowired
private Controller2 controller2;
#RequestMapping(value = "/method1", method = RequestMethod.GET)
public ModelAndView redirectFormPage() {
return controller2.redirectMethod();
}
just inject Controller2 and call the desired method
Great question. I like to use struts redirect action result type to avoid putting knowledge of the next view in the actions that handle posted forms.
For example, the action method for a class like UpdateContactInfoAction would return contactInfoSaved instead of preparing data for the next view and returning gotoShippingOptionsPage. The knowledge that the next step in the flow is to choose a shipping option is only in the struts config.
Anyone know how to do this with Spring MVC?

Why is my Spring 3 Validator Validating Everything on the Model?

I have a spring 3 controller with a validator for one of the methods. It insists on validating every object on the model. Would anyone be able to explain to me why it does this or if I'm doing something wrong?
According to the docs, 5.7.4.3 Configuring a JSR-303 Validator for use by Spring MVC (http://static.springsource.org/spring/docs/3.0.0.RC3/spring-framework-reference/html/ch05s07.html)
With JSR-303, a single javax.validation.Validator instance typically validates all model objects that declare validation constraints. To configure a JSR-303-backed Validator with Spring MVC, simply add a JSR-303 Provider, such as Hibernate Validator, to your classpath. Spring MVC will detect it and automatically enable JSR-303 support across all Controllers.
Example:
#Controller
public class WhaleController {
#Autowired
private Validator myValidator;
#Autowired
private WhaleService whaleService;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(this.myValidator);
}
#RequestMapping(value="/save-the-whales")
#Transactional
public void saveTheWhales(#Valid WhaleFormData formData, BindingResult errors, Model model) {
if (!errors.hasFieldErrors()) {
Whale whale = new Whale();
whale.setBreed( formData.getBreed() );
this.whaleService.saveWhale( whale );
model.addAttribute("whale", whale);
}
model.addAttribute("errors", errors.getFieldErrors());
}
}
When run it will complain that Whale is an invalid target for myValidator (which is set to validate WhaleFormData, and does so fine). Whale is a POJO with no validation constraints, annotation and no config anywhere. Through trial and error I've found that ANY object placed on the model will attempt to be validated and fail if the validator is not setup to handle it. Primitives are just fine.
Can anyone tell me why this is, point me to the appropriate documentation and/or tell me the best way to put something on the model without having it validated?
In the case above I would like to place "whale" on the model as it will now have a unique whaleId() that it received from my persistence layer.
Thanks!
I guess this behaviour is not covered in the documentation well.
The problem is caused by the following:
By default, #InitBinder-annotated method is called for each non-primitive model attribute, both incoming and outcoming (the purpose of calling it for outcoming attibutes is to allow you to register custom PropertyEditors, which are used by form tags when rendering a form).
DataBinder.setValidator() contains a defensive check that call Validator.supports() and throws an exception if false is returned. So, there is no attempt to perform a validation, just an early check.
The solution is to restrict the scope of #InitBinder to particular attribute:
#InitBinder("whaleFormData")
protected void initBinder(WebDataBinder binder) { ... }

Resources