package de.escalon.hypermedia.spring.siren;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import de.escalon.hypermedia.spring.DocumentationProvider;
import org.springframework.hateoas.RelProvider;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collection;
/**
* Http message converter which converts Spring Hateoas resource beans to siren messages. Treats the following rels as
* navigational by default: "self", "next", "previous", "prev". Created by Dietrich on 18.04.2016.
*/
public class SirenMessageConverter extends AbstractHttpMessageConverter<Object> {
private final SirenUtils sirenUtils;
ObjectMapper objectMapper = new ObjectMapper();
public SirenMessageConverter() {
sirenUtils = new SirenUtils();
}
/**
* Used to derive siren class (because Spring Hateoas rel providers normally derive rels from class names or class
* annotations).
*
* @param relProvider
* to determine siren class
*/
public void setRelProvider(RelProvider relProvider) {
sirenUtils.setRelProvider(relProvider);
}
/**
* Tells converter about rels which should be treated as navigational, in addition to the default ones.
*
* @param additionalNavigationalRels
* to add
*/
public void setAdditionalNavigationalRels(Collection<String> additionalNavigationalRels) {
sirenUtils.setAdditionalNavigationalRels(additionalNavigationalRels);
}
/**
* Sets request media type to be used as action type, instead of the default application/x-www-formurlencoded.
*
* @param requestMediaType
* type
*/
public void setRequestMediaType(String requestMediaType) {
sirenUtils.setRequestMediaType(requestMediaType);
}
/**
* Sets documentation provider, used to calculate rels.
*
* @param documentationProvider
* to use
*/
public void setDocumentationProvider(DocumentationProvider documentationProvider) {
sirenUtils.setDocumentationProvider(documentationProvider);
}
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
return null;
}
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
SirenEntity entity = new SirenEntity();
sirenUtils.toSirenEntity(entity, o);
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders()
.getContentType());
JsonGenerator jsonGenerator = this.objectMapper.getFactory()
.createGenerator(outputMessage.getBody(), encoding);
// A workaround for JsonGenerators not applying serialization features
// https://github.com/FasterXML/jackson-databind/issues/12
if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
jsonGenerator.useDefaultPrettyPrinter();
}
try {
this.objectMapper.writeValue(jsonGenerator, entity);
} catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
/**
* Determine the JSON encoding to use for the given content type.
*
* @param contentType
* the media type as requested by the caller
* @return the JSON encoding to use (never {@code null})
*/
protected JsonEncoding getJsonEncoding(MediaType contentType) {
if (contentType != null && contentType.getCharSet() != null) {
Charset charset = contentType.getCharSet();
for (JsonEncoding encoding : JsonEncoding.values()) {
if (charset.name()
.equals(encoding.getJavaName())) {
return encoding;
}
}
}
return JsonEncoding.UTF8;
}
}