Reading yaml config - java

I've been trying to wrap my head around this for hours but it doesn't make much sense what I'm doing wrong. I am trying to create a Map object in Java from a .yml file. Reason being for a map, I don't know what/how many children will appear under "present", so I rather have a dynamic way of creating a map object out of that..
Below is my .yml file. I would like the key-value pair under "present":
present:
now: LOCAL TESTING
later: testing
Below is my config class (everything commented out is what I've tried - in different combinations):
//#Data
#Component
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "present")
//#ConfigurationProperties
public class stat {
//#Getter
//#Data
#Value("${present}")
private Map<String, String> present;
//private Map<String, String> present = new HashMap<String, String>();
}
I tried looking at other SO posts and I feel like I understand it but my Spring Boot (v1.5.8) application isn't seeing that value. It keeps throwing an error for me or the map object is null (or not being populated).
I know that I can read values from this .yml file because if I try to obtain a single value with a code snippet below, it works:
#Data
#Value("${present.now}")
private String status; // String value "LOCAL TESTING"
Here are the other links that I've tried:
Spring Boot yaml configuration for a list of strings
how to convert yml file to java pojo
Am I missing something obvious? Thanks!

You can try to create a POJO to represent the yml structure you are trying to read from.
For example:
#Configuration
#ConfigurationProperties(prefix = "present")
#Data
public class Present {
private String now;
private String later;
}

