My project with roo generated CRUD is working ok, but now i need to change the way some entities are saved (For example i have a "User" attribute that i want set dynamically as the user logged)
At the moment i'm just moving the save() method from the aspect to the .java and modifying it as needed. It worked well so far but the roo console don't seem to like it, as it recreate the method in the aspect as soon as i change the method return type or other things.
I don't need an specific answer to this example, instead i'm looking to know if this is the best approach to modify/override the out-of-the-box create/show functionality for the entities provided by roo.
Edit: Added Example
One of my entities is "Servicio", it have some "ServiciosCollectionThymeleafController_Roo_Thymeleaf.aj" with a "ServiciosCollectionThymeleafController.create" method. I proceeded to push in all the .aj into the "ServiciosCollectionThymeleafController.java"
Then i made some minor changes in the create method and saved it. It worked, but when i opened the roo console the consoled generated the pushed aj again, just with the method i edited earlier.
Original create method on the aspect:
/**
* TODO Auto-generated method documentation
*
* @param servicio
* @param result
* @param model
* @return ModelAndView
*/
@PostMapping(name = "create")
public ModelAndView ServiciosCollectionThymeleafController.create(@Valid @ModelAttribute Servicio servicio, BindingResult result, Model model) {
if (result.hasErrors()) {
populateForm(model);
return new ModelAndView("/servicios/create");
}
Servicio newServicio = getServicioService().save(servicio);
UriComponents showURI = getItemLink().to(ServiciosItemThymeleafLinkFactory.SHOW).with("servicio", newServicio.getId()).toUri();
return new ModelAndView("redirect:" + showURI.toUriString());
}
The same method pushed in into the .java, and my modifications:
/**
* TODO Auto-generated method documentation
*
* @param servicio
* @param result
* @param model
* @return ModelAndView
*/
@PostMapping(name = "create")
public ModelAndView create(@Valid @ModelAttribute Servicio servicio, BindingResult result, Model model, Principal principal, Pageable pageable) {
if (result.hasErrors()) {
populateForm(model);
return new ModelAndView("/servicios/create");
}
Prestador current = (Prestador) personaService.findByUsername(principal.getName(), pageable).getContent().get(0);
if (current == null) {
populateForm(model);
return new ModelAndView("/servicios/create");
}
servicio.setPrestador(current);
Servicio newServicio = getServicioService().save(servicio);
return new ModelAndView("redirect:/ver-servicio/" + newServicio.getId());
}
Thanks.
Roo checks if a method is already included in the Java file by looking for the method signature (the method's name and the parameter types), as this is the way supported in java for overloading methods.
In your case, once you change the create method parameters, it is no longer the same method signature, and that's why Roo generates it again.
In general this is not a problem, as you have to change the clients of that method to use the new one. For example, if you add a new method to a Service, you will change also the implementation of the Controller to use that new method, and the one generated by Roo will not affect you.
In the case of controller methods the problem is related to the mapping. In your case you end up with two methods, the one added by you and the one generated by Roo, with the same request mapping. To solve it you just have to add the method generated by Roo without the mapping annotation.
In your case the code would be the following one:
public ModelAndView create(@Valid @ModelAttribute Servicio servicio, BindingResult result, Model model) {
throw new UnsupportedOperationException();
}
@PostMapping(name = "create")
public ModelAndView create(@Valid @ModelAttribute Servicio servicio, BindingResult result, Model model, Principal principal, Pageable pageable) {
if (result.hasErrors()) {
populateForm(model);
return new ModelAndView("/servicios/create");
}
Prestador current = (Prestador) personaService.findByUsername(principal.getName(), pageable).getContent().get(0);
if (current == null) {
populateForm(model);
return new ModelAndView("/servicios/create");
}
servicio.setPrestador(current);
Servicio newServicio = getServicioService().save(servicio);
return new ModelAndView("redirect:/ver-servicio/" + newServicio.getId());
}
As you have the create method with the default signature, it won't be regenerated by Roo. Also, it doesn't have the PostMapping annotation, so it will be ignored by Spring MVC which will call the create method with the new signature.
Additional note:
Also you will have to change how the link to the method is generated. All Thymeleaf controllers generated by Roo have a companion class (ending with LinkFactory) which is used to generate the links to that controller methods, avoiding the use of hardcoded URIs in the Thymeleaf pages as well as Controller redirects. Those LinkFactory classes are generated using the Spring's MvcUriComponentsBuilder.fromMethodCall utility which uses a fake method call to generate the link to that Controller method.
As you have a new method signature, you have to change the default implementation of the ServiciosCollectionThymeleafController toUri method. Push-in the toUri method to the Java file and change the implementation to something like this.
public UriComponents toUri(String methodName, Object[] parameters, Map<String, Object> pathVariables) {
...
if (methodName.equals(CREATE)) {
return SpringletsMvcUriComponentsBuilder.fromMethodCall(SpringletsMvcUriComponentsBuilder.on(getControllerClass()).create(null, null, null, null, null)).buildAndExpand(pathVariables);
}
...
}
Note I've added two additional null parameters to the create method call use the new method signature. With this change, all URIs generated from the Thymeleaf pages will point to the new method.