change datasource url at runtime in spring - java

I have spring application configured via annotations. Here is part of my config
#Configuration
#EnableTransactionManagement
public class JpaSpringConfiguration {
#Bean(destroyMethod = "close")
#Lazy
#Primary
public BasicDataSource dataSource(#Value("${statistics.hostname}") String statisticsHostname) {
final BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
String url = String.format("jdbc:postgresql://%s:5432/statistics-db", statisticsHostname);
dataSource.setUrl(url);
....
return dataSource;
}
#Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
final PropertyPlaceholderConfigurer placeholderConfigurer = new PropertyPlaceholderConfigurer();
placeholderConfigurer.setSystemPropertiesMode(SYSTEM_PROPERTIES_MODE_OVERRIDE);
Properties properties = new Properties();
properties.setProperty("statistics.hostname", "localhost");
placeholderConfigurer.setProperties(properties);
return placeholderConfigurer;
}
Until recently we had xml configuration
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="properties">
<props>
<prop key="statistics.hostname">localhost</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" lazy-init="true" destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://${statistics.hostname}:5432/statistics-db" />
<property name="username" value="user" />
<property name="password" value="password" />
</bean>
When user selected different server to connect to we set system property and closed application context and refreshed
System.setProperty("statistics.hostname", hostname)
applicationContext.close()
applicationContext.refresh()
This does not work when I use annotation configuration.
My questions are:
why it does not work now?
how to get rid of setting hostname via system property altogether?
EDIT: I just found out that I forgot ${} around the name of the parameter in method dataSource(). So it works now but question 2 still remains.

not sure why it doesnt work, but you may try to do couple more things:
Is closing context before refresh really needed? Try to only refresh it.
You can mark your bean as #RefreshScope ( but it requires spring cloud ) and refresh it using /refresh endpoint. That would require another endpoint to actually update your host on a bean before calling refresh.
"how to get rid of setting hostname via system property altogether?"
pass that to property file which is the way it is normally configured. If you are using spring boot, then you only have to configure:
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
...
properties. Datasource bean would be created using those values for you.

Related

JMX with spring without XML configuration, 100% annotation based?

I am using JMX in Spring application with XML configuration:
<bean id="jmxExporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=bean1" value-ref="bean1"/>
<entry key="bean:name=bean2" value-ref="bean2"/>
<entry key="bean:name=bean3" value-ref="bean3"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="*">
<bean class="com.test.listener"/>
</entry>
</map>
</property>
</bean>
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="1099" />
</bean>
<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=rmi" />
<property name="serviceUrl"
value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi" />
</bean>
I understand from various documents like instead of this XML configuration we could annotate it with #EnableMBeanExport and with #ManagedResource for the beans.
But i doubt how ConnectorServerFactoryBean gets configured with these annotations. Or is there any annotation available to configure RMI and connectorServerFactoryBean?
Also i need to know how to annotate, notificationListenerMappings configured?
P.S:
I have the code working for publisher and listener under XML configuration. I am planning to move it completely on annotation based as i do not want to disturb XML configuration already in PROD.
Edited
Found the following piece of code: planning to try it:
#Bean
public RmiRegistryFactoryBean registry() {
return new RmiRegistryFactoryBean();
}
#Bean
#DependsOn("registry")
public ConnectorServerFactoryBean connectorServer() throws MalformedObjectNameException {
ConnectorServerFactoryBean connectorServerFactoryBean = new ConnectorServerFactoryBean();
connectorServerFactoryBean.setObjectName("connector:name=rmi");
connectorServerFactoryBean.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/connector");
return connectorServerFactoryBean;
}
Edit 2:
I am proceeding on above mentioned approach and I am able to configure MBeans and able to publish notifications. But unfortunately I am stuck up with configuring NotificationListener through Annotation.
I tried adding the following:
#Bean
#DependsOn("registry")
public ConnectorServerFactoryBean connectorServer() throws MalformedObjectNameException {
ConnectorServerFactoryBean connectorServerFactoryBean = new ConnectorServerFactoryBean();
connectorServerFactoryBean.setObjectName("connector:name=rmi");
connectorServerFactoryBean.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/connector");
//TestListener is my NotificationListener class
ObjectName objectName = new ObjectName("bean:name=bean1");
connectorServerFactoryBean.getServer().addNotificationListener(objectName,
new TestListener(), null,null);
return connectorServerFactoryBean;
}
I am getting instanceNotFoundException stating bean:name=bean1 is not found. But I have configured like, #ManagedResource(objectName="bean:name=bean1") on my bean1.
Any help please on what i am missing?
#EnableMBeanExport has a server property, which reference the bean name of a server object.
see for example the test of this component, which use this server property : https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java

