Additional Media Types for Spring Data Rest Repository Controllers - spring

Trying to add the JSON-LD serialisation provided by the project Hydra Java to an application using Spring Data Rest and the controllers generated by #RepositoryRestResource
While I've been able to serialize as JSON-LD using manually created #RepositoryRestController using the application/ld+json media type, I'm unable to do the same for the controllers generated for the repositories annotated as RepositoryRest Resource. They seem to just accept application/json and application/hal+json
Is it possible to configure these controllers to also accept additional media types?
This is how I've configured the HydraMessageConverter provided by the Hydra Java project:
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer()
{
return new RepositoryRestConfigurerAdapter()
{
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(0, new HydraMessageConverter());
super.configureHttpMessageConverters(messageConverters);
}
}
}

Related

How to use Protobuf and JSON API simultaneously on SpringBoot?

My project only had Protobuf APIs, but now it has to provide JSON APIs, too.
protobufHttpMessageConverter was applied to all controllers by configuration.
#Configuration
class APIConfig : WebMvcConfigurer {
#Bean
fun protobufHttpMessageConverter(): ProtobufHttpMessageConverter {
val converter = ProtobufHttpMessageConverter()
converter.defaultCharset = StandardCharsets.UTF_8
return converter
}
}
But now it should be applied to some controllers that provide Protobuf APIs, and should not be applied to other controllers that provide JSON APIs.
Does SpringBoot have that kind of flexibility?

How to Inject custom method argument in Spring WebFlux using HandlerMethodArgumentResolver?

I want to create an custom method argument Resolver using Spring WebFlux. I am following link but its seem to be not working.
I am able to create the custom argument resolver using WebMvc.
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
public class MyContextArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyCustomeObject.class.isAssignableFrom(parameter.getParameterType())
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
.....
return Mono.just(new MyCustomeObject())
}
Please note that i am using HandlerMethodArgumentResolver from .web.reactive. package.
My AutoConfiguration file look like
#Configuration
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the class-path
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)//checks that the app is a reactive web-app
public class RandomWebFluxConfig implements WebFluxConfigurer {
#Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
MyContextArgumentResolver[] myContextArgumentResolverArray = {contextArgumentResolver()};
configurer.addCustomResolver(myContextArgumentResolverArray );
}
#Bean
public MyContextArgumentResolver contextArgumentResolver() {
return new MyContextArgumentResolver ();
}
My spring.factories looks like
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.XXXX.XXX.XXX.RandomWebFluxConfig
Please note that above configuration is part of the jar which is added in Spring WebFlux Boot project enabled using #EnableWebFlux .
It seems you're conflating two different problems here.
First, you should make sure that your method argument resolver works in a regular project.
For that, you need a #Configuration class that implements the relevant method in WebFluxConfigurer. Your code snippet is doing that but with two flaws:
Your configuration is using #EnableWebFlux, which is disabling the WebFlux auto-configuration in Spring Boot. You should remove that
it seems you're trying to cast a list of MethodArgumentResolver into a single instance and that's probably why things aren't working here. I believe your code snippet could be just:
configurer.addCustomResolver(contextArgumentResolver());
Now the second part of this question is about setting this up as a Spring Boot auto-configuration. I guess that you'd like WebFlux applications to automatically get that custom argument resolvers if they depend on your library.
If you want to achieve that, you should first make sure to read up a bit about auto-configurations in the reference documentation. After that, you'll realize that your configuration class is not really an auto-configuration since it will be applied in all cases.
You should probably add a few conditions on that configuration like:
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the classpath
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) // checks that the app is a reactive web app

Does Spring Boot Actuator have a Java API?

