How to customize Jackson ObjectMapper with Spring application.properties? - java

I want to enable the following jackson mapper feature:
MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES
According to https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html:
Could be configured in application.properties as follows:
spring.jackson.mapper.accept_case_insensitive_properties=true
But:
#RestController
public class MyServlet {
#RequestMapping("/test")
public void test(#Valid TestReq req) {
}
}
public class TestReq {
#NotBlank
private String name;
}
Usage:
localhost:8080/test?name=test //works
localhost:8080/test?Name=test //fails with 'name may not be blank'
So, the case insensitive property is not taken into account. But why?
By the way: even using Jackson2ObjectMapperBuilderCustomizer explicit does not work:
#Bean
public Jackson2ObjectMapperBuilderCustomizer initJackson() {
Jackson2ObjectMapperBuilderCustomizer c = new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
}
};
return c;
}
spring-boot-1.5.3.RELEASE

According to spring doc you can customize it.
I fix this problem by set my application.yml like this(spring 2.0):
spring:
jackson:
mapper:
ACCEPT_CASE_INSENSITIVE_PROPERTIES: true
Did you tried change your setting accept_case_insensitive_properties to UPPER CASE?
Also you can keep output to Upper Case by setting like this:
jackson:
mapper:
ACCEPT_CASE_INSENSITIVE_PROPERTIES: true
property-naming-strategy: com.fasterxml.jackson.databind.PropertyNamingStrategy.PascalCaseStrategy
Note that PascalCaseStrategy was deprecated now, but still working.

Simple answer: it is not possible.
The Jackson2ObjectMapperBuilderCustomizer affects the JSON POST requests only. It has no effect on the get query binding.

Related

How to create circuit breaker config from application.properties?

I have the following configuration with which I create circuit breakers at runtime:
#Configuration
public class CircuitBreakerConfiguration
{
public final static String DEFAULT_CIRCUIT_BREAKER_REGISTRY = "DEFAULT_CIRCUIT_BREAKER_REGISTRY";
private CircuitBreakerConfig getCircuitBreakerConfig()
{
return CircuitBreakerConfig.custom()
.failureRateThreshold(10)
.waitDurationInOpenState(Duration.ofMillis(30000))
.permittedNumberOfCallsInHalfOpenState(2)
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.slidingWindowSize(5)
.automaticTransitionFromOpenToHalfOpenEnabled(true)
.recordExceptions(CheckAvailabilityException.class)
.build();
}
#Bean
#Qualifier(DEFAULT_CIRCUIT_BREAKER_REGISTRY)
public CircuitBreakerRegistry getCircuitBreakerRegistry()
{
return CircuitBreakerRegistry.of(getCircuitBreakerConfig());
}
}
I want to move these configurations to my application.properties file.
I tried the following to override the default configs:
resilience4j.circuitbreaker.configs.default.sliding-window-size=10
resilience4j.circuitbreaker.configs.default.sliding-window-type=COUNT_BASED
resilience4j.circuitbreaker.configs.default.failure-rate-threshold=50
resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state=30s
resilience4j.circuitbreaker.configs.default.permitted-number-of-calls-in-half-open-state=2
resilience4j.circuitbreaker.configs.default.automatic-transition-from-open-to-half-open-enabled=true
resilience4j.circuitbreaker.configs.default.record-exceptions=com.example.web.domain.checkavailability.exceptions.CheckAvailabilityException
However, this doesn't seem to override the default configs too.
Don't know if this is still an open topic, but I was struggling with a similar question and managed to find this article which offers some guidance: https://heapsteep.com/13-circuit-breaker-resilience4j/
As such, here's what I've done:
create the default configs I want:
resilience4j.circuitbreaker.configs.default.register-health-indicator=true
resilience4j.circuitbreaker.configs.default.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.configs.default.sliding-window-type=TIME_BASED
resilience4j.circuitbreaker.configs.default.minimum-number-of-calls=50
create a instance that I will use in the annotation, and set up it like so:
resilience4j.circuitbreaker.instances.myInstance.baseConfig=default
annotate your method:
#Override
#CircuitBreaker(name = "myInstance", fallbackMethod = "fallbackmethod")
public String getName(int ID) {
\\ ...
}
I have been resting this setup with the #Retry function, but I would assume that it works the same way.

Configuring Jackson mixin in Spring Boot application

