Spring + Jackson: Wrapping Response Body in response object - java

This may be a strange question, although I wonder why it hasn't been asked or proposed before... so please correct me if any ignorance.
First off, I am using Jackson in conjunction with Spring and the #ResponseBody annotation.
Currently, for every request handler I am returning a "Response" wrapper object, as that is what the client expects. This wrapper is quite simple:
{ "response": { "data" : ACTUAL_DATA } }
Thing is, I'm not a fan of explicitly wrapping each return value for all my request handlers. I also do not like having to unwrap these response wrappers in my unit tests.
Rather, I wonder if it were possible to return the ACTUAL_DATA as it were, and to intercept and wrap this data elsewhere.
If this is in fact possible, would it then be possible to read the annotations attached to the intercepted request handler? This way I can use custom annotations to decide how to wrap the data.
For example something like this would be amazing (note that #FetchResponse and #ResponseWrapper are made up proposed annotations):
#RequestMapping(...)
#FetchResponse
#ResponseBody
public List<User> getUsers() {
...
}
#ResponseWrapper(FetchResponse.class)
public Object wrap(Object value) {
ResponseWrapper rw = new ResponseWrapper();
rw.setData(value);
return rw;
}
Anyone familiar with this territory? Or alternatively, and reasons why this might be bad practice?

Well, looks like I'm looking for Spring's "ResponseBodyAdvice" and "AbstractMappingJacksonResponseBodyAdvice".

For anybody looking on more info on this topic: I was facing the same issue as well, and thanks to the tip of kennyg, i managed to come up with the following solution:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
#ControllerAdvice
public class JSendAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof JSendResponse) {
return body;
}
return new JSendResponse<>().success(body);
}
}
This solution wraps all the objects returned in your controllers inside a (for this example) JSendResponse class, which saves you the hassle of returning JSendResponses in all of your controller methods.

I know it's been a while since the answer was accepted but I recently stumbled onto an issue with Jackson that allowed me to discover a problem with using ResponseBodyAdvice.
Jackson will not correctly serialize your polymorphic types that use #JsonTypeInfo / #JsonSubTypes if during runtime the values of your types are not known: i.e. for example if you have a generic container type like class ResponseWrapper<T> { List<T> objects; }. That is unless you provide Jackson with specialization of that generic type before you ask it to serialize your value, refer to Why does Jackson polymorphic serialization not work in lists? . Spring does this for you when you return say a list of T and that T is known because it's provided explicitly in the method return type (as in public List<MyEntity> getAllEntities();).
If you simply implement ResponseBodyAdvice and return a new, wrapped value from beforeBodyWrite() then Spring will no longer know your full generic type with its specialization, and it will serialize your response as ResponseWrapper<?> instead of ResponseWrapper<MyEntity>.
The only way around this is to both extend from AbstractJackson2HttpMessageConverter and override writeInternal(). See how the method treats the type here: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java#L437
And you also need to implement a Controller advice using AbstractMappingJacksonResponseBodyAdvice and your own custom MappingJacksonValue that includes Type targetType that custom HttpMessageConverter will use.
ResponseWrapper
public class ResponseWrapper<T> {
#Nullable Error error;
T result;
public ResponseWrapper(T result) {
this.result = result;
}
}
WrappingAdvice
#Component
public class WrappingAdvice extends AbstractMappingJacksonResponseBodyAdvice {
#Override
protected MappingJacksonValue getOrCreateContainer(Object body) {
MappingJacksonValue cnt = super.getOrCreateContainer(body);
if (cnt instanceof MyMappingJacksonValue) {
return cnt;
}
return new MyMappingJacksonValue(cnt);
}
#Override
protected void beforeBodyWriteInternal(
MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
MyMappingJacksonValue cnt = (MyMappingJacksonValue) bodyContainer;
Type targetType = getTargetType(bodyContainer.getValue(), returnType);
cnt.setValue(new ResponseWrapper(cnt.getValue()));
cnt.setTargetType(TypeUtils.parameterize(
ResponseWrapper.class,
targetType));
}
/**
* This is derived from AbstractMessageConverterMethodProcessor
*/
private Type getTargetType(Object value, MethodParameter returnType) {
if (value instanceof CharSequence) {
return String.class;
}
Type genericType;
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
genericType = ResolvableType.forType(returnType.getGenericParameterType()).getGeneric().getType();
} else {
genericType = returnType.getGenericParameterType();
}
return GenericTypeResolver.resolveType(genericType, returnType.getContainingClass());
}
public static class MyMappingJacksonValue extends MappingJacksonValue {
private Type targetType;
public MyMappingJacksonValue(MappingJacksonValue other) {
super(other.getValue());
setFilters(other.getFilters());
setSerializationView(other.getSerializationView());
}
public Type getTargetType() {
return targetType;
}
public void setTargetType(Type targetType) {
this.targetType = targetType;
}
}
}
JsonHttpMessageBodyConverter
#Component
public class JsonHttpMessageBodyConverter extends AbstractJackson2HttpMessageConverter {
// omitted all constructors
#Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if (object instanceof WrapAPIResponseAdvice.MyMappingJacksonValue) {
type = ((WrapAPIResponseAdvice.MyMappingJacksonValue) object).getTargetType();
}
super.writeInternal(object, type, outputMessage);
}
}

