null map when reading nested properties from yaml with configuration properties annotation - spring-boot

My application.yml looks like so
service:
cloud:
piglet:
published-host: http://localhost:29191
webhook:
headers:
gitlab: X-Gitlab-Token
The Config class
#Component
#ConfigurationProperties(prefix = "service.cloud.piglet.webhook")
public class WebhooksConsumerTokenHeadersProperties {
private final Map<String, String> headers;
public WebhooksConsumerTokenHeadersProperties(Map<String, String> headers) {
this.headers = headers;
}
public String getTokenHeaderName(String app) {
return headers.get(app);
}
}
I ran on debug and noticed that the headers map is null when initialising it in the constructor.

Related

#ConfigurationProperties failing to parse yaml file

Im trying to convert my yaml file into a list of objects that I can query during runtime. Ive tried to follow the documentation however my use case is a little more complicated than all the examples that I have seen and Im not sure what im missing.
When I try to run the below code I dont get any errors. But when I autowire the component Its empty.
#Data
#Component
#ConfigurationProperties(prefix = "kafka")
public class KafkaProperties {
private Map<String, KafkaTopicProperties> topics = new HashMap<>();
#Data
public class KafkaTopicProperties {
Map<String, KeyProperties> key;
Map<String, ValueProperties> value;
Map<String, BootstrapProperties> bootstrap;
#Data
public class KeyProperties {
Map<String, String> serializer;
}
#Data
public class ValueProperties {
Map<String, String> serializer;
}
#Data
public class BootstrapProperties {
Map<String, String> servers;
}
}
}
kafka:
topic-name:
key:
serializer: org.apache.kafka.common.serialization.StringSerializer
value:
serializer: org.apache.kafka.common.serialization.StringSerializer
bootstrap:
servers: localhost:9092
other-topic:
key:
serializer: org.apache.kafka.common.serialization.StringSerializer
value:
serializer: org.apache.kafka.common.serialization.StringSerializer
bootstrap:
servers: localhost:9092
#Autowired
KafkaProperties properties;

Force SpingBoot to use Gson over Jackson

I am trying to force SpringBoot to use Gson instead of Jackson. I've read most of the articles I've found online and I am still seeing Jackson being used. Here's what I've done
Added
spring:
http: { converters: { preferred-json-mapper: gson } }
mvc: { converters: {preferred-json-mapper: gson } }
in application.yaml
Updated POM
Added gson dependency
Added jackson-databind to exclusion list in spring-boot-starter-web depedency.
Added #EnableAutoConfiguration(exclude = JacksonAutoConfiguration.class) to main class.
Written below #Configuration class:
#Configuration
#Slf4j
public class MyConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters (List<HttpMessageConverters<?>> converters) {
log.debug("Setting gson converter");
converters.add(new GsonHttpMessageConverter(myCustomGsonInstance()));
}
public Gson myCustomGsonInstance() {
return new Gson();
}
}
When running tests in debug, I can see that Jackson is still listed in the HttpMessageConverters list and Gson is not.
Update:
This behavior is seen while running live and in the below test class.
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = MOCK)
#ExtendWith(MockitoExtension.class)
public class MyTestClass {
#Autowired
private MyController controller;
private MockMvc mockMvc;
#BeforeEach
public void setUp(){
mockMvc = MockMvcBuilders.standaloneSetup(controller)
// .setMessageConverters(new GsonHttpMessageConverter(myCustomGsonInstance())) // if I add this, the test passes.
.build();
}
#Test
public void happyFlow(){
// given
URI uri = "/test/uri";
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
// when
String responseBody = mockMvc.perform(get(uri).headers(headers)).andReturn().getResponse().getContentAsString();
// then
assertThat(responseBody, wasSerializedByGson());
}
}
It looks like you're using the wrong property for configuring the preferred JSON mapper. You are using spring.http.converters.preferred-json-mapper but the correct property is spring.mvc.converters.preferred-json-mapper. In application.yaml, that would be the following:
spring:
mvc:
converters:
preferred-json-mapper: gson
Spring Boot comes with Gson Auto Configuration support: Source Code
So you have to Autowire the Gson singleton instance to be used by your WebMvcConfigurer in addition to enabling the yaml property:
#Configuration
#Slf4j
public class MyConfig implements WebMvcConfigurer {
#Autowired
private Gson gson;
#Override
public void extendMessageConverters (List<HttpMessageConverters<?>> converters) {
log.debug("Setting gson converter");
converters.add(new GsonHttpMessageConverter(gson));
}
}
And the yaml properties borrowed from Andy Wilkinson:
spring:
mvc:
converters:
preferred-json-mapper: gson
With this setup Spring MVC is using the same Gson instance as the one Autowired in your configuration.
And in your test, it should look like this:
#WebMvcTest(MyController.class)
public class MyTestClass {
#Autowired
private MockMvc mockMvc;
#Autowired
private MyController controller;
#Test
public void happyFlow(){
// given
URI uri = "/test/uri";
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
// when
String responseBody = mockMvc.perform(get(uri).headers(headers)).andReturn().getResponse().getContentAsString();
// then
assertThat(responseBody, wasSerializedByGson());
}
}

Newly created / extended JHipster endpoint does not work (404 error)

