R P
R P

Reputation: 45

Spring MVC Controller pre process request body

I am working on spring REST APIs. In requirements, there are 2 POST requests with same URL but different request body. Since Spring MVC must have unique mappings across controller, I have to pre-process the request body to map to a specific POJO.

On the basis of session_type in request body, I have to map the request to specific POJO (JSON -> JAVA POJO).

For example, if 'session_type' in request body is 'typeX' then the request should map to ClassX POJO. If 'session_type' in request body is 'typeY' then the request should map to ClassY POJO.

If there a way to do it using some kind of requestbody annotation?

Upvotes: 1

Views: 5075

Answers (4)

Raedwald
Raedwald

Reputation: 48692

there are 2 POST requests with same URL but different request body

For a RESTful interface, the same URL should always indicate the same resource. The body of a request may contain different representations of that resource. You could create different HttpMessageContverter classes for the two different kinds of representation.

Upvotes: 0

Emerson Farrugia
Emerson Farrugia

Reputation: 11363

GETs shouldn't have request bodies, or at least if they do, the server side isn't required to do anything with them. As you describe it, this API isn't RESTful.

Assuming you don't care about that, try creating a controller method that takes a parent class of TypeX and TypeY, or interface that both TypeX and TypeY implement, annotate it with @SomethingMeaningfulToYou, then use a web argument method resolver to instantiate the child class you want.

It's a hack around a broken API though.

Upvotes: 0

n1ckolas
n1ckolas

Reputation: 4450

If you want to bind typeX and typeY, then you definitely need 2 handlers. But, why wouldn't we use param option of @RequestMapping:

@RequestMapping(method = RequestMethod.POST, 
        value = "/url", params = "session_type=typeX")
public String handleTypeX(@RequestBody @ModelAttribute TypeX typeX){
    //TODO implement
}

@RequestMapping(method = RequestMethod.POST,
        value = "/url", params = "session_type=typeY")
public String handleTypeY(@RequestBody @ModelAttribute TypeY typeY){
    //TODO implement
}

If you need some preparations (f.e. normalize params or perform model binding manually), then the approach above you may combine along with @InitBinder, but please note, that @InitBinder needs exact ULR's rules along with @ModelAttribute parameters in handlers.

EDIT: In Spring MVC there is no possibility to use 2 handlers for exact URL, i.e. when method/URL/params/consumes type are the same.

Thus I suggest use unified handler, where you would check necessary parameter and then manually convert into corresponding class. For finding necessary class I suppose it would be better to use Strategy pattern:

//class resolver according "session_type" parameter
//note, that you can use Spring autowiring capabilities
private final Map<String, Class> TYPES_CONTEXT = new HashMap<String, Class>(){
    {
        this.put("x", TypeX.class);
        this.put("y", TypeY.class);
        //TODO probably other classes
    }
}


@RequestMapping(method = RequestMethod.POST,
        value = "/url")
public @ResponseBody String handleAnyType(@RequestBody Map<String, String> body){
    String sessionType = body.get("session_type");

    //TODO handle case if sessionType is NULL

    Class convertedClass = TYPES_CONTEXT.get(sessionType);

    //TODO handle case if class is not found

    Object actualObject = objectMapper.convertValue(body, convertedClass);

    //now we use reflection for actual handlers, but you may refactor this in the way you want, f.e. again with Strategy pattern
    //note that current approach there should be contract for methods names
    Method actualHandler = this.getClass().getMethod("handle" + actualObject.getClass().getSimpleName());

    return (String)actualHandler.invoke(this, actualObject);
}

public String handleTypeX(TypeX typeX){
    //TODO implement
}

public String handleTypeY(TypeY typeY){
    //TODO implement
}

//TODO probably other methods

This approach doesn't handle validation and some things were omitted, but I believe this might be helpful.

Upvotes: 5

Michail Nikolaev
Michail Nikolaev

Reputation: 3775

I think you should created controller with one method for both types, and call required component\method in it depending on typeX or typeY.

Upvotes: 0

Related Questions