Spring controllers composition instead of inheritance - java

I develop REST application based on Spring MVC.
I have following hierarhy of Controllers:
UserController extends ImageController
ImageController extends SuggestController // Controller that has GET-DELETE-POST for image
SuggestController extends CrudController// Controller that has some suggest methods
CrudController // CRUD operations
So I had following mappings in runtume:
/users (POST-GET-PUT-DELETE)
/users/suggest (GET, POST(Spring Pageable))
/users/image (POST-GET-DELETE)
It was ok before I realized, that one controller must be able to give me images of its stuff, but cannot implement "suggest" methods:
/stuff (POST-GET-PUT-DELETE)
/stuff/image (POST-GET-DELETE)
And another one does not have "image" functionality, but has "suggest":
/things (POST-GET-PUT-DELETE)
/things/suggest (GET, POST(Spring Pageable))
Java says : "Use composition in such cases":
StuffController {
#InlineThisController
ImageController imagedController;
#InlineThisController
CrudController crudController;
... //some additional methods
}
So how can I acheive this in Spring MVC without boilerplate for delegation? Annotations? XML?

Spring MVC will not allow you to override method annotated with #RequestMapping, or more exactly it does not allow you to annotate the overriding method with #RequestMapping and will use the mapping in base class.
But you can always define 2 methods in base class : one annotated with #RequestMapping that only delegates to second not annotated. Then you are then free to override the second method in subclasses. Example :
Abstract base class for a CRUD controller
public abstract class AbstractCRUDController<K extends Serializable, X>
// X is the data class, K is the class of the key (int or String)
#RequestMapping({"/{data}"})
public String show(#ModelAttribute("data") X data, HttpSession session, Model model,
#RequestParam(value = "pagesize", required = false, defaultValue = "-1") int pageSize,
WebRequest request,
#RequestParam(value = "all", defaultValue = "false") boolean all) {
return doShow(data, session, model, pageSize, request, all);
}
protected String doShow(X data, HttpSession session, Model model,
int pageSize, WebRequest request, boolean all) {
return this.getPrefix() + "/view";
}
#RequestMapping(value={"/{data}/edit"}, method=RequestMethod.GET)
public String edit(#ModelAttribute("data") X data, Model model) {
return doEdit(data, model);
}
protected String doEdit(#ModelAttribute("data") X data, Model model) {
model.addAttribute("title", editTitle);
return this.getPrefix() + "/edit";
}
#RequestMapping(value={"/{data}/edit"}, method=RequestMethod.POST)
public String update(#ModelAttribute("data") X data, BindingResult result, Model model) {
if (data == null) {
throw new NoSuchElementException();
}
if (save(data, result, model, SimpleSaveType.UPDATE, null) != null) {
return "redirect:" + savedUrl(data);
}
else {
model.addAttribute("title", editTitle);
return getPrefix() + "/edit";
}
}
public K save(X data, BindingResult result, Model model, SaveType saveType, K id) {
...
}
...
public abstract String getPrefix();
}
Concrete implementation for class ProcessQ
#Controller
#RequestMapping(ProcessController.PREFIX)
public class ProcessController extends AbstractCRUDController<Integer, ProcessQ> {
public static final String PREFIX = "/process";
#Override
public String getPrefix() {
return PREFIX;
}
#Override
protected String doShow(ProcessQ process, HttpSession session, Model model,
int pageSize, WebRequest request, boolean all) {
String viewName = super.doShow(process, session, model, pageSize, request, all);
// specialized processing for class ProcessQ
...
return viewName;
}
...
}
Example is taken from a real program, that's the reason why you can see elements for pagination, error processing and access to underlying request.

Here are possible approaches:
Use Lombok's #Delegate
Use Kotlin's delegation support
Use Java's default interface methods (looks uglier than 1 and 2)

Related

Is it possible to conditionally assign the value of Required in #RequestParam?