I want to create a new endpoint that extends the existing jhimetrics endpoint (or extend the results of the existing jhimetrics). The application is generated with JHipster.
So what I have done is:
add the new endpoint to the array in application.yml file, specifically:
management:
endpoints:
web:
base-path: /management
exposure:
include: [ ..., "health", "info", "jhimetrics", "roxhens"]
created the ExtendedMetricsEndpoint.java with the following content:
// imports, etc...
#Endpoint(id = "roxhens")
public class ExtendedMetricsEndpoint {
private final JHipsterMetricsEndpoint delegate;
private final SimpUserRegistry simpUserRegistry;
public ExtendedMetricsEndpoint(
JHipsterMetricsEndpoint delegate,
SimpUserRegistry simpUserRegistry
) {
this.delegate = delegate;
this.simpUserRegistry = simpUserRegistry;
}
#ReadOperation
public Map<String, Map> getMetrics() {
Map<String, Map> metrics = this.delegate.allMetrics();
HashMap<String, Integer> activeUsers = new HashMap<>();
activeUsers.put("activeUsers", this.simpUserRegistry.getUserCount());
metrics.put("customMetrics", new HashMap(activeUsers));
return metrics;
}
}
created the configuration file for this endpoint:
// imports etc...
#Configuration
#ConditionalOnClass(Timed.class)
#AutoConfigureAfter(JHipsterMetricsEndpointConfiguration.class)
public class ExtendedMetricsEndpointConfiguration {
#Bean
#ConditionalOnBean({JHipsterMetricsEndpoint.class, SimpUserRegistry.class})
#ConditionalOnMissingBean
#ConditionalOnAvailableEndpoint
public ExtendedMetricsEndpoint extendedMetricsEndpoint(JHipsterMetricsEndpoint jHipsterMetricsEndpoint, SimpUserRegistry simpUserRegistry) {
return new ExtendedMetricsEndpoint(jHipsterMetricsEndpoint, simpUserRegistry);
}
}
What step am I missing here, or what am I doing wrong?
I had the same issue and after 2 days of struggle I was able to find a solution which works for me:
#Component
#WebEndpoint(id = "xxxmetrics")
public class XXXMetricsEndpoint {
private final MeterRegistry meterRegistry;
public SrnMetricsEndpoint(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
#ReadOperation
public Map<String, Map> allMetrics() {
Map<String, Map> stringMapMap = new LinkedHashMap<>();
return stringMapMap;
}
}
Application yml:
management:
endpoints:
web:
base-path: /management
exposure:
include: [... , 'health', 'info', 'jhimetrics', 'xxxmetrics' ,'metrics', 'logfile']
This way the request: /management/xxxmetrics works.
The spring docs: https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-endpoints-custom
Edit:
spring version: 5.1.10, spring-boot-actuator: 2.1.9

I can not inject Map<String, String> from YAML file

I have this properties in my YAML file:
request-topic:
topics:
IMPORT_CHARGES: topic-name-1
IMPORT_PAYMENTS: topic-name-2
IMPORT_CATALOGS: topic-name-3
And this class:
#Getter
#Setter
#Component
#ConfigurationProperties(prefix = "topic-properties")
public class TopicProperties {
private Map<String, String> topics = new HashMap<>();
public String getTopicNameByType(String type){
return topics.get(type);
}
}
But when I autowire this properies I get empty Map:
#Service
public class TopicRouterImpl implements TopicRouter {
private final TopicProperties topics;
public TopicRouterImpl(TopicProperties topics) {
this.topics = topics;
}
#PostConstruct
public void init(){
topics.getTopicNameByType("IMPORT_CHARGES");
}
#Override
public String getTopicName(MessageType messageType) {
return topics.getTopicNameByType(messageType.name());
}
}
This is due to the name mismatch in your yaml file it should be equals to the specified prefix : topic-properties. Like this :
topic-properties:
topics:
IMPORT_CHARGES: topic-name-1
IMPORT_PAYMENTS: topic-name-2
IMPORT_CATALOGS: topic-name-3

Jersey 2.13 + Bean Validation

I'm using gradle and the following libs:
ext.library['jersey'] = "org.glassfish.jersey:project:2.13"
ext.library['jersey_jettison'] = "org.glassfish.jersey.media:jersey-media-json-jettison:2.13"
ext.library['jersey_jackson'] = "org.glassfish.jersey.media:jersey-media-json-jackson1:2.13"
ext.library['jersey_spring'] = "org.glassfish.jersey.ext:jersey-spring3:2.13"
ext.library['jersey_bean_validation'] = "org.glassfish.jersey.ext:jersey-bean-validation:2.13"
I created the bean validation structure, but its not validating at all. No error messages, nothing. This is the structure I've created:
The DTO
public class MergeSchedulesDto {
#NotNull(message = "validation.invalid.mergeFrom")
private Long mergeFrom;
#NotNull(message = "validation.invalid.mergeTo")
#NotEmpty(message = "validation.invalid.mergeTo")
private List<Long> mergeTo;
The Service
#Path("merge")
#POST
#Consumes({ MediaType.APPLICATION_JSON })
public Response merge(#Valid MergeSchedulesDto dto, #QueryParam("units") List<Long> units) {
The config
public class ApplicationJAXRS extends Application {
public ApplicationJAXRS() {
}
#Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>();
properties
.put("jersey.config.server.provider.packages",
"com.sifionsolution.sig.academic.resource.service,com.sifionsolution.sig.integration.resource.filter,com.sifionsolution.sig.academic.param.converter,com.sifionsolution.sig.datatables.resource.service,com.sifionsolution.sig.datatables.converter");
return properties;
}
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
singletons.add(new Jackson1Feature());
singletons.add(new ValidationExceptionMapper());
return singletons;
}
}
EDIT I forgot the provider:
#Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
return Response.status(BAD_REQUEST).entity(exception.getMessage()).build();
}
}
EDIT 2: I removed the JUnit test because I didnt test using Jersey Test Framework.
The problem here is that the ValidationExceptionMapper is not beeing called.
Put "#Valid" in your, like this:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response insert(#Valid T obj) throws Exception{
...
}
This works here.

Resources