Camel and Activemq setup with Spring Boot

I have noticed in several examples, the common way to configure activemq with camel is with the following beans. I would like to know if Spring Boot already configures any of these beans by default. I know that if the activemq jars are on the class path a default connection factory is created, but what about everything below?
<bean id="jmsConnectionFactory"
class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<bean id="pooledConnectionFactory"
class="org.apache.activemq.pool.PooledConnectionFactory"
init-method="start" destroy-method="stop">
<property name="maxConnections" value="8"/>
<property name="connectionFactory" ref="jmsConnectionFactory"/>
</bean>
<bean id="jmsConfig"
class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="pooledConnectionFactory"/>
<property name="concurrentConsumers" value="10"/>
</bean>
<bean id="jms"
class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="configuration" ref="jmsConfig"/>
<property name="transacted" value="true"/>
<property name="cacheLevelName" value="CACHE_CONSUMER"/>
</bean>
or
#Bean
public ActiveMQConnectionFactory getConnectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(brokerURL);
return connectionFactory;
}
#Bean(initMethod = "start", destroyMethod = "stop")
public PooledConnectionFactory getPooledConnectionFactory() {
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory();
pooledConnectionFactory.setMaxConnections(maxConnections);
pooledConnectionFactory.setConnectionFactory(getConnectionFactory());
return pooledConnectionFactory;
}
#Bean
public JmsConfiguration getJmsConfiguration() {
JmsConfiguration jmsConfiguration = new JmsConfiguration();
jmsConfiguration.setConnectionFactory(getPooledConnectionFactory());
return jmsConfiguration;
}
#Bean
public JmsConfiguration getJmsHighPriorityConfiguration() {
JmsConfiguration jmsConfiguration = new JmsConfiguration();
jmsConfiguration.setConnectionFactory(getPooledConnectionFactory());
jmsConfiguration.setPriority(8);
return jmsConfiguration;
}
#Override
protected void setupCamelContext(CamelContext camelContext) throws Exception {
ActiveMQComponent activeMQComponent = new ActiveMQComponent();
activeMQComponent.setConfiguration(getJmsConfiguration());
camelContext.addComponent("activemq", activeMQComponent);
ActiveMQComponent activeMQHighPriorityComponent = new ActiveMQComponent();
activeMQHighPriorityComponent.setConfiguration(getJmsHighPriorityConfiguration());
camelContext.addComponent("activemq-high-priority", activeMQHighPriorityComponent);
}
Meanwhile there are some spring-boot-starters which can be used to have ActiveMQ and Camel running within Spring Boot.
ActiveMQ
Start with spring-boot-starter-activemq in your pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
Configuration
Have a look what's configurable through this at all - its documented in Appendix A. Common application properties (search for 'activemq' and 'jms').
Alternative approach: from my point of view, its best to determine what's configurable in Sprint Boot and what not is having a look at their auto-configuration mechanism:
ActiveMQAutoconfiguration which shows how ActiveMQ and JMS Configuration relate to each other
ActiveMQProperties determining ActiveMQ-specific properties which can be set
Camel
Apache Camel provides its own Spring Boot integration. Basically you also have to add camel-spring-boot-starter:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>2.17.3</version>
</dependency>
Configuration
I haven't found a good example configuration file, so again, have a look at configuration is exposed through CamelConfigurationProperties class.
In general - like you mentioned - you might end up in registering some of your beans manually if you don't find all properties exposed via this configuration.

