Getting a map property from environment variable with Spring - java

I'm creating a microservice with Spring Boot that uses some properties loaded both with #Value and #ConfigurationProperties. Those properties will have default values defined in the application.yaml but I want to override them with environment variables. I've manage to do it with almost every basic property type but I'm unable with maps.
This is the code that retrieves the property, so the map is found in configuration.map:
#Data
#ConfigurationProperties(prefix = "configuration")
public class MapConfig{
private Map<String, Object> map;
}
The default values I have in the application.yaml are:
configuration:
map:
a: "A"
b: 2
c: true
This works fine and I get a map containing those key-value entries. Problem comes when I try to initialize the map with the environment variables. I've tried with multiples variants of CONFIGURATION_MAP='{aa:"aa", bb:12, cc:true}' and every time I start the application I get the default values defined in application.yaml but no trace of the environment map.
I also tried adding variables like CONFIGURATION_MAP_AA='HELLO' and I was able to put new values in my map. However all I can do is adding information but the default values I wrote in the yaml are still there. In this case I'm getting:
{
"aa": "HELLO",
"a": "A",
"b": 2,
"c": true
}
This isn't the behavior I'm looking for. I want to completely override the default map instead of adding new info. It also has the problem that keys added this way are always transformed to lowercase so I can't override keys with camelCase. And values are also casted to strings even when my map is <String,Object>.
Can anyone tell me how to properly initialize maps with environment variables or point me in the right direction? Thanks a lot!

The problem is the order in which the properties are been loaded.
Have a look at this doc that explains the loading order. I don't know if it will be
useful for your scenario but you can try creating a profile (for example application-other.yml) and load this. It will load after the application.yml
The cast is coming from the most general type in your list. In the end all three "name, 1, true" can be Strings. I can't think of a way of loading Object but you can recast your String on what you like ( if its needed. ) Else break it into multiple maps that are type specific ( Map<String,String> , Map<String,Boolean>, Map<String,Integer> )

Related

Specify a Set in OpenAPI

