Load external properties file with same keys - java

I have two properties file in a folder in file system.
The path of this folder is passed using system properties -D=path/to/properties/folder
ex: java -jar -DpropDir=abc/def app.jar
These are properties files.
Please note that both files have common key username,password.
mysql.properties
url=jdbc:mysql://localhost:3306
username=root
password=pass
vertica.properties
dburl=jdbc:vertica://123.123.123:4321/abcd
username=abcd
password=pass123
Now I want to access all these properties in respective classes.
MySqlProperties.java and VerticaProperties.java like this.
#Component
public class VerticaProperties {
#Value("${dburl}")
private String dburl;
#Value("${username}")
private String username;
#Value("${password}")
private String password;
public String getDbUrl() {
return dburl;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
and similarly MySqlProperties.java
#Component
public class MySqlProperties {
#Value("${url}")
private String url;
#Value("${username}")
private String username;
#Value("${password}")
private String password;
public String getDbUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
But as key is same value is overriding for username and password.
How to access mysql.properties in MySqlProperties.java and vertica.properties in VerticaProperties.java classes.

You import external properties using #PropertySource
Given the location is shared as
-Dmysql.properties=file:/path-to-mysql.properties -Dvertica.properties=file:/path-to-vertica.properties
#Component
#PropertySource("${vertica.properties}")
public class VerticaProperties {
.....
}
#Component
#PropertySource("${mysql.properties}")
public class MySqlProperties {
....
}
or Given
-Dmysql.properties=/path-to-mysql.properties -Dvertica.properties=/path-to-vertica.properties
#Component
#PropertySource("file:${vertica.properties}")
public class VerticaProperties {
.....
}
#Component
#PropertySource("file:${mysql.properties}")
public class MySqlProperties {
....
}
Additionally , you may also use prefix with #ConfigurationProperties along with #PropertySource.
The annotation works best when we have hierarchical properties that all have the same prefix, so we mention the prefix too as a part of the annotation.
Add prefix to keys like mysql.url , vertica.url in respective files
#Component
#PropertySource("${vertica.properties}")
#ConfigurationProperties(prefix="vertica")
public class VerticaProperties {
.....
}
#Component
#PropertySource("${mysql.properties}")
#ConfigurationProperties(prefix="mysql")
public class MySqlProperties {
....
}

Related

Values not getting picked up from application.properties

I'm trying to access values from my application.properties file in the Neo4jConnectionFactory class. I am able to access the properties within my Setup beans class. But, I want to be able to access them from Neo4jConnectionFactory. When I try to do so I get null values. Is this possible?
Applications.properties
neo4j.url = bolt://localhost:7687
neo4j.user = neo4j
neo4j.password = neo4j
SetupBeans.java
#Component
public class SetupBeans {
#Bean
public Neo4jClient neo4jClient() {
return new Neo4jConnectionFactory().build();
}
}
Neo4jConnectionFactory.java
#Configuration("neo4j")
public class Neo4jConnectionFactory {
#Value("${neo4j.url}")
private String url;
#Value("${neo4j.user}")
private String user;
#Value("${neo4j.password}")
private String password;
public Neo4jClient build() {
return new Neo4jClient(getUrl(), getUser(), getPassword());
}
}
MyApplication.java
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I think this automatically prepends the prefix neo4j
#Configuration("neo4j")
So your #Value statments should omit the prefix, like this:
#Value("${url}")
private String url;
#Value("${user}")
private String user;
#Value("${password}")
private String password;

Prefix for nested configuration properties in spring

Spring boot 2.0.0.RELEASE
I have properties class:
#Configuration
#ConfigurationProperties(prefix="person")
public class PersonProperties {
private AddressProperties addressProperties;
public AddressProperties getAddressProperties() {
return addressProperties;
}
public void setAddressProperties(final AddressProperties addressProperties) {
this.addressProperties = addressProperties;
}
public static class AddressProperties {
private String line1;
public String getLine1() {
return line1;
}
public void setLine1(final String line1) {
this.line1 = line1;
}
}
}
And application.yml:
person:
address:
line1: line1OfAddress
It is not binding properly as my AddressProperties object is null.
When a class has the same name as yml properties AddressProperties -> Address it is working well. I tried to add Qualifier or ConfigurationProperties with a prefix address but it is not working. Unfortunately, I cannot find useful information about this case in spring docs.
How to specify a prefix for nested properties?
Property defined in yaml / property file should match with the variables defined in class.
Either change yaml file as
person:
# addressProperties will also work here
address-properties:
line1: line1OfAddress
Or define your bean as
#Configuration
#ConfigurationProperties(prefix = "person")
public class PersonProperties {
// here variable name doesn't matter, it can be addressProperties as well
// setter / getter should match with properties in yaml
// i.e. getAddress() and setAddress()
private AddressProperties address;
public AddressProperties getAddress() {
return address;
}
public void setAddress(AddressProperties address) {
this.address = address;
}
}
If you want to get all properties under address without defining them in separate bean you can define your PersonProperties class as
#Configuration
#ConfigurationProperties(prefix = "person")
public class PersonProperties {
private Map<String, Object> address;
public Map<String, Object> getAddress() {
return address;
}
public void setAddress(Map<String, Object> address) {
this.address = address;
}
}
Here PersonProperties#address will contain {line1=line1OfAddress}
Now All properties under address will be in the Map.
You could simply un-nest the two classes, allowing each to have it's own prefix.
First class:
#Configuration
#ConfigurationProperties(prefix="person")
public class PersonProperties {
private AddressProperties addressProperties;
public AddressProperties getAddressProperties() {
return addressProperties;
}
public void setAddressProperties(final AddressProperties addressProperties) {
this.addressProperties = addressProperties;
}
}
Second class:
#Configuration
#ConfigurationProperties(prefix="person.address")
public class PersonAddressProperties {
private String line1;
public String getLine1() {
return line1;
}
public void setLine1(final String line1) {
this.line1 = line1;
}
}
Edit: As was pointed out in the comments, you'd have to inject both of these classes if one block of code needed to refer to both sets of properties.

mapping configs in application yml file to enum types

#Reports
reports:
PnLReport:
reportId: 10
path: \\\\pathto\\PnLreport\\
BalanceSheetReport:
reportId: 11
path: \\\\pathto\\balancesheet\\
schedule-10:
description: Deliver pnl reports
report: 10
format: PDF, XLS
I have the above properties defined in aapplication.yml file in my Spring Boot application.
How can I map the repordId and path properties to a enum for example for each of the reports types. For example:
public enum ReportType{
PNL(...)
BALANCE(...);
private final String reportId;
private final String path;
private ReportType(String reportId, String path) {
this.identifier = identifier;
}
Next, I would like map between report: 10 under the schedule-10 property to the reportId to derive the path in a FileService class for example so that I can look if the files exist in the path. How can I do this mapping?
This is the only way I can think of for my requirement, is there a better approach to this?
I'm not sure I would recommend using an enumeration for this since what you're looking for is more like a configurable property instance. Maybe consider using a simple class instead and read two instances of it?
public class ReportType {
private Integer reportId;
private String path;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public void getReportId() {
return reportId;
}
public Integer setReportId(Integer reportId) {
this.reportId = reportId;
}
}
#Component
#ConfigurationProperties(prefix = "reports")
public class ReportTypes {
public ReportType PlnReport;
public ReportType BalanceSheetReport;
}

How to group properties with Spring Boot Configuration Properties

According to Spring Boot documentation, properties can be grouped and a property may appear in more than one group. But at the moment when we create a property class marked with #ConfigurationProperties(prefix="test1") the group name will be the prefix which is test1. Now if I have another property class for example with prefix as "test2" how can I say this latter one has a property from the group test1?
--- UPDATE ---
Added nested class but it's not working
#Configuration
#Profile({"wmx"})
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "myapp.wmx", locations = {"classpath:application-wmx.properties", "classpath:myapp-env.properties"})
public class WmxProperties {
/**
* The WMX implementation to be loaded.
*/
#NotNull(message = "Must be configured.")
private ProfileEnum profile;
//#ConfigurationProperties(locations = "classpath:myapp-env.properties")
public static class Env {
/**
* Host name for WMX.
*/
private String host;
/**
* Port number for WMX.
*/
//#Pattern(regexp = "^[1-9]\\d*$", message = "Positive port number only.")
private Integer port;
/**
* Provider name.
*/
#NotBlank
private String providerName;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getProviderName() {
return providerName;
}
public void setProviderName(String providerName) {
this.providerName = providerName;
}
}
public ProfileEnum getProfile() {
return profile;
}
public void setProfile(ProfileEnum profile) {
this.profile = profile;
}
}
The commented annotation #ConfigurationProperties on the inner class is done after failing my tests. Spring doesn't load those properties with or without the annotation unless they are in the same property file, in this case application-emx.properties. Why is that? I want to separate these properties
=== RESOLVED ====
I noticed that I had to add a field of type the nested class with getter/setter methods otherwise Spring won't load the properties in the nested class
You can compose them with help of inner classes:
Property file
test1.property1=...
test1.test2.property2=...
test1.test2.property3=...
Java/Spring mapping:
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#Getter
#Setter
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(locations = "classpath:myapp.properties")
public class ApplicationProperties {
private String property1;
private Test2 test2;
#Getter
#Setter
#ConfigurationProperties(prefix = "test2")
public static class Test2 {
#NotNull
private String property2;
#NotNull
private String property3;
}
}
We had success with this approach, because java composition mimics structure of property file. Also properties are validatable, so you can fail fast if configuration is not right.
Downside of this approach is that properties are mutable.
If your properties file is getting too big, your application most probably has wider problems.
The annotation processor automatically considers inner classes as nested properties. Make sure you have getters and setters defined.
#ConfigurationProperties(prefix="server")
public class ServerProperties {
private String name;
private Host host;
// ... getter and setters !!!
public static class Host {
private String ip;
private int port;
// ... getter and setters !!!
}
}
The same effect can be achieved with non-inner class but you should use the #NestedConfigurationProperty annotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested.
#ConfigurationProperties(prefix="server")
public class ServerProperties {
private String name;
#NestedConfigurationProperty
private Host host;
// ... getter and setters !!!
}
public class Host {
private String ip;
private int port;
// ... getter and setters
}

How to use #Configuration to define configurations runtime?

I am pretty new to spring so please bear with me and feel free to suggest more reading with right links if you see necessary.I am using a class to define a connection settings for different Rest endpoints I will be calling out to (username port etc.). So I am thinking of storing them to application.properties as follows:
twitter.username="a"
twitter.password="b"
twitter.host="www.twitter.com"
facebook.username="c"
facebok.password="d"
facebook.host="www.facebook.com"
Now I want to define a class that will take all the prefix (such as "twitter" or "facebook") and return me the configuration class based off of the corresponding properties in applicaton.properties.
I was thinking of doing something similar to following:
#Configuration
#PropertySource("classpath:application.properties")
public class RESTConfiguration
{
class RESTServer{
public String username;
public String password;
public String host;
private RESTServer(String username, String password, String host)
{
this.username = username;
this.password = password;
this.host = host;
}
}
#Autowied
private String ednpointName;
#Bean
public RESTServer restServer(Environment env)
{
return new RESTServer(env.getProperty(ednpointName + ".user"),
env.getProperty(ednpointName + ".password"),
env.getProperty(ednpointName+".host"));
}
}
But it clearly won't work since there will be only one Bean and I won't have a way to pass multiple endpointName. Help appreciated!
A better approach would be to use a factory design pattern. Something along the lines of:
#Configuration
#PropertySource("classpath:application.properties")
public class RESTConfiguration
{
#Bean
public RESTServerFactory restServer()
{
return new RESTServerFactory()
}
}
public class RESTServer {
public String username;
public String password;
public String host;
private RESTServer(String username, String password, String host)
{
this.username = username;
this.password = password;
this.host = host;
}
}
public class RESTServerFactory {
#Autowired Environment env;
public RESTServer getRESTServer(String endpointName)
{
return new RESTServer(env.getProperty(ednpointName + ".username"),
env.getProperty(ednpointName + ".password"),
env.getProperty(ednpointName+".host"));
}
}
An example of using this factory:
#Autowired RESTServerFactory factory;
public RESTServer createServer(String endpoint) {
return factory.getRESTServer(endpoint);
}
Two things to do:
Move RestServer outside to its own class, it should not be an inner class of RestConfiguration.
Use scope=DefaultScopes.PROTOTYPE in order to allow creation of several instances of RestServer, one per injection point:
#Bean(scope=DefaultScopes.PROTOTYPE)
public RESTServer restServer(Environment env) { ...

Categories