Spring: #NestedConfigurationProperty List in #ConfigurationProperties - spring

Hi I am trying to get the following configuration up and running.
#ConfigurationProperties(prefix="my")
public class Config {
#NestedConfigurationProperty
private List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>();
public List<ServerConfiguration> getServers() {
return this.servers;
}
}
#ConfigurationProperties(prefix = "server")
public class ServerConfiguration {
private String name;
private String description;
}
So, I want to have multiple server configs nested in objects.
I tried setting the properties with the following properties file. I can see that the list is added up by items but the members of the server are never set (name, description)
my.servers[0].name=test
my.servers[0].server.name=test
my.servers[1].name=test
my.servers[1].server.name=test

To extend what Maciej said already.
#ConfigurationProperties should be set only on root objects (that is objects that are responsible to handle a given prefix. There is no need to annotated nested objects with it.
#NestedConfigurationProperty is only used by the meta-data generator (to indicate that a property is not a single value but something we should explore to generate additional meta-data. In the case of the List there isn't any finite amount of properties so the meta-data will have to stop at the list.
In any case you need a getter and a setter for each singular property. We don't do field binding and we require a public getter to avoid exposing unnecessary properties in the meta-data.

You need to add setters and getters to ServerConfiguration
You don't need to annotate class with nested properties with #ConfigurationProperties
There is a mismatch in names between ServerConfiguration.description and property my.servers[X].server.name=test

Related

Manipulate annotated property values as the DTO hits the Spring controller?

Can you manipulate properties (e.g. set null ones to Hello world) annotated with a custom annotation as they are handed over to methods in your controller?
For example, let's assume we have a nested DTO:
public class MyDto {
#MyAnnotation
private String myProperty;
private String unannotatedPropety;
private InnerEntity innerEntity;
// Getters and setters omitted for brevity
}
public class InnerEntity {
#MyAnnotation
private String anotherProperty;
// Getters and setters omitted for brevity
}
#RestController(...)
public class MyController {
#PostMapping(...)
public Mono<ResponseEntity<MyResponse>> myRequestHandler(#RequestBody Mono<MyEntity> json) {
// ..
}
}
I initially thought a ConditionalGenericConverter could do the trick (its signature allows for null values to be converted, and it provides TypeDescriptors for its source and target properties, making introspection a breeze), but for controllers, Spring actually uses HttpMessageConverter for payloads (kudos), it seems, and I didn't want to reinvent the entire Jackson deserializer.
On the other hand, Spring + Hibernate Validator manage to introspect payloads and check all properties for specific annotations, so getting to annotated properties should be possible, I hope...
I could probably use AspectJ, but I want this to work in general, and not rely on the payload being of a specific type (like MyDto)... I'm basically hoping there exists a hook I can use, just like the converters API, that does the heavy lifting of reflection for me...
Is there an (easy) approach to do what I want to do?

Merging common Hikari properties with data source specific properties?

I am creating a spring boot app which has multiple data sources (7 in total) and whilst properties like dbUrl, username and password are data source specific, a great many are common. I obviously don't want to duplicate the properties for each data source and am trying to work out how I can create a HikariConfig instance for each datasource using a blended set of common properties.
The properties have the following format
spring.oracle.datasource.driverClassName
spring.oracle.datasource.autoCommit
spring.oracle.datasource.instance[0].dbUrl
spring.oracle.datasource.instance[0].username
spring.oracle.datasource.instance[0].password
spring.oracle.datasource.instance[1].dbUrl
spring.oracle.datasource.instance[1].username
spring.oracle.datasource.instance[1].password
spring.oracle.datasource.instance[2].dbUrl
spring.oracle.datasource.instance[2].username
spring.oracle.datasource.instance[2].password
spring.oracle.datasource.instance[n].dbUrl
spring.oracle.datasource.instance[n].username
spring.oracle.datasource.instance[n].password
I did try using configuration properties with a class that had the format below (lombok annotations ommitted)
public class DataSourceProperties extends HikariConfig {
public List<Instance> instance;
public static class Instance {
public String dbUrl;
public String username;
public String password;
}
}
But although the object is populated correctly I cannot figure out how I then create the n HikariConfig instances using the properties in the pojo. Clone or BeanUtils does not work, as it copies across all of the null fields which are rejected by the HikarConfig setters.
Anyone know a possible solution without duplicating the common properties and resorting to manual creation of the HikariConfig instances?
In the end, I used Spring's BeanUtils and wrote a function which found the null fields so they could be excluded.
Spring BeanUtils API Doc