I am using OpenAPI / Swagger to specify my API.
One thing that I could not find out is how to specify a Set.
I am using https://editor.swagger.io/ and I typed in the whole API. For a property that I want to specify as Set I wrote the following:
myProperty:
uniqueItems: true
type: array
description: some description
items:
type: string
I would have guessed that uniqueItems does the trick and a Set is generated, but this is not the case. Instead the following code is generated:
#JsonProperty("myProperty")
private List<String> myProperty = null;
Is there a way to generate something like
#JsonProperty("myProperty")
private Set<String> myProperty = null;
instead?
I found a possible solution here in SO, but this requires some configuration in a pom.xml. However, the online editor that I am using gives me only the option to generate code for different platforms but does not accept a pom file.
OpenAPI (version 3) supports the following data types:
string
number
integer
boolean
array
object
There is no support for set data type in OpenAPI v3. The closest data type is an array with property uniqueItems set to true (as you've suggested). But it's still an array with a constraint on the uniqueness of its items, not a set.
So, your request cannot be resolved on the OpenAPI level.
However, there might be an option on the code generator level, and you would need to address the issue to the code generator of your choice.

Spring Boot/YAML: How to read a map of Strings to Lists?

I have a property that looks like:
#Value(...)
Map<String, List<String>> classToMethods;
I want to populate this map via my application.yml in a Spring Boot application via the #Value("...") annotation. Some sample values are:
"java.lang.Double" => ["parseDouble", "toString"]
"java.lang.String" => ["toUpperCase"]
How do I represent this in my application.yml file? Also, I would like to put placeholder for environmentally injected values from our deployment tool. So for example, I currently have:
some:
property: ${ENV_VALUE:default_value_if_no_env}
Basically, this is the syntax I use for injecting values for the specific environment(value for ENV_VALUE varies for each environment). I am looking for a way to format the YML map such that this kind of environment replacements are also possible.
Alternative I am thinking of is just creating a String and manually parsing it during constructor injection but would like to avoid if possible.

How can I handle a giant static configuration class used in multiple applications?

A related group of applications I've been assigned share a database table for configuration values that has a table with columns 'application', 'config_name', 'config_type' (IE String, Integer), and 'config_value'. There's also a stored procedure that takes in a string (applicationName) and returns all config names, types, and values where applicationName == application.
In each application, a wrapper class is instantiated which contains a static ThreadLocal (hereafter 'static config)', and that static config pulls all values from the config table for the application.
When loading configuration values, the stored procedure returns a massive list of properties that are iterated over, going through a massive list of if-else statements testing whether the 'config_name' column matches a string literal, and if so, loads the value into a differently named variable.
EX:
if (result.isBeforeFirst()) {
while(result.next()) {
if (result.getString("config_name").equals("myConfig1") {
myConfigurationValue1 = result.getString(config_value); }
else if (result.getString("config_name").equals("myConfig2") {
myConfigurationValue2 = result.getString(config_value); }
}}
These cover between 60-100ish configs per app, and each application has an identical Configuration class save for the names of the properties they're trying to read.
So my questions are:
Having one gigantic configuration class is poor design, right? I'm not entirely sure how to break them down and I can't share them here, but I'm assuming best practice would be to have multiple configuration classes that have all the components needed to perform a particular operation, IE 'LocalDatabaseConfig' or '(ExternalSystemName)DatabaseConfig'?
Once broken down, what's the best way to get config valuee where needed without static access? If I have each class instantiate the configuration it needs I'll be doing a lot of redundant db operations, but if I just pass them from the application entry point then many classes have to 'hold on' to data they don't need to feed it to later classes... Is this a time when static config classes are the best option??
Is there an elegant way to load properties from the DB (in core java - company is very particular with third party libraries) without using this massive if-else chain I keep thinking that ideally we'd just dynamically load each property as it's referenced, but the only way I can think to do that is to use another stored procedure that takes in a unique identifier for a property and load it that way, but that would involve a lot more string literals...m
(Might be invalidated by 3) Is there a better way for the comparison in the pseudo-code above to test for a property rather than using a string literal? Could this be resolved if we just agreed to name our configuration properties in the application the same way they're named in the DB?
Currently every application just copy-pastes this configuration class and replaces the string literals and variable names; many of the values are unique in name and value, some are unique in value but are named the same between applications (and vice versa), and some are the same name and value for each application, but because the stored procedure fetches values based on application, redundant db entries are necessary (despite that many such values are supposed to be the same at all times, and any change to one needs to be performed on the other versions as well). Would it make sense to create a core library class that can construct any of the proposed 'broken down' configuration classes? IE, every application needs some basic logging configurations that don't change across the applications. We already have a core library that's a dependency for each application, but I don't know whether it would make sense add all/some/none of the configuration classes to the core library...
Thanks for your help! Sorry for the abundance of questions!?
The cascading if-then-else might be eliminated by using a while loop to copy the database-query results into two maps: a Map[String, String] for the string-based configuration variables, and a Map[String, Integer] for the integer configuration variables. Then the class could provide the following operations:
public String lookupStringVariable(String name, String defaultValue) {
String value = stringMap.get(name);
if (value == null) {
return defaultValue;
} else {
return value;
}
}
public int lookupIntVariable(String name, int defaultValue) {
Integer value = intMap.get(name);
if (value == null) {
return defaultValue;
} else {
return value.intValue();
}
}
If there is a requirement (perhaps for runtime performance) to have the configuration values stored in fields of the configuration class, then the configuration class could make the above two operations private and use them to initialize fields. For example:
logLevel = lookupIntVariable("log_level", 2);
logDir = lookupStringVariable("log_dir", "/tmp");
An alternative (but complementary) suggestion is to write a code generator application that will query the DB table and generate a separate Java class for each value in the application column of the DB table. The implementation of a generated Java class would use whatever coding approach you like to query the DB table and retrieve the application-specific configuration variables. Once you have written this generator application, you can rerun it whenever the DB table is updated to add/modify configuration variables. If you decide to write such a generator, you can use print() statements to generate the Java code. Alternatively, you might use a template engine to reduce some of the verbosity associated with print() statements. An example of a template engine is Velocity, but the Comparison of web template engines Wikipedia article lists dozens more.
You would be better off separating the database access from the application initialisation. A basic definition would be Map<String,String> returned by querying for one application's settings:
Map<String,String> config = dbOps.getConfig("myappname");
// which populates a map from the config_name/config_value queries:
// AS: config.put(result.getString("config_name"), result.getString("config_value");
Your application code then can initialise from the single application settings:
void init(Map<String,String> config) {
myConfigurationValue1 = config.get("myConfig1");
myConfigurationValue2 = config.get("myConfig2");
}
A benefit of this decoupling is that you define test cases for your application by hardwiring the config for different permutations of Map settings without accessing a huge test database configurations, and can test the config loader independently of your application logic.
Once this is working, you might consider whether dbOps.getConfig("myappname") caches the per-application settings to avoid excessive queries (if they don't change on database), or whether to declare Config as a class backed by Map but with calls for getInt / get and default values, and which throw RuntimeException if missing keys:
void init(Config config) {
myConfigurationValue1 = config.get("myConfig1", "aDefaultVal");
myConfigurationInt2 = config.getInt("myConfig2", 100);
}

How to get the map values from "yml" file as a Map?

I have a "details.yml" file, considering all the setting for getting all values from "yml.file" is done. But I am unable to store Map values into
"Map"
Here is my "details.yml" file below
details:
company:XYZ
values:
name: Manish
last: Raut
And in my class file i am able to get the values of "company" from yml file using #Value("${company}")
#Component
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "details")
public class abcd() {
#Value("${company}")
String company;
#Value("${values}")
Map<String, String> values =new HashMap<String, String>();
...............................
}
i am not able to get those values in Mao which i created in this class, but i am getting values for "Company".
Help me with this?
Im the past, I added getter/setter for MAP type and it was work.
Did you try it? (getter/setter for 'values')
I'm not really sure what marshalling framework Spring uses behind the scenes and how it configures it (you could probably find out with some debugging and maybe make it work for your case), but you could always add an extra layer to your application and configure your own.
For instance you could use Jackson with yaml dataformat - https://dzone.com/articles/read-yaml-in-java-with-jackson.

How to create dynamic Bean?

I have a text file which contain comma separated data which is the attribute of our bean.
e.g. name,age,gender,city,zipcode
We read the text file and we have a list which contain all the attribute. Here we need to create a dynamic Bean which contain the attribute based on that list which we get after reading text file, but we have different text files with different fields. So how should I create a dynamic bean which can contain the attributes according to the list which we will get after reading test file? Please give me some solution on this issue.
It isn't a dynamic Bean,
but I would use a HashMap:
HashMap<String, String> values = new HashMap<String, String>();
values.put("name", "Sebastian Blablabla");
values.put("city", "MyTown");
System.out.println(values.get("name"));
System.out.println(values.containsKey("city"));
System.out.println(values.containsKey("zipcode"));
Dynamic beans by Oracle use Maps too, look here:
http://docs.oracle.com/cd/E23095_01/Platform.93/ATGProgGuide/html/s0210dynamicbeans01.html
I would just Use a Super- Class; Just like, i dont know..
public class Item

Categories

Resources