SpringBoot: Wrap the Controller Response into Another Object - java

Consider the below Controller method,
#GetMapping("/getdata")
public Data getDetails() {
try {
Data obj = template.getForObject("http://localhost:8090/details/get", Data.class);
return obj;
} catch (Exception e) {
.....
}
}
Let's say the response object for the above endpoint looks like,
data : {
name : "ABCD",
age : "20"
}
Now what I am trying to achieve is to Wrap the entire response object under another object, so the final response should look like,
{
status : "SOMETHING",
response : {
name : "ABCD",
age : "20"
},
extra : null
}
So the issue is, I don't want to create a function under each controller method to sent values to this Wrapper Object. My question is, Is there any possibility in Spring Framework that allows me to create a Global Wrapper function somewhere, and it will automatically pick it and wrap the response from the controller?

You will need to implement a ResponseBodyAdvice as a #ControllerAdvice. First, you need to have a model for your generic response.
public class GenericResponse<T> {
private String status;
private String extra;
private T response;
}
Then you need to implement the ResponseBodyAdvice itself:
#ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object response, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
GenericResponse<Object> genericResponse = new GenericResponse<>();
output.setStatus("SOMETHING"); // I guess that you need some logic here
output.setResponse(response);
return genericResponse;
}
}

Related

Retrofit 2 - How to make request without Call object

I use retrofit 2 and I have UserService with rest methods which return objects Call.
I would like to invoke these methods and return just data object.
I have this:
#GET("users")
Call<List<UserDTO>> getUsers();
but I want:
#GET("users")
List<UserDTO> getUsers();
I know that was possible by default in retrofit 1.9 but i couldn't find solution for this problem.
I dont want invoke method, execute call, get body and make try..catch every time when I use it.
When I invoke method from my second example I receive error:
Could not locate call adapter for java.util.List<>
Is it possible to handle this case in any adapter? And how to do it ?
I resolved this problem like that:
public class CustomCallAdapter<T> implements CallAdapter<T, T> {
private Type returnType;
public CustomCallAdapter(Type returnType) {
this.returnType = returnType;
}
#Override
public Type responseType() {
return returnType;
}
#Override
public T adapt(Call<T> call) {
try {
return call.execute().body();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static class Factory extends CallAdapter.Factory {
#Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
return new CustomCallAdapter(returnType);
}
}
}

No Content in Spring Boot Rest

How do I configure Spring Boot to return 204 in GET methods (typically findAll methods) when the method does not fetch records? I would not like to do treatment in each method, type the code below:
if(!result)
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
return new ResponseEntity<Void>(HttpStatus.OK)
I'd like to transform this method:
#GetMapping
public ResponseEntity<?> findAll(){
List<User> result = service.findAll();
return !result.isEmpty() ?
new ResponseEntity<>(result, HttpStatus.OK) : new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
In this one:
#GetMapping
public List<User> findAll(){
return service.findAll();
}
If the result from findAll() is empty or null then my controller should return 204 instead of 200.
You could register a custom ResponseBodyAdvice which allows customizing the response of #ResponseBody or ResponseEntity handler methods (right before the content is being serialized by a MessageConverter):
#ControllerAdvice
class NoContentControllerAdvice implements ResponseBodyAdvice<List<?>> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return List.class.isAssignableFrom(returnType.getParameterType());
}
#Override
public List<?> beforeBodyWrite(List<?> body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body.isEmpty()) {
response.setStatusCode(HttpStatus.NO_CONTENT);
}
return body;
}
}

How to wrap JSON response from Spring REST repository?