I created a mixin for my class. The mixin itself works fine, it's not the issue that most people have where they mix faterxml/codehaus annotations.
I tested it in a unit test, creating the ObjectMapper "by hand" while using the addMixIn method - it worked just fine.
I want to use that mixin to modify the response jsons returned from my REST endpoints.
I've tried to customize Spring Boot's ObjectMapper in many different ways:
BuilderCustomizer:
#Bean
public Jackson2ObjectMapperBuilderCustomizer addMixin(){
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.mixIn(MyClass.class, MyClassMixin.class);
}
};
}
Builder:
#Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return new Jackson2ObjectMapperBuilder().mixIn(MyClass.class, MyClassMixin.class);
}
Converter:
#Bean
public MappingJackson2HttpMessageConverter configureJackson(){
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(MyClass.class, MyClassMixin.class);
converter.setObjectMapper(mapper);
return converter;
}
ObjectMapper:
#Autowired(required = true)
public void configureJackon(ObjectMapper jsonMapper){
jsonMapper.addMixIn(MyClass.class, MyClassMixin.class);
}
None of these work.
As of Spring Boot 2.7, there is built-in support for mixins.
Adding the following annotation:
#JsonMixin(MyClass::class)
class MyClassMixin{
will register mixin in the auto-configured ObjectMapper.
This might depend on Spring Boot version but as per Customize the Jackson ObjectMapper defining a new Jackson2ObjectMapperBuilderCustomizer bean is sufficient
The context’s Jackson2ObjectMapperBuilder can be customized by one or more Jackson2ObjectMapperBuilderCustomizer beans. Such customizer beans can be ordered (Boot’s own customizer has an order of 0), letting additional customization be applied both before and after Boot’s customization.
I had tried the above and it did not work for me either. While debugging, I noticed that the ObjectMapper inside the message converter was null.
Referring to the post get registered message converters, I ended up replacing the default message converter for Jackson, allowing me to customize the object mapper to my needs:
#SpringBootApplication
#RestController
public class MixinTest {
public static void main(String[] args) {
SpringApplication.run(MixinTest.class, args);
}
static class Person {
private String title;
private String name;
private String nullField;
private LocalDate date;
Person(String title, String name) {
this.title = title;
this.name = name;
this.date = LocalDate.now();
}
// getters here...
}
// this will exclude nullField
#JsonInclude(JsonInclude.Include.NON_NULL)
interface PersonMixin {
#JsonProperty("fullName")
String getName();
}
#Bean
public Jackson2ObjectMapperBuilderCustomizer personCustomizer() {
return jacksonObjectMapperBuilder ->
jacksonObjectMapperBuilder.mixIn(Person.class, PersonMixin.class);
}
#Bean
public MappingJackson2HttpMessageConverter myMessageConverter(
// provided by Spring
RequestMappingHandlerAdapter reqAdapter,
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
ObjectMapper mapper = jacksonObjectMapperBuilder
.featuresToEnable(SerializationFeature.INDENT_OUTPUT)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.modulesToInstall(new JavaTimeModule())
.build();
// **replace previous MappingJackson converter**
List<HttpMessageConverter<?>> converters =
reqAdapter.getMessageConverters();
converters.removeIf(httpMessageConverter ->
httpMessageConverter.getClass()
.equals(MappingJackson2HttpMessageConverter.class));
MappingJackson2HttpMessageConverter jackson = new
MappingJackson2HttpMessageConverter(mapper);
converters.add(jackson);
reqAdapter.setMessageConverters(converters);
return jackson;
}
#GetMapping("/test")
public Person get() {
return new Person("Mr", "Joe Bloggs");
}
}
Which outputs the following in the browser after hitting http://localhost:8080/test:
{
"title" : "Mr",
"date" : "2019-09-03",
"fullName" : "Joe Bloggs"
}
This way, I should be able to add as many customizers as necessary. I'm sure there's a better way to do this. It seems hacky to replace internals like this...

How to prevent Spring MVC from interpreting commas when converting to a Collection in Spring Boot?

We basically have the same problem as this question poses, but for lists and additionally, we are looking for a global solution.
Currently we have a REST call that is defined like this:
#RequestMapping
#ResponseBody
public Object listProducts(#RequestParam(value = "attributes", required = false) List<String> attributes) {
The call works fine and the list attributes will contain two elements "test1:12,3" and "test1:test2" when called like this:
product/list?attributes=test1:12,3&attributes=test1:test2
However, the list attributes will also contain two elements, "test1:12" and "3" when called as follows:
product/list?attributes=test1:12,3
The reason for this is, that in the first case, Spring will use a ArrayToCollectionConverter in the first case. In the second case it will use a StringToCollectionConverter which will split the argument using "," as a separator.
How can I configure Spring Boot to ignore the comma in the parameter? The solution should be global if possible.
What we have tried:
This question does not work for us, because we have a List instead of an array. Besides, this would be a controller-local solution only.
I also tried to add this configuration:
#Bean(name="conversionService")
public ConversionService getConversionService() {
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.setConverters(Collections.singleton(new CustomStringToCollectionConverter()));
bean.afterPropertiesSet();
return bean.getObject();
}
where CustomStringToCollectionConverter is a copy of the Spring StringToCollectionConverter, but without the splitting, however, the Spring converter still gets called preferentially.
On a hunch, I also tried "mvcConversionService" as a bean name, but that did not change anything either.
You can remove the StringToCollectionConverter and replace it with your own in WebMvcConfigurerAdapter.addFormatters(FormatterRegistry registry) method:
Something like this:
#Configuration
public class MyWebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class,Collection.class);
registry.addConverter(String.class,Collection.class,myConverter);
}
}
For me it worked fine even without the line for adding a new converter but because #Strelok did not provide an example how to write a new converter here is a full solution:
#Configuration
class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class, Collection.class);
registry.addConverter(String.class, Collection.class, noCommaSplitStringToCollectionConverter());
}
public Converter<String, Collection> noCommaSplitStringToCollectionConverter() {
return Collections::singletonList;
}
}
My version is a modified and update one of a string to array converter you can find here:
How to disable spring boot parameter split
I managed to solve the problem by applying the following:
how-to-prevent-parameter-binding-from-interpreting-commas-in-spring-3-0-5
The trick is done by the following lines of code
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String[].class, new StringArrayPropertyEditor(null));
}
More information:
Why escaped comma is the delimiter for #RequestParam List?

