Override Property value in PropertyPlaceholderConfigurer - spring

I need to override a property value given in my property file while loading my JBOSS Application server.
I tried out with below code overriding processProperties() method in PropertyPlaceholderConfigurer.
My property file has this entry
base.url="defaultUrl"
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
protected String convertPropertyValue(String originalValue) {
return (originalValue != null) ? originalValue.trim() : originalValue;
}
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
super.processProperties(beanFactory, props);
for (Enumeration names = props.propertyNames(); names.hasMoreElements();) {
String key = (String) names.nextElement();
props.put("base.url", getUpdatedUrl());
}
}
}
I am injecting base.url value in a placeholder ${base.url} in application context.
How should I update the value of given property in run time. The above code always take the value in the property file not the updated value.

Blowing the dust off from this question. This can be done using PropertyPlaceholderConfigurer and adding a new property file (at the end of the list), where you put the properties you want to override. (The name/file path of the property file can contain an environment variable you pass at build time). Here's the javadoc of PropertiesLoaderSupport#setLocations:
Note: Properties defined in later files will override properties
defined earlier files, in case of overlapping keys. Hence, make sure
that the most specific files are the last ones in the given list of
locations.
As of Spring 5.2 this was deprecated in favor of PropertySourcesPlaceholderConfigurer:
Specialization of PlaceholderConfigurerSupport that resolves ${...}
placeholders within bean definition property values and #Value
annotations against the current Spring Environment and its set of
PropertySources.
Some examples here

Related

In a spring.config.import factory how do you access properties in the invoking document?

I am creating a spring.config.import factory and can't see how to access properties from the invoking(parent) document
# mish-mash of properties without a common prefix
key1 = value1
key2 = value2
# now I want to read key1, key2 inside the factory
spring.config.import = myfactory:
Inside my implementation
#Configuration
public class MyFactoryResolver implements ConfigDataLocationResolver<MyResource>, Ordered {
#Override
public List<MyResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
// how do I get the values or key1 and key2
// tried
binder.bind("key1", String.class).get(); // FAIL
// key1 needs to be in a higher source like environment KEY1=VALUE1
}
// tried as field
#Value("${key1}")
String key1; // always null
// cannot inject; too early in life-cycle ?
}
Now spring-cloud-client can do this
spring.cloud.config.username = johndoe
spring.cloud.config.password = john's password
spring.config.import = configserver:http://myserver.com
— so clearly it is possible to read a property value from the parent doc.
But I can't see how this code works — this code is baroque/mysterious to me:
it doesn't read a property like spring.cloud.config.username simply
instead it creates a sidekick #ConfigurationProperties ClientConfigProperties bean (how?) that wraps all spring.cloud.config.* key/values. This bean can't be injected (too early in the life-cycle??), so it is retrieved from a Binder and then all the properties are available; so if these properties can be
read off a sidekick, why I can't I read them easily...
my properties don't have a standard prefix so it is not easy to create a #ConfigurationProperties sidekick
...also in the code of spring-cloud-config-client you sometimes see new ClientConfigProperties(...) — I always thought this was forbidden in DI-land as the container won't be able to manage it for you.
TL;DR - what I am looking for is a way to read property/key values from the context(binder?) of the document or a sister document that invoked the factory; without having to create a side-kick bean and forcing all properties to confirm to prefix-naming. (This is a legacy application where property names were not enforced...).
Update: I attemped to duplicate the sidekick pattern — standardised property names to a prefix and one segment, created a holder #ConfiguationProperties bean and added it as an EnableAutoConfiguration factory (copy-pasta from spring-cloud-config). Copied code:
private MyProperties resolveHook(ConfigDataLocationResolverContext context) {
// TODO Auto-generated method stub
boolean registered = context.getBootstrapContext().isRegistered(MyProperties.class);
System.out.println("RESOLVER: MyProperties is registered = " + registered);
if (registered) {
return context.getBootstrapContext().get(MyProperties.class);
}
Binder localBinder = context.getBinder();
BindHandler localHandler = context.getBootstrapContext().getOrElse(BindHandler.class, null);
System.out.println("RESOLVER: BindHandler is null? " + (localHandler == null));
BindResult<MyProperties> object = localBinder.bind(MyProperties.PREFIX,
Bindable.of(MyProperties.class), localHandler);
System.out.println("RESOLVER: object is bound? " + (object.isBound()));
if (object.isBound()) {
MyProperties properties = object.get();
context.getBootstrapContext().registerIfAbsent(MyProperties.class, InstanceSupplier.of(properties));
System.out.println(
"RESOLVER: register object of type " + (properties.getClass().getName()) + " " + properties);
return properties;
}
return null;
}
Holy cow - this actually works - the sidekick bean is created and all the fields are injected from the parent document. Then the resolver can read off the property values — however this is surely the most obscure and round-about way of doing things, and there has to be a simpler method?
Credit for the answer goes to the Spring Team (philwebb at GH) when I posted this as a question to the Spring repository https://github.com/spring-projects/spring-boot/issues/32854:
I mistakenly used the binder that can be constructor injected when implementing a ConfigDataLocationResolver. Instead one should use the binder from the first parameter of the overriden methods:
// Constructor
public MyResolver(Log log, Binder binder) {
super();
this.log = log;
// don't use this object to access properties
// from the invoking properties file
this.binder = binder;
}
#Override
boolean isResolvable(ConfigDataLocationResolverContext context...) {
Binder localBinder = context.getBinder();
// this object has access to properties defined in the invoking
// parent application.properties
}
TL;DR there are number of binder objects one can access (constructor injection, from a context) — one should be using the Binder from the context and not the constructor.

