I have been struggling to find any examples of how one could override the default Spring Boot Data Rest JpaRepository behavior to return something other than a HAL response. I have found that both Siren and Json-LD can meet the requirements but not any examples of how to get it to work with spring. I am using Spring 5, Spring Data Rest 2.0.4.RELEASE. Please note, I am not looking to do this for a custom rest controller, but for the default repository provided by extending JpaRepository. This is my first time posting so I apologize for any rules I might have inadvertently broken.
Update: I was able to find a library hydra-spring that has a SirenMessageConverter, following the example on the repo I was able to do the following:
@Configuration
@EnablePluginRegistries(RelProvider.class)
public class Config implements WebMvcConfigurer {
private static final boolean EVO_PRESENT =
ClassUtils.isPresent("org.atteo.evo.inflector.English", null);
@Autowired
private PluginRegistry<RelProvider, Class<?>> relProviderRegistry;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(sirenMessageConverter());
converters.add(hydraMessageConverter());
converters.add(halConverter());
converters.add(uberConverter());
converters.add(xhtmlMessageConverter());
converters.add(jsonConverter());
}
@Bean
public RepositoryRestConfigurerAdapter repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
@Override
public void configureHttpMessageConverters(
List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(0, sirenMessageConverter());
}
};
}
@Bean
public HttpMessageConverter<?> uberConverter() {
UberJackson2HttpMessageConverter converter = new UberJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(HypermediaTypes.UBER_JSON));
return converter;
}
private HttpMessageConverter<?> xhtmlMessageConverter() {
XhtmlResourceMessageConverter xhtmlResourceMessageConverter = new XhtmlResourceMessageConverter();
xhtmlResourceMessageConverter.setStylesheets(
Arrays.asList(
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"
));
xhtmlResourceMessageConverter.setDocumentationProvider(new JsonLdDocumentationProvider());
return xhtmlResourceMessageConverter;
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
final ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setWarnLogCategory(resolver.getClass()
.getName());
exceptionResolvers.add(resolver);
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(HypermediaTypes.SIREN_JSON);
}
@Bean
public HydraMessageConverter hydraMessageConverter() {
return new HydraMessageConverter();
}
@Bean
public SirenMessageConverter sirenMessageConverter() {
SirenMessageConverter sirenMessageConverter = new SirenMessageConverter();
sirenMessageConverter.setRelProvider(new DelegatingRelProvider(relProviderRegistry));
sirenMessageConverter.setDocumentationProvider(new JsonLdDocumentationProvider());
sirenMessageConverter.setSupportedMediaTypes(Collections.singletonList(HypermediaTypes.SIREN_JSON));
return sirenMessageConverter;
}
@Bean
public ObjectMapper jacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
return objectMapper;
}
@Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
MappingJackson2HttpMessageConverter jacksonConverter = new
MappingJackson2HttpMessageConverter();
jacksonConverter.setSupportedMediaTypes(Arrays.asList(MediaType.valueOf("application/json")));
jacksonConverter.setObjectMapper(jacksonObjectMapper());
return jacksonConverter;
}
@Bean
public CurieProvider curieProvider() {
return new DefaultCurieProvider("ex", new UriTemplate("http://localhost:8080/webapp/hypermedia-api/rels/{rels}"));
}
@Bean
public MappingJackson2HttpMessageConverter halConverter() {
CurieProvider curieProvider = curieProvider();
RelProvider relProvider = new DelegatingRelProvider(relProviderRegistry);
ObjectMapper halObjectMapper = new ObjectMapper();
halObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
halObjectMapper.registerModule(new Jackson2HalModule());
halObjectMapper.setHandlerInstantiator(new
Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider, null));
MappingJackson2HttpMessageConverter halConverter = new
MappingJackson2HttpMessageConverter();
halConverter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON));
halConverter.setObjectMapper(halObjectMapper);
return halConverter;
}
@Bean
RelProvider defaultRelProvider() {
return EVO_PRESENT ? new EvoInflectorRelProvider() : new DefaultRelProvider();
}
@Bean
RelProvider annotationRelProvider() {
return new AnnotationRelProvider();
}
}
This is my current Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="`http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd`">
<modelVersion>`4.0.0`</modelVersion>
<groupId>com.knowledgebase.DataRest</groupId>
<artifactId>DataRest</artifactId>
<version>2.1.0.M2</version>
<packaging>jar</packaging>
<name>DataRest</name>
<description>Demo project for Spring Boot DataRest</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath /><!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
<dependency>
<groupId>de.escalon.hypermedia</groupId>
<artifactId>hydra-spring</artifactId>
<version>0.4.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<url>http://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
</project>
However this still fails to deliver the results I am looking for. I know that some of the changes I introduced in the config class are making a difference since the output now looks like this:
{
"_embedded": {
"ex:contacts": []
},
"_links": {
"self": {
"href": "http://localhost:8080/contacts{?page,size,sort}",
"templated": true
},
"profile": {
"href": "http://localhost:8080/profile/contacts"
},
"search": {
"href": "http://localhost:8080/contacts/search"
},
"curies": [
{
"href": "http://localhost:8080/webapp/hypermedia-api/rels/{rels}",
"name": "ex",
"templated": true
}
]
},
"page": {
"size": 20,
"totalElements": 0,
"totalPages": 0,
"number": 0
}
}
As you can see the Curries provider is the only thing that actually works. For some reason spring boot data rest JpaRepository does not change the default application type to SIREN_JSON despite the:
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(HypermediaTypes.SIREN_JSON);
}