How to read Azure app config values from Java SpringBoot app - java

I have a Springboot app that is trying to connect and read values from an app config resource.
Some keys in the Azure app config (which I am unable to change) are in this format
`
/application/config.datasource.jdbc-url
/application/config.datasource.password
/application/config.datasource.username`
I have a config Java class with prefix ("config"), but I don't know what member variables I should have in order to access "datasource.jdbc-url", "datasource.password" etc.
If the app config was just /application/config.username
then I could just use the below in my Java class
String username;
but for some of the configs that include multiple dots and some dashes (which Java identifiers can't have), how can I read the values?
Thank you!

Your use case is similar to this sample azure-spring-cloud-starter-appconfiguration-config-sample, please try to define the properties class to bind the configurations in Azure App Configuration.

Adding to #Moarchy Chan#ConfigurationProperties(prefix = "config.datasource")
In Azure portal>App configuration> Configuration explorer, I have created keys and values for Username, Password and Jdbc url.
This is my Class UserProperties,
#ConfigurationProperties(prefix = "config.datasource")
public class UserProperties {
private String username;
private String password;
private String jdbcurl;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getJdbcurl() {
return jdbcurl;
}
public void setJdbcurl(String jdbcurl) {
this.jdbcurl = jdbcurl;
}
}
Here is the UserController I have created,
public class UserController {
private final UserProperties properties;
public UserController(UserProperties properties) {
this.properties = properties;
}
#GetMapping("/Anu")
public String getUsername() {
return "username: " + properties.getUsername();
}
#GetMapping("/vyshu")
public String getPassword() {
return "password: " + properties.getPassword();
}
#GetMapping("/priya")
public String getJdbcurl() {
return "jdbcurl: " + properties.getJdbcurl();
}
}
This the bootstrap for my code, you should add your connection string in {},
spring.cloud.azure.appconfiguration.stores[0].connection-string= ${APP_CONFIGURATION_CONNECTION_STRING}
This is the output for my Username,
This is the output for my Password,
This is the output for my jdbcurl,

You have two options depending on what you are looking for. You can either use #Value("config.datasource.jdbc-url") or you can use nested properties.
public class Datasource {
private String username;
private String password;
private String jdbcurl;
...
}
#ConfigurationProperties(prefix = "config")
class MyProperties {
private Datasource datasource = new Datasource();
...
}

Related

How to refactor the way of getting multiple #Value from a properties file

I need to get some values from aplication.properties file and these values depend on the country, for instance these are the below values that I need to fetch:
#Value("${fr.Name}")
private String frName;
#Value("${fr.address}")
private String frAddress;
#Value("${de.Name}")
private String deName;
#Value("${de.address}")
private String deAddress;
#Value("${bz.Name}")
private String bzName;
#Value("${bz.address}")
private String bzAddress;
Then in my service method, I will be using multiple if else statement and my list of values keep increasing each time I add new country
public String apply(string country){
if ("fr".equals(country))
return frName + frAddress
else if ("de".equals(country))
return deName + deAddress
else if ("bz".equals(country))
return bzName + bzAddress
}
So how can I refactor it without using multiple if-else statements?
Look at spring configuration properties. this gives an opportunity to handle configuration properties more declarative way
https://www.baeldung.com/configuration-properties-in-spring-boot
EDIT
#Component
#ConfigurationProperties(prefix = "my", ignoreUnknownFields = false)
public class CountryBasedProps {
Map<String,CountryProps> config = new HashMap<>();
public Map<String, CountryProps> getConfig() {
return config;
}
public void setConfig(Map<String, CountryProps> config) {
this.config = config;
}
public static class CountryProps{
String name;
String address;
/*
other properties, with getters/setters
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
#Override
public String toString() {
return "CountryProps{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
#Override
public String toString() {
return "CountryBasedProps{" +
"config=" + config +
'}';
}
}
properties:
my.config.fr.name=French
my.config.fr.address=Paris
my.config.de.name=German
my.config.de.address=Berlin
output
CountryBasedProps{config={fr=CountryProps{name='French', address='Paris'}, de=CountryProps{name='German', address='Berlin'}}}
Note:
add #EnableConfigurationProperties() into your project
2.7.1. Loading YAML
You can load the configuration file directly into Map using #ConfigurationProperties and then get the corresponding value with respective key. So you can avoid using if block
application.yml If you are using application.yml
countries:
fr: fnamefaddress
de: dnamedaddress
bz: bnamebaddress
application.properties : If you are using application.properties
countries.fr=fnamefaddress
countries.de=dnamedaddress
countries.be=bnamebaddress
Configuration Class
#Configuration
#ConfigurationProperties
public class CountriesConfig {
#Getter
private Map<String, String> countries = new HashMap<String, String>();
}
output :
{bz=bnamebaddress, de=dnamedaddress, fr=fnamefaddress}
So in code you can simply use get or getOrDefault on Map
countries.get("fr");

Jackson deserialization of YAML file into Map (with no custom deserializer)

I'm trying to load a YAML file into an instance of a Map. Is there a way to load it without defining the custom deserializer (e.g. using annotations?)?
This is an example of my YAML file:
- site: First Site
url: some_url
username: some_name
password: some_password
- site: Second Site
url: its_url
username: its_name
password: its_password
This is the java bean class to deserialize one "site configuration" into (generated by http://www.jsonschema2pojo.org/):
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.commons.lang3.builder.ToStringBuilder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"site",
"url",
"username",
"password"
})
public class SiteConfiguration {
#JsonProperty("site")
private String site;
#JsonProperty("url")
private String url;
#JsonProperty("username")
private String username;
#JsonProperty("password")
private String password;
/**
* No args constructor for use in serialization
*
*/
public SiteConfiguration() {
}
/**
*
* #param site
* #param username
* #param password
* #param url
*/
public SiteConfiguration(String site, String url, String username, String password) {
super();
this.site = site;
this.url = url;
this.username = username;
this.password = password;
}
#JsonProperty("site")
public String getSite() {
return site;
}
#JsonProperty("site")
public void setSite(String site) {
this.site = site;
}
#JsonProperty("url")
public String getUrl() {
return url;
}
#JsonProperty("url")
public void setUrl(String url) {
this.url = url;
}
#JsonProperty("username")
public String getUsername() {
return username;
}
#JsonProperty("username")
public void setUsername(String username) {
this.username = username;
}
#JsonProperty("password")
public String getPassword() {
return password;
}
#JsonProperty("password")
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return new ToStringBuilder(this).append("site", site).append("url", url).append("username", username).append("password", password).toString();
}
}
And finally, this is the code that deserializes the YAML above into Map.
private static Map<String,SiteConfiguration> sites;
public static SiteConfiguration getSiteConfiguration(String key) {
if (sites == null) {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
try {
// todo this finishes on: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token
// sites = mapper.readValue(YamlReader.class.getClass().getResource("/site-configuration.yaml"), new TypeReference<Map<String,SiteConfiguration>>() {});
SiteConfiguration[] sitesArray = mapper.readValue(YamlReader.class.getClass().getResource("/site-configuration.yaml"), SiteConfiguration[].class);
sites = new HashMap<>();
for (SiteConfiguration site : sitesArray) { //todo is there Jackson built-in deserialization?
sites.put(site.getSite(), site);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return sites.get(key);
}
As you can see, I'm doing it in two steps. First I deserialize the file into an array of SiteConfiguration instances, then I put those into a Map where site field represents the map key.
Is there a way to do the same while omitting the array of SiteConfiguration instances? Is the only way to do it using a custom deserializer?
Change the structure of your YAML. If you want to deserialize your data as a map, it needs to start as a map in the YAML file. You have a sequence of mappings in your example, but you want a mapping of mappings.
https://yaml.org/spec/1.2/spec.html#id2759963
Example 2.4. Sequence of Mappings
(players’ statistics)
-
name: Mark McGwire
hr: 65
avg: 0.278
-
name: Sammy Sosa
hr: 63
avg: 0.288
Example 10.1. !!map Examples
Block style: !!map
Clark : Evans
Ingy : döt Net
Oren : Ben-Kiki
Try this instead:
First Site:
site: First Site
url: some_url
username: some_name
password: some_password
Second Site:
site: Second Site
url: its_url
username: its_name
password: its_password
This structure works with Jackson 2.9.9 with no custom deserializers.

Jackson serialization with null

Jackson annotations are for serialization but I cannot find the solution to have two different views with different behavior. Having the following json:
{
"username": "username",
"manager": null,
"password": "pwd"
}
I'd like to have to following output in case the first view (public: no null and no sensitive information):
{
"username": "username"
}
For the second view (internal: incoming nulls are showed sensitive information is hashed):
{
"username": "username",
"manager": null,
"password": "hashedValue"
}
The problem is when the optional fields are not provided like in the following json:
{
"password": "pwd"
}
I'd like to have an empty for public and only the hashed password in the internal view:
{
"password": "hashedValue"
}
I have the following class:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
private String username;
private String manager;
private String password;
#JsonView(Views.Internal.class)
#JsonSerialize(using = Md5Serializer.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getManager() {
return manager;
}
public void setManager(String manager) {
this.manager = manager;
}
}
This can do most what I'd like to have but it cannot make difference if a value is null because of the default value or because it's coming from the JSON.
An other approach was to use a Map inside the object:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
private Map<String, String> content = new HashMap<>();
#JsonView(Views.Internal.class)
#JsonSerialize(using = Md5Serializer.class)
#JsonIgnore
public String getPassword() {
return this.content.get("password");
}
#JsonSetter
public void setPassword(String password) {
this.content.put("password", password);
}
#JsonIgnore
public String getUsername() {
return this.content.get("username");
}
#JsonSetter
public void setUsername(String username) {
this.content.put("username", username);
}
#JsonIgnore
public String getManager() {
return this.content.get("manager");
}
#JsonSetter
public void setManager(String manager) {
this.content.put("manager", manager);
}
#JsonAnyGetter
public Map<String, String> getContent() {
return this.content;
}
}
In this case the problem that the password is not hashed (and the hashed value cannot be stored here).
If the getters are used and the JsonAnyGetter is removed then the incoming null cannot be serialized as the JsonInclude ignores the field. I was reading a lot that a BeanSerializerModifier can be used but that is also needed to be register in all ObjectMapper instance.
Also I'm not sure that without setting the view I'd like to have only the Public view but currently all non-null values are shown. Any idea how it can be achieved only by annotations?

Generating JSON from POJO for a specific scenario

I have used Jackson and JSONObject to generate a plain JSON - things are fine here. I have a specific case where my pojo looks like below and i need the JSON is the specified format.
package test;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "login")
public class LoginApi implements IRestBean {
private String username;
private String password;
private String sfSessionId;
private String sfServerUrl;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSfSessionId() {
return sfSessionId;
}
public void setSfSessionId(String sfSessionId) {
this.sfSessionId = sfSessionId;
}
public String getSfServerUrl() {
return sfServerUrl;
}
public void setSfServerUrl(String sfServerUrl) {
this.sfServerUrl = sfServerUrl;
}
}
The JSON that i am able to generate looks like this:
{
"username" : null,
"password" : null,
"sfSessionId" : null,
"sfServerUrl" : null
}
But this is not my requirement - i need the JSON in the below format so that my server accepts this as a valid JSON:
{
"#type":"login",
"username":"username#domain.com",
"password":"password",
"sfSessionId":null,
"sfServerUrl":null
}
Please help. Thanks in advance!
Add a private field to the POJO with the type.
#XmlRootElement(name = "login")
public class LoginApi implements IRestBean {
...
#XmlAttribute(name = "type")
private String getJsonType() {
return "login";
}
...
}
Note the use of XmlAttribute to automatically append an "#" to the name.
Change the IRestBean interface to include the #JsonTypeInfo annotation:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="#type")
public interface IRestBean {
...
}
Next, annotate the LoginApi class with #JsonTypeName:
#XmlRootElement(name = "login")
#JsonTypeName("login")
public class LoginApi implements IRestBean {
...
}
These are both Jackson-specific annotations.

