Inject values with Spring and YAML with nested properties - java

I want to inject some values from a YAML to the Spring context.
The structure of the YAML is similar so I did not want to duplicate code, but the Spring startup is failing because it is not being able to inject the value to the placeholder.
Please note my application.properties:
server.port=8084
activeProfile=dev
autoAgents.supplier.id=0
autoAgents.supplier.name=test
autoAgents.supplier.serviceType=REST
autoAgents.supplier.authType=1
autoAgents.supplier.adapter=test
autoAgents.supplier.username=test
autoAgents.supplier.secret=test
autoAgents.supplier.apiPassword=12345
autoAgents.client.id=1
autoAgents.client.name=test
autoAgents.client.serviceType=REST
autoAgents.client.authType=1
autoAgents.client.adapter=
autoAgents.client.username=test
autoAgents.client.secret=test
autoAgents.client.apiPassword=12345
Then I am injecting this values on the YAML, application.yml
activeProfile: ${activeProfile}
autoAgents:
supplier:
isSupplier: true
meta:
id: ${autoAgents.supplier.id}
name: ${autoAgents.supplier.name}
serviceType: ${autoAgents.supplier.serviceType}
authType: ${autoAgents.supplier.authType}
adapter: ${autoAgents.supplier.adapter}
credentials:
username: ${autoAgents.supplier.username}
secret: ${autoAgents.supplier.secret}
apiPassword: ${autoAgents.supplier.apiPassword}
client:
isSupplier: false
meta:
id: ${autoAgents.client.id}
name: ${autoAgents.client.name}
serviceType: ${autoAgents.client.serviceType}
authType: ${autoAgents.client.authType}
adapter: ${autoAgents.client.adapter}
credentials:
username: ${autoAgents.client.username}
secret: ${autoAgents.client.secret}
apiPassword: ${autoAgents.client.apiPassword}
And then I am importing this to a configuration property context:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
#Data
public class TwoConnectConfigurationProperties {
private String activeProfile;
#Value("${autoAgents.supplier}")
private AutoAgentDup supplier;
#Value("${autoAgents.client}")
private AutoAgentDup client;
}
But #Value("${autoAgents.supplier}") is not working.
Please advise.

As mentioned earlier it does not make sense to inject values to yaml, you can just create the "application.yaml" with the values directly. And just delete the ".properies" file.
You might want to take a look how to easily inject the properties with common suffix into a bean. Its nicely described here:
https://www.baeldung.com/configuration-properties-in-spring-boot
You will have a bean:
#Configuration
#ConfigurationProperties(prefix = "autoAgents.supplier")
public class AutoAgentSupplierProperties {
private long id;
private String name;
// ... rest of the properies properties
}
You might want the same for the "auto.agent" client.
If you want to avoid code duplication, you can have a bean with the common properties. Extend that class with 2 new classes. One for supplier and one for agent - and annotate those with
#ConfigurationProperties
annotation.