Configure build-in the Jackson MapDeserializer

I have a project with spring mvc and i wanna invoke method "setIgnorableProperties" from MapDeserializer globally, but I dont know how get this class from ObjectMapper, can you help me? Thx for advice.
I see it, like that:
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
mapDeserializer.getContentType();
converters.forEach(httpMessageConverter -> {
if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter converter = (MappingJackson2HttpMessageConverter) httpMessageConverter;
ObjectMapper mapper = converter.getObjectMapper();
MapDeserializer mapDes = mapper.(What I have to invoke?) ;
mapDes.setIgnorableProperties({"#id", "#ref"});
}
});
}
That property is not meant to be configured directly; you will need to use #JsonIgnoreProperties annotation for Map-valued properties.
You can create convenience annotation, if you want, by:
#Retention(RetentionPolicy.RUNTIME) // IMPORTANT
#JacksonAnnotationsInside
#JsonIgnoreProperties({ "#id", "#ref" })
public #interface MapIgnorals
and then use like:
public class Stuff {
#MapIgnorals public Map<String,Object> values;
}

How to configure a default #RestController URI prefix for all controllers?

I know you can set the server.contextPath in application.properties to change the root context.
Also, I can add an additional context in the application config for Spring Boot like the following example (in Groovy) to add an "/api" to the URL mappings of the root context:
#Bean
ServletRegistrationBean dispatcherServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean(new DispatcherServlet(), "/")
reg.name = "dispatcherServlet"
reg.addInitParameter("contextConfigLocation", "")
reg.addUrlMappings("/api/*")
reg.loadOnStartup = 2
reg
}
}
I am trying to have a separate base URI "/api" specifically for web service calls, that I can leverage for security, etc. However using the above approach will mean that any of my URIs, web service or not, can be reached with "/" or "/api", and provides no concrete segregation.
Is anyone aware of a better approach to set a base path for all #RestController(s) using configuration, without having to formally prefix every controller with /api/? If I am forced to manually prefix the URI for each controller, it would be possible to mistakenly omit that and bypass my security measures specific to web services.
Here is a reference in Stack Overflow to the same type of question, which was never completely answered:
Spring Boot: Configure a url prefix for RestControllers
In continuation to the currently accepted solution the github issue addresses the same.
Spring 5.1 and above you can implement WebMvcConfigurer and override configurePathMatch method like below
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
}
Now all the #RestControllers will have /api as the prefix path alongside the path configured.
Official Documentation
There's a new solution to solve this kind of problem available since Spring Boot 1.4.0.RC1 (Details see https://github.com/spring-projects/spring-boot/issues/5004)
The solution of Shahin ASkari disables parts of the Auto configuration, so might cause other problems.
The following solution takes his idea and integrates it properly into spring boot. For my case I wanted all RestControllers with the base path api, but still serve static content with the root path (f.e. angular webapp)
Edit: I summed it up in a blog post with a slightly improved version see https://mhdevelopment.wordpress.com/2016/10/03/spring-restcontroller-specific-basepath/
#Configuration
public class WebConfig {
#Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
private final static String API_BASE_PATH = "api";
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
Class<?> beanType = method.getDeclaringClass();
RestController restApiController = beanType.getAnnotation(RestController.class);
if (restApiController != null) {
PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
.combine(mapping.getPatternsCondition());
mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
mapping.getMethodsCondition(), mapping.getParamsCondition(),
mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
}
super.registerHandlerMethod(handler, method, mapping);
}
};
}
};
}
}
Also You can achieve the same result by configuring WebMVC like this:
#Configuration
public class PluginConfig implements WebMvcConfigurer {
public static final String PREFIX = "/myprefix";
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix(PREFIX, c -> c.isAnnotationPresent(MyCustomAnnotation.class));
}
}
Implement WebMvcConfigurer on any #Configuration class.
Override configurePathMatch method.
You can do many useful things with PathMatchConfigurer e.g. add prefix for several classes, that satisfy predicate conditions.
I had the same concern and was not a fan of the Spring EL option due to the issues documented and I wanted the prefix to be tightly controlled in the controllers but I did not want to depend on the developers doing the right thing.
There might be a better way these days but this is what I did. Can you guys see any downsides, I am still in the process of testing any side-effects.
Define a custom annotation.
This allows a developer to explicitly provide typed attributes such as int apiVersion(), String resourceName(). These values would be the basis of the prefix later.
Annotated rest controllers with this new annotation
Implemented a custom RequestMappingHandlerMapping
In the RequestMappingHandlerMapping, I could read the attribute of the custom annotation and modify the final RequestMappingInfo as I needed. Here are a few code snippets:
#Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new MyCustomRequestMappingHandlerMapping();
}
}
And in the MyCustomRequestMappingHandlerMapping, overwrite the registerHandlerMethod:
private class MyCustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private Logger myLogger = LoggerFactory.getLogger(MyCustomRequestMappingHandlerMapping.class);
public MyCustomRequestMappingHandlerMapping() {
super();
}
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
// find the class declaring this method
Class<?> beanType = method.getDeclaringClass();
// check for the My rest controller annotation
MyRestController myRestAnnotation = beanType.getAnnotation(MyRestController.class);
if (myRestAnnotation != null) {
// this is a My annotated rest service, lets modify the URL mapping
PatternsRequestCondition oldPattern = mapping.getPatternsCondition();
// create a pattern such as /api/v${apiVersion}/${resourceName}
String urlPattern = String.format("/api/v%d/%s",
myRestAnnotation.apiVersion(),
myRestAnnotation.resourceName());
// create a new condition
PatternsRequestCondition apiPattern =
new PatternsRequestCondition(urlPattern);
// ask our condition to be the core, but import all settinsg from the old
// pattern
PatternsRequestCondition updatedFinalPattern = apiPattern.combine(oldPattern);
myLogger.info("re-writing mapping for {}, myRestAnnotation={}, original={}, final={}",
beanType, myRestAnnotation, oldPattern, updatedFinalPattern);
mapping = new RequestMappingInfo(
mapping.getName(),
updatedFinalPattern,
mapping.getMethodsCondition(),
mapping.getParamsCondition(),
mapping.getHeadersCondition(),
mapping.getConsumesCondition(),
mapping.getProducesCondition(),
mapping.getCustomCondition()
);
}
super.registerHandlerMethod(handler, method, mapping);
}
}
Slightly less verbose solution which doesn't duplicate the logic of checking the annotation, but only changes the mapping path:
private static final String API_PREFIX = "api";
#Bean
WebMvcRegistrationsAdapter restPrefixAppender() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
if (mappingForMethod != null) {
return RequestMappingInfo.paths(API_PREFIX).build().combine(mappingForMethod);
} else {
return null;
}
}
};
}
};
}
Side effects
Your error controller will also be mapped under /api/error, which breaks error handling (DispatcherServlet will still redirect errors to /error without prefix!).
Possible solution is to skip /error path when adding /api prefix in the code above (one more "if").
Someone has filed an issue in the Spring MVC Jira and come up with a nice solution, which I am now using. The idea is to use the Spring Expression Language in the prefix placed in each RestController file and to refer to a single property in the Spring Boot application.properties file.
Here is the link of the issue: https://jira.spring.io/browse/SPR-13882

Categories