Spring: Environment specific configuration - java

Using Spring I need some kind of environment (dev|test|prod) specific properties.
I have exactly one configuration file (myapp.properties) and for some reasons I cannot have more than one configuration file (even spring can handle more than one).
So I need the possibility to add properties with a prefix like
dev.db.user=foo
prod.db.user=foo
and tell the application which prefix (environment) to use with a VM-argument like -Denv-target or something like this.

I use for this purpose a subcass of PropertyPlaceholderConfigurer:
public class EnvironmentPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private static final String ENVIRONMENT_NAME = "targetEnvironment";
private String environment;
public EnvironmentPropertyPlaceholderConfigurer() {
super();
String env = resolveSystemProperty(ENVIRONMENT_NAME);
if (StringUtils.isNotEmpty(env)) {
environment = env;
}
}
#Override
protected String resolvePlaceholder(String placeholder, Properties props) {
if (environment != null) {
String value = props.getProperty(String.format("%s.%s", environment, placeholder));
if (value != null) {
return value;
}
}
return super.resolvePlaceholder(placeholder, props);
}
}
and using it in applicationContext.xml (or any other spring-configuration file):
<bean id="propertyPlaceholder"class="EnvironmentPropertyPlaceholderConfigurer">
<property name="location" value="classpath:my.properties" />
</bean>
In my.properties you can define properties like:
db.driverClassName=org.mariadb.jdbc.Driver
db.url=jdbc:mysql:///MyDB
db.username=user
db.password=secret
prod.db.username=prod-user
prod.db.password=verysecret
test.db.password=notsosecret
Thereby you can prefix properties keys by an environment key (e.g. prod).
Using the vm argument targetEnvironment you can choose the enviroment you like to use, e.g. -DtargetEnvironment=prod.
If no environment-specific-property exists, the default one (without a prefix) is choosen. (You should always define a default one.)

I don't know what are your constraints to avoid having more than one configuration file but you can use something like -Denvtarget=someValue and in java do:
//Obtain the value set in the VM argument
String envTarget= System.getProperty("env-target");
Properties properties;
try {
properties = PropertiesLoaderUtils.loadAllProperties("myapp.properties");
} catch (IOException exception) {
//log here that the property file does not exist.
}
//use here the prefix set in the VM argument.
String dbUser = properties.getProperty(envTarget+".db.user");

If you have environment variable and want to get property according this variable you can declare your properties that way:
<property name="username" value="${${env-target}.database.username}" />
<property name="password" value="${${env-target}.database.password}" />
Also make sure that you use properly configured property-placeholder:
<context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>
Or, if you use special property configurer (e.g. EncryptablePropertyPlaceholderConfigurer), set properties:
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
But as mentioned earlier it is better to use profiles.

Related

File processing order with Spring Batch