I have 2 #RequestParam parameters in my Controller. I want to set the Required value of both the parameters based on a Condition. The condition could be like, if one of the parameter is passed, the other has to passed. So set the required of other as true and vice-versa. Otherwise set both false if none of the parameters are passed.
#RestController
public class TestController {
#GetMapping("/test")
public void test(#RequestParam(value = "a", required = (b !=null) String a,
#RequestParam(value = "b", required = (a !=null) ) String b,) {
{
}
}
The syntax of using the variable name inside #RequestParam() is wrong, but I wanted to explain what I want.
You can do it using one of the 2 following ways:
Using Spring AOP and create a surrounding aspect for that request
mapping
Using HandlerInterceptorAdapter to intercept the requests for a given URI
1. Using Spring AOP
Create an annotation like the following:
public #interface RequestParameterPairValidation {
}
Then you can annotate your request mapping method with it:
#GetMapping("/test")
#RequestParameterPairValidation
public void test(
#RequestParam(value = "a", required = false) String a,
#RequestParam(value = "b", required = false) String b) {
// API code goes here...
}
Create an aspect around the annotation. Something like:
#Aspect
#Component
public class RequestParameterPairValidationAspect {
#Around("#annotation(x.y.z.RequestParameterPairValidation) && execution(public * *(..))")
public Object time(final ProceedingJoinPoint joinPoint) throws Throwable {
Object[] requestMappingArgs = joinPoint.getArgs();
String a = (String) requestMappingArgs[0];
String b = (String) requestMappingArgs[1];
boolean requestIsValid = //... execute validation logic here
if (requestIsValid) {
return joinPoint.proceed();
} else {
throw new IllegalArgumentException("illegal request");
}
}
}
Note that it would be a good option to return 400 BAD REQUEST here since the request was not valid. Depends on the context, of course, but this is a general rule of thumb to start with.
2. Using HandlerInterceptorAdapter
Create a new interceptor mapping to your desired URI (in this case /test):
#Configuration
public class CustomInterceptor extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(new CustomRequestParameterPairInterceptor())
.addPathPatterns("/test");
}
}
Define the logic for validation inside the custom interceptor:
public class CustomRequestParameterPairInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object obj, Exception exception) throws Exception {
}
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object obj, ModelAndView modelAndView) throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
// Run your validation logic here
}
}
I would say the 2nd option is the best one since you can directly control the answer of the request. In this case it might be a 400 BAD REQUEST, or anything else that makes more sense in your case.
You can use Optional here in an intelligent manner here like this:
#GetMapping("/test")
#RequestParameterPairValidation
public void test(#RequestParam("a") Optional<String> a,
#RequestParam("b") Optional<String> b){
String aVal = a.isPresent() ? a.get() : null;
String bVal = b.isPresent() ? b.get() : null;
//go for service call here based on your logic
}
I hope this works for your requirement.
You can use Java EE #Size Validation annotation with Spring (but you must have a Java EE validation API implementor on the classpath, i.e hibernate ). With hibernate, you can import this dependency using maven
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
Then the entire thing becomes:
#RestController
#Validated
public class TestController {
#GetMapping("/test")
public void test(#RequestParam(value = "a", required = true ) #Size(min=1) String a,
#RequestParam(value = "b", required = true) #Size(min=1) String b) {
{
}
}
In Java you can pass only constants as parameters of any annotation.
That's why it's impossible to do it this way.
However, you can validate all that kind of things in the method itself.

Spring Controller design pattern inheritance?

Two questions:
1:
All the Controllers extend from the MasterController and implement its abstract method in every #GetMapping in order to have consistency. In every #Controller I have to populate the variables inherited from the MasterController. There is no assurance that I do all of them all the time. A shortcut would be to call a method from MasterController to populate all. Is it possible to catch the same request on multiple places so I do not need to execute the same methods every time but it "gets done" automatically?
#SessionAttributes(value = {"user", "..."})
public abstract class MasterController<T>
{
public Model MODEL;
public HashMap<String, String> INCOMING_PARAMS;
public RequestType REQUEST_TYPE;
public User USER;
public abstract T request(
#ModelAttribute("user") User _user,
#RequestParam HashMap<String, String> _params,
Model _model,
SessionStatus _sessionStatus,
HttpServletRequest _request,
HttpServletResponse _response);
public String otherCommonFunction(String _a)
{
...
}
}
#Controller
public class LoginController extends MasterController<String>
{
#GetMapping(value = "/account-login-page")
#Override
public String request(
#ModelAttribute("user") User _user,
#RequestParam HashMap<String, String> _params,
Model _model,
SessionStatus _sessionStatus,
HttpServletRequest _request,
HttpServletResponse _response)
{
USER = _user; //Do this every time
REQUEST_TYPE = RequestType.GET; //Do this every time
INCOMING_PARAMS = _params; //Do this every time
MODEL = _model; //Do this every time
...
return "..."
}
}
2:
If there are multiple #GetMapping in one #Controller, how can I have assurance that all of the methods have exactly the same parameters? An abstract method can be inherited once in the child class. It is possible to just copy and paste it but that looks unprofessional. Is there any solution to this?

How to decorate all requests to take a value from header and add it in the body parameter?

Background
I'm creating RESTful services using Spring MVC. Currently, I have the following structure for a controller:
#RestController
#RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController {
#RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
public ResponseEntity<MyEntity> createMyEntity(
#RequestBody MyEntity myEntity,
#RequestHeader("X-Client-Name") String clientName) {
myEntity.setClientName(clientName);
//rest of method declaration...
}
#RequestMapping(path={ "/{id}"} , method=RequestMethod.PUT)
public ResponseEntity<MyEntity> updateMyEntity(
#PathVariable Long id,
#RequestBody MyEntity myEntity,
#RequestHeader("X-Client-Name") String clientName) {
myEntity.setClientName(clientName);
//rest of method declaration...
}
#RequestMapping(path={ "/{id}"} , method=RequestMethod.PATCH)
public ResponseEntity<MyEntity> partialUpdateMyEntity(
#PathVariable Long id,
#RequestBody MyEntity myEntity,
#RequestHeader("X-Client-Name") String clientName) {
myEntity.setClientName(clientName);
//rest of method declaration...
}
}
As you can see, all these three methods receive the same parameter for the header #RequestHeader("X-Client-Name") String clientName and applies it in the same way on each method: myEntity.setClientName(clientName). I will create similar controllers and for POST, PUT and PATCH operations will contain almost the same code but for other entities. Currently, most entities are designed to support this field vía a super class:
public class Entity {
protected String clientName;
//getters and setters ...
}
public class MyEntity extends Entity {
//...
}
Also, I use an interceptor to verify that the header is set for requests.
Question
How can I avoid repeating the same code through controller classes and methods? Is there a clean way to achieve it? Or should I declare the variable and repeat those lines everywhere?
This question was also asked in the Spanish community. Here's the link.
My suggestion is to store the header value in the request scoped bean inside the Spring interceptor or filter. Then you may autowire this bean wherever you want - service or controller and use the stored client name value.
Code example:
public class ClientRequestInterceptor extends HandlerInterceptorAdapter {
private Entity clientEntity;
public ClientRequestInterceptor(Entity clientEntity) {
this.clientEntity = clientEntity;
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String clientName = request.getHeader("X-Client-Name");
clientEntity.setClientName(clientName);
return true;
}
}
In your configuration file:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(clientRequestInterceptor());
}
#Bean(name="clientEntity")
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Entity clientEntity() {
return new Entity();
}
#Bean
public ClientRequestInterceptor clientRequestInterceptor() {
return new ClientRequestInterceptor(clientEntity());
}
}
Then, lets assume we have to use this bean in our controller:
#RestController
#RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController {
#Autowired
private Entity clientEntity; // here you have the filled bean
#RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
public ResponseEntity<MyEntity> createMyEntity(#RequestBody MyEntity myEntity) {
myEntity.setClientName(clientEntity.getClientName());
//rest of method declaration...
}
// rest of your class methods, without #RequestHeader parameters
}
I have not compiled this code, so correct me if I made some mistakes.
I've got an interesting answer in the Spanish site (where I also posted this question) and based on that answer I could generate mine that adapts to this need. Here's my answer on SOes.
Based on #PaulVargas's answer and an idea from #jasilva (use inheritance in controller) I though on a stronger solution for this case. The design consists of two parts:
Define a super class for controllers with this behavior. I call this class BaseController<E extends Entity> because Entity is the super class for almost al my entities (explained in the question). In this class I'll retrieve the value of #RequestBody E entity parameter and assign it into a #ModelAttribute parameter like #PaulVargas explains. Generics power helps a lot here.
My controllers will extend BaseController<ProperEntity> where ProperEntity is the proper entity class I need to handle with that controller. Then, in the methods, instead of injecting #RequestBody and #RequestHeader parameters, I'll only inject the the #ModelAttribute (if needed).
Aquí muestro el código para el diseño descrito:
//1.
public abstract class BaseController<E extends Entity> {
#ModelAttribute("entity")
public E populate(
#RequestBody(required=false) E myEntity,
#RequestHeader("X-Client-Name") String clientName) {
if (myEntity != null) {
myEntity.setCreatedBy(clientName);
}
return myEntity;
}
}
//2.
#RestController
#RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController extends BaseController<MyEntity> {
#RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
public ResponseEntity<MyEntity> createMyEntity(
#ModelAttribute("entity") MyEntity myEntity) {
//rest of method declaration...
}
#RequestMapping(path={ "/{id}"} , method=RequestMethod.PUT)
public ResponseEntity<MyEntity> updateMyEntity(
#PathVariable Long id,
#ModelAttribute("entity") MyEntity myEntity) {
//rest of method declaration...
}
#RequestMapping(path={ "/{id}"} , method=RequestMethod.PATCH)
public ResponseEntity<MyEntity> partialUpdateMyEntity(
#PathVariable Long id,
#ModelAttribute("entity") MyEntity myEntity) {
//rest of method declaration...
}
}
In this way, I don't need to rewrite those lines of code in every method and controller, achieving what I've asked.
You could consider using RequestBodyAdvice. See the javadocs.
The HttpInputMessage object where you can access the http headers, is passed into the interface methods.

