Spring data rest how to serve hal+json serialization for content-type json - spring-boot

We are using spring-data-rest to expose our repositories.
Choosing as default type application/hal+json produce the desired presentation of the resources and links, with content exposed as _embedded and links as _links.
Unfortunately, Firefox is not able to render application/hal+json, suggesting user to download the hal+json document if the user is navigating the api using the browser.
As a possible solution to this, we would like to support application/json as defaultMediaType and also hal+json if the user is explicitly requiring that.
Using application/json as defaultMediaType with spring-data-rest tough bypass the hal+json serialization provided by spring-hateoas, so the resources are displayed with "content" and "links".
I've tried to extends the RepositoryRestConfigurerAdapter, overriding the configureHttpMessageConverter this way
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
halConverter.setObjectMapper(mapper);
halConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaTypes.HAL_JSON));
//make sure this is inserted first
messageConverters.add(0, halConverter);
super.configureHttpMessageConverters(messageConverters);
}
but the converter is not used to render the resources.
Any one has any suggestion on how to tackle this?

Related

Spring: How to publish a multipart/form-data request to a Rabbit Queue

The application has an endpoint that consumes multipart/form-data and maps it to a MultipartFile obj. From there, we attempt to upload the file to the vendor. If its successful, nothing else occurs other than a HTTP.200 series
If there is a failure, we want to publish a message to a Rabbit Queue where fields in the message include things like "endpoint", "data", "headers" so that we can consume the messages and hit the endpoint at a later date.
The problem is that I can't deserialize the MultipartFile. Ive tried using ByteArrayResource as well but get the error No serializer found for class java.io.ByteArrayInputStream
Is there a better way to do this?
Also doing this approach because there are multiple endpoints that take in a file in different conventions, so being able to replicate the request and not have logic and just recall the endpoint is ideal
Ultimate question: How can I write value as string on a ByteArrayResource or MultipartFile obj with Jackson
Wouldn't it be easier to save the file to disk and send the path to the queue?
In any cases, ByteArrayResource should work if you add the converter:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
MappingJackson2HttpMessageConverter converter = new
MappingJackson2HttpMessageConverter(mapper);
return converter;
}

Multiple default content type in spring boot

