I frequently use standard Java property files for configuring my Groovy applications. One feature I have been missing is the ability to use variables as part of the property value so they can be expand dynamically during use. I thought I could provide this functionality using the following design:
Use a special format to annotate the properties that should be expanded. I have chosen to enclose such templates in double exclamation marks (!!). These property values are essentially a template to be expanded with the local variables
Before using the properties in the application, use the groovy 'evaluate' method to expand application variables in the template
Re-assign the original property key to the new value before use
So, if I have a property file config.properties with properties like:
version=2.3
local_lib=!!${env['GROOVY_HOME']}/${configProps.getProperty('version')}/lib!!
The local_lib property will be expanded from the GROOVY_HOME environment variable and the version property value.
In my application, I have coded this as follows:
//Load the environment variables and configuration file
env=System.getenv()
configFile=new File('config.properties')
configProps= new Properties()
configProps.load(configFile.newDataInputStream())
//Replace configuration property values with their expanded equivalent
configProps.each{
//if a property value is a template we evaluate it
if (it.value.startsWith('!!')){
valTemplate=it.value.replace('!!','"')
it.value=evaluate(valTemplate)
}
}
//then we use the expanded property values
This seems to work. When I do
println configProps
I see that the value is expanded and not null
However, the getProperty method for the expanded property returns null.
assert configProps.getProperty('local_lib')=='C:\\DEVTOOLS\\groovy-2.4.7/2.3/lib'
| | |
| null false
[local_lib:C:\DEVTOOLS\groovy-2.4.7/2.3/lib, version:2.3]
What is causing this discrepancy? I would have expected to return the value shown in the property map.
Your local_lib value looks like a String, but it isn't. It is a GString, only lazily coerced to String as needed (like when printing out the configProps map value).
Thus, a little known effect of Properties.getProperty() takes effect here. When the actual map value is not a String, Properties.getProperty() returns null.
So, in order to get the desired behavior, you need to coerce the GString to String before you store the value in the property map. Like so:
it.value=evaluate(valTemplate).toString()
or
it.value=evaluate(valTemplate) as String
Then you should see the desired results downstream.
Related
I'm making a config in my Spring application and I want to define a map:
#Value("${ordering:#{{:}}")
private Map<String, List<String>> ordering;
Here's what's in my config:
ordering = {'SOMEVALUE' : {'ONE', 'THREE', 'TWO'}, 'OTHERVALUE' : {'THREE', 'ONE', 'TWO'}}
But this always gets read as null (so I assume invalid SpEL).
The name of the variable and config value aren't misspelled and other values are loaded from the same config, so those parts are set up properly.
How should such a map be defined, what am I screwing up? Is there an online tool for parsing SpEL?
I tried several things: adding and removing the single ticks, same for double ticks and wrapping the lists in an extra {}, but none of these helped and the value is still set to null.
I also tried modifying the annotation, to no avail:
#Value("#{${ordering:#{{:}}}")
Two changes are needed.
First change:
The entry in the config file needs to be adjusted. SpEL uses a {key:value} notation (reference), where the key should not be enclosed in quotes.
So, use this:
ordering = {SOMEVALUE : {'ONE', 'THREE', 'TWO'}, OTHERVALUE : {'THREE', 'ONE', 'TWO'}}
Second change:
Now you can refer to this as follows in your Java:
#Value("#{${ordering}}")
private Map<String, List<String>> ordering;
And if you want to provide a default empty map, you can use {:} as follows:
#Value("#{${ordering_MISSING : {:} }}")
private Map<String, List<String>> ordering2;
Because there is no property called ordering_MISSING, the default will be used.
Reference: inline maps
{:} by itself means an empty map.
"is there an online tool for parsing SpEL?" - I am not aware of one. Someone else may know of one.
I think I figured out what I was doing wrong: My actual SpEL might have been correct at some point, but I'm parsing the data read from that and storing it in another variable, at which point ordering was null. What I forgot to consider is that the other field I'm parsing ordering into might (and does) get evaluated first.
I have a classloader application that reads the system property sun.boot.class.path
But I've found in the JDK 9's release note that this property has been removed.
System.getProperty("sun.boot.class.path"); // In JDK 9/10 this returns null
But I still want to retrieve this property value in JDK 10. How can it be done?
I'm expecting a value like the following:
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/resources.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jsse.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jce.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jfr.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/classes
FYI:
I don't want use the -Xbootclasspath option. Just need the path value.
Delete the boot. it should work like that :
System.getProperty("java.class.path")
To understand more :
The System class has two methods used to read system properties: getProperty and getProperties.
The System class has two different versions of getProperty. Both retrieve the value of the property named in the argument list. The simpler of the two getProperty methods takes a single argument, a property key For example, to get the value of path.separator, use the following statement:
System.getProperty("path.separator");
The getProperty method returns a string containing the value of the property. If the property does not exist, this version of getProperty returns null.
The other version of getProperty requires two String arguments: the first argument is the key to look up and the second argument is a default value to return if the key cannot be found or if it has no value. For example, the following invocation of getProperty looks up the System property called subliminal.message. This is not a valid system property, so instead of returning null, this method returns the default value provided as a second argument: "Buy StayPuft Marshmallows!"
System.getProperty("subliminal.message", "Buy StayPuft Marshmallows!");
The last method provided by the System class to access property values is the getProperties method, which returns a Properties object. This object contains a complete set of system property definitions.
I'm using Jersey and want a set that, when not added as a query param, defaults to a set containing more than one object.
I basically want this:
#DefaultValue("test1", "test2")
#QueryParam("test")
private Set<MyEnum> test;
to return a set containing the enums "test1" and "test2".
I'm having no problem getting a single default value to work but I would like multiple. The docs are a bit cryptical, is it possible?
According to this source from stackoverflow and the official documentation you can only do this by manually checking if the object is null then set the default value
I am using a properties file in my java project for storing the path of various resources.
Ex :- Here is my properties file :-
MACHINE_NAME = "//pranay"
Json_path1 = MACHINE_NAME//Json//j1.txt
Json_path2 = MACHINE_NAME//Json//j2.txt
The value of key MACHINE_NAME is not getting replaced with its value i.e pranay in another keys such as Json_path1 and Json_path2. Therefore i am unable to get the correct path.
How to give the key MACHINE_NAME so that its value gets replaced in the other key values.
You can't do this automatically - it's simply not a feature of Java property files. You'll need to write code to do this wherever you plan to load/use the properties file.
You should think about:
whether you want to make this more explicit, e.g. using ${MACHINE_NAME} instead of just MACHINE_NAME
whether you have a fixed list of replacements you want to support, or whether you want to pick up anything that looks like it's a replacement (much easier if you have a syntax such as ${...} of course)
whether replacements can be recursive, e.g. MACHINE_NAME = ${USER_NAME}-laptop - and how to handle cycles if so
Standard Properties mechanism cannot handle this, but there are extensions. Try to look at eproperties. In other case do the substitutions yourself.
While programming my bukkit plugin, i realized that i needed to have my own config file so i can add comments into the file. I also needed to be able to update the config (if it has been created and is old).
I had also recently finished a simple jQuery plugin, where I used jQuery.extend and i merged two settings arrays. I wanted to know if this was possible in java with config files.
My Question:
Is there a way i can merge the new default config with the one the user already has? (Removing non-needed values or changing the names of the same strings)
An Explanation of the question:
Here is an example config.yml i might have:
# Comment here....
myString: defaultString
myBool: false
myList:
- Value
- Value 2
Pretty simple. Lets say this is my default config. The plugin has copied this config (if it is not already there) inside the plugin folder. But, this brings up one issue:
What if i need to update my config? (Add/Remove a bool, string, etc.)
One day, i say "I no longer need that boolean myBool". I remove it from the default config.yml and the config looks something like this:
# Comment here....
myString: defaultString
myList:
- Value
- Value 2
Or, i might need to add an extra string myNewString:
# Comment here....
myString: defaultString
myNewString: string
myList:
- Value
- Value 2
If i rewrite the config yml to my new "Default" config file, i will lose all the user's configuration settings.
Is there a way i can merge the new default config with the one the user already has and just add the new string with the default values?
If you are using Spring then you can make use of YamlPropertiesFactoryBean. It has built in support for reading multiple yaml files and merging them together. So that way you can obtain merged Map<String,Object> from your yaml files. Then if you wish you can make use of ObjectMapper to convert it to particular type.
eg.
YamlMapFactoryBean factory = new YamlMapFactoryBean();
factory.setResolutionMethod(ResolutionMethod.OVERRIDE_AND_IGNORE);
factory.setResources(...resources);
Map<String, Object> yamlValueMap = factory.getObject();
If multiple resources are provided the later ones will override entries in
the earlier ones hierarchically; that is, all entries with the same nested key
of type {#code Map} at any depth are merged. For example:
<pre class="code">
foo:
bar:
one: two
three: four
</pre>
plus (later in the list)
<pre class="code">
foo:
bar:
one: 2
five: six
</pre>
results in an effective input of
<pre class="code">
foo:
bar:
one: 2
three: four
five: six
</pre>
Note that the value of "foo" in the first document is not simply replaced
with the value in the second, but its nested values are merged.
Bukkit has a built in YamlConfiguration class with methods which allow you to get a value or specify a default value to retrieve if none exist, such as getString(String path, String default).
Gets the requested String by path, returning a default value if not found.
If the String does not exist then the specified default value will returned regardless of if a default has been identified in the root Configuration.
However it does not have a way to remove values from the configuration. If the new configuration is so different from the previous one, you might consider creating the new configuration, deleting the old and renaming the new to take it's place. Though I wouldn't be too concerned about it unless it's significantly different.