#autowire beans and #value properties after object mapper deserialized json

I am using spring framework.
I am using objectMapper to desiriale store.json file:
service:
objectMapper.readValue(new File(jsonFilePath), Store.class)
store.json:
{
"type": "Store",
"name": "myStore",
}
Store.class:
#Value("${store.size:1000}")
private Integer sroreSize;
#autowire
private storePersistency storePersistency;
public Store(#JsonProperty("name") String name) {
super(name);
}
I am trying find out how to #autowire beans and #value properties in store.class, beans and properties that exist in applicationContext.
In current example sroreSize and storePersistency still null.
I know that I can inject fields to object mapper and then use #JacksonInject annotation but I have a lot of field to inject - not a good option for me.
Custom desirializer also not a good option for me.
Is there any way not to use custom desirializer or not to inject every bean/property that I need in store.class?
Something that injects all the beans and properties and I simply can use it in Store.class.
So you want some Store fields like storePersistency and sroreSize to be initialized once at application startup (which is when Spring will setup the application context) and then at runtime create multiple different Store objects differing in some fields as name that are initialized by Jackson.
I suggest annotating Store with #Component to get Spring to initialize #Value and #Autowired fields. The #Scope annotation will cause a new independent Store instance to be created each time. Simplified example:
#Component
#Scope(SCOPE_PROTOTYPE)
class Store {
private String name;
#Value("${store.size:1000}")
private Integer sroreSize;
}
Then the key is method readerForUpdating where you can pass an existing instance of Store and Jackson will update that instead of creating a new one as usually:
Store store = context.getBean(Store.class);
objectMapper.readerForUpdating(store).readValue("{\"name\":\"myStore\"}");
Where context is a Spring ApplicationContext reference that I autowired in a test class. You don't need to use the return value of readValue in this case, just inspect the existing store variable and name will be updated.

how to retrieve objects when using inheritance in spring Data

Say I have a class structure as follows, it is pretty basic inheritance:
Manager extends Person {
private String name;
Manager() {
}
}
Clerk extends Person {
private String salary;
}
In spring Data if I store these in Mongo, is it possible to configure it to map the correct class when I do a getById. I assume i will have to store some class info?
What i dont want to do is the need to create seperate repository classes if i can avoid it, also i dont know what the object will be when i do a getById
If you are using spring-data-mongodb MongoRepository to write data in your database according to your entity model, a _class field will be added to document roots and to complex property types (see this section). This fields store the fully qualified name of the Java class and it allows disambiguation when mapping from MongoDb Document to Spring data model.
However, if you only use MongoRepository to read from your database, you need to tell Spring-data how to map your entities explicitly. You will need to Override Mapping with Explicit Converters.
PersonReadConverter.class
public class PersonReadConverter implements Converter<Document, Person> {
#Override
public Contact convert(Document source) {
if (source.get("attribute_specific_to_Clerk") != null) {
Clerk clerk = new Clerk();
//Set attributes using setters or defined constructor
return clerk;
}
else {
Manager manager = new Manager()
//Set attribute using setters or defined constructor
return manager;
}
}
}
Then, you have to Register Spring Converters with the MongoConverter.
You can find an example of my own at: Spring Data Mongo - How to map inherited POJO entities?

Prefix properties of multiple PropertyPlaceholderConfigurers

I want to use Spring's PropertyPlaceholderConfigurer to read two property files. I can load each of them by using one of the following tags:
<context:property-placeholder location="class path:com/myapp/internal.properties"/>
<context:property-placeholder location="file://${settings.location}/external.properties"/>
I am not allowed to change the keys in those two files. Both files may contain entries with the same key.
I need to inject the value of a specific file.
//Pseudocode of injecting a property of a specific file
#Value("${internal.properties:my.key}")
String internalValue;
#Value("${external.properties:my.key}")
String externalValue;
So how to specify the file, and not only the key?
you will have to translate it to xml if needed:
public class InternalPropertyPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer{
public UploaderPropertyPlaceholderConfigurer() {
setLocations(new ClassPathResource[]{
new ClassPathResource("com/myapp/internal.properties"),
});
setPlaceholderPrefix("$internal{");
setPlaceholderSuffix("}");
}
and register it in spring ( or use #Component in above class ):
#Bean
public InternalPropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new InternalPropertyPlaceholderConfigurer();
}
this way you should be able to inject properties with this rather ugly syntax:
#Value("$internal{your.key}")
private String value;
If it works, then just add 2nd bean for external :)

Resources