Trouble with Spring #ConfiurationProperties extending Mao - spring

I am trying to load come properties from config using the Spring #Configuration & #ConfigurationProperties combination. I have a POJO that extends HashMap<Integer, String> and has a single variable inside. (See MyConfigPojo below). From the .yaml file, I have the same shape. However, when booting up the app, I get an error when trying to parse the string for the defaultValue into an Integer.
#Configuration
#ConfigurationPropeties(prefix = "my-config")
public class MyConfigPojo extends HashMap<Integer, String> {
private String defaultValue;
private String getValueForIdOrDefault(int id); // this.get(id) OR ELSE defaultValue
}
In config I have this:
myConfig:
1: "my first value"
23: "my 23rd value"
defaultValue: "cheese"
Which results in a
APPLICATION FAILED TO START
Description:
Failed to bind properties under 'myPackage' to MyConfigPojo:
Property: myConfig[1]
Value: cheese
I thought that was weird, so I turned on TRACE logs and found this:
99 common frames omittedCaused by: java.lang.NumberFormatException: For input string: "defaultValue" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
Is there a way to intercept reading this file or to tell Spring how to set the values properly?

You are trying to read a Map from .yml file, for that, you need #ConfigurationProperties annotation enough, don't need #Configuration annotation(if using spring boot). If you are using spring-framework, you need #Configuration also.
Use this example:
myConfig:
map:
1: "my first value"
23: "my 23rd value"
defaultValue: "cheese"
MyConfigPojo.java
package com.org;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
#Configuration
#ConfigurationProperties(prefix = "my-config")
public class MyConfigPojo {
private HashMap<String, String> map;
public HashMap<String, String> getMap() {
return map;
}
public void setMap(HashMap<String, String> map) {
this.map = map;
}
}
Now, you can Autowire this MyConfigPojo class anywhere(for instance, in controller class) and read those map keys and values.
#Autowired
MyConfigPojo pojo;
Now, you have considered keys & Values as String datatype for that Map, you will not get NumberFormatException.

Related

Conditionally create bean if Map of properties is not empty with SpEL

I have a class with a method that is periodically invoked with the #Scheduled annotation. The method does some bulk operations on a given set of properties.
If there are no properties set, I don't need the scheduled method invocation nor the instantiated class. Therefore, I've added this SpEL expression to check whether the properties are set:
#Service
#ConditionalOnExpression("#(T(java.util.Map)('${myproperties.people:{:}}')).size() > 0")
public class PeopleService { ... }
Example values in the application.yml could be:
myproperties:
people:
uuid1:
name: Mark
age: 32
uuid2:
name: Jeff
age: 36
Unfortunately I get this error message:
Caused by: org.springframework.expression.spel.SpelParseException: Expression [#(T(java.util.Map)('{:}')).size() > 0] #1: EL1043E: Unexpected token. Expected 'identifier' but was 'lparen(()'
Note that I came up with {:} for an empty map as default value here: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions-inline-maps
If I use this SpEL, I get the following error: "#(T(java.util.Map)(${myproperties.people:})).size() > 0"
Caused by: org.springframework.expression.spel.SpelParseException: Expression [#(T(java.util.Map)()).size() > 0] #1: EL1043E: Unexpected token. Expected 'identifier' but was 'lparen(()'
What is the correct way to accomplish this?
This annotation:
#ConditionalOnExpression("#(T(java.util.Map)('${myproperties.people:{:}}')).size() > 0")
Doesn't work because Spring read LITERALLY all your properties like that:
myproperties.people.uuid1.name=Mark
myproperties.people.uuid1.age=32
myproperties.people.uuid2.name=Jeff
myproperties.people.uuid2.age=36
If you check Spring Boot documentation, this page explains that "the condition matches if spring.example.values is present in the Environment but does not match if spring.example.values[0] is present.".
So for Spring, property "myproperties.people" just doesn't exist. On the same page, they explain that "it is better to use a custom condition for such cases."
A workaround is to define a property class (but it's not wasted, because you will use this class to parse your yaml as a map):
#ConfigurationProperties(prefix = "myproperties")
public class MyPeopleProperties {
private Map<String, String> people;
public Map<String, String> getPeople() {
return people;
}
public void setPeople(Map<String, String> people) {
this.people = people;
}
}
And on your main class:
...
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
...
#Service
#Conditional(PeopleService.PeopleServiceCondition.class)
public class PeopleService {
...
public static class PeopleServiceCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MyPeopleProperties config = Binder.get(context.getEnvironment())
.bind("myproperties", MyPeopleProperties.class).orElse(null);
return config != null && config.getPeople() != null && !config.getPeople().isEmpty();
}
}
}
So myproperties.people will be bound as map, then we check if this map exists on the bean declaration condition.
Circling back around to the SpEL implementation, you actually can do it entirely in SpEL but it's really ugly:
#ConditionalOnExpression(
"(" +
"T(org.springframework.boot.context.properties.bind.Binder)" +
".get(environment)" +
".bind('myproperties.people', T(java.util.Map))" +
".orElse(null)" +
"?.size()" +
"?: 0" +
") > 0"
)
This will work even for deeply-nested config subtrees - the simplistic binding to the Map class just creates a bunch of denormalized/flattened <String,String> entries with keys like uuid1.name. That means that if you are just testing for the existence of any sub-tree elements under the myproperties.people namespace, the above will work fine. It is, in effect, exactly what you probably would have meant when writing #ConditionalOnProperty("myproperties.people")

