How do include a property at runtime in a spring context? - java

How do I add properties at runtime and use them to populate placeholders in my context files?
First, a little background: I have a batch data loader which loads a database. I have a requirement that the database password is not stored on disk for this program, so I allow the user to enter it interactively.
I am using hibernate, and my datasource is configured in a context file, with the actual parameters in another properties file.
Some attributes of what I'm looking for
The property is available only at runtime (user entered password)
It can be provided before the context loads
It doesn't need to dynamically change
Other related properties are still loaded via a normal properties file
db.xml:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${hibernate.connection.driver_class}" />
<property name="url" value="${hibernate.connection.url}" />
<property name="username" value="${hibernate.connection.username}" />
<property name="password" value="${hibernate.connection.password}" />
and so on...
</bean>
database.properties:
hibernate.connection.username=Username
### hibernate.connection.password= #don't want the password stored
hibernate.connection.url=<the url>
hibernate.connection.driver_class=oracle.jdbc.driver.OracleDriver
And my main context.xml:
<import resource="classpath:db.xml" />
<context:property-placeholder location="classpath:database.properties" />
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:database.properties" />
</bean>
plus more...
What I want to do is add a new property (hibernate.connection.password) to the context before refreshing it at runtime so that the corresponding value is replaced in db.xml.
My current attempt looks like this
Properties prop = new Properties();
prop.setProperty("hibernate.connection.password", thePassword);
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setProperties(prop);
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"my-context.xml"}, false); // Don't refresh after loading
ctx.addBeanFactoryPostProcessor(ppc);
ctx.refresh();
I must be doing something wrong, however, since I get an exception that tells me the properties from my database.properties file are not being used.
2016-03-14 14:58:41 WARN ClassPathXmlApplicationContext:546 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'dataSource' defined in class path resource [db.xml]: Could not resolve placeholder 'hibernate.connection.driver_class' in string value "${hibernate.connection.driver_class}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'hibernate.connection.driver_class' in string value "${hibernate.connection.driver_class}"
To be sure my setup for the database.properties file is correct, if I remove the call to ctx.addBeanFactoryPostProcessor(ppc), I instead get an exception complaining about the missing password
2016-03-14 16:52:10 WARN ClassPathXmlApplicationContext:546 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'dataSource' defined in class path resource [db.xml]: Could not resolve placeholder 'hibernate.connection.password' in string value "${hibernate.connection.password}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'hibernate.connection.password' in string value "${hibernate.connection.password}"
Error: Invalid bean definition with name 'dataSource' defined in class path resource [db.xml]: Could not resolve placeholder 'hibernate.connection.password' in string value "${hibernate.connection.password}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'hibernate.connection.password' in string value "${hibernate.connection.password}"
And if the password is present in the properties file, everything works fine.
My question then is how do I cause both the database.properties and the runtime-defined property to post-process my context files?
Edit
One solution I have found is to manually load the properties file into the PropertyPlaceholderConfigurer.
ppc.setLocation(new ClassPathResource("database.properties"));
This works, however the property set programmatically will be overridden by any matching property in the properties file, which makes it feel like a workaround. Also, I still don't understand why both PropertyPlaceholderConfigurers don't get used (the one defined in the context file and the one defined in java)

In order for Spring to read the properties from database.properties you'll have to define a bean of Properties Placeholder like below:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>database.properties</value>
</property>
</bean>
Then you can use it in your dataSource bean as follows:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${hibernate.connection.driver_class}" />
<property name="url" value="${hibernate.connection.url}" />
<property name="username" value="${hibernate.connection.username}" />
<property name="password" value="${hibernate.connection.password}" />
and so on...
</bean>
If you want to pass the password at run-time then you can pass it as an argument as follows:
-Dhibernate.connection.password=<password>

Related

Spring Data Context - lookup placeholder variables from Java map, not properties file

Currently in my data context XML file, it's looking up values to substitute from a application.properties file:
<bean id="applicationProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" lazy-init="default">
<property name="location" value="classpath:application.properties" />
</bean>
<bean id="appleDataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="url" value="${apple.url}" />
</bean>
I'd like to change this from being looked up from the application.properties file, to being read out of a Properties/Map object.
This is because the configuration could come from more than one place (i.e. not just the classpath), and this logic is already implemented in my application.
I've seen MapPropertySource but I think while that can be done when the bean is configured in Java, I'm not sure this logic can be implemented when working with the XML file alone?

