Overriding properties with System in Spring - java

I develop a rest WS that will run on AWS BeanStalk.
For the moment, the datasource is configured with property files:
database.driverClass=org.postgresql.Driver
database.jdbcUrl=jdbc:postgresql://localhost:5432/public
database.username=postgres
database.password=postgres
And in context.xml:
<bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
<property name="driverClass" value="${database.driverClass}" />
<property name="jdbcUrl" value="${database.jdbcUrl}"/>
....
But now, I need to have a preprod environnement, using AWS Beanstalk, that expose my system properties like RDS_HOSTNAME, RDS_PORT, RDS_DB_NAME, ...
Is there a way to keep the same system, like writing
database.jdbcUrl=jdbc:postgresql://#{RDS_HOSTNAME}:#{RDS_PORT}/#{RDS_DB_NAME}
In preprod.property?
Or to reset database.jdbcUrl with system property in context.xml?

You could do
<context:property-placeholder ignore-unresolvable="true" ignore-resource-not-found="true" location="classpath:database.properties, file:preProd.properties" />
And preProd.properties will be on the preprod machine. The keys will be the same but the values will be different. This way if preProd.properties is not found (on dev machine for example) the database.properties will be used. If the file preProd.properties is present it will override the values from database.properties.
If you are using maven you could also use maven profile and maven-replacer-plugin.

Related

How to connect OS environment variables to placeholders in application.properties in a Spring Web app project?

1) I created environment file as mentioned here
I created a file called Prod.env and entered following
SPRING_DATASOURCE_URL="jdbc:mysql://5.6.7.8:3306/ab?autoReconnect=true&characterEncoding=utf8"
SPRING_DATASOURCE_USERNAME="root"
SPRING_DATASOURCE_PASSWORD="IWin"
And then I executed this command export $(cat Prod.env | xargs)
2) Then I created application.properties under WEB-INF with following code
spring.datasource.driver-class-name:com.mysql.jdbc.Driver
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
3) Then in spring-security.xml
<b:bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<b:property name="location">
<b:value>/WEB-INF/application.properties</b:value>
</b:property>
</b:bean>
<b:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<b:property name="driverClassName" value="com.mysql.jdbc.Driver" />
<b:property name="url" value="${spring.datasource.url}" />
<b:property name="username" value="${spring.datasource.username}" />
<b:property name="password" value="${spring.datasource.password}" />
</b:bean>
Now, when I save everything and restart I get
SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'dataSource' defined in ServletContext resource [/WEB-INF/spring/appServlet/spring-security.xml]: Could not resolve placeholder 'SPRING_DATASOURCE_URL' in string value "jdbc:mysql:${SPRING_DATASOURCE_URL}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'SPRING_DATASOURCE_URL' in string value "jdbc:mysql:${SPRING_DATASOURCE_URL}"
What I'm trying to achieve is:
Access an environment variable via application.properties (or doing some configuration in spring-security.xml ) , but not with java code. What am I missing here?
I have searched several questions, but none explains it clearly. Please help
Note: Mine is not a spring boot project. It is normal Spring 4.0.3 web app.
The normal approach is that the application should not know anything about the environment type (ENV, INTP, PROD, etc.).It means, do not specify environment (stage) name, but use the same name of properties file, an put needed version of the file into the classpath. For instance, on DEV environment you put to the classpath the DEV version of application.properties, on PROD environment you put to the classpath the PROD version of application.properties.
In your case I'd suggest that you remove application.properties from WEB-INF and put it outside of WAR (if you use EAR, also outside of EAR). Put it to the classpath of your application or classpath of application server, and configure your bean as follows:
<b:bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<b:property name="location">
<b:value>classpath*:application.properties</b:value>
</b:property>
</b:bean>
Property spring.datasource.driver-class-name it is not needed in the properties file, because you have already defined it in XML directly:
<b:property name="driverClassName" value="com.mysql.jdbc.Driver" />
Other properties you can define in application.properties as follows:
spring.datasource.url="jdbc:mysql://5.6.7.8:3306/ab?autoReconnect=true&characterEncoding=utf8"
spring.datasource.username=root
spring.datasource.password=IWin
Put application.properties not into WAR, but to the classpath of your app server.

How to register Spring Cloud config server before any XML bean is declared?

I am trying to migrate a spring app who uses PropertyPlaceholderConfigurer to resolve all the XML placeholders in it's bean declarations to a spring cloud usage, I can check that the config server is contacted and responds with the respective data generated from a git repository, however, at server startup during the BeanFactoryPostProcessor registration the XML context fails to resolve the placeholders.
I assumed that by removing the bean definition:
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreUnresolvablePlaceholders" value="false" />
<property name="properties">
<bean class="org.apache.commons.configuration.ConfigurationConverter"
factory-method="getProperties">
<constructor-arg>
<ref bean="domainConfiguration" />
</constructor-arg>
</bean>
</property>
</bean>
And adding the POM dependency for config client and respective enviroment variables the placeholders should work but they dont.
Can I manually set the config server in a higher priority?
Or as an alternative, teach PropertyPlaceholderConfigurer to consume a config server?
If you are using spring-cloud-config, this should work out of the box. When spring will build/start the ApplicationContext, first it will create a bootstrap (parent) context which will happen before creating the main context. Getting the properties of the config server should happen in the bootstrap phase so that your beans which are created in the normal context should be able to get those properties.
Check out the Client Side Usage part of the documentation for an example and check out the usage of the bootstrap.properties file.
If you don't have spring-boot (it should work w/o it as well but the docs are spring-boot centric), check out this repo or this GitHub issue, you will need a ConfigServicePropertySourceLocator.