I have a spring REST controller which returns the following JSON payload:
[
{
"id": 5920,
"title": "a title"
},
{
"id": 5926,
"title": "another title",
}
]
The REST controller with its corresponding get request method:
#RequestMapping(value = "example")
public Iterable<Souvenir> souvenirs(#PathVariable("user") String user) {
return new souvenirRepository.findByUserUsernameOrderById(user);
}
Now the Souvenir class is a pojo:
#Entity
#Data
public class Souvenir {
#Id
#GeneratedValue
private long id;
private String title;
private Date date;
}
Regarding https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside and http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ I would like to wrap the response within an object so that it is not vulnerable to attacks. Of course I could do something like this:
#RequestMapping(value = "example")
public SouvenirWrapper souvenirs(#PathVariable("user") String user) {
return new SouvenirWrapper(souvenirRepository.findByUserUsernameOrderById(user));
}
#Data
class SouvenirWrapper {
private final List<Souvenir> souvenirs;
public SouvenirWrapper(List<Souvenir> souvenirs) {
this.souvenirs = souvenirs;
}
}
This results in the following JSON payload:
{
"souvenirs": [
{
"id": 5920,
"title": "a title"
},
{
"id": 5926,
"title": "another title",
}
]
}
This helps in preventing some JSON/Javascript attacks but I don't like the verbosity of the Wrapper class. I could of course generalize the above approach with generics. Is there another way to achieve the same result in the Spring ecosystem (with an annotation or something similar)? An idea would be that the behaviour is done by Spring automatically, so whenever there is a REST controller that returns a list of objects, it could wrap those objects within an object wrapper so that no direct list of objects get serialized?
I ended up with the following solution (thanks to #vadim-kirilchuk):
My controller still looks exactly as before:
#RequestMapping(value = "example")
public Iterable<Souvenir> souvenirs(#PathVariable("user") String user) {
return new souvenirRepository.findByUserUsernameOrderById(user);
}
I added the following implementation of ResponseBodyAdvice which basically gets executed when a controller in the referenced package tries to respond to a client call (to my understanding):
#ControllerAdvice(basePackages = "package.where.all.my.controllers.are")
public class JSONResponseWrapper implements ResponseBodyAdvice {
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
#SuppressWarnings("unchecked")
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof List) {
return new Wrapper<>((List<Object>) body);
}
return body;
}
#Data // just the lombok annotation which provides getter and setter
private class Wrapper<T> {
private final List<T> list;
public Wrapper(List<T> list) {
this.list = list;
}
}
}
So with this approach I can keep my existing method signature in my controller (public Iterable<Souvenir> souvenirs(#PathVariable("user") String user)) and future controllers don't have to worry about wrapping its Iterables within such a wrapper because the framework does this part of the work.
Based in your solution I ended up with a more flexible option. First I created an annotation to activate the behaviour whenever I want and with a customizable wrapper attribute name:
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface JsonListWrapper {
String name() default "list";
}
This annotation can be used on the entity class so it's applied to all controllers responses of List<MyEntity> or can be used for specifics controller methods.
The ControllerAdvice will look like this (note that I return a Map<Object> to dynamically set the wrapper name as a map key).
public class WebResponseModifierAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(final Object body,
final MethodParameter returnType,
final MediaType selectedContentType,
final Class<? extends HttpMessageConverter<?>> selectedConverterType,
final ServerHttpRequest request,
final ServerHttpResponse response) {
if (body instanceof List && selectedContentType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
return checkListWrapper(body, returnType);
} else {
return body;
}
}
/**
* Detects use of {#link JsonListWrapper} in a response like <tt>List<T></tt>
* in case it's necesary to wrap the answer.
*
* #param body body to be written in the response
* #param returnType controller method return type
* #return
*/
private Object checkListWrapper(final Object body,
final MethodParameter returnType) {
String wrapperName = null;
try {
// Checks class level annotation (List<C>).
String typeName = "";
String where = "";
String whereName = "";
// Gets generic type List<T>
Type[] actualTypeArgs = ((ParameterizedType) returnType.getGenericParameterType()).getActualTypeArguments();
if (actualTypeArgs.length > 0) {
Type listType = ((ParameterizedType) returnType.getGenericParameterType()).getActualTypeArguments()[0];
if (listType instanceof ParameterizedType) {
Type elementType = ((ParameterizedType) listType).getActualTypeArguments()[0];
elementType.getClass();
try {
typeName = elementType.getTypeName();
Class<?> clz = Class.forName(typeName);
JsonListWrapper classListWrapper = AnnotationUtils.findAnnotation(clz, JsonListWrapper.class);
if (classListWrapper != null) {
where = "clase";
whereName = typeName;
wrapperName = classListWrapper.name();
}
} catch (ClassNotFoundException e) {
log.error("Class not found" + elementType.getTypeName(), e);
}
}
}
// Checks method level annotations (prevails over class level)
JsonListWrapper methodListWrapper = AnnotationUtils.findAnnotation(returnType.getMethod(), JsonListWrapper.class);
if (methodListWrapper != null) {
where = "método";
whereName = returnType.getMethod().getDeclaringClass() + "." + returnType.getMethod().getName() + "()";
wrapperName = methodListWrapper.name();
}
if (wrapperName != null) {
if (log.isTraceEnabled()) {
log.trace("#JsonListWrapper detected {} {}. Wrapping List<{}> in \"{}\"", where, whereName, typeName, wrapperName);
}
final Map<String, Object> map = new HashMap<>(1);
map.put(wrapperName, body);
return map;
}
} catch(Exception ex) {
log.error("Error getting type of List in the response", ex);
}
return body;
}
}
This way you can use either:
#JsonListWrapper(name = "souvenirs")
public class Souvenir {
//...class members
}
...or
#JsonListWrapper(name = "souvenirs")
#RequestMapping(value = "example")
public ResponseEntity<List<Souvenir>> souvenirs(#PathVariable("user") String user) {
return new souvenirRepository.findByUserUsernameOrderById(user);
}

Jersey: hardcode POST/PUT ObjectMapper, without needing Content-Type header

I have a Jersey 1.19.1 resource that implements a #PUT and #POST method. The #PUT method expects a JSON string as the input/request body, while the #POST method accepts plain text.
For the JSON mapping I am using Jackson 2.8.
Since the resource is defined to work this way, I don't want the client to be required to specify a Content-Type request header, just because Jersey needs it to figure out which ObjectMapper to use on the request body.
What I want instead, is to tell Jersey "Use this ObjectMapper for this #PUT input", or "Always assume this input will have an application/json Content-Type on this method."
#Produces(MediaType.APPLICATION_JSON)
#Path("/some/endpoint/{id}")
public class MyResource {
#PUT
public JsonResult put(
#PathParam("id") String id,
// this should always be deserialized by Jackson, regardless of the `Content-Type` request header.
JsonInput input
) {
log.trace("PUT {}, {}, {}", id, input.foo, input.bar);
return new JsonResult("PUT result");
}
#POST
public JsonResult post(
#PathParam("id") String id,
// this should always be treated as plain text, regardless of the `Content-Type` request header.
String input
) {
log.trace("POST {}, {}", id, input);
return new JsonResult("POST result");
}
}
I only found this answer, but that's not what I'm looking for, as the solution there seems to be that the client should be required to add the correct Content-Type header, or otherwise do the object mapping manually.
I managed to come up with a workaround. Instead of declaring which ObjectMapper to use on a Jersey resource method, I decided to create a ResourceFilter, corresponding ResourceFilterFactory, and an annotation type. Whenever a resource class or method is annotated with this type, the ResourceFilter will override the request's Content-Type to whatever is declared in the annotation's parameter.
Here's my code:
OverrideInputType annotation:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface OverrideInputType {
// What the Content-Type request header value should be replaced by
String value();
// which Content-Type request header values should not be replaced
String[] except() default {};
}
OverrideInputTypeResourceFilter:
public class OverrideInputTypeResourceFilter implements ResourceFilter, ContainerRequestFilter {
private MediaType targetType;
private Set<MediaType> exemptTypes;
OverrideInputTypeResourceFilter(
#Nonnull String targetType,
#Nonnull String[] exemptTypes
) {
this.targetType = MediaType.valueOf(targetType);
this.exemptTypes = new HashSet<MediaType>(Lists.transform(
Arrays.asList(exemptTypes),
exemptType -> MediaType.valueOf(exemptType)
));
}
#Override
public ContainerRequest filter(ContainerRequest request) {
MediaType inputType = request.getMediaType();
if (targetType.equals(inputType) || exemptTypes.contains(inputType)) {
// unmodified
return request;
}
MultivaluedMap<String, String> headers = request.getRequestHeaders();
if (headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
headers.putSingle(HttpHeaders.CONTENT_TYPE, targetType.toString());
request.setHeaders((InBoundHeaders)headers);
}
return request;
}
#Override
public final ContainerRequestFilter getRequestFilter() {
return this;
}
#Override
public final ContainerResponseFilter getResponseFilter() {
// don't filter responses
return null;
}
}
OverrideInputTypeResourceFilterFactory:
public class OverrideInputTypeResourceFilterFactory implements ResourceFilterFactory {
#Override
public List<ResourceFilter> create(AbstractMethod am) {
// documented to only be AbstractSubResourceLocator, AbstractResourceMethod, or AbstractSubResourceMethod
if (am instanceof AbstractSubResourceLocator) {
// not actually invoked per request, nothing to do
log.debug("Ignoring AbstractSubResourceLocator {}", am);
return null;
} else if (am instanceof AbstractResourceMethod) {
OverrideInputType annotation = am.getAnnotation(OverrideInputType.class);
if (annotation == null) {
annotation = am.getResource().getAnnotation(OverrideInputType.class);
}
if (annotation != null) {
return Lists.<ResourceFilter>newArrayList(
new OverrideInputTypeResourceFilter(annotation.value(), annotation.except()));
}
} else {
log.warn("Got an unexpected instance of {}: {}", am.getClass().getName(), am);
}
return null;
}
}
Example MyResource demonstrating its use:
#Produces(MediaType.APPLICATION_JSON)
#Path(/objects/{id}")
public class MyResource {
#PUT
// #Consumes(MediaType.APPLICATION_JSON)
#OverrideInputType(MediaType.APPLICATION_JSON)
public StatusResult put(#PathParam("id") int id, JsonObject obj) {
log.trace("PUT {}", id);
// do something with obj
return new StatusResult(true);
}
#GET
public JsonObject get(#PathParam("id") int id) {
return new JsonObject(id);
}
}
In Jersey 2, you could do this with a post-matching ContainerRequestFilters

How can I use Spring REST with MappingJackson2XmlHttpMessageConverter and XSD validation

I'm creating a Spring 4 REST application, using MappingJackson2XmlHttpMessageConverter to convert incoming XML requests to domain objects. Is there any way to apply XSD validation in that process? If not, I think my fallback is to just make the #RequestBody a String, parse and validate it, and then convert it to the domain object. Is there a better approach?
One approach to this may be to write a custom HttpMessageConverter<T> that checks XSD validation (look here for a way to validate XML with XSD) before returning the object.
Suppose that you have the following method in your Controller class:
#RequestMapping(value = "/")
public CustomObject getCustomObject(#RequestParam(value = "id") String id){
return new CustomObject();
}
Then your converter may look like this:
public class CustomObjectConverter implements HttpMessageConverter<CustomObject> {
// a real message converter that will respond to ancillary methods and do the actual work
protected HttpMessageConverter<Object> delegateConverter;
public CustomObjectConverter (HttpMessageConverter<Object> delegate) {
super(delegate, personService);
super.delegateConverter = delegate;
this.employeePhotoBaseUrl = employeePhotoBaseUrl;
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canRead(clazz, mediaType) && CustomObject.class.equals(clazz);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canWrite(clazz, mediaType) && CustomObject.class.equals(clazz);
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return delegateConverter.getSupportedMediaTypes();
}
#Override
public CustomObject read(Class<? extends CustomObject> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return (CustomObject) delegateConverter.read(clazz, inputMessage);
}
#Override
public void write(CustomObject t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if(validationOK)
delegateConverter.write(t, contentType, outputMessage);
else
// You may implement a custom exception handler to return a proper HTTP error code
throw new YourCustomException();
}
}
Remember to configure your new converter. I do this in my configuration class:
#Configuration
#EnableWebMvc
public class RestConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// initialize your MappingJackson2XmlHttpMessageConverter
MappingJackson2XmlHttpMessageConverter xmlMessageConverter = xmlMessageConverter();
//Here we add our custom-configured HttpMessageConverters
converters.add(new CustomObjectConverter(xmlMessageConverter));
super.configureMessageConverters(converters);
}
}

Categories