Related

SpringBoot: Interceptor to read particular field from request and set it in the response

All requests and responses handled by our Spring Rest Controller has a Common section which has certain values:
{
"common": {
"requestId": "foo-bar-123",
"otherKey1": "value1",
"otherKey2": "value2",
"otherKey3": "value3"
},
...
}
Currently all my controller functions are reading the common and copying it into the response manually. I would like to move it into an interceptor of some sort.
I tried to do this using ControllerAdvice and ThreadLocal:
#ControllerAdvice
public class RequestResponseAdvice extends RequestBodyAdviceAdapter
implements ResponseBodyAdvice<MyGenericPojo> {
private ThreadLocal<Common> commonThreadLocal = new ThreadLocal<>();
/* Request */
#Override
public boolean supports(
MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return MyGenericPojo.class.isAssignableFrom(methodParameter.getParameterType());
}
#Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
var common = (MyGenericPojo)body.getCommon();
if (common.getRequestId() == null) {
common.setRequestId(generateNewRequestId());
}
commonThreadLocal(common);
return body;
}
/* Response */
#Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return MyGenericPojo.class.isAssignableFrom(returnType.getParameterType());
}
#Override
public MyGenericPojo beforeBodyWrite(
MyGenericPojo body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
body.setCommon(commonThreadLocal.get());
commonThreadLocal.remove();
return body;
}
}
This works when I test sending one request at a time. But, is it guaranteed that afterBodyRead and beforeBodyWrite is called in the same thread, when multiple requests are coming?
If not, or even otherwise, what is the best way of doing this?
I think that there is no need of your own ThreadLocal you can use request attributes.
#Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
var common = ((MyGenericPojo) body).getCommon();
if (common.getRequestId() == null) {
common.setRequestId(generateNewRequestId());
}
Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.map(ServletRequestAttributes::getRequest)
.ifPresent(request -> {request.setAttribute(Common.class.getName(), common);});
return body;
}
#Override
public MyGenericPojo beforeBodyWrite(
MyGenericPojo body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.map(rc -> rc.getAttribute(Common.class.getName(), RequestAttributes.SCOPE_REQUEST))
.ifPresent(o -> {
Common common = (Common) o;
body.setCommon(common);
});
return body;
}
EDIT
Optionals can be replaced with
RequestContextHolder.getRequestAttributes().setAttribute(Common.class.getName(),common,RequestAttributes.SCOPE_REQUEST);
RequestContextHolder.getRequestAttributes().getAttribute(Common.class.getName(),RequestAttributes.SCOPE_REQUEST);
EDIT 2
About thread safety
1) standard servlet-based Spring web application we have thread-per-request scenario. Request is processed by one of the worker threads through all the filters and routines. The processing chain will be executed by the very same thread from start to end . So afterBodyRead and beforeBodyWrite guaranteed to be executed by the very same thread for a given request.
2) Your RequestResponseAdvice by itself is stateless. We used RequestContextHolder.getRequestAttributes() which is ThreadLocal and declared as
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
And ThreadLocal javadoc states:
his class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via
its get or set method) has its own, independently initialized copy of
the variable.
So I don't see any thread-safety issues into this sulotion.
Quick answer: RequestBodyAdvice and ResponseBodyAdvice are invoked within the same thread for one request.
You can debug the implementation at: ServletInvocableHandlerMethod#invokeAndHandle
The way you're doing it is not safe though:
ThreadLocal should be defined as static final, otherwise it's similar to any other class property
Exception thrown in body will skip invocation of ResponseBodyAdvice (hence the threadlocal data is not removed)
"More safe way": Make the request body supports any class (not just MyGenericPojo), in the afterBodyRead method:
First call ThreadLocal#remove
Check if type is MyGenericPojo then set the common data to threadlocal
Also I have already answered this thread, but I prefer another way to solve such kind of problems
I would use Aspect-s in this scenario.
I have written included this in one file but you should create proper separate classes.
#Aspect
#Component
public class CommonEnricher {
// annotation to mark methods that should be intercepted
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface EnrichWithCommon {
}
#Configuration
#EnableAspectJAutoProxy
public static class CommonEnricherConfig {}
// Around query to select methods annotiated with #EnrichWithCommon
#Around("#annotation(com.example.CommonEnricher.EnrichWithCommon)")
public Object enrich(ProceedingJoinPoint joinPoint) throws Throwable {
MyGenericPojo myGenericPojo = (MyGenericPojo) joinPoint.getArgs()[0];
var common = myGenericPojo.getCommon();
if (common.getRequestId() == null) {
common.setRequestId(UUID.randomUUID().toString());
}
//actual rest controller method invocation
MyGenericPojo res = (MyGenericPojo) joinPoint.proceed();
//adding common to body
res.setCommon(common);
return res;
}
//example controller
#RestController
#RequestMapping("/")
public static class MyRestController {
#PostMapping("/test" )
#EnrichWithCommon // mark method to intercept
public MyGenericPojo test(#RequestBody MyGenericPojo myGenericPojo) {
return myGenericPojo;
}
}
}
We have here an annotation #EnrichWithCommon which marks endpoints where enrichment should happen.
If it's only a meta data that you copy from the request to the response, you can do one of the followings:
1- store the meta in the request/response header,and just use filters to do the copy :
#WebFilter(filterName="MetaDatatFilter", urlPatterns ={"/*"})
public class MyFilter implements Filter{
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("metaData", httpServletRequest.getHeader("metaData"));
}
}
2- move the work into the service layer where you can do the cope through a reusable common method, or have it run through AOP
public void copyMetaData(whatEverType request,whatEverType response) {
response.setMeta(request.getMeta);
}