Cloud properties not read by Spring application in Cloudfoundry

I am deploying a Spring application in Cloudfoundry which needs to access mysql database. Now as per the tutorial , if the spring version is higher than 3.1, i can use profiles for the cloud and the cloud properties will be available for the application. The mysql service is registered as p-mysql in my case so my spring config looks like this
<beans profile="cloud">
<context:property-placeholder location="classpath:/app.conf" />
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
p:location="file:#{systemProperties['app.conf']}" />
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- DB connection properties -->
<property name="driverClass" value="${db_driver:oracle.jdbc.OracleDriver}" />
<property name="jdbcUrl" value="${cloud.services.p-mysql.connection.jdbcUrl}" />
<property name="user" value="${cloud.services.p-mysql.connection.username}" />
<property name="password" value="${cloud.services.p-mysql.connection.password}" />
</bean>
</beans>
I need the app.conf for other keys not related to db services. When i deploy this i get the error
OUT org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'datasource' defined in URL [jar:file:/home/vcap/app/.java-buildpack/tomcat/webapps/ROOT/WEB-INF/lib/test-server-1.3.0.BUILD-SNAPSHOT.jar!/spring-bootstrap.xml]: Could not resolve placeholder 'cloud.services.p-mysql.connection.jdbcUrl'
Is there something i missed , when i look for the env for app, i do see the properties are available for the app (cf env app_name)
What i could have missed? do i have to include
cloudfoundry-runtime in the pom which i dont have now?
or include the cloud namespace in the spring app
Any documentation that mentions cloudfoundry-runtime is obsolete. The current documentation recommends the use of Spring Cloud Connectors for Spring applications on Cloud Foundry.
You should include Spring Cloud Connectors in your project, then you could do something as simple as this:
<cloud:data-source id="datasource" service-name="mysql-service"/>
You could also create the datasource bean yourself using property placeholders if you really need more control. Additional documentation on configuring Spring Cloud Connectors via XML is here: https://github.com/spring-cloud/spring-cloud-connectors/tree/master/spring-cloud-spring-service-connector#the-cloud-namespace

jndi look up for DefaultFtpSessionFactory

I have ftp connection properties in .properties file and following code for spring bean.
<bean id="ftpConnectionFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
<property name="host" value="${ftp.host}"/>
<property name="port" value="${ftp.port}"/>
<property name="username" value="${ftp.username}"/>
<property name="password" value="${ftp.password}"/>
</bean>
Above method does work using properties file inside web app and placeholder configuration. But what I want is to keep these properties in server, let's say tomcat context.xml file.
I have spring integration which uses this factory.
<int-ftp:outbound-channel-adapter id="ftpOutbound"
channel="ftpChannel"
remote-directory="${ftp.remoteDir}"
remote-file-separator="\"
session-factory="ftpConnectionFactory"
/>
Is there a way that I can externalize these properties in server and look up using jndi. For datasource I am currently doing it. But I don't know how to do it for session factory. The reason why I want to do this is to hide the password and other details.
If Tomcat can correctly bind the object to the JNDI from context.xml, there is no difference to get access to that object from JNDI lookup as you do it for DataSource.
Show, please, how you do it for DataSource from Spring, and how you configure ftpConnectionFactory, and I'll try to help you.
You could use a PropertyPlaceholderConfigurer as follows
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:external.properties</value>
</property>
</bean>
See more examples at 5.8.2 Customizing configuration metadata with a BeanFactoryPostProcessor and Spring PropertyPlaceholderConfigurer Example

Controlling log file location via JNDI in Spring?

I have a Java web application that uses the SLF4J logging facade. To date, we use the Log4J implementation underneath (though we are considering a switch to Logback). Log4J is currently configured via a log4j.xml configuration file that is placed in the root of our classpath.
In any event, we use JNDI to configure other aspects of our application so I am very familiar with how to set that up and pull a string from JNDI into a Spring configuration file.
However, I am at a loss to figure out how to create a Log4J appender from within a Spring configuration file. Better yet, can one completely configure Log4J via Spring and skip the log4j.xml configuration file altogether? I am hoping I don't have to do this programmatically.
I found a Spring class called Log4jWebConfigurer but this requires that the WAR run exploded (don't want that if I can help it) and also that the log file resides within the web-app directory (definitely don't want that).
First get the main directory via JNDI:
<jee:jndi-lookup id="myAppHome" jndi-name="myAppHome" />
Then use that bean in a Spring Expression Language statement in the the following way:
<bean id="log4jInitialization" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list>
<value>#{ myAppHome + '/conf/log4j.xml'}</value>
</list>
</property>
</bean>

Categories