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

12,314

Solution 1

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:

  1. 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.

  2. 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.

Solution 2

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.

Share:
12,314
Luiggi Mendoza
Author by

Luiggi Mendoza

🤯 Wild software engineer unleashed in a crazy world 📺 One Piece, FMAB 🎮 Souls games 🧑🏽‍🍳 Peruvian &amp; keto food (kinda) 🌱 Gardener wannabe You can contact me anytime to [email protected]. My LinkedIn Profile.

Updated on June 22, 2022

Comments

  • Luiggi Mendoza
    Luiggi Mendoza about 2 years

    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.

  • Luiggi Mendoza
    Luiggi Mendoza over 8 years
    Did you noticed that @RequestBody MyEntity myEntity is a request parameter filled by the request body? I haven't tested the code yet, but I'm not sure that the parameter is being filled by Spring because now it's a managed bean (noticed by WebConfig#clientEntity) when in my case is a plain POJO.
  • Thomas Weglinski
    Thomas Weglinski over 8 years
    @LuiggiMendoza I've updated my answer, to show what I'm thinking of in exemplary controller method that you provided.
  • Luiggi Mendoza
    Luiggi Mendoza over 8 years
    Using your approach, I still have to write this line: myEntity.setClientName(clientEntity.getClientName()); in every method of every controller I need.
  • AdamSkywalker
    AdamSkywalker about 8 years
    can you explain the order of method invocation? when populate method is called in this scenario?
  • Luiggi Mendoza
    Luiggi Mendoza about 8 years
    @AdamSkywalker populate method is called before calling the proper method that will handle the request. In this method, the request body will be read because it has a parameter @RequestBody(required=false) E myEntity. Then, it will generate a variable that will be stored as model attribute. Later, the method that handles the request is called e.g. createMyEntity or updateMyEntity. Since Spring already injected itself the ModelAttribute, it can retrieve it as parameter when declaring @ModelAttribute("entity") MyEntity myEntity and use it inside the method.
  • AdamSkywalker
    AdamSkywalker about 8 years
    I understood the ModelAttribute injection, but what makes the guarantee that populate method is called earlier than actual methods?
  • Luiggi Mendoza
    Luiggi Mendoza about 8 years
    @AdamSkywalker please check here Spring MVC docs (it refers to old 3.2 but works good for this concept), search Using @ModelAttribute on a method
  • kingoleg
    kingoleg over 5 years
    Hi, Max. Can you advise any option to get request params/uri data? HttpInputMessage consists only headers and body...
  • maxhuang
    maxhuang over 5 years
    Hi, you can look at HandlerMethodArgumentResolver interface. In the resolveArgument method, you can access the HttpServletRequest and HttpInputMessage from the NativeWebRequest param. This is how you do that: HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);
  • kingoleg
    kingoleg over 5 years
    Thanks, Max. I found that @ModelAttribute work perfect for my case
  • PravyNandas
    PravyNandas about 4 years
    Working great. Since I'm using a handler layer to process the request instead of doing it on controller, I have access to all the headers before reaching to the controller eliminating the need of adding header in each method signature. Thanks @ThomasWeglinski
  • RukaDo
    RukaDo about 4 years
    It doesn't work for me. When I autowire, i get the object with null fields @PravyNandas
  • user1123432
    user1123432 over 3 years
    WebMvcConfigurerAdapter is now deprecated, use WebMvcConfigurer instead.
  • Hubert Grzeskowiak
    Hubert Grzeskowiak about 3 years
    I upvoted before testing. I am not sure how this solution can possibly work, since the beans are instantiated explicitly. This works around Spring's dependency injection