Using jdbc properties file to set dataSource in applicatioinContext.xml.Why I must use ${jdbc.XXX} but not ${XXX}?

I am training to integrate the Struts2 and Spring and Hibernate.I using a properties file to set the dataSource:
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
And this is the db.properties following:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\://localhost\:3306/sp3
jdbc.username=root
jdbc.password=123456
I find the example about this in spring-reference,but I just don't know why I must use ${jdbc.XXXXX} but not ${XXXXX}.I try to write "username=root","password=123456",and then it cause "Access denied for user 'Administrator'#'localhost' (using password: YES)"
If using the Expression Language principle:${jdbc.username} meanings "getJdbc().getUsername();"because in the struts-tag,${model} means getModel(),is it right?
I find the source about PropertyPlaceholderConfigurer and ComboPooledDataSource ,but I can not find any code about getJdbc();
Thank you for your help.
I don't understand what you're asking. jdbc.username is just a text, or a key if you want. You could have used "BLABLABLA=root" in db.properties and in your xml <property name="user" value="${BLABLABLA}" />.
Probably you are confusing PropertyPlaceholderConfigurer with PropertyOverrideConfigurer.
If you had the problems with your properties, you would have got an exception something like->
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'XYZ' in string value "${XYZ}".
What do you mean by 'everything is ok'? Were you able to access DB with previous setup?
The exception you are getting seems related to username and password combination OR you don;t have proper rights/GRANTS.
jdbc.XXXXX is just a key in the property file. You could have used anything in place of it.When you perform ${something} it would just pick the value for key 'something' from the property file and use it for populating the properties of bean.
For example :
<property name="jdbcUrl" value="${jdbc.url}" /> is just setting the value for a field named 'jdbcUrl' in class ComboPooledDataSource.

Spring - how to use PropertyPlaceholderConfigurer to dynamically load files

i have the next properties files with Spring Framework
config.properties
with content
environment=devel //posible values: devel, testing, prod
and with the previous environment property, choose some of the following files to load dynamically
config-service1-devel.properties
config-service1-testing.properties
config-service1-prod.properties
config-serviceN-devel.properties
config-serviceN-testing.properties
config-serviceN-prod.properties
and then, with spring i want load the properties, i'm solve to load the first properties file but i dont understand how to use expression language to complete the values of the dependent properties.
<bean id="MainApplicationProperties"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location"
value="file://#{systemProperties['jboss.server.home.dir']}/conf/services.properties" />
<property name="placeholderPrefix" value="$mainProperty{" />
<property name="placeholderSuffix" value="}" />
</bean>
<bean id="SecondApplicationProperties"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
depends-on="MainApplicationProperties">
<property name="locations">
<list>
<value>file://#{systemProperties['jboss.server.home.dir']}/conf/serviceOne/service1-$mainProperty{environment}.properties</value>
<value>file://#{systemProperties['jboss.server.home.dir']}/conf/serviceTwo/service2-$mainProperty{environment}.properties</value>
<value>file://#{systemProperties['jboss.server.home.dir']}/conf/serviceN/serviceN-$mainProperty{environment}.properties</value>
</list>
</property>
</bean>
the error output is the next,
java.io.FileNotFoundException: /..../conf/serviceOne/service1-$mainProperty{environment}.properties (No such file or directory)
my opinion is, the value has not replaced
helpme, thanks
The problem is that when BeanFactoryPostProcessors are starting to be invoked, they are already instantiated. So even thou the first PropertyPlaceholderConfigurer modifies the bean definition of the second PropertyPlaceholderConfigurer, it has no effect as both beans have been already instantiated.

How do i get actual value instead of placeholder while loading bean?