We customize the Spring Boot Actuator Info endpoint to include the application version number generated during our Jenkins build. We're using gradle to do this:
if (project.hasProperty('BUILD_NUMBER')) {
version = "${BUILD_NUMBER}"
} else {
version = "0.0.1-SNAPSHOT"
}
That works great for adding the version to the /info endpoint, but I'd like to access it when the application starts and print it to the application log.
I'm hoping the values are exposed in some property value (similar to spring.profiles.active) or through a Java API. That way, I could do something like this:
public class MyApplication{
public static void main(String[] args) throws Exception {
SpringApplication.run(MyApplication.class, args);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
System.out.println(environment.getProperty("spring.fancy.path.to.info.version"));
}
}
Looking through the docs, I'm not finding a way to access these values easily in code. Has anyone else had luck with this?
To get exactly the same properties of an actuator endpoint that are exposed through the REST endpoints, you can inject in one of your classes an instance of the respective endpoint class. In your case, the "right" endpoint class would be the InfoEndpoint. There are analogous endpoint classes for metrics, health, etc.
The interface has changed a little between Spring Boot 1.5.x and Spring Boot 2.x. So the exact fully qualified class name or read method name may vary based on the Spring Boot version that you are using. In Boot 1.5.x, you can find most of the endpoints in the org.springframework.boot.actuate.endpoint package.
Roughly, this is how you could build a simple component for reading your version property (assuming that the name of the property inside the info endpoint is simply build.version):
#Component
public class VersionAccessor {
private final InfoEndpoint endpoint;
#Autowired
public VersionAccessor(InfoEndpoint endpoint) {
this.endpoint = endpoint;
}
public String getVersion() {
// Spring Boot 2.x
return String.valueOf(getValueFromMap(endpoint.info()));
// Spring Boot 1.x
return String.valueOf(getValueFromMap(endpoint.invoke()));
}
// the info returned from the endpoint may contain nested maps
// the exact steps for retrieving the right value depends on
// the exact property name(s). Here, we assume that we are
// interested in the build.version property
private Object getValueFromMap(Map<String, Object> info) {
return ((Map<String, Object>) info.get("build")).get("version");
}
}

Spring Boot with Two MVC Configurations