Retrieve all key-value pairs from properties file in Spring Boot with a given prefix and suffix

I have been trying for sometime now. I want to read from the properties file and store as a hashmap.
Here is an example.
sample.properties
pref1.pref2.abc.suf = 1
pref1.pref2.def.suf = 2
...
...
Here is the Config class.
#ConfiguraionProperties
#PropertySource(value = "classpath:sample.properties")
public class Config{
#Autowired
private Environment env;
public HashMap<String, Integer> getAllProps(){
//TO-DO
}
}
I want to be able to return {"abc":1, "def":2};
I stumbled upon answers like using PropertySources, AbstractEnvironment etc., but still can't get my head around using it.
Thanks in advance!
The class
org.springframework.boot.actuate.endpoint.EnvironmentEndpoint
reads all configured properties and puts them in a Map.
This is used to return all properties and their values from a REST endpoint. See production ready endpoints
You can copy the 3 methods that build the Map with all properties from class EnvironmentEndpoint into your own. Then just iterate over the Map and select all properties by their key.
I have done that in one project, worked quite well.
its possible using spring boot #component, #PropertySource and #ConfigurationProperties
create a component like
#Component
#PropertySource(value = "classpath:filename.properties")
#ConfigurationProperties(prefix = "pref1")
public class Properties{
/**
*should be same in properties pref1.pref2.abc.suf = 1
*It will give u like abc.suf = 1 , def.suf = 2
*/
private Map<String,String> pref2;
//setter getter to use another place//
public Map<String, String> getPref2() {
return pref2;
}
public void setPref2(Map<String, String> pref2) {
this.pref2= pref2;
}
}
use in other class suing #autowired
public class PropertiesShow{
#Autowired
private Properties properties;
public void show(){
System.out.println(properties.getPref2());
}
}

Preventing Spring Boot from creating nested map from dot-separated key in application.yml?

I have a problem with Spring Boot creating a nested map from a dot-separated key. It's essentially the same problem that is described here, but the solution suggested there doesn't work for me. I'm using Spring Boot 1.5.3.RELEASE in case it matters. My applications.yml file contains this:
props:
webdriver.chrome.driver: chromedriver
My config class:
#Configuration
#EnableConfigurationProperties
public class SpringConfig {
private Map<String, String> props = new HashMap<>();
#ConfigurationProperties(prefix = "props")
public void setProps(Map<String, String> props) {
this.props = props;
}
#ConfigurationProperties(prefix = "props")
#Bean(destroyMethod="", name = "props")
public Map<String, String> getProps() {
return props;
}
}
Unfortunately, after Spring Boot processes the YAML file, the dot separated key gets split up into sub-maps. The result from callig getProps() and printing the result to System.out looks like this:
{webdriver={chrome={driver=chromedriver}}}
I've tried changing the type of the props field to Properties, Map<String, Object> etc, but nothing seems to make any difference.
I haven't found any way of manipulating the parsing behavior to accomplish what I want. Any help is much appreciated. I've spent so much time on this, that I'll go blind if I look at the code any further.
Try using YamlMapFactoryBean this will load YAML as MAP.
#Bean
public YamlMapFactoryBean yamlFactory() {
YamlMapFactoryBean factory = new YamlMapFactoryBean();
factory.setResources(resource());
return factory;
}
public Resource resource() {
return new ClassPathResource("application.yml");
}
public Map<String, String> getProps() {
props = yamlFactory().getObject();
return props;
}
The output looks
props{webdriver.chrome.driver=chromedriver}
After much experimenting, this seemed to work:
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
#ConfigurationProperties
public class SpringConfig {
private Properties info = new Properties();
public Properties getProps() {
return info;
}
}
}
But I had to put single quotes around the YAML entry, otherwise Spring Boot would make the property nested:
props:
'webdriver.chrome.driver': chromedriver
'foo.bar.baz': foobarbaz
A couple of things I noticed. The getter for the Properties (getProps() in this case) must be declared public, and it has to match the property key that you're trying to bind in the YAML. I.e. since the key is 'props', the getter has to be called getProps(). I guess it's logical, and documented somewhere, but that had slipped me by somehow. I thought by using the prefix="foobar" on the #ConfigurationProperties annotation, that wasn't the case, but it seems to be. I guess I should RTFM ;-)