Connect to Oracle DB from Spring-jdbc with Oracle Wallet authentification

I was using Spring-jdbc with org.apache.commons.dbcp.BasicDataSource using the username and password for the connection. I want to use BasicDataSource because I only have one connection.
I had this code:
<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
id="dataSource">
<property name="driverClassName" value="${database.driverClassName}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
</bean>
Now I have to use authentication based in Oracle Wallet, I don't have problem in a simple application test without Spring but I can't integrate this new authentication with Spring. Does anyone know How I can do it??
You mention "simple application test" so I'm assuming you need to configure your unit tests. In a unit test config class (for example class TestSpringWebConfig extends SpringWebConfig) this gets you an Oracle datasource using a wallet (bonus: the following uses a proxy database account):
System.setProperty("oracle.net.tns_admin", "path/to/your/tnsnames");
OracleDataSource ds = new OracleDataSource();
Properties props = new Properties();
props.put("oracle.net.wallet_location", "(source=(method=file)(method_data=(directory=path/to/your/wallet)))");
/*
Use the following only if you have a proxy user database account instead of a normal DB account
A test user's username could go here though
*/
props.put(OracleConnection.CONNECTION_PROPERTY_PROXY_CLIENT_NAME, "proxy-user-name");
ds.setConnectionProperties( props );
ds.setURL("jdbc:oracle:thin:/#dbAlias"); //dbAlias should match what's in your tnsnames
return ds;
This also assumes you have the following in your JDK:
In JAVA_HOME/jre/lib/security/java.security, add the following to the "List of providers":
security.provider.11=oracle.security.pki.OraclePKIProvider
And add the following jars from Oracle to JAVA_HOME/jre/lib/ext:
osdt_cert.jar
osdt_core.jar
oraclepki.jar
And of course, all of the above assumes the ojdbc7 jar is in your application's classpath already.

Loading Hibernate properties outside context isn't working