So I figured it out (for those who have this problem later):
#Value isn't needed and the prefix param in the #ConfigurationProperties isn't needed.
Then you need to have a getter method for the fields that you want - I thought the Lombok library had autogenerated these but I was wrong (probably need to read more about it later - #Setter and #Data won't work properly).
So it should look something like this:
#Component
#EnableConfigurationProperties
#ConfigurationProperties
public class stat {
private Map<String, String[]> present = new HashMap<String, String[]>();
public Map<String, String[]> getPresent() {
return present;
}
}
Now let's give a more complex example (nested maps). Say that my .yml file looks like this:
parent:
present:
foo: dey, tok
bar: ar, jerbs
later:
foo: day, dok
mar: r, darbs
The POJO would look like this:
#Component
#EnableConfigurationProperties
#ConfigurationProperties
public class stat {
private Map<String, Map<String, String[]>> parent = new HashMap<String, Map<String, String[]>>();
public Map<String, Map<String, String[]>> getParent() {
return parent;
}
}
Another key thing to note is that the field that you are obtaining a value from must match the variable name - it may not matter if you use prefix but it still didn't work for me. Hope this helps.

Related

Spring MVC - Reading properties file using Java Configuration

Values from .properties file could not read due to exception (org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'genderOptions' cannot be found)
I have configured the property place holder. My property file is having two entries (M=MALE, F=FEMALE) I wanted to populate this as a list of options in checkbox while submitting the form.
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Controller
#RequestMapping("/player")
#PropertySource(ignoreResourceNotFound = true, value =
"classpath:gender.properties")
public class PlayerController {
#Value("#{genderOptions}")
public Map<String, String> genderOptions;
#RequestMapping("/playerForm")
public String showPlayerForm(Model model) {
Player player = new Player();
model.addAttribute("player", player);
model.addAttribute("genderOptions", genderOptions);
return "player-form";
}
If you want to use genderOptions as Map in the Controller, then first you need specify it in the form of key-value in gender.properties file.
genderOptions = {M:'Male', F:'Female'}
And while accessing it in the controller, you need to make following changes in order to let spring cast it in Map.
#Value("#{${genderOptions}}")
private Map<String, String> mapValues;
And if you need to get the value of a specific key in the Map, all you have to do is add the key's name in the expression:
#Value("#{${genderOptions}.M}")
private String maleKey;

Loading Map<String List<String>> from properties file in spring [duplicate]

Is it possible to use Spring #Value, to map values from properties file to the HashMap.
Currently I have something like this, and mapping one value is not a problem.
But I need to map custom values in HashMap expirations.
Is something like this possible?
#Service
#PropertySource(value = "classpath:my_service.properties")
public class SomeServiceImpl implements SomeService {
#Value("#{conf['service.cache']}")
private final boolean useCache = false;
#Value("#{conf['service.expiration.[<custom name>]']}")
private final HashMap<String, String> expirations = new HashMap<String, String>();
Property file: 'my_service.properties'
service.cache=true
service.expiration.name1=100
service.expiration.name2=20
Is it posible to map like this key:value set
name1 = 100
name2 = 20
You can use the SPEL json-like syntax to write a simple map or a map of list in property file.
simple.map={'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}
map.of.list={\
'KEY1': {'value1','value2'}, \
'KEY2': {'value3','value4'}, \
'KEY3': {'value5'} \
}
I used \ for multiline property to enhance readability
Then, in Java, you can access and parse it automatically with #Value like this.
#Value("#{${simple.map}}")
Map<String, String> simpleMap;
#Value("#{${map.of.list}}")
Map<String, List<String>> mapOfList;
Here with ${simple.map}, #Value gets the following String from the property file:
"{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}"
Then, it is evaluated as if it was inlined
#Value("#{{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}}")
You can learn more in the official documentation
Is it possible to use Spring #Value, to map values from properties file to the HashMap?
Yes, it is. With a little help of code and Spel.
Firstly, consider this singleton Spring-bean (you should scan it):
#Component("PropertySplitter")
public class PropertySplitter {
/**
* Example: one.example.property = KEY1:VALUE1,KEY2:VALUE2
*/
public Map<String, String> map(String property) {
return this.map(property, ",");
}
/**
* Example: one.example.property = KEY1:VALUE1.1,VALUE1.2;KEY2:VALUE2.1,VALUE2.2
*/
public Map<String, List<String>> mapOfList(String property) {
Map<String, String> map = this.map(property, ";");
Map<String, List<String>> mapOfList = new HashMap<>();
for (Entry<String, String> entry : map.entrySet()) {
mapOfList.put(entry.getKey(), this.list(entry.getValue()));
}
return mapOfList;
}
/**
* Example: one.example.property = VALUE1,VALUE2,VALUE3,VALUE4
*/
public List<String> list(String property) {
return this.list(property, ",");
}
/**
* Example: one.example.property = VALUE1.1,VALUE1.2;VALUE2.1,VALUE2.2
*/
public List<List<String>> groupedList(String property) {
List<String> unGroupedList = this.list(property, ";");
List<List<String>> groupedList = new ArrayList<>();
for (String group : unGroupedList) {
groupedList.add(this.list(group));
}
return groupedList;
}
private List<String> list(String property, String splitter) {
return Splitter.on(splitter).omitEmptyStrings().trimResults().splitToList(property);
}
private Map<String, String> map(String property, String splitter) {
return Splitter.on(splitter).omitEmptyStrings().trimResults().withKeyValueSeparator(":").split(property);
}
}
Note: PropertySplitter class uses Splitter utility from Guava. Please refer to its documentation for further details.
Then, in some bean of yours:
#Component
public class MyBean {
#Value("#{PropertySplitter.map('${service.expiration}')}")
Map<String, String> propertyAsMap;
}
And finally, the property:
service.expiration = name1:100,name2:20
It's not exactly what you've asked, because this PropertySplitter works with one single property that is transformed into a Map, but I think you could either switch to this way of specifying properties, or modify the PropertySplitter code so that it matches the more hierarchical way you desire.
From Spring 4.1.x ( I can't remember specific version though ), you can do something like
#Value("#{${your.properties.key.name}}")
private Map<String, String> myMap;
where your.properties.key.name in your properties file should be something like
your.properties.key.name={\
name1 : 100, \
name2 : 200 \
}
Just make sure that you should create PropertySourcesPlaceholderConfigurer bean to make it work both in your app and if you are writing any unit test code to test your code, otherwise ${...} placeholder for the property value won't work as expected and you'll see some weird SpringEL errors.
The quickest Spring Boot based solution I can think of follows. In my particular example I am migrating data from one system to another. That is why I need a mapping for a field called priority.
First I've created the properties file (priority-migration.properties) like such:
my.prefix.priority.0:0
my.prefix.priority.10:1
my.prefix.priority.15:2
my.prefix.priority.20:2
another.prefix.foo:bar
and put it on the classpath.
Assuming you want to use the map in a spring managed bean/component, annotate your class with:
#Component
#PropertySource("classpath:/priority-migration.properties")
What you actually want in your map is of course only the key/value pairs which are prefixed with my.prefix, i.e. this part:
{
0:0
10:1
15:2
20:2
}
To achieve that you need to annotate your component with
#ConfigurationProperties("my.prefix")
and create a getter for the priority infix. The latter proved to be mandatory in my case (although the Sring Doc says it is enough to have a property priority and initialize it with a mutable value)
private final Map<Integer, Integer> priorityMap = new HashMap<>();
public Map<Integer, Integer> getPriority() {
return priorityMap;
}
In the End
It looks something like this:
#Component
#ConfigurationProperties("my.prefix")
#PropertySource("classpath:/priority-migration.properties")
class PriorityProcessor {
private final Map<Integer, Integer> priorityMap = new HashMap<>();
public Map<Integer, Integer> getPriority() {
return priorityMap;
}
public void process() {
Integer myPriority = priorityMap.get(10)
// use it here
}
}
I make one solution inspired by the previous post.
Register property file in the Spring configuration:
<util:properties id="myProp" location="classpath:my.properties"/>
And I create component:
#Component("PropertyMapper")
public class PropertyMapper {
#Autowired
ApplicationContext applicationContext;
public HashMap<String, Object> startWith(String qualifier, String startWith) {
return startWith(qualifier, startWith, false);
}
public HashMap<String, Object> startWith(String qualifier, String startWith, boolean removeStartWith) {
HashMap<String, Object> result = new HashMap<String, Object>();
Object obj = applicationContext.getBean(qualifier);
if (obj instanceof Properties) {
Properties mobileProperties = (Properties)obj;
if (mobileProperties != null) {
for (Entry<Object, Object> e : mobileProperties.entrySet()) {
Object oKey = e.getKey();
if (oKey instanceof String) {
String key = (String)oKey;
if (((String) oKey).startsWith(startWith)) {
if (removeStartWith)
key = key.substring(startWith.length());
result.put(key, e.getValue());
}
}
}
}
}
return result;
}
}
And when I want to map all properties that begin with specifix value to HashMap, with #Value annotation:
#Service
public class MyServiceImpl implements MyService {
#Value("#{PropertyMapper.startWith('myProp', 'service.expiration.', true)}")
private HashMap<String, Object> portalExpirations;
Solution for pulling Map using #Value from application.yml property coded as multiline
application.yml
other-prop: just for demo
my-map-property-name: "{\
key1: \"ANY String Value here\", \
key2: \"any number of items\" , \
key3: \"Note the Last item does not have comma\" \
}"
other-prop2: just for demo 2
Here the value for our map property "my-map-property-name" is stored in JSON format inside a string and we have achived multiline using \ at end of line
myJavaClass.java
import org.springframework.beans.factory.annotation.Value;
public class myJavaClass {
#Value("#{${my-map-property-name}}")
private Map<String,String> myMap;
public void someRandomMethod (){
if(myMap.containsKey("key1")) {
//todo...
} }
}
More explanation
\ in yaml it is Used to break string into multiline
\" is escape charater for "(quote) in yaml string
{key:value} JSON in yaml which will be converted to Map by #Value
#{ } it is SpEL expresion and can be used in #Value to convert json int Map or Array / list Reference
Tested in a spring boot project
Use the same variable name as the Yaml name
Eg:
private final HashMap<String, String> expiration
instead of
private final HashMap<String, String> expirations
Or something similar to this in properties file
org.code=0009,0008,0010
org.code.0009.channel=30,40
org.code.0008.channel=30,40
org.code.0010.channel=30,40
in Java, read org.code and then loop thru each org.code and build org.code..channel and put it into a map....

Marshall/Unmarshall Nested Map with JaxB

There are several previous questions around using JaxB to marshall/unmarshall a java.util.Map, many of which get pointed back to this example, which works great:
http://blog.bdoughan.com/2013/03/jaxb-and-javautilmap.html
However, I can't get JaxB to be able to marshall/unmarshall instances of Map if the map is not a member of the #XmlRootElement. For example, here's a root element class,
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static class Customer {
private MyField myField
MyField getMyField() {
return myField
}
void setMyField(MyField myField) {
this.myField = myField
}
}
The definition of it's field class:
#XmlAccessorType(XmlAccessType.FIELD)
public static class MyField{
Map<String, String> getSomeMap() {
return someMap
}
void setSomeMap(Map<String, String> someMap) {
this.someMap = someMap
}
#XmlElement
private Map<String, String> someMap = new HashMap<String, String>()
}
And some code to drive the marshalling:
JAXBContext jc = JAXBContext.newInstance(Customer.class)
Customer customer = new Customer()
MyField myField1 = new MyField()
myField1.someMap.put("foo", "bar")
myField1.someMap.put("baz", "qux")
customer.myField = myField1
Marshaller marshaller = jc.createMarshaller()
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
marshaller.marshal(customer, System.out)
This example results in:
java.util.Map is an interface, and JAXB can't handle interfaces.
java.util.Map does not have a no-arg default constructor.
I am writing my code in Groovy rather than Java, but I don't think it should make much of a difference.
I was able to encounter the same behavior using JAXB by creating a TestController of type #RestController, using Spring Boot.
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
#RestController
#RequestMapping(value = "test")
class TestController {
#RequestMapping(value = "findList")
List findList() {
["Test1", "Test2", "Test3"] as ArrayList<String>
}
#RequestMapping(value = "findMap")
Map findMap() {
["T1":"Test1", "T2":"Test2", "T3":"Test3"] as HashMap<String,String>
}
#RequestMapping(value = "")
String find(){
"Test Something"
}
}
With JAXB as the default implementation in SpringBoot, I could reproduce the issue that the /test/findList would correctly render XML, but /test/findMap would generate an error as described in the initial posting.
For me, the solution to the problem is to switch the XML rendering library to Jackson (there are others like XStream as well).
Using Gradle for the build file (build.gradle), I simply add the Jackson dependencies, very similar to how you would if using Maven:
'com.fasterxml.jackson.core:jackson-core:2.7.1',
'com.fasterxml.jackson.core:jackson-annotations:2.7.1',
'com.fasterxml.jackson.core:jackson-databind:2.7.1-1',
'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.7.1',
'org.codehaus.woodstox:woodstox-core-asl:4.4.1',
I have experienced this before myself. Bottom line is that the warning is telling you exactly the problem. You have defined your field as type java.util.Map. JAXB does not support interfaces. To correct your problem, you need to change the declaration of your field to a concrete Map type like:
private HashMap<String, String> someMap = new HashMap<String, String>()
Your other option is described in the link you referenced. You need to have a
MapAdapter class as referenced in the link you provided and then include that in the annotation, hinting to JAXB how it should marshal/unmarshal the Map type.
I think this link gives a clearer example of how to create and implement the MapAdapter:
JAXB: how to marshall map into <key>value</key>
The answer to the specific issue I was having ended up being removing the #XmlElement annotation from the Map field like so:
#XmlAccessorType(XmlAccessType.FIELD)
public static class MyField{
Map<String, String> getSomeMap() {
return someMap
}
void setSomeMap(Map<String, String> someMap) {
this.someMap = someMap
}
//#XmlElement Remove this annotation
private Map<String, String> someMap = new HashMap<String, String>()
}
Without that annotation, the marshalling/unmarshalling works fine, and still interprets the Map as an XmlElement - there seems to be a bug with that annotation specifically. However, as #dlcole points out, an alternative (that would allow you to have more control over the format of your serialized representation) is to use Jackson rather than JAXB.

Entity to Map<String, Object>

I would like to clarify if it is possible to create Map<String, Object> from JPA entity through Hibernate. I mean is it possible to convert persistent object (entity) to Map that contains all entity properties as keys and properties' values as values. I understand that properties can be retrieved through Reflections but I can't figure out how to map it with proper values. I found only one solution and it is to use Spring's JdbcTemplate but it is not an option in my case. If anyone have possible solution please let me know. Thank you in advance.
You could try using #Converter as follows
#Entity
public class SomeEntity{
#Id
//...
#Convert(converter = MyConverter.class)
Map<String,Object> map;
}
and build up your converter as you wish for example convert to/from json
#Converter
public class MyConverter implements
AttributeConverter<Map<String, Object>, String> {
#Override
public String convertToDatabaseColumn(Map<String, Object> map) {
return jsonStr(map);
}
#Override
public Map<String, Object> convertToEntityAttribute(String s) {
return mapFromJson(s);
}
You can access the properties through reflection with the assistance of the Apache BeanUtil library, if your entity follows JavaBean naming conventions for all of its properties.
Step one: Wrap your entity in a WrapDynaBean object.
Step two: Create a DynaBeanPropertyMapDecorator, further wrapping the DynaBean.
Step three: Well, that's it. DynaBeanPropertyMapDecorator implemements Map<String, Object> so your job is done there. getFoo() on your original object may now be found by decorator.get("foo"). (Note that you've lost type-safety here, but you did ask for a Map<String, Object>...)

Correct data type with Swagger/Springfox when property has #XmlJavaTypeAdapter annotation

I have annotated an endpoint with swagger annotations. In the #ResponseHeader I set the returning class as response. This class contains a property which is annotated with #XmlJavaTypeAdapter. The adapter is changing the data type of the property. Unfortunately Swagger shows the type of the property, not the return type of the Adapter. Is it possible to do this?
What I already tried is to annotate the property with #ApiModelProperty(). But it was not possible for me to set the dataType to List (Primitive data types or just a list was working).
Thanks :)
The following was not working:
#ApiModelProperty(dataType = "List<Map<String, String>>")
public Map<String, String> someMap = new HashMap<>();
I had to create an Interface
public interface ListOfMap extends List<Map<String, String>> {}
And then I used this interface in the ApiModelProperty:
#ApiModelProperty(dataType = "ListOfMap")
public Map<String, String> someMap = new HashMap<>();
With this, everything worked :)

Categories