How can I instantiate a specific sub-type for a #RequestBody parameter based on the requested URI for a Spring MVC controller method?

Given the following basic domain model:
abstract class BaseData { ... }
class DataA extends BaseData { ... }
class DataB extends BaseData { ... }
I want to write a Spring MVC controller endpoint thus ...
#PostMapping(path="/{typeOfData}", ...)
ResponseEntity<Void> postData(#RequestBody BaseData baseData) { ... }
The required concrete type of baseData can be inferred from the typeOfData in the path.
This allows me to have a single method that can handle multiple URLs with different body payloads. I would have a concrete type for each payload but I don't want to have to create multiple controller methods that all do the same thing (albeit each would do very little).
The challenge that I am facing is how to "inform" the deserialization process so that the correct concrete type is instantiated.
I can think of two ways to do this.
First use a custom HttpMessageConverter ...
#Bean
HttpMessageConverter httpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
#Override
public Object read(final Type type, final Class<?> contextClass, final HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// TODO How can I set this dynamically ?
final Type subType = DataA.class;
return super.read(subType, contextClass, inputMessage);
}
};
}
... which gives me the challenge to determine the subType based on the HttpInputMessage. Possibly I could use a Filter to set a custom header earlier when the URL is available to me, or I could use a ThreadLocal also set via a Filter. Neither sounds ideal to me.
My second approach would be to again use a Filter and this time wrap the incoming payload in an outer object which would then provide the type in a way that enables Jackson to do the work via #JsonTypeInfo. At the moment this is probably my preferred approach.
I have investigated HandlerMethodArgumentResolver but if I try to register a custom one it is registered AFTER the RequestResponseBodyMethodProcessor and that class takes priority.
Hmm, so after typing all of that out I had a quick check of something in the RequestResponseBodyMethodProcessor before posting the question and found another avenue to explore, which worked neatly.
Excuse the #Configuration / #RestController / WebMvcConfigurer mash-up and public fields, all for brevity. Here's what worked for me and achieved exactly what I wanted:
#Configuration
#RestController
#RequestMapping("/dummy")
public class DummyController implements WebMvcConfigurer {
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#interface BaseData {}
public static class AbstractBaseData {}
public static class DataA extends AbstractBaseData {
public String a;
}
public static class DataB extends AbstractBaseData {
public String b;
}
private final MappingJackson2HttpMessageConverter converter;
DummyController(final MappingJackson2HttpMessageConverter converter) {
this.converter = converter;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(
new RequestResponseBodyMethodProcessor(Collections.singletonList(converter)) {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(BaseData.class)
&& parameter.getParameterType() == AbstractBaseData.class;
}
#Override
protected <T> Object readWithMessageConverters(
NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
throws IOException, HttpMediaTypeNotSupportedException,
HttpMessageNotReadableException {
final String uri =
webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI();
return super.readWithMessageConverters(
webRequest, parameter, determineActualType(webRequest, uri));
}
private Type determineActualType(NativeWebRequest webRequest, String uri) {
if (uri.endsWith("data-a")) {
return DataA.class;
} else if (uri.endsWith("data-b")) {
return DataB.class;
}
throw new HttpMessageNotReadableException(
"Unable to determine actual type for request URI",
new ServletServerHttpRequest(
webRequest.getNativeRequest(HttpServletRequest.class)));
}
});
}
#PostMapping(
path = "/{type}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<? extends AbstractBaseData> post(#BaseData AbstractBaseData baseData) {
return ResponseEntity.ok(baseData);
}
}
The key to this is that I stopped using #RequestBody because that is what was preventing me overriding the built-in behaviour. By using #BaseData instead I get a HandlerMethodArgumentResolver that uniquely supports the parameter.
Other than that it was a case of assembling the two objects that already did what I needed, so autowire a MappingJackson2HttpMessageConverter and instantiate a RequestResponseBodyMethodProcessor with that one converter. Then pick the right method to override so that I could control what parameter type was used at a point that I had access to the URI.
Quick test. Given the following payload for both requests ...
{
"a": "A",
"b": "B"
}
POST http://localhost:8081/dummy/data-a
... gives a response of ...
{
"a": "A"
}
POST http://localhost:8081/dummy/data-b
... gives a response of ...
{
"b": "B"
}
In our real-world example this means that we will be able to write one method each that supports the POST / PUT. We need to build the objects and configure the validation possibly - or alternatively if we use OpenAPI 3.0 which we are investigating we could generate the model and validate without writing any further code ... but that's a separate task ;)

