Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC: Appropriate extension point for wrapping API Responses

I have a simple REST API, build using Spring MVC @Controllers and @RequestMapping. I'd like to start wrapping responses to provide additional with metadata.

For example, given a call that would return

HTTP GET: /users/1
{
    "userName" : "Jack Jackerson"
}

I'd like to wrap it, as follows:

{
    "metadata" : 
    {
        "callLimit" : "50",
        "callsRemaining" : "49"
    },
    "result" :
    {
        "userName" : "Jack Jackerson"
    }
}  ..etc..

Additionally, I'd like to support standard set of parameters for managing lists (limit and offset).

As this touches all the api methods, I'd like to implement it as a decorator of some spring internal service, so the methods themselves can focus on their actual logic, and keep this boilerplate stuff centralized.

I've started down the path of decorating the HttpMessageConverter's that are registered, and wrapping them with a decorator.

However, this doesn't provide me access to the inbound request for methods that don't declare a @RequestBody. (Many don't)

Ideally, I need to be higher in the stack - RequestResponseBodyMethodProcessor.writeWithMessageConverters() looks like a good candidate, but I don't know how to hook in here.

What options are available with Spring MVC to implement this type of API-Wide processing of request / responses?

like image 657
Marty Pitt Avatar asked Nov 01 '12 08:11

Marty Pitt


1 Answers

Here's the implementation I used:

public class MetadataInjectingReturnValueHandler implements HandlerMethodReturnValueHandler {

    private final HandlerMethodReturnValueHandler delegate;

    public MetadataInjectingReturnValueHandler(HandlerMethodReturnValueHandler delegate)
    {
        this.delegate = delegate;
    }
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return delegate.supportsReturnType(returnType);
    }

    @Override
    public void handleReturnValue(Object returnValue,
            MethodParameter returnType, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest) throws Exception {
        returnValue = wrapResult(returnValue); //Omitted
        delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
}



@Component
public class MetadataInjectionFactoryBean implements InitializingBean {

    @Autowired
    private RequestMappingHandlerAdapter adapter;
    @Override
    public void afterPropertiesSet() throws Exception {
        HandlerMethodReturnValueHandlerComposite returnValueHandlers = adapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> handlers = Lists.newArrayList(returnValueHandlers.getHandlers());
        decorateHandlers(handlers);
        adapter.setReturnValueHandlers(handlers);
    }
    private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        for (HandlerMethodReturnValueHandler handler : handlers) {
            if (handler instanceof RequestResponseBodyMethodProcessor)
            {
                MetadataInjectingReturnValueHandler decorator = new MetadataInjectingReturnValueHandler(handler);
                int index = handlers.indexOf(handler);
                handlers.set(index, decorator);
                log.info("Metadata Injecting decorator wired up");
                break;
            }
        }       
    }

}
like image 130
Marty Pitt Avatar answered Sep 21 '22 04:09

Marty Pitt