Property configuration of java classes in groovy script with spring - java

I want to use Groovy scripts to utilize some java classes that are have spring annotations along the lines of the following:
#Component
class SomeUtility {
#Value("${foo}")
public string String fooValue;
}
Ideally I would like to configure foo in an application.properties file in the same manner as done in spring-boot
I have tried adding spring to the classpath and running something like this in a groovy script:
def ctx = new GenericApplicationContext()
new ClassPathBeanDefinitionScanner(ctx).scan('dylan')
ctx.refresh()
def b = ctx.getBean(SomeUtility)
println b.fooValue
but the output is ${foo} whether a properties file is in place or not - so I guess the #Value value processing is not happening.
I have also tried adding spring-boot to the classpath and running the above script - but I suspect I am not actually triggering spring-boot in that case.
Is there any way that I can do this - what I want is essentially to be able to configure the values easily then get hold of the components in a groovy script.

Since you are using Groovy, you need to do single quote #Value('${foo}'), as double quotes will be picked up by Groovy as a GString before Spring has a chance to look at its own context.

Related

How do I replace the values of a YAML file with definitions in the same file? [duplicate]

We have a spring boot application with configuration being driven from application.yml file. In this configuration file we use the feature of defining a property by referring to another property inside the same application.yml file:
my-games-app:
base-property: foo
games:
- game-one:
game-name: ${my-games-app.base-property}one
game-location: ${my-games-app.base-property}/one
- game-two:
game-name: ${my-games-app.base-property}two
game-location: ${my-games-app.base-property}/two
And we have a #ConfigurationProperties bean loading games configuration:
#Configuration
#ConfigurationProperties(prefix = "my-games-app.games")
public class GamesConfig {
private Map<String, Game> games;
...
}
Useless to say the above is just an example, in reality it is a very complex setup with GamesConfig bean being used as a constructor argument for many other beans inside our application:
#Component
public class GamesRunner {
private final GamesConfig gamesConfig;
...
}
Everything works as expected. The problem we have is related to testing the beans where GamesConfig is injected; in the above example GamesRunner. At the moment we use #SpringBootTest to get hold of the beans we want to test. This again, works OK but the main inconvenient is that the whole application needs to be started in order to access the GamesConfig bean. This means setting up a lot of infrastructure such as a Database a JMS message broker and a Kafka broker. This takes time and makes our CI builds longer to run which started to become a bit of an inconvenient. Because the beans we want to test don't need any other setup than having the GamesConfig constructor argument provided we would prefer to have unit tests in place rather than integration tests as they are much faster to run.
In other words, we want to be able to recreate GamesConfig by hand by parsing our application.yml with a test helper method. To do this we use snakeyaml library:
public final class TestHelper {
public static GamesConfig getGamesConfig() {
var yaml = new Yaml();
var applicationYaml = (Map<String, Object>) yaml.load(readResourceAsString("application.yml");
return createGamesConfig(applicationYaml.get("games");
}
private static GamesConfig createGamesConfig(Object config) {
// The config Object passed here is a `Map<String, Map<String, String>>`
// as defeined in our `games` entry in our `application.yml`.
// The issue is that game name and game locations are loaded exactly like
// configured without property place holders being resolved
return gamesConfig;
}
}
We resolved the issue by manually parsing the property placeholders and looking up their values in the application.yml file. Even if our own property placeholder implementation is quite generic, my feeling is that this extra work is not needed as it should be a basic expectation the library would have some specific set up to do this out of the box. Being very new to snakeyaml I hope someone else hit the same problem and knows how to do it.
We use snakeyaml because it just happened to be in the class path as a transitive dependency, we are open to any suggestions that would achieve the same thing.
Thank you in advance.
To my knowledge, SnakeYAML only supports substitution of environment variables, which is why what you want is not possible as far as I know. What you can do instead, of course, is simply use Spring's classes without setting up a full ApplicationContext.
For example, assuming your game config from above, you could use:
final var loader = new YamlPropertySourceLoader();
final var sources = loader.load(
"games-config.yml",
new ClassPathResource("games-config.yml")
);
final var mutablePropertySources = new MutablePropertySources();
sources.forEach(mutablePropertySources::addFirst);
final var resolver = new PropertySourcesPropertyResolver(mutablePropertySources);
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
System.out.println(resolver.getProperty("my-games-app.games[0].game-one.game-name"));
System.out.println(resolver.getProperty("my-games-app.games[0].game-one.game-location"));
System.out.println(resolver.getProperty("my-games-app.games[1].game-two.game-name"));
System.out.println(resolver.getProperty("my-games-app.games[1].game-two.game-location"));
which outputs:
fooone
foo/one
footwo
foo/two
If you are actually interested in how Spring does it, a good starting point is the source code of the PropertySourcesPlaceholderConfigurer class.

Can you override a library's Spring property placeholders?

We are working on moving our application to use only Spring-Boot application.properties files. The old way we were doing was that each library/dependency would have their properties stored in a dedicated properties file like res/environment/some-library-override.properties. The values would then be retrieved in the library using #Value("$some-library-{PROPERTY_NAME}").
However, since moving all of these override properties to dedicated application.properties files, it is no longer resolving the properties and we get errors like java.lang.NumberFormatException: For input string: "$some-library-{PROPERTY_NAME}".
I assume this is because it is still expecting the property to be in that dedicated properties file.
Is there a solution to this that doesn't involve modifying the library/dependency? Is it possible to have it ignore the prefix and only look for the PROPERTY_NAME in the application.properties files?
if you have declared propertie var likeproperty.name=XXXX or added environment var like PROPERTY_NAME=XXXX.
you need to use this way
#Value("some-library-${property.name}")
// will inject value "some-library" + "XXXX"

Configuring global list of allowed classes for serialization

I am using Inifinispan v12.1 with String Boot v2.5.2 via org.infinispan:infinispan-spring-boot-starter-embedded. In our application we are using custom classes which we would like to cache (very common case), however it turned out that starting from v10 these classes need to be listed in "allow list".
We are using infinispan.xml configuration passed via infinispan.embedded.config-xml property as advised by sample project.
Question: How is it possible to configure allow list globally for all caches by the means of XML configuration file?
I have considered the following options:
System property infinispan.deserialization.allowlist.regexps (from ClassAllowList) – not good choice as configuration will be spread between XML file and e.g. some other place. More over if the property is renamed in future Infinispan versions one would notice it only when application is run.
Defining the <cache-container><serialization><allow-list> as to documentation is not good option because will result several identical per-cache XML configuration blocks.
The corresponding Java Config for Spring Boot application would be:
#org.springframework.context.annotation.Configuration
public class InfinispanConfiguration {
#Bean
public InfinispanGlobalConfigurationCustomizer globalCustomizer() {
return builder -> builder.allowList().addRegexp("^org\\.mycompany\\.");
}
}
P.S. Javadoc in GlobalConfiguration assumes that there is <default> XML section the configuration can be read from, but in fact XML does not support it anymore.
P.P.S. Arguably the dots in the packages should be escaped in SpringEmbeddedModule and start with ^ because ClassAllowList uses Matcher#find() (boolean regexMatch = compiled.stream().anyMatch(p -> p.matcher(className).find());):
serializationAllowList.addRegexps("^java\\.util\\..*", "^org\\.springframework\\..*");

Spring Boot external properties not loaded

I'm starting with Spring Boot, so am first going through the Spring Boot reference Guide
I'm currently having a look at the Externalized Configuration
My goal is to have a simple Component to read the value out of application.properties, which, if I'm not mistaken, should be loaded automatically, without any further configuration.
The example in the reference is as follows:
import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*
#Component
public class MyBean {
#Value("${name}")
private String name;
// ...
}
with the following explanation:
On your application classpath (e.g. inside your jar) you can have an
application.properties that provides a sensible default property value
for name. When running in a new environment, an application.properties
can be provided outside of your jar that overrides the name; and for
one-off testing, you can launch with a specific command line switch
(e.g. java -jar app.jar --name="Spring").
Seems simple enough. So, I have created following Component:
#Component
public class Admin {
#Value("${name}")
private String name;
public String getName(){
return "ReadName_" + name;
}
}
And, in my recources folder, the following application.properties file (which, I verified, is copied into my .jar)
name=myName
So, if I follow the logic in the reference (which is also mentioned in this post: Read application.properties, and run my application.
The code compiles and runs without error, but each time I print the value of getName() from an instance of Admin on my screen, it just gives a "ReadName_null" instead of "ReadName_myName", the value in the properties file.
As the reference states, in 24.2:
By default SpringApplication will convert any command line option
arguments (starting with ‘--’, e.g. --server.port=9000) to a property
and add it to the Spring Environment. As mentioned above, command line
properties always take precedence over other property sources.
So, just to check, I run my .jar file with both the application.properties with the key present, and add --name=passedName as a command line argument.
Even though this value should be automatically added to the environment, and overrule anything (or in my case: nothing) that is currently there, and I expect the getName() to return "ReadName_passedName", it still returns "ReadName_null".
EDIT: I print the command line arguments I pass while booting, so I know that the argument is indeed read by the system as "--name=passedName"
Is there anything obvious I'm missing here?
If you do new Admin (), you are creating a new instance of Admin and not a spring bean. So you don't get any advantages which spring provides(for example Dependency Injection) And hence the value is null.
Instead you should Autowire it in your class. This way spring will inject the instance of Admin that it has created (with injected values)
#Autowired
Admin admin;
And before you ask, yes by default all beans are singleton(unless specified otherwise). So no matter whereever you Autowire Admin, you will get the same instance.
This is a broad topic, you should read about it.

Java Properties Automatically Use Test Resources

I have a few classes that need some environment specific configuration. I turned to using properties files, which are loaded in the constructor of the class.
public class MyClass {
public MyClass() {
try {
ValidatedEnvironmentProperties props = new ValidatedEnvironmentProperties();
props.load(MyClass.class.getResourceAsStream("/myclass.properties"));
ValidatedEnvironmentProperties extends Properties. Basically, it uses a Java System Property to set a key prefix. I set the System property to, say, production, and in the properties files, I have staging.url=... and production.url=.... This allows me build/runtime selection of which configuration is used while not needing to change property file name.
myclass.properties is stored in src/main/resources.
That works fine, and I rather like the how it works. My problem is that I'm sort of stuck with TestNG. I want to test a bunch of other properties in TestNG unit tests. This led me to create src/test/resources/myclass.properties. Instead of "environment" keys, I use test names like bad_url_test.url=this_ain't_a_url.
I was thinking that src/test/resources would get a higher priority in the classpath/classloader (terminology?), causing the test-specific properties to load. Then, for my various tests, I just set the Java System property to bad_url_test, instantiate MyClass, test my assertions, and then set the System property to a new test, instantiate a new object, and repeat.
I believe the source of the problem is this line in MyClass's constructor:
props.load(MyClass.class.getResourceAsStream("/myclass.properties"));
In MyClassTest, I put these lines to try to understand what's happening:
File f = new File(MyClass.class.getProtectionDomain().getCodeSource().getLocation().getPath());
System.out.println("MyClass classpath: " + f.toString());
f = new File(MyClassTest.class.getProtectionDomain().getCodeSource().getLocation().getPath());
System.out.println("MyClassTest classpath: " + f.toString());
Output is:
MyClass classpath: /home/fandingo/code/project/build/classes/main
MyClassTest classpath: /home/fandingo/code/project/build/classes/test
MyClassTest is correct, but I need something accessible within MyClass's constructor that will automatically prefer /src/test/resources/ when running tests but /src/main/resources/ when running normally.
You need inversion of control. i.e. MyClass needs to be told where to get its properties from and not decide on its own.
There are lots of ways to do this but they all come down to the same idea: MyClass should not know at compile time where its properties come from.
e.g.
In MyClass.java
ValidatedEnvironmentProperties props = new ValidatedEnvironmentProperties();
String resourceSupplierClassName = System.getProperty("resource-supplier-class-name",
MyClass.class.getName());
Class<?> resourceSupplierClass = Class.forName(resourceSupplierClassName);
props.load(resourceSupplierClass.getResourceAsStream("/myclass.properties"));
In MyClassTest.java
System.setProperty("resource-supplier-class-name", MyClassTest.class.getName());
Again, there are many ways to do this. Instead of passing around system properties, etc. you can also use dependency injection (a form of inversion of control). You can change the MyClass constructor to take your props as an argument and then put the onus on the instantiators of MyClass to provide the props or you can use a dependency injection framework such as Spring or Guice to manage creating the appropriate props instance for main/test execution and provide it to objects that need it as needed.

Categories