I'm facing some strange problem here and right now I'm stuck. I've read some other posts on this regards and some of then helped me to configure my enviroment, but for some reason that I couldn't find out why it is not working.
I need to place the hibernate.connection.url, hibernate.connection.username and hibernate.connection.password properties outside my WAR file to make easier to configure theese parameters when the system is deployed on different servers. So after digging over the Google I find this solution:
applicationContext.xml:
...
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="sessionFactory" class="br.myapp.DataBaseConfig"/>
And the class DataBaseConfig
public class DataBaseConfig extends AnnotationSessionFactoryBean {
private final BaseConfig baseConfig = new BaseConfig();
public DataBaseConfig() {
super();
}
#Override
protected SessionFactory buildSessionFactory() throws Exception {
Properties dbConfig = new Properties();
dbConfig = baseConfig.getPropertiesFile("myPropertiesFile");
Configuration config = new Configuration();
Enumeration<Object> props = dbConfig.keys();
while ( props.hasMoreElements() ){
String key = (String)props.nextElement();
config.setProperty( key , dbConfig.getProperty(key) );
}
config.addResource("hibernate.cfg.xml");
SessionFactory sessionFactory = config.buildSessionFactory();
return sessionFactory;
}
}
And my hibernate.cfg.xml file looks like:
<hibernate-configuration>
<session-factory>
<mapping class="br.myapp.model.SomeClass1" />
<mapping class="br.myapp.model.SomeClass2" />
<mapping class="br.myapp.model.SomeClass3" />
</session-factory>
</hibernate-configuration>
My myPropertiesFile is:
hibernate.connection.driver_class=oracle.jdbc.driver.OracleDriver
hibernate.connection.url=jdbc:oracle:thin:#localhost:1524:MyDB
hibernate.connection.username=user
hibernate.connection.password=passpass
hibernate.default_schema=myschema
hibernate.connection.oracle.jdbc.V8Compatible=true
hibernate.show_sql=true
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
#I'm usign c3p0 to pool connections
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=300
hibernate.c3p0.max_statements=50
hibernate.c3p0.idle_test_period=3000
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.region.factory_class=net.sf.ehcache.hibernate.EhCacheRegionFactory
With this enviroment when I'm trying to get something from the database without success on this way:
final SomeClass1 sm = repository.find(SomeClass1.class)
.where( Restrictions.eq( "someField" , varFromWebForm ) )
.uniqueResult();
But sm always returns null, and on the console there is no query printed even with hibernate.show_sql=true setted.
The strange thing is that when I change the configurations on my applicationContext.xml file like this
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="configLocation">
<value>classpath:/hibernate.cfg.xml</value>
</property>
</bean>
And the hibernate.cfg.xml file to this
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<property name="hibernate.connection.url">jdbc:oracle:thin:#localhost:1524:MyDB</property>
<property name="hibernate.connection.username">user</property>
<property name="hibernate.connection.password">passpass</property>
<property name="hibernate.default_schema">myschema</property>
<property name="hibernate.connection.oracle.jdbc.V8Compatible">true</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</property>
<mapping class="br.myapp.model.SomeClass1" />
<mapping class="br.myapp.model.SomeClass2" />
<mapping class="br.myapp.model.SomeClass3" />
</session-factory>
</hibernate-configuration>
All works fine. So it is like the code that read the properties and inject it on the Configuration to Hibernate (DataBaseConfig) is not working properly.
Can someone point me out what am I doing wrong?
UPDATE:
There is no exceptions on the console. it just doesn't work.
UPDATE 2
This is the log from the apache server. I'm using VRaptor as controllerManager and the only things that appear when I call my method to do the query is the requisition to the controller nothing else, therefore I think that it is not important to put it here.
I've try to put the log here but the SO doesn't let me because it extrapolate the characters limit for a question so, here is a link to pastebin: http://pastebin.com/cASD7EPu
Thanks.
When you say (DataBaseConfig) is not working properly : Do you have somes Exceptions and a StackTrace returned by the Spring Container. Try To launch your app in debug/trace mode to see what happen exactly.
I've managed to solve my own question by changing the way I read the configurations. The waqy I was doing does not work and I couldn't find out why. Which is like this:
#Override
protected SessionFactory buildSessionFactory() throws Exception {
Properties dbConfig = new Properties();
dbConfig = baseConfig.getPropertiesFile("myPropertiesFile");
Configuration config = new Configuration();
Enumeration<Object> props = dbConfig.keys();
while ( props.hasMoreElements() ){
String key = (String)props.nextElement();
config.setProperty( key , dbConfig.getProperty(key) );
}
config.addResource("hibernate.cfg.xml");
SessionFactory sessionFactory = config.buildSessionFactory();
return sessionFactory;
}
So I was reading the Hibernate#Configuration docs and find the method configure(File file). Then I took the hibernate.cfg.xml out of my context and put it on my configs directory (which I get from a Util class of my project) and it work fine. The final method is like:
#Override
protected SessionFactory buildSessionFactory() throws Exception {
Configuration config = new Configuration();
config.configure(
new File( BaseConfig.CONFIG_BASE
+ File.separator
+ "hibernate.cfg.xml" )
);
SessionFactory sessionFactory = config.buildSessionFactory();
return sessionFactory;
}
And now it is working fine. I would like to find out why the previous configuration did not work to understand. This is not the best solution but solved my problem.
I've a friend here that always says: "It isn't an ugly solution until someone else show a better one!". So for now I will stick with this one.
Thanks to the ones who came here and tryied to help me.
I will not mark this answer as the right one for some time to see if someone else can show me a better solution :)

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.

Categories

Resources