I have a question about the order of processing of csv files with Spring Batch, I have several csv files to process, when I launch the spring batch I do not know in what order spring processes these files.
I would like to know how Spring chose the first file to treat ? and where can I find this setting ?
Is it possible to define a sort ? for example, processing files based on a date written on the file name? and how can I customize the choice of the first files to process?
Thank you all,
Spring batch's MultiResourceItemReader uses Comparator<Resource> to preserve ordering. If we don't provide comparator, then the default ordering will be based on file name. If you want to give your custom sorting, you can write your own comparator logic like following(sort based on last modified time).
public class FileModifiedComparator implements Comparator<FileSystemResource>{
#Override
public int compare(FileSystemResource file1, FileSystemResource file2) {
//comparing based on last modified time
return Long.compare(file1.lastModified(),file2.lastModified());
}
}
You can modify the comparator to check modify your sort logic such as file name, created etc. for eg: return file1.getFilename().compareTo(file2.getFilename()); or return Long.compare(file1.contentLength(),file2.contentLength());
and in the MultiResourceItemReader bean, set this comparator.
<bean id="fileModifiedComparator" class="FileModifiedComparator"/>
<bean id="multiResourceReader"
class=" org.springframework.batch.item.file.MultiResourceItemReader">
<property name="resources" value="file:inputs/input*.csv" />
<property name="delegate" ref="flatFileItemReader" />
<property name="comparator" ref="fileModifiedComparator" />
</bean>
#Bean
public MultiResourceItemReader<Employee> criminalBackgroundReader() {
MultiResourceItemReader<Employee> resourceItemReader = new MultiResourceItemReader<Employee>();
ClassLoader cl = this.getClass().getClassLoader();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
Resource[] resources = null;
try {
resources = resolver.getResources("file:" + EmployeeInputFilePath);
} catch (IOException e) {
log.error("===== Error occurding reading input file directory: ", e);
return null;
}
resourceItemReader.setResources(resources);
resourceItemReader.setDelegate(flatFileRpReader());
resourceItemReader.setComparator(new FileComparator());
resourceItemReader.setStrict(false);
return resourceItemReader;
}
public class FileComparator implements Comparator<Resource>{
#Override
public int compare(Resource file1, Resource file2) {
//comparing based on File Name compare it to Enum-InputFileType
return InputFileType.fromString(StringUtils.substringBefore(file1.getFilename(), ".")) .compareTo(InputFileType.fromString(StringUtils.substringBefore(file1.getFilename(),"."));
}
}

Spring - How to get subset of properties file without enumerating all required properties

I am loading my properties file as following:
#Configuration
#PropertySource("classpath:app.properties")
class MyApp {
#Bean
public PropertySourcesPlaceholderConfigurer PropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the properties file, I have several database-related properties:
database.dataSource.url=jdbc:postgresql://localhost:${db-port:5432}/mydb
database.dataSource.x=...
database.dataSource.y=...
database.dataSource.z=...
Note:
${db-port} should be replaced by either the value of property/environment variable db-port or 5432. In my case, I am defining the environment variable db-port when spawning the Tomcat container.
All database-related properties are grouped under database. root. This is intentional, see below.
I want to avoid that I have to enumerate/hardcode all possible database-related properties in my code. Luckily, the database layer in use (Hikari) has the nice feature that I can pass all properties via a java.util.Properties. So, I want retrieve all defined properties under database.* and simply forward it to Hikari.
For this, I wrote the following utility:
#Component
public class PropertyFetcher
{
#Autowired
private ConfigurableEnvironment environment;
public Properties get(final String key) {
final Properties p = new Properties();
for (final PropertySource<?> s : environment.getPropertySources()) {
if (s instanceof EnumerablePropertySource) {
for (final String k : ((EnumerablePropertySource) s).getPropertyNames()) {
if (k.startsWith(key) && k.length() > key.length()) {
p.put(k.substring(key.length()), s.getProperty(k));
}
}
}
}
return p;
}
}
Now, when calling get("database."), I have all database-related properties as defined in the properties file. Great! But, the value for property dataSource.url is now
jdbc:postgresql://localhost:${db-port:5432}/mydb
instead of
jdbc:postgresql://localhost:9876/mydb
So, for some reason, the ${db-port:5432} is not resolved (yet?) when going via this route (ConfigurableEnvironment).
How can this be fixed? Or is there a better way to get all properties under a certain root without having to enumerate/hardcode them into the code?
Please note that in the default scenario, the ${db-port:5432} in property database.dataSource.url=jdbc:postgresql://localhost:${db-port:5432}/mydb is correctly resolved. I tested this by defining the following member and logging it:
#Value("${database.dataSource.url}")
final String url; // holds jdbc:postgresql://localhost:9876/mydb
You should read the property values from real environment only. Then only you will get actual or effective value of a property.
This will require a little change in your code.
change this line:
p.put(k.substring(key.length()), s.getProperty(k));
to this:
p.put(k.substring(key.length()), environment.getProperty(k));

Spring xml ...loading variable conditionally

My problem is as follows.
In my spring configuration, I would like to read a JVM property. Now based on the JVM property I want to pick a username value defined in some runtime property.
For example,
I have a JVM property defined instanceId. It can have primary or secondary string values.
Also, I have two runtime properties.
PrimaryAccount=123
SecondaryAccount=456
Now based on the jvm property value.
// pseudo code
if instanceId = primary
Bean ABC should be passed 123 in its constructor argument
if instanceId = secondary
Bean ABC should be passed 456 in its constructor argument
I am trying this
<constructor-arg>
<value>#{ systemProperties['newsAppIndexDataNode'].equals('primary') ? ${instance_primary} : ${instance_secondary} }</value>
</constructor-arg>
but I am getting error
Field or property '123' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext'
Use the following Java Configuration
#Configuration
public class SomeConfig {
#Autowired
private Environment environment;
#Bean
public YourBeanType yourBeanType() {
final String jvmProperty = environment.getProperty("instanceId");
if(jvmProperty.equals("primary")) {
return new YourBeanType(123);
}
else if(jvmProperty.equals("secondary")) {
return new YourBeanType(456);
}
return new YourBeanType(-1); //return whatever is meaningfull here, or throw an exception
}
}
EDIT
The XML configuration would probably look like this (not exactly equivalent since id doesn't check for 'secondary'):
<bean class="YourBean">
<constructor-arg index="0" value="#{systemProperties['instanceId'].equals('primary') ? '456' : '123' }">
</bean>

Looping through all the properties in a file, with spring and java

Normally i would populate a field using annotations when I knew the property name like so :
#Value("${myproperties.myValue}")
private String myString
However I now want to loop through all the properties in a file, when their names are unknown, and store both there value and name. What's the best way with spring and java ?
Actually if you need only to read properties from a file and not to use these properties in Spring's property placeholders, then the solution is simple
public class Test1 {
#Autowired
Properties props;
public void printProps() {
for(Entry<Object, Object> e : props.entrySet()) {
System.out.println(e);
}
}
...
<util:properties id="props" location="/spring.properties" />
The #Value mechanism works through the PropertyPlaceholderConfigurer which is in turn a BeanFactoryPostProcessor. The properties used by it are not exposed at runtime. See this previous answer of mine for a possible solution.
I could not find a simpler solution than this
class PropertyPlaceholder extends PropertyPlaceholderConfigurer {
Properties props;
#Override
protected Properties mergeProperties() throws IOException {
props = super.mergeProperties();
return props;
}
}
public class Test1 {
#Autowired
PropertyPlaceholder pph;
public void printProps() {
for(Entry<Object, Object> e : pph.props.entrySet()) {
System.out.println(e);
}
}
...
...
<bean class="test.PropertyPlaceholder">
<property name="locations">
<value>/app.properties</value>
</property>
</bean>
Using enumeration to loop through Properties

Using properties defined by <context:property-placeholder> in a factory method

I have written a factory bean that creates a cache manager based on the properties that are configured in a application specific properties file.
The concept is that multiple implementations can be chosen, each using other configuration properties.
For example:
noop cache, without parameters,
ehcache with #max objects
memcache with multiple ips and ports configured.
I think it is nice to not specify all cache-application specific parameters in the application-context.xml, but read them from the existing properties sources.
My attempt was using a EnvironementAware interface to get access to the Environement. But it seems that the property file that is configured using <context:property-placeholder> is not contained in the PropertiesSources.
example.properties
cache.implementation=memcached
cache.memcached.servers=server1:11211,server2:11211
application-context.xml
<context:property-placeholder location="example.properties"/>
<bean id="cacheManager" class="com.example.CacheManagerFactory"/>
In CacheManagerFactory.java
public class CacheManagerFactory implements FactoryBean<CacheManager>, EnvironmentAware {
private Environement env;
#Override
public CacheManager getObject() throws Exception {
String impl = env.getRequiredProperty("cache.implementation"); // this fails
//Do something based on impl, which requires more properties.
}
#Override
public void setEnvironment(Environment env) {
this.env = env;
}
#Override
public Class<?> getObjectType() {
return CacheManager.class;
}
#Override
public boolean isSingleton() {
return true;
}
}
In config file like this :
<context:property-placeholder location="classpath:your.properties" ignore-unresolvable="true"/>
...
<property name="email" value="${property1.email}"/>
<!-- or -->
<property name="email">
<value>${property1.email}</value>
</property>
or in code :
#Value("${cities}")
private String cities;
where the your.properties contains this :
cities = my test string
property1.email = answer#stackvoerflow.com

Categories