What's the best way to load a yaml file to Map(not an environment configuration file) in spring boot web project?

In my data framework layer, I'd like to read an yaml from src/main/resources.
The file name is mapconfigure.yaml. It's associated with the business data, not just environment configuration data.
Its content's like:
person1:
name: aaa
addresses:
na: jiang
sb: su
person2:
name: bbb
addresses:
to: jiang
bit: su
I want to store this information into a HashMap.
Does it mean to use some spring annotation like #ConfigurationProperties?
How to achieve this in details?
In addition, I can't change the file name. It means I have to use mapconfigure.yaml as the file name, not application.yml or application.properties.
The structure of my HashMap is as follows:
HashMap<String, Setting>
#Data
public class Setting{
private String name;
private HashMap<String, String> addresses
}
My expected HashMap's as follows:
{person1={name=aaa, addresses={na=jiang, sb=su}}, person2={name=bbb, addresses={to=jiang, bit=su}}}
I'm not sure if I can use YamlMapFactoryBean class to do this.
The return type of the getObject method in YamlMapFactoryBean class is Map<String, Object>, not a generic type, like Map<String, T>.
Spring boot doc just said
Spring Framework provides two convenient classes that can be used to load YAML documents. The YamlPropertiesFactoryBean will load YAML as Properties and the YamlMapFactoryBean will load YAML as a Map.
But there isn't a detailed example.
UPDATE:
In github, I created a sample. It's Here.
In this sample, I want to load myconfig.yaml to theMapProperties object in SamplePropertyLoadingTest class. Spring boot version is 1.5.1, so I can't use location attribute of #ConfigurationProperties.
How to do this?
You can indeed achieve this with #ConfigurationProperties.
From Spring Boot 1.5.x onwards (lack of #ConfigurationProperies locations attr.):
new SpringApplicationBuilder(Application.class)
.properties("spring.config.name=application,your-filename")
.run(args);
#Component
#ConfigurationProperties
public class TheProperties {
private Map<String, Person> people;
// getters and setters are omitted for brevity
}
In Spring Boot 1.3.x:
#Component
#ConfigurationProperties(locations = "classpath:your-filename.yml")
public class TheProperties {
private Map<String, Person> people;
// getters and setters are omitted for brevity
}
The Person class for above examples looks like this:
public class Person {
private String name;
private Map<String, String> addresses;
// getters and setters are omitted for brevity
}
I have tested the code with the following file: your-filename.yml
defined in src/main/resources, the contents:
people:
person1:
name: "aaa"
addresses:
na: "jiang"
sb: "su"
person2:
name: "bbb"
addresses:
to: "jiang"
bit: "su"
Please let me know if you need any further assistance.
try this
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
PropertySource<?> applicationYamlPropertySource = loader.load(
"properties", new ClassPathResource("application.yml"), null);// null indicated common properties for all profiles.
Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
Properties properties = new Properties();
properties.putAll(source);
return properties;
} catch (IOException e) {
LOG.error("application.yml file cannot be found.");
}

How to use #ConfigurationProperties for list-valued or array-valued mapping?