How do you make Spring fail fast when using placeholders with #ConfiguraitonProperties and an environment variable is not set?

When using placeholders to externalise configuration in an application.yaml file, and an associated properties class, how do you make sure Spring fails during startup when it can't resolve a placeholder, instead of just using the placeholder itself as the verbatim value?
For example, given this application.yaml file:
example.key: ${MY_ENV_VAR}
and this properties POJO:
#ConfigurationProperties(prefix="example")
public class AcmeProperties {
public String key;
// Getters, setters, constructors omitted...
}
if MY_ENV_VAR is not set on the system, how do you make Spring throw an exception at startup, instead of setting key to literally ${MY_ENV_VAR}?
Note, Spring doesn't return an empty String, which we could force by defining a default with ${MY_ENV_VAR:defaultValue}, or null (SpEL is not evaluated in #ConfigurationProperties, so this can not be defined as the default, and the behaviour of System.getenv("MY_ENV_VAR") returning null in the case of an undefined environment variable isn't mirrored), it literally just uses the placeholder as the value. We'd rather Spring stopped launching the app altogether instead, for all properties in AcmeProperties. Just using #Validated with Hibernate Validator on the classpath doesn't do it, neither does #ConfigurationProperties(prefix="example", ignoreInvalidFields=false) (which is the default anyway).
A manual check for the value containing ${...} could of course be added to all String properties in the POJO, but this is more error-prone, and would also not work if the actual environment variable was set to a string containing that character sequence. Is there a way to check if the placeholder can be resolved, instead of if the value after resolution is complete?
You can have a custom validator which could look like:
object UnresolvedPropertiesValidator : Validator {
override fun supports(clazz: Class<*>): Boolean {
return clazz == String::class.java
}
override fun validate(target: Any, errors: Errors) {
val stringVal = target as String
if (stringVal.startsWith(SystemPropertyUtils.PLACEHOLDER_PREFIX) && stringVal.endsWith(SystemPropertyUtils.PLACEHOLDER_SUFFIX)) {
errors.reject(
"prop.validation",
"Could not resolve placeholder $target"
)
}
}
}
And then create a bean with the name configurationPropertiesValidator:
class UnresolvedPropertiesValidatorAutoConfiguration {
#Bean
fun configurationPropertiesValidator(): UnresolvedPropertiesValidator {
return UnresolvedPropertiesValidator
}
}
As you can see from these https://github.com/spring-projects/spring-boot/blob/24a52aa66ddb92cd14acb2b41d9f55b957a44829/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationProperties.java#L47
https://github.com/spring-projects/spring-boot/blob/9630f853be3183f4872428e2e65b6ef4be7a9b7a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java#L105,
the validator with the name above will be applied after resolving the prop, and it simply ensures the final value doesn't start with ${ and end with }.
This works for us. But hopefully, the issue will be resolved soon, and we will remove this workaround.

