Spring boot ConfigurationProperties - Inject a TreeMap with a comparator - java

I have the following config properties in a spring boot app:
#Configuration
#ConfigurationProperties(prefix = "test")
public class ConfigProperties {
private Map<String, String> values = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
public void setValues(Map<String, String> values) {
this.values = values;
}
I want to end up with a Map that ignore the case of the keys.
When the injection occurs, the setValues method receives a treemap without the comparator.
I fixed this by changing the setter to this:
public void setValues(Map<String, String> values) {
this.values.clear();
this.values.putAll(values );
}
Is there a proper way to do this? Am I missing something?

I think you are doing everything correctly to copy all entries of one arbitrary map into a sorted TreeMap with a custom comparator. Basically you only have two options: clear and putAll or create a new instance and putAll
But, I don't think this should be something that the configuration is responsible for. Your intended order likely has some business value, depending its use. So ordering should happen in the code that uses the values of the configuration. But then again, see both options above.

Related

Assert that map contains not null vales by given keys

I have a code that consumes map of properties with string keys which represents some kind of context. And I want this code to fail if the map does not contain some of the required properties.
The corresponding code might look like this:
SomeResultType businessMethod(Map<String, String> context) {
Assert.isTrue(context.containsKey("A"), "A property must not be empty");
Assert.isTrue(context.containsKey("B"), "B property must not be empty");
Assert.isTrue(context.containsKey("C"), "C property must not be empty");
// ...
}
I wrote a simple replacement by myself with signature like this
public static <K, V> void mapContainsKeys(Map<K, V> map, K... keys)
But I think that something like this must be already implemented somewhere. So I'm searching for library replacement of this code.
It would be great if Spring guys implemented something like this in org.springframework.util.Assert.
if you want to just check that a map contains a list of keys, use this:
map.keySet().containsAll(keys)
if you want more details, to know which ones were missing:
Set<String> missingKeys = new HashSet<>(keys);
missingKeys.removeAll(map.keySet());
and then:
if (!missingKeys.isEmpty()) {
throw new IllegalArgumentException(missingKeys + " keys missing");
}
The fact that you had to write a helper method means that you are probably manipulating maps all over your code.
Looks like a code smell to me, you should probably map your properties to an object that you can validate once and pass everywhere.
Since you are using Spring, and if you are using Spring Boot (most people do nowadays), you can use #ConfigurationProperties to map your configuration properties to an object.
This way you can also add validation like #javax.validation.constraints.NotNull and make sure your properties are valid.

Spring Configuration creating complex data structure with yaml

Using yaml in my Spring-boot application (with snakeyaml dependency 1.16) I am attempting to create a #ConfigurationProperties based off of my application.yml file. I want to create a data structure like the json below which is a Map with String Keys and Array values.
mapName: {
"key1": ["elem0","elem1"],
"key2": ["hello","world"]
}
Attempting to create a Spring configuration class as follows
#Component
#ConfigurationProperties(prefix = "channel-broker")
#EnableConfigurationProperties
public class BrokerConfiguration {
private Map<String, Set<String>> broker = new HashMap<>();
public Map<String, Set<String>> getBroker() {
return broker;
}
}
I have tried the following for my yaml
channel-broker:
broker: {message-delivery: ['all'], facebook: ['client1']}
Attempt two
channel-broker:
message-delivery: ['all']
facebook: ['client1']
Attempt three
channel-broker:
message-delivery:
- ['all']
facebook:
- ['client1']
I have also tried initializing the HashMap in the #ConfigurationProperties class as such ... new HashMap<String, Set<String>> this didn't work either
All attempts result in this error which makes me believe its an error when converting to the object not that there is anything wrong with the yaml syntax.
Caused by: org.springframework.beans.InvalidPropertyException: Invalid
property 'brokerTest[message-delivery][0]' of bean class
[my.classpackage.clasname]:
Property referenced in indexed property path
'brokerTest[message-delivery][0]' is neither an array nor a List nor a
Map; returned value was [all]
Is it possible to create such an object? How would I accomplish this
-UPDATE-
If I change the Set to an ArrayList (or List interface) this works but that isn't what I'm looking for. changed to this
private Map<String, ArrayList<String>> brokerTest = new HashMap<>();
but need this doesn't work with Set interface either:
private Map<String, HashSet<String>> brokerTest = new HashMap<>();
This issue was being caused by the format of the yaml file. The following structure allowed me to build my graph like data structure out of yaml
channel-broker:
broker:
message-delivery:
all
facebook:
client1,client2
The Set doesn't want anything extra surrounding the key. Note if your Set will contain multiple values you can add a comma to separate them. Just like Json the last element will not have a comma after.
What you are looking for is this :
channel-broker: {broker: {message-delivery:['all', ...], facebook:['client1', ...]}}
see Complete idiot's introduction to yaml
If you use [] then it's an array so arraylist works, for hashset/hashmap you need to use {} brackets.
channel-broker: {
broker: {
message-delivery:{'all', '123'},
facebook:{'client1', 'cleant2'}
}
}
will work for hashset.
(hashmap example)

Inject a synchronizedMultiMap with Guava & Spring?

The documentation for using Guava HashMultimap stresses the importance of wrapping your multimap through Multimaps.synchronizedMultimap upon initialization for a thread-safe access. Given that, I know I can create the following multimap:
private Multimap<Short, String> successfulMultimap =
Multimaps.synchronizedMultimap(HashMultimap.<Short, String>create());
However, my multimap needs to be injected using Spring because it will be used by another class on my service.
Without the synchronized wrapper, I know I can use something along these lines:
//setter
public void setSuccessfulMultimap(Multimap<Short, String> successfulMultimap) {
this.successfulMultimap = successfulMultimap;
}
<!-- XML configuration -->
<bean id="myBean" factory-method="create" class="com.google.common.collect.HashMultimap"/>
But seeing as I need to initialize it as thread-safe, I'm lost on how to "spring"-ify it. Can someone help me on how to inject a synchronized multimap or any good approach to it?
You should be able to put the appropriate code in the spring set method:
//setter
public void setSuccessfulMultimap(Multimap<Short, String> value) {
successfulMultimap = Multimap.synchronizedMultimap(value);
}
Since it is set after object construction, you may also want to make the successfulMultimap member volatile to ensure the initialization is visible to other threads.

Injecting a map of properties via #Value alone

How does one populate a map of values using the #Values annotation, without defining anything in applicationContext.xml or any other XML file.
I am using spring boot, which doesn't have any XML files, and nor do I want any XML files, so please don't tell me to declare any property reader beans in XML etc.
Also, this is a properties injection question - please don't suggest using a database to store the data - that's not an answer, and not possible for my situation anyway.
Also, I can't use YAML either (due to deployment/operational requirements).
I have tried declaring this injection:
#Value("${myprop}")
Map<Integer, String> map;
And this one
#Value("${myprop.*}")
Map<Integer, String> map;
with these entries application.properties:
myprop.1=One
myprop.2=Two
myprop.3=Three
and then tried
myprop[1]=One
myprop[2]=Two
myprop[3]=Three
But no good - just explodes with
Could not autowire field: ... Could not resolve placeholder 'myprop'
I have found a work-around with an injected String[] specified as key1:value1,key2:value2,... that I then parse in code, but I'd prefer to not do that because a) it's more code, and b) the list is going to be quite long, and all pairs on one line is going to be hard to read and maintain.
Is there a way to automatically build a map from several properties?
I don't care what the property names are, what the field type or the annotation is; I'm just trying to inject one key/value pair per property.
Not sure if this applies to your scenario entirely (you have there a Map<Integer, String> but in the end you say you just need a key-value pair in a Map), but maybe it could give you some more ideas:
assuming a #Configuration class where the .properties file is loaded as a java.util.Properties object:
#Configuration
public class Config {
#Bean(name = "mapper")
public PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("META-INF/spring/application.properties"));
return bean;
}
#Bean
public MyBean myBean() {
return new MyBean();
}
}
the MyBean class where those Properties are being used, injected using SPeL:
public class MyBean {
#Value("#{mapper}")
private Map props;
public Map getProps() {
return props;
}
}
So, in the end you don't use xml (of course), you need to use a PropertiesFactoryBean to load the .properties file and, using #Value, Spring will inject the Properties into a Map. The extra code (compared to, probably, #PropertySource) is the PropertiesFactoryBean and you don't need to parse the values in your code manually (compared to your workaround that injects a String[]).
Hope this helps.
How about defining a bean in your Java config for this?
#Bean
public Map<Integer, String> myProps(Properties properties) {
Map<Integer, String> map = new HashMap<>();
// implement logic to populate map from properties
return map;
}
And in your class:
#Autowirded
Map<Integer, String> map;
Hello for people serach a simply solution to this problem. 😄
put the sequent line to your application.properties:
myprop = "{1:'One',2:'Two',3:'Three'}"
then in your Spring application put the line:
#Value("#{${myprop}}")
Map<Integer, String> map;

Spring bean initialize maps using external property file

I have a class which has a Map as its member variable. Something like this -
public Clas Engine{
private Map<String,List<String>> filesByKey;
public void setFilesByKey(Map<String,List<String>> map) {
this.filesByKey = map;
}
public Map<String,List<String>> getFilesByKey() {
return filesByKey;
}
}
User can specify any number of keys in the map and its not predefined concept. They can basically group any number of files into one key and provider the map Value.
I was using PropertyOverrideConfigurer and in the properties file, I was trying to do something like this -
engine.filesByKey[key1][0]=file1
engine.filesByKey[key1][1]=file2
engine.filesByKey[key2][0]=anotherfile1
engine.filesByKey[key2][1]=anotherfile2
Now this is not working because the the List value corresponding to key1 or key2 is null to being with. So Spring Bean creation fails with the message that it can not set value to a property which is NULL.
What is the best way to handle this situation?
You should be able to use the LazyMap & LazyList from commons collections to achieve this.
Try to initialize your filesByKey variable with DefaultedMap from commons collections. It can return default value instead of null if map doesn't contain required key.

Categories