Is it possible to have application.properties depend on multiple profiles? - spring-boot

Is it possible to have a Spring Boot properties file depend on two or more profiles? Something like application-profile1-profile2.properties?

Spring Boot does not support this out of the box. It only supports a single profile as described here.
However, it does provide enough flexibility to add your own property sources using EnvironmentPostProcessor.
Here is an example of how to implement this:
public class MultiProfileEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private final ResourceLoader resourceLoader = new DefaultResourceLoader();
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
String[] activeProfiles = environment.getActiveProfiles();
for (int i = 2; i <= activeProfiles.length; i++) {
Generator.combination(activeProfiles).simple(i)
.forEach(profileCombination -> {
String propertySourceName = String.join("-", profileCombination);
String location = "classpath:/application-" + propertySourceName + ".properties";
if (resourceLoader.getResource(location).exists()) {
try {
environment.getPropertySources().addFirst(new ResourcePropertySource(propertySourceName, location));
} catch (IOException e) {
throw new RuntimeException("could not add property source '" + propertySourceName + "'", e);
}
}
});
}
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
Couple of things to note:
This implementation only supports .properties files but can easily be extended to .yml files as well.
getActiveProfiles already returns the profiles in an order where the last one wins. This implementation relies on this order and builds the different file names leveraging this order. i.e. if active profiles are: profile1,profile2,profile3 then application-profile1-profile3.properties is supported but application-profile3-profile1.properties isn't, and application-profile1-profile3.properties will override properties defined in application-profile1.properties or application-profile3.properties.
This implementation uses a third party library com.github.dpaukov:combinatoricslib3 to create the different sets of profiles.
The property sources are added to the front of the property source list to override existing sources. But if you have custom property sources that should take precedence you need to modify this a bit to consider them in the order, i.e. by leveraging methods like Environment.addAfter.
Registering an EnvironmentPostProcessor is done using the spring.factories file.

There are 4 ways I know.
insert .yaml or .properties programmatically like Asi Bross Said. Use ResourceLoader or YamlPropertySourceLoader to insert.
Use .yaml. but it will be replace when you have a another spring project to dependent it.
Use properties instead of profiles. (For api project)
Use one #PropertySource to define properties file A.
Get the variables from properties file A and assign them to the parameters in another #PropertySource file path expression.
For example:
resources
/-application.properties <-- remove or empty,because it will be override by application project
/-moduleA
/-application.properties <-- Intellij can identify properties files started with application-
/-application-mysql-dev.properties
/-application-psql-dev.properties
/-application-psql-prod.properties
The content of resources/moduleA/application.properties :
moduleA.custom.profile1=mysql
moduleA.custom.profile2=dev
Content of Java Config file:
#SpringBootApplication
#PropertySources({
#PropertySource("/moduleA/application.properties"),
#PropertySource("/moduleA/application-${moduleA.custom.profile1}-${moduleA.custom.profile2}.properties"),
})
public class ModuleConfig {}
Use properties instead of profiles. (For application project)
resources
/-application.properties
/-application-mysql-dev.properties
/-application-psql-dev.properties
/-application-psql-prod.properties
The content of resources/application.properties :
moduleA.custom.profile1=mysql
moduleA.custom.profile2=dev
The content of SpringMvcApplication.java:
#SpringBootApplication
#PropertySource("/application-${moduleA.custom.profile1}-${moduleA.custom.profile2}.properties")
public class SpringMvcApplication {...}

Related

Load custom properties file in Spring Boot MVC Main