How to retrieve resource method's Annoations in MessageBodyWriter in Jersey?

jersey 2.21.
I have a resource file like below
……
#POST
#Path("/userReg")
#Produces("application/json;charset=UTF-8")
public JsonResp userReg(UserRegReq userRegReq) throws LoginNameExists {
HttpHeaderUtils.parseHeaders(userRegReq, headers);
//JsonResp is a custom java class.
JsonResp result = new JsonResp();
//will throw LoginNameExists
User user = userManager.register(userRegReq.getLoginName(), userRegReq.getPassword());
//success
result.setResult(0);
result.setData(user.getId);
return result;
}
……
To return the result to client, I implement a custom MessageBodyWriter like below
#Produces("application/json")
public class MyRespWriter implements MessageBodyWriter<JsonResp> {
#Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return type == JsonResp.class;
}
#Override
public long getSize(JsonResp jsonResp, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return 0;
}
#Override
public void writeTo(JsonResp jsonResp, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
//if these no exception in userReg(),
//the parameter annotations contains the annotations
//such as POST, Path, Produces;
//but if there is an exception in userReg(),
//the parameter annotations contains none of POST, Path, Produces;
//So, is there any way to retrieve the original annotations all along?
//JsonUtils is a custom java class.
String data = JsonUtils.toJsonString(jsonResp);
Writer osWriter = new OutputStreamWriter(outputStream, "UTF-8");
osWriter.write(data);
osWriter.flush();
}
}
And to handle the exceptions, I implement a ExceptionMapper like this:
public class MyExceptionMapper implements ExceptionMapper<Exception> {
public Response toResponse(Exception e) {
JsonResp result = new JsonResp();
//error
result.setResult(-1);
result.setErrMsg("System error.");
return Response.ok(result, MediaType.APPLICATION_JSON_TYPE).status(Response.Status.OK).build();
}
}
Now, if everything is ok and there’s no exception, the code execution router is userReg() -> MyRespWriter.writeTo(), the parameter "annotations" of MyRespWriter.writeTo() contains the correct annotations of method userReg(), such as POST, Path, Produces.
But if userReg() throws exception, the code execution router is userReg() -> MyExceptionMapper.toResponse() -> MyRespWriter.writeTo(), the parameter "annotations" of method MyRespWriter.writeTo() has none of the annotations of method userReg().
I want to know, is there any way that MyRespWriter.writeTo() can retrieve the original annotations all along?
You can inject ResourceInfo, then get the Method with ri.getResourceMethod(), then call method.getAnnotations() to get the annotations.
public class MyRespWriter implements MessageBodyWriter<JsonResp> {
#Context
ResourceInfo ri;
...
Annotations[] annos = ri.getResourceMethod().getAnnotations();

Spring MVC / Rest - return standard response

In Spring MVC, I am writing something using a controller, essentially performing REST plus a few additions.
Most of the functionality is called from ExtJS to spring, and to adhere to their conventions, the return is always a json object in the format:
{
success: true,
data: { ... }
}
or in the case of an error:
{
success: false,
message: { ... }
}
For the exceptions it is easy - Spring provides #ControllerAdvice and #ExceptionHandler etc so that errors can be trapped in one place and the standard response works.
However is there an easy way to force all of the valid responses to be passed through in the same way, so that the success element could be sent every time with the data sent back in the data object. in other words, currently for every call I have to do something like
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public HashMap<String, Object> add(#RequestBody ManagedView targetTest) {
ManagedView result = service.add(targetTest);
HashMap<String, Object> ret = new HashMap<String, Object>();
ret.put("success", new Boolean(true));
ret.put("data", result);
return ret;
}
whereas I would like to do is have something like
#RequestMapping(method = RequestMethod.POST)
public ManagedView add(#RequestBody ManagedView targetTest) {
return service.add(targetTest);
}
// and each response intercepted by something like this
public HashMap<String, Object> addWrapper(Object result) {
HashMap<String, Object> ret = new HashMap<String, Object>();
ret.put("success", new Boolean(true));
ret.put("data", result);
return ret;
}
I appreciate I could just create a standard utility type function to do this but is there something that does this out of the box without me having to add the same code to all of the methods - preferably set globally.
Thanks in advance
I think you can use org.springframework.web.servlet.HandlerInterceptor.
It contains preHandle(),postHandle() and afterCompletion() methods. In your case you can use postHandle() method to manipulate the ModelAndView object before render it to view page,and it will be common point for all the controllers.
This is a solution I just used, I went through several approaches until I found this one, which is the simplest one I think:
#ControllerAdvice
public class StandardResponseBodyWrapAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
returnType.getMethodAnnotation(ResponseBody.class) != null);
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
ApiResponseDTO<Object> resp = new ApiResponseDTO<>();
resp.setVersion("1.0");
resp.setSuccess(true);
resp.setData(body);
return resp;
}
}