I've got a project where I'd like to configure String-keyed "groups"
of String-keyed "commands" of arrays of Strings. That is, I'd like to
be able to express something like the following in the config.yml, and
consume via #ConfigurationProperties(prefix="config.base"):
---
config:
base:
"bin group":
- "Directory Listing": ["/bin/ls", "-la"]
- "Server Date/Time": ["/bin/date", "-u"]
"usr/bin group":
- "Find .txt Files": ["/usr/bin/find", ".", "-name", "*.txt"]
"usr/local/bin group":
- "Tree Listing": ["/usr/local/bin/tree"]
Ideally, I'd want the #ConfigurationProperties object to be a LinkedHashMap<String, LinkedHashMap<String, String[]>>
but I can't figure out how to do that. Or anything reasonably close to that.
The closest I've gotten is like the following:
package us.w7tek.bug;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import javax.annotation.PostConstruct;
import java.util.LinkedHashMap;
#EnableConfigurationProperties
#SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
#ConfigurationProperties("someConfig")
#Bean
public ExternalizedConfig externalizedConfig() { return new ExternalizedConfig(); }
public static class ExternalizedConfig extends LinkedHashMap<String, String[]> {
// oops, #ConfigurationProperties ends up putting LinkedHashMap<String, String> in the values of the top-level mapping,
// and that second-level LinkedHashMap has keys that could have come from Integer#toString
}
#Controller
public static class ControllerThatConsumesConfig {
private static final String A_KEY_THAT_COULDNT_BE_A_PROPERTY_NAME = "this == config cannot be expressed as a bean with properties, because the keys cannot be made into Java language identifiers for bean property setters and getters";
#Autowired
ExternalizedConfig config;
#PostConstruct
void init() {
String[] strings = config.get(A_KEY_THAT_COULDNT_BE_A_PROPERTY_NAME); // ClassCastException occurs here
// doesn't have to occur in #PostConstruct, that was just a convenient place for my demo.
}
}
}
with the following example application.yml in the project:
---
someConfig:
"this is a key": ["this", "value", "is", "not", "an", "String[]"]
"this is another key": ["it", "is", "deserialized", "as", "LinkedHashMap", "having", "keys", "like", "\"0\"", "and", "\"1\"", "etc."]
"this == config cannot be expressed as a bean with properties, because the keys cannot be made into Java language identifiers for bean property setters and getters": ["thereby", "subverting", "Java's", "static", "typing", "and", "resulting", "in", "ClassCastException", "at", "runtime"]
As indicated in the comment, that code explodes when the Spring Boot #ConfigurationProperties binder creates an object of type LinkedHashMap<String, LinkedHashMap<String, LinkedHashMap<String, String>>> and places it in the config field. Of course, as soon as any method accesses config according to its statically-declared type, a ClassCastException occurs. I'm not sure whether to believe this is a bug with the property binder code used by #ConfigurationProperties, or just my gross misunderstanding. I think that the above code is the simplest possible thing that exhibits the problem. Also found at https://github.com/w7tek/demo-configproperties-bug.git, in case anyone wants to compile and run to see the stack trace.
Does anyone have any examples of #ConfigurationProperties with collections? I can see the way forward from where I'm at, by simply matching the declared type to the actual type Spring has deserialized, but that ends up being significantly less convenient to use. I'd really like to get the innermost values of this config as List<> or array type, if possible, but I can't figure out how.
Here is what you needed:
don't use tab, use 2 space for each inner element.
config:
base:
"bin group":
"Directory Listing": ["/bin/ls", "-la"]
"Server Date/Time": ["/bin/date", "-u"]
"usr/bin group":
"Find txt Files": ["/usr/bin/find", ".", "-name", "*.txt"]
"usr/local/bin group":
"Tree Listing": ["/usr/local/bin/tree"]
and here is the Configuration class:
#Configuration
#ConfigurationProperties(prefix = "config")
public class Conf_Test {
private LinkedHashMap<String, LinkedHashMap<String, List<String>>> base;
public LinkedHashMap<String, LinkedHashMap<String, List<String>>> getBase() {
return base;
}
public void setBase(LinkedHashMap<String, LinkedHashMap<String, List<String>>> base) {
this.base = base;
}
}
Apperantly, you cannot use "." inside the map key, it just cut the key so I removed the one in "Find .txt Files" key. Also, spring-boot doesn't support auto-groving arrays inside map, so String[] is not possible for now but list is working.

Resources