I have created a myApp.properties in resources folder location and mentioned the server.port in this file.
myApp.properties
myApp.server.port=8020
Now I want to read load this property into my application. But I have to read this before I actually a server.
Here I am trying to do like this
#SpringBootApplication
#ComponentScan(basePackages = {"com.myorg.myapp" })
#EnableConfigurationProperties
#PropertySource("classpath:myApp.properties")
#Component
public class MyAppApplication {
#Value("${myApp.server.port}")
private static String serverPort;
public static void main(String[] args) throws Exception{
try {
SpringApplication appCtxt = new SpringApplication(MyAppApplication.class);
appCtxt.setDefaultProperties(Collections
.singletonMap("server.port", serverPort));
appCtxt.run(args);
} catch (Exception e) {
e.printStackTrace();
}
}
But serverPort is coming as null.
I also tried to create a separate Config file like this but it can't be accessed in static main
#Configuration
#PropertySource("myApp.properties")
#ConfigurationProperties
public class MyAppConfig {
#Value("${myApp.server.port}")
private String serverPort;
/**
* #return the serverPort
*/
public String getServerPort() {
return serverPort;
}
}
Any suggestion would be helpful.
Spring boot injects properties during the initialization of the application context.
This happens (gets triggered) in the line:
appCtxt.run(args);
But you try to access the property before this line - that why it doesn't work.
So bottom line, using "#Value" in the main method doesn't work and it shouldn't.
Now from the code snippet, it looks like you could merely follow the "standards" of spring boot and create the file application.properties with:
server.port=1234
The process of starting the embedded web server in spring boot honors this property and bottom line it will have the same effect and Tomcat will be started on port 1234
Update 1
Based on OP's comment:
So, how can I have multiple application.properties.
In the Spring Boot's documentation it is written that application.properties are resolved from the classpath. So you can try the following assuming you have different modules A,B,C and web app D:
Create src/main/resources/application.properties inside each of 4 modules and pack everything together. The configuration values will be merged (hopefully they won't clash)
If you insist on naming properties A.properties, B.properties and C.properties for each of non-web modules, you can do the following (I'll show for module A, but B and C can do the same).
#Configuration
#PropertySource("classpath:A.properties")
public class AConfiguration {
}
Create in Module A: src/main/resources/A.properties
If you need to load the AConfiguration automatically - make the module A starter (using autoconfig feature of spring-boot):
Create src/resources/META-INF/spring.factories file with the following content:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
<package_of_AConfiguration>.AConfiguration
Also this has been the requirement to separate C from entire bundle where it might run as bundle for some and as a separate for some others
Although I haven't totally understood the requirement, but you can use #ConditionalOnProperty for configuration CConfiguration (that will be created just like AConfiguration.java in my previous example) but this times for module C.
If the conditional is met, configuration will run and load some beans / load its own properties or whatever. All in all conditionals (and in particular Profiles in spring) can help to reach the desired flexibility.
By default, the application.properties file can be used to store property pairs, though you can also define any number of additional property files.
If you save myApp.server.port=8020 in application.properties, it will work fine.
To register a custome property file, you can annotate a #Configuration class with the additional #PropertySource annotation:
#Configuration
#PropertySource("classpath:custom.properties")
#PropertySource("classpath:another.properties")
public class ConfigClass {
// Configuration
}
make sure, your class path is correct.

Reading applicatiion.properties once in a singleton class

I have a singleton configuration class where I want to store all the properties for our web application.
How do we read in the application.properies file like any other properties file without using annotations?
What is the the fully qualified filename for application.properies i.e. /application.properies?
We only want to read application.properties once.
Spring boot already reads all the properties stored in application.properties and much more, read Externalized Configuration documentation.
If you want to map one property named server.port you can just use #Value("${server.port}") Integer port.
If you want to access to all the properties loaded by Spring Boot, you can use the Environment object and access to all loaded PropertySources and retrieve all values from each property source.
In this this answer shows how. However, to avoid losing the precedence order of loaded properties, you have to reverse the property source list. Here you can find the code to load all the properties without losing the spring precedence order:
#Configuration
public class AppConfiguration {
#Autowired
Environment env;
public void loadProperties() {
Map<String, Object> map = new HashMap();
for (Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator().reverse(); it.hasNext(); ) {
PropertySource propertySource = (PropertySource) it.next();
if (propertySource instanceof MapPropertySource) {
map.putAll(((MapPropertySource) propertySource).getSource());
}
}
}
}

How do i config ssl artifacts in a spring application?

I am looking for help involving a spring application ( someone else's design ) that uses kafka consumers and producers.
The design allows for a config.properties file that contains an entry like so:
kafkaAddress=10.10.10.12:9093,10.10.10.11:9093,10.10.10.10:9093
Such config is picked up by some kind of spring bean code like this...
Java code...
private String kafkaAddress;
public String getKafkaAddress() {
return kafkaAddress;
}
public void setKafkaAddress(String kafkaAddress) {
this.kafkaAddress = kafkaAddress;
}
And it shows up in a Properties object I see with debugger.
"kafkaAddress" -> "10.10.10.12:9093,10.10.10.11:9093,10.10.10.10:9093"
The code passes the Properties object to constructor of the Kafka client
I thought this all happened through the design of spring and matching config "kafkaAddress" with a String of matching name kafkaAddress
Well I want to config for ssl such things as ...
ssl.truststore.location=C:\some\path\kafka.client.10.10.10.11.truststore.jks
ssl.truststore.password=mySecretWord
I put such things in config but what do i put for my setter and getter?
I can't put String ssl.truststore.location, right?
In config.properties I use ...
ssl_truststore_password=mySecretPassword
In setters and getters in MyConsumerConfig.java file I use...
public void setSsl_truststore_password(String ssl_truststore_password ) {
this.ssl_truststore_password = ssl_truststore_password;
}
public String getSsl_truststore_password() {
return this.ssl_truststore_password;
}
Before call to constructor .java file I ...
if(StringUtils.isNotEmpty(myConsumerConfig.getSsl_truststore_password())) {
consumerProps.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG,
onlineConsumerConfig.getSsl_truststore_password());
}
The SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG is defined in package org.apache.kafka.common.config.SslConfigs ...
public static final String SSL_TRUSTSTORE_PASSWORD_CONFIG "ssl.truststore.password"
The spring framework links the "ssl_truststore_password" in the config file to the getters and setters such as ...
MyConsumerConfig.setSsl_truststore_password(String ssl_truststore_password)

Using a properties file for an xtext validation rule

I have an Xtext project, and i would like to use an external properties file to be used in validation..
e.g. for the Hello world! project, and the following properties file...
hello.properties:
name=world
...create a validation rule that checks for Hello world! that world is the value of name in the properties file.
I would like the properties to only be read in once, such as when eclipse loads rather than every time the validation method is run as I am guessing this will be very slow. Where can I read them in so that this is the case?
Thanks, Sean
You may want to provide a class that allows to retrieve the values from the properties file. This class should be marked as #Singleton and clients of that implementation have to obtain the only instance via dependency injection.
#Singleton
public class MyPropertiesAccess {
private Properties properties;
public Properties getProperties() {
if (properties == null) {
properties = ...load...
}
return properties;
}
}
public class MyDslValidator {
#Inkect MyPropertiesAccess propertiesAccess;
}

OSGi force bundle start twice with different configurations

I'm using embedded Felix in my application. Application can potentially deal with lot of plugins that exposes similar interface IFoo. There is default an implementation FooImpl Hopefully for most plugins default FooImpl can be used with specific configuration files.
I would like dynamically install and start the same bundle (with FooImpl) when new configuration file appears. I've reviewed already FileInstall but have no idea how to apply it there.
UPDATE: Deployment sequence. The jar containing FooImpl and IFoo is stable, but I need hot-deploy of new instances that are result of uploading new .cfg file to scope of FileInstall. So desired is very simple - user uploads .cfg, new service (instance of FooImpl) is appeared.
Using Factory Configurations would allow you to create different instances of FooImpl based on different configurations.
For example in Declarative Services you can create a component like
import org.apache.felix.scr.annotations.*;
import org.apache.sling.commons.osgi.PropertiesUtil;
#Component(metatype = true,
name = FooImpl.SERVICE_PID,
configurationFactory = true,
specVersion = "1.1",
policy = ConfigurationPolicy.REQUIRE)
public class FooImpl implements IFoo
{
//The PID can also be defined in interface
public static final String SERVICE_PID = "com.foo.factory";
private static final String DEFAULT_BAR = "yahoo";
#Property
private static final String PROP_BAR = "bar";
#Property(intValue = 0)
static final String PROP_RANKING = "ranking";
private ServiceRegistration reg;
#Activate
public void activate(BundleContext context, Map<String, ?> conf)
throws InvalidSyntaxException
{
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put("type", PropertiesUtil.toString(config.get(PROP_BAR), DEFAULT_BAR));
props.put(Constants.SERVICE_RANKING,
PropertiesUtil.toInteger(config.get(PROP_RANKING), 0));
reg = context.registerService(IFoo.class.getName(), this, props);
}
#Deactivate
private void deactivate()
{
if (reg != null)
{
reg.unregister();
}
}
}
Key points here being
You use a component of type configurationFactory
In the activate method you read the config and then based on that register a service
In deactivate you explicitly unregister the service
End users would then create config file with name <pid>-<some name>.cfg. Then DS would then activate the component.
Then you can create multiple instances by creating configuration (using File Install like) file with name <pid>-<some name>.cfg like com.foo.factory-type1.cfg
Refer to JdbcLoginModuleFactory and its associated config for one such example.
If you want to achieve the same via plain OSGi then you need to register a ManagedServiceFactory. Refer to JaasConfigFactory for one such example.
Key points here being
You register a ManagedServiceFactory instance with configuration PID as the service property
In the ManagedServiceFactory(String pid, Dictionary properties) callback register instances of FooImpl based on the config properties
Sounds like you want to only have one bundle with FooImpl installed but have it register multiple IFoo services, one for each configuration. Look at Declarative Services and use factory configurations with Config Admin to establish the multiple configurations for the DS component.

Resources