Spring session bean and RESTEasy + Jackson nullPointerException

I am building a spring based WebApp including a RESTful method call.
I use RESTeasy and jackson to return the username of the current logged in user
(this information is stored in a session bean called "UserBean")
UserBean:
#Component("userBean")
#Scope("session")
public class UserBean implements Serializable {
#Autowired
private InitApp app;
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
OverviewBean is the bean that contains the rest method (including the jackson conversion to json):
#Component("overviewBean")
#Scope("view")
#Path("/repairs")
public class OverviewBean {
#Autowired
private InitApp app;
#Autowired
private UserBean userBean;
private List<Repair> openRepairsClient;
private List<Repair> closedRepairsClient;
#PostConstruct
public void fillRepairs() {
try {
String username = userBean.getUsername();
openRepairsClient = app.repairService.findOpenRepairsByClient((Client) app.userService.getUser(userBean.getUsername()));
closedRepairsClient = app.repairService.findClosedRepairsByClient((Client) app.userService.getUser(userBean.getUsername()));
} catch (UserServiceException ex) {
Logger.getLogger(OverviewBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
//Getters and setters openRepairsClient/closedRepairsClient
#GET
#Path("/getrepairs")
#Produces("application/json")
public String getOpenRepairsInJson() {
String username = userBean.getUsername();
return "test";
}
}
fillRepairs() is able to use userBean without any errors. For example the "String username = userBean.getUsername();" within the try catch returns the username correctly.
My issue is that when getOpenRepairsInJson gets called it throws a nullPointerException
on "String username = userBean.getUsername();". It seems that my userBean is not "linked"
at the moment of the method call. What am I doing wrong?
Thanks in advance!

Categories