We are migrating monolithic netty web application into multiple spring boot microservices. Currently the consumers are not passing Content-Type header in the request.
To support that i have added below code in my application
#Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(APPLICATION_XML)
But the problem is i have endpoints with application/json as content type.
Is it possible to have default content type based on url's or is it possible to add headers in the request using some filter or interceptor.
If you add mediaType to your configuration it will enable content negotiation for whichever media types you want.
Spring checks three things, in this order
Add an extension to your request, e.g. /endpoint becomes /endpoint.json if you want JSON
Add the format parameter, e.g. /endpoint becomes /endpoint?format=json
Add the requisite Accept header, e.g. Accept=application/json (unless you configure the ContentNegotiationConfigurer to ignore the Accept header.
#Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
Configured in this manner, your endpoints will marshal XML unless overridden by one of the means described above.
If you cannot make changes to your clients to ask that they request the format they expect, you can override the behavior of a specific endpoint by adding produces to the mapping, like this
#GetMapping("/endpoint", produces = MediaType.APPLICATION_JSON_VALUE)
or
#RequestMapping("/endpoint", produces = MediaType.APPLICATION_JSON_VALUE)
This answer assumes that you've imported the correct dependencies into your project, you'll need the jackson XML mapper. Also, assuming you leave the content negotiation configuration in place, I would highly recommend that on your methods that you specify produces, that you include produces = {"application/xml", "application/json"} just in case your clients start following the proper specs and send an Accept header. Also, note that you'll need to make sure your objects are annotated with at a minimum, #XmlRootElement
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

Why do we need jackson databind?

I am new in Spring MVC. My question is, why do we need jackson databind? Because We can receive the Request Params by #ModelAttribute and requests through http PUT or POST by #RequestBody. I can't find a reason why we need jackson databind to convert json/xml to POJO or vice versa.
Thanks.
Why do we need jackson databind?
Because representing structured data is much easier using XML (or JSON) than using simple name-value pairs.
Because it is more convenient to send and receive JSON from the client side when you are doing AJAX.
Because once you have to deal with sending and receiving JSON or XML in the server side Java app, it is more convenient to deal with structured data as POJOs.
None of the above points mean you have to use a binding. There are other ways of dealing with each of the above. But many Java developers think that data bindings the better way to go: more efficient in terms of developer time, and more reliable. Especially if you are implementing services with a complex APIs. That's why they are popular.
And as other answers/comments point out, if you are using #RequestBody, then that is using a binding library under the hood to give you the POJOs. In the case of Spring, it is Jackson that is being used.
By default, when an endpoint expects a JSON document as input and a given controller method argument is annotated with #RequestBody, Spring will use Jackson databind features to map the incoming JSON document to a Java object. You don't need to use the Jackson's ObjectMapper directly, as Spring does it for you.
For example purposes, consider the following HTTP request to create a comment:
POST /comments HTTP/1.1
Host: example.org
Content-Type: application/json
{
"content": "Lorem ipsum"
}
And the following class which represents a comment:
#Data
public class Comment {
private String content;
}
A #RestController to handle such request would be like:
#RestController
#RequestMapping("/comments")
public class CommentController {
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Foo> createComment(#RequestBody Comment comment) {
// By default, Spring will rely on Jackson databind to map the incoming
// JSON document to the comment argument annotated with #RequestBody
...
}
}
If you are interested in the Spring component that maps the incoming JSON document to a Java object, have a look at the MappingJackson2HttpMessageConverter class:
Implementation of HttpMessageConverter that can read and write JSON using Jackson 2.x's ObjectMapper.
This converter can be used to bind to typed beans, or untyped HashMap instances.
By default, this converter supports application/json and application/*+json with UTF-8 character set. [...]
If you are creating a HTTP API and exposing resources that can be manipulated with JSON representations, it's unlikely you'll use #ModelAtribute. Such annotation is particularly useful when you are dealing with web views.
When you get some request in some data types like json/xml, the Spring MVC platform will try to deserialize this request attributes in some model object of your project.
But the platform itself don't provide a des-serialize implementation out of the box. So it will try to use some des-serializer provider in the classpath like jackson, jersey, gson, etc.
As you said - is possible to use #ModelAttribute - but this annotation is a better option to a request from a form view in the front-end. In cases rest json/xml requests, the #ModelAttribute won't be able to convert correctly the received data to a business class of your program.

How to set content-type correctly in thymeleaf when mixing html and json templates

I'm working on a single page application using spring boot and thymeleaf. I have two kinds of templates; one producing the SPA scaffolding page as html and multiple producing json responses.
The json responses are being sent back with a content-type of text/html when I would like them to be application/json.
How do I have the content-types set correctly? Do I need two thymeleaf view resolvers? I tried #GetMapping(value = Routes.EVENTS, produces = MediaType.APPLICATION_JSON_VALUE) on the #Controller but to no effect.
I'm sure there are a few approaches to solving this. Here is the one that worked for me.
I figured it out by looking in the Spring Boot documentation on custom view resolvers. This lead me to looking at the ThymeleafAutoConfiguration class. Then a bit of judicious debugging in the Spring framework helped to fill in the gaps.
#Bean
public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setContentType("application/json");
viewResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[] {"*.json"});
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}

Custom Spring MVC HTTP Patch requests with Spring Data Rest functionality

What is the best practice for supporting HTTP PATCH in custom Spring MVC controllers? Particularly when using HATEOAS/HAL? Is there an easier way to merge objects without having to check for the presence of every single field in the request json (or writing and maintaining DTOs), ideally with automatic unmarshalling of links to resources?
I know this functionality exists in Spring Data Rest, but is it possible to leverage this for use in custom controllers?
I do not think you can use the spring-data-rest functionality here.
spring-data-rest is using json-patch library internally. Basically I think the workflow would be as follows:
read your entity
convert it to json using the objectMapper
apply the patch (here you need json-patch) (I think your controller should take a list of JsonPatchOperation as input)
merge the patched json into your entity
I think the hard part is the fourth point. But if you do not have to have a generic solution it could be easier.
If you want to get an impression of what spring-data-rest does - look at org.springframework.data.rest.webmvc.config.JsonPatchHandler
EDIT
The patch mechanism in spring-data-rest changed significantly in the latest realeases. Most importantly it is no longer using the json-patch library and is now implementing json patch support from scratch.
I could manage to reuse the main patch functionality in a custom controller method.
The following snippet illustrates the approach based on spring-data-rest 2.6
import org.springframework.data.rest.webmvc.IncomingRequest;
import org.springframework.data.rest.webmvc.json.patch.JsonPatchPatchConverter;
import org.springframework.data.rest.webmvc.json.patch.Patch;
//...
private final ObjectMapper objectMapper;
//...
#PatchMapping(consumes = "application/json-patch+json")
public ResponseEntity<Void> patch(ServletServerHttpRequest request) {
MyEntity entityToPatch = someRepository.findOne(id)//retrieve current state of your entity/object to patch
Patch patch = convertRequestToPatch(request);
patch.apply(entityToPatch, MyEntity.class);
someRepository.save(entityToPatch);
//...
}
private Patch convertRequestToPatch(ServletServerHttpRequest request) {
try {
InputStream inputStream = new IncomingRequest(request).getBody();
return new JsonPatchPatchConverter(objectMapper).convert(objectMapper.readTree(inputStream));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

Resources