Can you or can't you use a property from a config file when using Spring #Condition?

I have read pretty much everything I can find on StackOverflow and other sites and I don't see a definitive answer anywhere.
I have a class that implements #Condition that I use in a #Configuration file to conditionally load some beans. I am doing something like this:
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metdata) {
String property = context.getEnvironment().getProperty("some.prop.from.file");
boolean enable = Boolean.parseBoolean(property);
return enable;
}
}
When debugging I see that getting the property from the environment always returns null, even though the property is injected in other beans using #Value.
So my question can you or can't you attempt to get a property value from a file within a #Condition class? Can you only get System properties? I would think that this is a common use case that I would think Spring could handle.
Had to add the property to application.properties and not the other property files that are loaded during startup.

Spring Boot - Detect and terminate if property not set?

Is there any way for a Spring Boot web application to abort at startup if a required property is not set anywhere (neither in the application.properties file nor the other property sources)? Right now, if the property is included in another property, it seem that Spring Boot simply avoids substitution.
For example, in my application.properties file, I have the line:
quartz.datasource.url=jdbc:hsqldb:${my.home}/database/my-jobstore
Right now, if "my.home" is not set elsewhere, Spring Boot is setting the url literally to "jdbc:hsqldb:${my.home}/database/my-jobstore" (no substitution).
I would like to have the application fail to start if the property my.home were not set anywhere else.
To throw a friendly exceptions just put a default null value in property, check and throw a exception in afterProperty method.
#Component
public static class ConfigurationGuard implements InitializingBean {
#Value("${my.home:#{null}}")
private String myHomeValue;
public void afterPropertiesSet() {
if (this.myHomeValue == null or this.myHomeValue.equals("${my.home}") {
throw new IllegalArgumentException("${my.home} must be configured");
}
}
}
Create a bean with a simple #Value(${my.home}) annotated field. - Then Spring will try to inject that value and will fail and therefore stop when the value is not there.
Just #Value(${my.home}) private String myHomeValue; is enough for normal (not Boot) Spring applications for sure! But I do not know whether Boot has some other configuration to handle missing values: If there is an other failure management than you could check that value in an PostCreation method.
#Component
public static class ConfigurationGuard implements InitializingBean {
#Value(${my.home})
private String myHomeValue;
/**
* ONLY needed if there is some crude default handling for missing values!!!!
*
* So try it first without this method (and without implements InitializingBean)
*/
public void afterPropertiesSet() {
if (this.myHomeValue == null or this.myHomeValue.equals("${my.home}") {
throw new IllegalArgumentException("${my.home} must be configured");
}
}
}
The default behaviour in current versions of Spring Boot (1.5.x, 2.0.x, 2.1.x) is to throw an exception if a placeholder can not be resolved.
There will a be an exception like this one :
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'app.foo.undefined' in value "${app.foo.undefined}"
It works because a bean of type PropertySourcesPlaceholderConfigurer (from spring-context) is automatically registered in Spring Boot, in this class : PropertyPlaceholderAutoConfiguration. And by default, the property ignoreUnresolvablePlaceholders in PropertySourcesPlaceholderConfigurer is set to false, which means an exception must be thrown if a placeholder is unresolved (be it nested or not).
Although they work, I think the approach in the foremost answer is somewhat brittle, as it only works for the predefined name(s), and will silently stop checking the when someone changes quartz.datasource.url in the configs to use a different expansion.
Ideally, I want this value of ignoreUnresolvablePlaceholders to be false to get wholesale expansion checking when parsing my configs such as application.properties or its YAML variants, but it's hard-coded to true for these cases. This unfortunately leaves strings such as ${FOO} in its unexpanded form if FOO cannot be found, making troubleshooting extremely painful. This is especially the case for fields that don't readily appear in the logs such as passwords.
While I couldn't find a way of changing ignoreUnresolvablePlaceholders short of modifying Spring Boot's classes, I did find an alternative of using a custom PropertySource implementation and defining a new syntax such as "${!FOO}" to indicate FOO must exist as an environment variable or die. (The OP didn't mention whether my.home is an environment variable but the code below is for environment variables.)
First, an EnvironmentPostProcessor implementation is required for registering the custom PropertySource. This StrictSystemEnvironmentProcessor.java does this as well as holds the implementation of the custom PropertySource:
package some.package;
#Order(Ordered.LOWEST_PRECEDENCE)
class StrictSystemEnvironmentProcessor implements EnvironmentPostProcessor {
private static final String PROPERTY_SOURCE_NAME = "STRICT_" + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (environment.getPropertySources().contains(PROPERTY_SOURCE_NAME)) {
return;
}
SystemEnvironmentPropertySource delegate = (SystemEnvironmentPropertySource)environment.getPropertySources()
.get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
environment.getPropertySources().addLast(new StrictSystemEnvironmentPropertySource(delegate));
}
private static class StrictSystemEnvironmentPropertySource extends SystemEnvironmentPropertySource {
public StrictSystemEnvironmentPropertySource(SystemEnvironmentPropertySource delegate) {
super(PROPERTY_SOURCE_NAME, delegate.getSource());
}
#Override
public Object getProperty(String name) {
if (name.startsWith("!")) {
String variableName = name.substring(1);
Object property = super.getProperty(variableName);
if (property != null) {
return property;
}
throw new IllegalStateException("Environment variable '" + variableName + "' is not set");
}
return null;
}
}
}
Instead of returning null, an exception is thrown for names that start with !.
This META-INF/spring.factories is also required so that Spring initializes our EnvironmentPostProcessor:
org.springframework.boot.env.EnvironmentPostProcessor=some.package.StrictSystemEnvironmentProcessor
Then henceforth, I can write all environment variables substitutions in my configs as ${!FOO} to get strict existance checking.
You can also create a #ConfigurationProperties bean, and decorate it with #Validated and #NotNull. This will throw an exception during startup when the value is not present (or null), e.g.
#Validated
#ConfigurationProperties("my")
public class MyProperties {
#NotNull
private String home;
// getter/setter, or constructor. See #ConstructorBinding.
}
For reference: Spring Boot 2.6 - #ConfigurationProperties Validation.
Note that you may need to add spring-boot-starter-validation, or another validator, depending on your project.
Then, you can just supply it as a dependency when needed, e.g.
#Component
public class AnotherBean {
private final MyProperties myProps;
public AnotherBean(MyProperties myProps) {
this.myProps = myProps;
}
// some code that uses myProps.getHome()
}

Is it possible to have non String type values in a Spring Environment PropertySource

I'm trying to add ConfigSlurper's ConfigObjectinto an application context Environment in order to provide configuration values to the context.
The MapPropertySorce itself only expects its values to be of type Object only. But in the end property resolution fails as the EnvironmentAccessor will try to cast each ConfigObject to String.
So basically the question is, is there support for non String property resource values? Any supporting classes there (different EnvironmentAccessor?)
class ConfigSlurperLearningSpec extends Specification {
def configurationResource = new ClassPathResource("/META-INF/de.troi/application.configuration")
ConfigObject configuration = new ConfigSlurper().parse(configurationResource.getURL())
def "use as application context property source"() {
expect:
AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext()
context.register(PropertySourceInjection)
context.getEnvironment().getPropertySources().addLast(new MapPropertySource("mapConfiguration", configuration))
context.refresh()
String configuredValue=context.getBean('testBean')
configuredValue=='create'
}
}
#Configuration
class PropertySourceInjection {
#Value("#{environment['entityManagerFactory']['jpaPropertyMap']['hibernate.hbm2ddl.auto']}")
Object hibernateHbm2ddlAuto;
#Bean
String testBean() {
return new String(hibernateHbm2ddlAuto.toString())
}
}
It is definitely possible to inject non-string objects via ConfigSlurper.
Take a look at (shameless plug) https://github.com/ctzen/slurper-configuration

Resources