Jersey route to #Consumes based on MediaType parameter?

I suspect this can't be done, but maybe there's a trick I'm missing. I want to use to different methods that take the same MediaType, but have different parameters to the mediatype. Perhaps this is abusing MediaType parameters...
#POST
#Consumes("application/json;internal=true")
public Response handleInternal(String request) {
}
#POST
#Consumes("application/json;internal=false")
public Response handleExternal(String request) {
}
Jersey complains I have two methods consuming the same MediaType, which is true. I was hoping it'd go on to pick the right one by the parameter. Is there some trick to making this work? In a nutshell, I have two use cases for how to treat the information coming in (specifically, domain level validation) and this seemed like a decent way to distinguish between those two.
You could use a MessageBodyReader along with two user types, one for internal json and the other for external json
1- Create two types than extends String (via delegation -using lombok is easier-):
#RequiredArgsConstructor
public class InternalJSON {
#Delegate
private final String _theJSONStr;
}
#RequiredArgsConstructor
public class ExternalJSON {
#Delegate
private final String _theJSONStr;
}
2- Create the MessageBodyReader type
#Provider
public class MyRequestTypeMapper
implements MessageBodyReader<Object> {
#Override
public boolean isReadable(final Class<?> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
// this matches both application/json;internal=true and application/json;internal=false
return mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
}
#Override
public Object readFrom(final Class<Object> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String,String> httpHeaders,
final InputStream entityStream) throws IOException,
WebApplicationException {
if (mediaType.getSubType().equals("internal=true") {
// Build an InternalJSON instance parsing entityStream
// ... perhaps using JACKSON or JAXB by hand
} else if (mediaType.getSubType().equals("internal=false") {
// Build an ExternalJSON instance parsing entityStream
// ... perhaps using JACKSON or JAXB by hand
}
}
}
3- Register your MessageBodyReader at the Application (this is optional since jersey will scan the classpath for #Provider annotated types
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
...
s.add(MyRequestTypeMapper .class);
return s;
}
4- Reformat your rest methods usgin the two user types for internal and external json

Categories