Why you need "nested properties"? If you only want to access them in application, just take values from .properties file and fill them as values to .yml file. E.g.: profile: dev, or
autoAgents:
client:
id: 1
Properties from .yml file can be accessed from code same way as from .properties file.
Your problem is in way how you access properties. When you use "#Configuration properties", you have to specific which one to use (e.g. #ConfigurationProperties("autoAgents.client").

Related

Rewrite a property name in Spring Boot

I have a library that has a configuration class with #ConfigurationProperties(prefix ="b") and #ConditionalOnProperty(prefix = "b", name= "c"), in the project where I included this configuration class, the property "b" is nested under a property called "a" instead of being at the root level of the properties.yml, with this the property "b" name is "a.b" and the configuration class will get ignored, the property name needs to be kept "a.b" in the property file, is there a way to tell spring boot to ignore the "a" prefix for the property "b" or to rewrite the property name from "a.b" to "b" after application.yml gets loaded ?
You can (probably, depending on the exact context of the config class you're working with) use YAML anchor and alias to meet this need. YAML anchors are similar to anchors (<a> tags) in HTML; you mark some part of the yaml with an anchor, then refer to it with a reference somewhere else. The reference is an alias to the anchored value.
In your example, assuming the conditional config class is something like this:
#Configuration
#ConditionalOnProperty(prefix = "b", name = "foo")
#ConfigurationProperties(prefix ="b")
#Getter #Setter
public class SO_75435896 {
private String foo;
}
You can have a YAML config like this to populate it:
a.b: &b-alias
foo: bar
b: *b-alias
The anchor name (b-alias in my example) can be whatever you want, there's nothing special about that name I chose.
If you want to read more about YAML anchors, this is a decent explanation. It's part of Bitbucket documentation, but provides a pretty good general-purpose explanation with examples. There are, of course, plenty of other web pages about YAML anchors.
So your configuration class and properties files cannot be adapted. You can rename it at runtime in your application.yml :
BProperties
#ConfigurationProperties(prefix ="b")
public class BProperties {
private String c;
private String d;
}
And this properties file :
my-props-file.properties
a.b.c=value1
a.b.d=value2
You can rename the properties at runtime in your application.yml (define in classpath under src/main/ressources/) :
application.yml
b:
c: ${a.b.c}
d: ${a.b.d}

spring.application.name property in application.yaml is fetched as null if profile is active

I have an application.yaml file which specifies the service's name:
spring:
application:
name: "my-microservice"
Now, when I try to fetch it using #Value("spring.application.name") inside my code, I'm able to do that successfully.
But I'm also using a "dev" profile for which I created a separate application-dev.yaml, but I didn't specify the spring.application.name inside this yaml.
Now, when I try to run my code, the #Value annotation gives me a null.
I thought that the fields not specified by application-dev.yaml should be populated using application.yaml, but apparently this is not happening.
Am I supposed to just copy every common field from the default to the dev's application file? Or is there any other way? Any help is appreciated, Thanks :)
You need to use Spring Expression Language which says we should write it as
#Value("${spring.application.name}")
private String appName;
For Default value if key is not present in yaml/yml or properties file
#Value("${spring.application.name: defaultValue}")
private String appName;
The last way you can fetch value is using environment object
#Autowired
private Environment environment;
String appName = environment.get("spring.application.name");
Ok I figured out the problem.
It is true that the default application.yaml properties are read by spring for all profiles (unless they have been overwritten by a profile's application file)
But in my case, I was trying to use the #Value annotation on a static field.
#Value("${spring.application.name}")
private static String applicationName;
This caused the null exception in my code. Removing the static context from the field worked for me.

Reg Spring #Entity annotation

I have a class which has the annotation
#Entity(value = "mongo_collection_name", noClassnameStored = true)
public class Class_Name{ .... }
As you see in the above code snippet, the mongo collection name is hard coded. Can I get this value from a properties file or Consul? What should I do in order to read it from properties file or Consul?
You can create a conf file and load them as dynamic properties using annotations Configuration and PropertySource
Follow the steps:
Create a conf file with the property. For example: /path/to/file.conf
collectionName=mongo_collection_name
Create a Configuration class
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.PropertySource
#Configuration
#PropertySource("file:/path/to/file.conf")
class AppProperties {
}
Now you can get the value with annotation Value in either Service class or Application like below
#Value("${collectionName}")
String collectionName
Let me know if you have questions.

Read CF environment nested JSON with #ConfigurationProperties

I have an application on SpringBoot that is deployes to Cloud Foundry. It has a bound service that attaches some information to VCAP_SERVICES that I can use in my application.properties file to receive security related data.
This services variables are in the user-provided sub-json of whole VCAP_SERVICE environment variable and looks like this:
"user-provided": [
{
"credentials": {
"auth-scopes": {
"group-1": "superadmin",
"group-2": "admin"
},
"base64ClientCredential": "SSBBTSBOT1QgVEhBVCBTSUxMWSA6LUQ=",
"client-id": "my-app",
"client-secret": "g1234567890",
"token-info-uri": "https://XYZ.predix.io/check_token",
"user-info-uri": "https://XYZ.predix.io/userinfo"
},
"label": "user-provided",
"name": "my-app-creds",
"syslog_drain_url": "",
"tags": [],
"volume_mounts": []
}
]
In my application.properties I have
security.properties.client-id=${vcap.services.my-app-creds.credentials.client-id}
security.properties.client-secret=${vcap.services.my-app-creds.credentials.client-secret}
security.properties.token-info-uri=${vcap.services.my-app-creds.credentials.token-info-uri}
security.properties.user-info-uri=${vcap.services.my-app-creds.credentials.user-info-uri}
security.properties.auth-scopes=${vcap.services.my-app-creds.credentials.auth-scopes}
I also created class SecurityProperties that reads those properties and stores in JAVA:
#Component
#PropertySource("classpath:application.properties")
#ConfigurationProperties(prefix = "security.properties")
public class SecurityProperties {
String clientId;
String clientSecret;
String tokenInfoUri;
String userInfoUri;
//Required getters and setters
}
And this class successfully binds those fields to information received from application.properties.
The problem occurs when I want to receive Map:
"auth-scopes": {
"group-1": "superadmin",
"group-2": "admin"
}
I tried adding to SecurityProperties class field:
HashMap<String, String> authScopes;
But it fails
I also tried with inner class
AuthScopes authScopes;
public static class AuthScopes {
//various different implementations
//to read this but no success at all
}
I ran out of other ideas. I just can't figure out how to get this. I will also accept that auth-scopes json from VCAPS will be read as a string, and then I will parse it - no problem. The issue is that even if I add to SecurityProperties field String authScopes it is bound with exactly this string: "${vcap.services.my-app-creds.credentials.auth-scopes}". Nothing useful.
If you have any idea - please share.
Spring Boot provides the vcap. properties via the CloudFoundryVcapEnvironmentPostProcessor. That class reads the VCAP_SERVICES JSON and flattens it into a set of discrete properties. If you add Spring Boot actuators to your application and access the /env endpoint, you will see the set of vcap. properties that get created.
In your example, the properties will contain these values:
"vcap": {
...
"vcap.services.my-app-creds.credentials.auth-scopes.group-1": "******",
"vcap.services.my-app-creds.credentials.auth-scopes.group-2": "******",
}
There is no vcap.services.my-service.credentials.auth-scopes property in that list, because the JSON get completely flattened. The property names use the dot notation to reflect the JSON structure, but the hierarchy is not preserved in the set of properties. In short, what you are trying to do won't work with Boot properties.
One alternative would be to set those values as a string instead of a hash in the JSON, as in "auth-scopes": "group-1:superadmin,group-2:admin" or "auth-scopes": "group-1=superadmin,group-2=admin". Your SecurityProperties could then get the value of vcap.services.my-service.credentials.auth-scopes and parse the String into a Map.

How to load nested key value pairs from a properties file in SpringBoot

Is there a better manner to implement properties file having key-value pairs as value using Spring/Spring Boot?
I want to create a property file where the key contains a couple of key-value pair as value.
I tried below implementation:-
Properties file:-
Fiat=model:pet,year:1996
Honda=model:dis,year:2000
And i have below class trying to read the properties file.
#Component
#PropertySources(#PropertySource("classpath:sample.properties"))
public class PropertiesExtractor {
#Autowired
private Environment env;
public String pullValue(String node) {
String value = env.getProperty(node);
System.out.println(value);//for Fiat, i get syso as **model:pet,year:1996**
}
}
I need to parse the values using java, to get the individual value. Is this the only way out to implement this.
Is there a better way to use nested property files in Java?
Create a Car object or something with a model and a year property. Then create something like this
#ConfigurationProperties("foo")
public class CarProperties {
private Map<String,Car> cars;
// Getters/Setters
}
Add add #EnableConfigurationProperties(CarProperties.class) in your main configuration class.
Then you can inject that config as follows:
foo.cars.Fiat.model=pet
foo.cars.Fiat.year=1996
foo.cars.Honda.model=dis
foo.cars.Honda.year=2000
There is more info in the doc.
You can use yaml files with spring as well:
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-yaml
This way, you can work with
Fiat:
model: pet
year: 1996
Honda:
model: dis
year: 2000

Categories

Resources