Removing duplication from Spring controllers

I have been looking for a way to somehow reduce the amount of code that is duplicated with subtle variance in my Spring MVC controllers, but searching through the SO questions so far has only yielded some questions without any satisfactory answers.
One example of duplication that I want to remove is this, where the user creation page and the role creation page share similarities:
#RequestMapping(value = "user/create", method = RequestMethod.GET)
public String create(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Displaying user creation page.");
return "user/create";
}
#RequestMapping(value = "role/create", method = RequestMethod.GET)
public String create(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Displaying role creation page.");
return "role/create";
}
A slightly more involved variant of duplication that I would like to remove is the one for posting the create form:
#RequestMapping(value = "user/create", method = RequestMethod.POST)
public String save(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Entering save ({})", user);
validator.validate(user, errors);
validator.validatePassword(user, errors);
validator.validateUsernameAvailable(user, errors);
String encodedPassword = encoder.encode(user.getPassword());
user.setPassword(encodedPassword);
if (errors.hasErrors()) {
return create(user, errors);
} else {
service.save(user);
}
return "redirect:/user/index/1";
}
#RequestMapping(value = "role/create", method = RequestMethod.POST)
public String save(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Entering save({})", role);
validator.validate(role, errors);
if (errors.hasErrors()) {
return create(role, errors);
} else {
service.save(role);
}
return "redirect:/index";
}
This example includes a validate then save if correct and a redirect to the error page if things don't go as planned.
How to remove this duplication?
Spring uses your handler method parameter types to create class instances from the request parameters or body. As such, there is no way to create a handler (#RequestMapping) method that could take an Object and check if it is either a Role or a User. (Technically you could have both parameters and just check which one isn't null, but that is terrible design).
Consequently, you need a handler method for each. This makes sense since, even through the logic is similar, it is still specific to the exact type of model object you are trying to create. You perform different validation, call a different service method, and return a different view name.
I say your code is fine.
Thought I would provide the solution that I settled on in the hope that it might help someone. My gf suggested that I use the name of the entity as a path variable for the controller, and this has proved to provide a very nice solution for the problem at hand.
The two methods now look like this:
#RequestMapping(value = "{entityName}/create", method = RequestMethod.GET)
public String create(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Displaying create page for entity named: [{}]", entityName);
return handlerFactory.getHandler(entityName).getCreateView();
}
#RequestMapping(value = "{entityName}/create", method = RequestMethod.POST)
public String save(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Saving entity of type {}", entityName);
CrudHandler handler = handlerFactory.getHandler(entityName);
handler.getCreateValidator().validate(entity, errors);
if (errors.hasErrors()) {
return create(entityName, entity, errors);
}
handler.preSave(entity);
handler.getService().save(entity);
return "redirect:" + DASHBOARD_URL;
}
The CrudHandler interface has implementations for each entity, and provides the controller with the entity specific classes that it needs, such as service and validator. A sample CrudHandler implementation looks like this for me:
#Component
public class RoleCrudHandler implements CrudHandler {
private static final String ENTITY_NAME = "role";
public static final String CREATE_VIEW = "role/create";
public static final String EDIT_VIEW = "role/edit";
#Resource
private RoleService roleService;
#Resource
private RoleValidator validator;
#Resource
private CrudHandlerFactory handlerFactory;
#PostConstruct
public void init() {
handlerFactory.register(ENTITY_NAME, this);
}
#Override
public GenericService getService() {
return roleService;
}
#Override
public Validator getCreateValidator() {
return validator;
}
#Override
public Validator getUpdateValidator() {
return validator;
}
#Override
public BaseEntity createEntity() {
return new Role();
}
#Override
public void preSave(BaseEntity entity) {
}
#Override
public String getCreateView() {
return CREATE_VIEW;
}
#Override
public String getUpdateView() {
return EDIT_VIEW;
}
}
If someone sees some ways to improve this, feel free to share. Hope this will be of use for someone.

Could Spring MVC call #ModelAttribute after #RequestMapping?

I have a controller like this:
#Controller
public class HomeController {
#RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(#RequestParam("user") User user, ModelMap model){
SaveUserToDatabase(user);
return "index";
}
#ModelAttribute("user")
String getUser() {
return LoadCurrentUserFromDataBase();
}
}
In short, my views would render user in almost every actions in HomeController,
but I don't want to code:
model.addAttribute("user", LoadCurrentUserFromDataBase())
in every actions, instead I'm seeking a way like #ModelAttribute to expose user to all my views.
However, according to the docs, #ModelAttribute methods in a controller are invoked before #RequestMapping methods, within the same controller.
As to my code, getUser is called before update, but i'd like to get the updated user.
Is there a way to expose the user attribute after actions without explicitly call model.addAttribute in every actions?
Each time you do a POST, make a redirection. That way, your POST method will only be responsible for updating the data, and the updated data will be loaded by the target controller.
So in this case, the update() method would redirect to another controller which would call the getUser() method before its GET method.
The Post / redirect / GET solution is valid if it works for you.
However, I had a similar situation where I had model attributes that needed to be written by all my controllers, after request processing (tracking information mostly). What I did was to register a custom interface (e.g. AdditionalModelDataSupplier), which I apply to all controllers that need to provide additional data. The interface would have a method like this:
void provideAdditionalData(Model model, HttpServletRequest request);
Now, I wrote an interceptor that in the postHandle method checks the Controller bean for this interface and calls this method:
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws Exception {
AdditionalModelDataSupplier modelDataSupplier = findAdditionalModelDataSupplier(handler);
if (modelDataSupplier != null) {
final ModelMap modelMap = modelAndView.getModelMap();
final Model targetModel;
if (modelMap instanceof Model) {
targetModel = (Model) modelMap;
} else {
// the modelmap doesn't implement model, so we need to provide a wrapper view
targetModel = new ForwardingModel(modelMap);
}
modelDataSupplier.provideAdditionalData(targetModel, request);
}
}
#Nullable
private static AdditionalModelDataSupplier findAdditionalModelDataSupplier(final Object handler) {
if (handler instanceof AdditionalModelDataSupplier) {
return (AdditionalModelDataSupplier) handler;
}
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Object bean = handlerMethod.getBean();
if (bean instanceof AdditionalModelDataSupplier) {
return (AdditionalModelDataSupplier) bean;
}
}
return null;
}
(the ForwardingModel class mentioned above is trivial to create, it just delegates everything to the ModelMap)

Categories

Resources