I have a Spring Boot app with a REST API, using Jackson for the JSON view configuration. It works great and I can get all the Spring Boot goodness.
However, I need to add an additional REST API that is similar but with different settings. For example, among other things, it needs a different Jackson object mapper configuration because the JSON will look quite a bit different (e.g. no JSON arrays). That is just one example but there are quite a few differences. Each API has a different context (e.g. /api/current and /api/legacy).
Ideally I'd like two MVC configs mapped to these different contexts, and not have to give up any of the automatic wiring of things in boot.
So far all I've been able to get close on is using two dispatcher servlets each with its own MVC config, but that results in Boot dropping a whole bunch of things I get automatically and basically defeats the reason for using boot.
I cannot break the app up into multiple apps.
The answer "you cannot do this with Boot and still get all its magic" is an acceptable answer. Seems like it should be able to handle this though.
There's several ways to achieve this. Based on your requirement , Id say this is a case of managing REST API versions.
There's several ways to version the REST API, some the popular ones being version urls and other techniques mentioned in the links of the comments.
The URL Based approach is more driven towards having multiple versions of the address:
For example
For V1 :
/path/v1/resource
and V2 :
/path/v2/resource
These will resolve to 2 different methods in the Spring MVC Controller bean, to which the calls get delegated.
The other option to resolve the versions of the API is to use the headers, this way there is only URL, multiple methods based on the version.
For example:
/path/resource
HEADER:
X-API-Version: 1.0
HEADER:
X-API-Version: 2.0
This will also resolve in two separate operations on the controller.
Now these are the strategies based on which multiple rest versions can be handled.
The above approaches are explained well in the following: git example
Note: The above is a spring boot application.
The commonality in both these approaches is that there will need to be different POJOS based on which Jackson JSON library to automatically marshal instances of the specified type into JSON.
I.e. Assuming that the code uses the #RestController [org.springframework.web.bind.annotation.RestController]
Now if your requirement is to have different JSON Mapper i.e. different JSON mapper configurations, then irrespective of the Spring contexts you'll need a different strategy for the serialization/De-Serialization.
In this case, you will need to implement a Custom De-Serializer {CustomDeSerializer} that will extend JsonDeserializer<T> [com.fasterxml.jackson.databind.JsonDeserializer] and in the deserialize() implement your custom startegy.
Use the #JsonDeserialize(using = CustomDeSerializer.class) annotation on the target POJO.
This way multiple JSON schemes can be managed with different De-Serializers.
By Combining Rest Versioning + Custom Serialization Strategy , each API can be managed in it's own context without having to wire multiple dispatcher Servlet configurations.
Expanding on my comment of yesterday and #Ashoka Header idea i would propose to register 2 MessageConverters (legacy and current) for custom media types. You can do this like that:
#Bean
MappingJackson2HttpMessageConverter currentMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// set features
jsonConverter.setObjectMapper(objectMapper);
jsonConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("json", "v2")));
return jsonConverter;
}
#Bean
MappingJackson2HttpMessageConverter legacyMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// set features
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
Pay attention to the custom media-type for one of the converters.
If you like , you can use an Interceptor to rewrite the Version-Headers proposed by #Ashoka to a custom Media-Type like so:
public class ApiVersionMediaTypeMappingInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
try {
if(request.getHeader("X-API-Version") == "2") {
request.setAttribute("Accept:","json/v2");
}
.....
}
}
This might not be the exact answer you were looking for, but maybe it can provide some inspiration. An interceptor is registered like so.
If you can live with a different port for each context, then you only have to overwrite the DispatcherServletAutoConfiguration beans. All the rest of the magic works, multpart, Jackson etc. You can configure the Servlet and Jackson/Multipart etc. for each child-context separately and inject bean of the parent context.
package test;
import static org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME;
import static org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
#EnableAutoConfiguration(exclude = {
Application.Context1.class,
Application.Context2.class
})
public class Application extends WebMvcConfigurerAdapter {
#Bean
public TestBean testBean() {
return new TestBean();
}
public static void main(String[] args) {
final SpringApplicationBuilder builder = new SpringApplicationBuilder().parent(Application.class);
builder.child(Context1.class).run();
builder.child(Context2.class).run();
}
public static class TestBean {
}
#Configuration
#EnableAutoConfiguration(exclude = {Application.class, Context2.class})
#PropertySource("classpath:context1.properties")
public static class Context1 {
#Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// custom config here
return dispatcherServlet;
}
#Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/test1");
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// custom config here
return registration;
}
#Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(TestBean testBean) {
System.out.println(testBean);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// custom config here
return builder;
}
}
#Configuration
#EnableAutoConfiguration(exclude = {Application.class, Context1.class})
#PropertySource("classpath:context2.properties")
public static class Context2 {
#Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// custom config here
return dispatcherServlet;
}
#Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/test2");
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// custom config here
return registration;
}
#Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(TestBean testBean) {
System.out.println(testBean);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// custom config here
return builder;
}
}
}
The context1/2.properties files currently only contain a server.port=8080/8081 but you can set all the other spring properties for the child contexts there.
In Spring-boot ypu can use different profiles (like dev and test).
Start application with
-Dspring.profiles.active=dev
or -Dspring.profiles.active=test
and use different properties files named application-dev.properties or application-test.properties inside your properties directory.
That could do the problem.

Adding custom HttpMessageConverter to spring-boot/spring-data-rest application

I want to add a custom HttpMessageConverter to a spring-boot application with spring data rest, but it turns out there are several places I have to inject my message converter. As a reference project for this situation, take https://github.com/olivergierke/spring-restbucks.
I am able to add my message converter by putting this into the basic configuration class, Restbucks.java:
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
#Override
public void configureHttpMessageConverters(
List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(0, myMessageConverter());
}
};
}
#Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(0, myMessageConverter());
}
};
}
Adding both is necessary because there are two different sets of message converters, available in the following beans:
A bean named restmappingHandlerAdapter from WebMvcAutoConfiguration$EnableWebMvcConfiguration - has 14 message converters including mine, handles /pages and /engine
A bean named repositoryExporterHandlerAdapter from SpringBootRepositoryRestMvcConfiguration - has 6 message converters, including my message converter, and handles /orders
As a workaround, I add my message converter twice, as shown above.
But why are there two distinct sets of message converters to customize? Why do /pages and /engine use a different setup than /orders? Is there some misconfiguration going on or is that how it is supposed to be?
I believe this is how it is supposed to be. I'd presume that the configuration of Spring Web MVC is separate to the config of Spring Data Rest so that they will play nice together, and so that you can customise the SDR repository exporter output without messing with your vanilla MVC controller output, and vice versa.
So any endpoint that is managed by SDR uses the SDR set, any other custom controller will use the Web MVC set.

Resources