I am getting the placeholder instead of its property value while loading the beans.
properties file
tm.web.keystore.key.password=WaheeD
tm.web.tcp.backlog=1024
tm.web.min.jetty.threads=8
tm.web.max.jetty.threads=25
appcontext.xml file
<bean class="com.intel.ssg.mconsole.core.web.WebServer" id="webServer">
<property name="port" value="${tm.web.port}" />
<property name="address" value="${tm.web.address}" />
<property name="warLocation" value="${tm.home}/mconsole.war" />
<property name="secure" value="${tm.web.secure}" />
<property name="keystoreLocation" value="${tm.web.keystore.location}" />
<property name="keystorePassword" value="WaheeD" />
<property name="keyPassword" value="${tm.web.keystore.key.password}" />
<property name="tcpBacklog" value="${tm.web.tcp.backlog}" />
<property name="minJettyThreads" value="${tm.web.min.jetty.threads}" />
<property name="maxJettyThreads" value="${tm.web.max.jetty.threads}" />
</bean>
Loading it via marshaller
try {
FileInputStream fis = new FileInputStream(getAppContextFile());
try {
return (Beans) JAXBUtil.getUnmarshaller().unmarshal(fis);
} finally {
fis.close();
}
In Beans,I am getting value as ${tm.weberver.port}for bean webServer port rathen than its exact value..suppose 8443 port.
You post a properties file that doesn't have any property for port number, a context file that uses ${tm.web.port}, and your text says that you're using ${tm.weberver.port}.
I see three possible places for your mistake. That's assuming that you are actually setting other properties. If not, see Andrey's comment.
You dont need need to expressly load beans from your ApplicationContext.xml.
Make sure the following are present:
In your ApplicationContext.xml there must be a property loader for your property file, e.g. PropertyPlaceholderConfigurer
In your application, a application context loader is required. There may one built into your current framework e.g. WebApplicationContext in DispatcherServlet for Spring MVC. If you're starting out you can use ClassPathXmlApplicationContext.

instantiating spring bean outside the container (for testing)

I have following in my applicaionContext.xml
<bean id="IbatisDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:#123.210.85.56:1522:ORCL"/>
<property name="username" value="mydb"/>
<property name="password" value="mydbpwd"/>
</bean>
<bean id="myMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:sql-map-config-oracle.xml"/>
<property name="dataSource" ref="IbatisDataSource"/>
</bean>
then in my code I have:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SqlMapClient sqlclient = (SqlMapClient) ctx.getBean("myMapClient");
doing this gives me the following error:
Error creating bean with name
'myMapClient' defined in class
path resource
[applicationContext.xml]: Invocation
of init method failed; nested
exception is
java.lang.NoClassDefFoundError:
com/iplanet/ias/admin/common/ASException
I don't understand why is it looking for that class? I am trying to do everything outside the container. So it should not even be looking for that class...but nonetheless just to make it work I tried looking for class called ASException so I could put it on the classpath but no where can I find ASException class.
Any pointers?
Images of stack trace and my compile test / run test libs
Edit
Solution:
Even though I thought everything was outside the container...there was ONE thing that was not outside the container.
Notice the property configLocation:
<property name="configLocation" value="classpath:sql-map-config-oracle.xml"/>
actual content of sql-map-config-oracle.xml is
<sqlMapConfig>
<settings enhancementEnabled="true" useStatementNamespaces="true" />
<transactionManager type="JDBC">
<dataSource type="JNDI">
<property name="DataSource" value="my/jndi/mydb" />
</dataSource>
</transactionManager>
<sqlMap resource="somemapping.xml"/>
</sqlMapConfig>
JNDI stuff does not need to be there!
sql-map-config-oracle.xml should simply be:
<sqlMapConfig>
<settings enhancementEnabled="true" useStatementNamespaces="true" />
<sqlMap resource="somemapping.xml"/>
</sqlMapConfig>
You definitely have a runtime dependency issue as #Cletus said org.springframework.orm.ibatis.SqlMapClientFactoryBean was compiled with com.iplanet.ias.admin.common.ASException but now you don't have it in your classpath -- Spring can't find it. You should Look at the source for SqlMapClientFactoryBean to see where ASException is called -- Spring should have a dist with all it's dependencies in it, you can also look in there when doing your investigation.
This class was found during compilation but not during running:
com/iplanet/ias/admin/common/ASException
So when you're running the program, it can't seem to find this class, which belongs to the Sun app or portal server that you're using. In short: it's a classpath error.

Categories

Resources