I have a spring based web application and in my application context xml file, I have defined a bean which has all the parameters to connect to database. As part of this bean, for one of the parameters, I have a password key, as shown in the below example and I wanted the value should come from a /vault/password file. This /vault/password is not part of the project/application. This /vault/password will be there in host machine by default.
What is the syntax in applicationContext.xml bean definition, to read a value from a file outside of application context.
<bean class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" id="dataSource">
<property name="url" value="jdbc:postgresql://postgres:5432/" />
<property name="username" value="postgres" />
<property name="password" value="/vault/password" />
</bean>
Something like this is probably your best bet:
How to correctly override BasicDataSource for Spring and Hibernate
PROBLEM:
Now I need to provide custom data source based on server environment
(not config), for which I need to calculate driverClassName and url
fields based on some condition.
SOLUTION:
Create a factory (since you need to customize only the creation phase
of the object, you don't need to control the whole lifetime of it).
public class MyDataSourceFactory {
public DataSource createDataSource() {
BasicDataSource target = new BasicDataSource();
if (condition) {
target.setDriverClassName("com.mysql.jdbc.Driver");
target.setUrl("jdbc:mysql://localhost/test?relaxAutoCommit=true");
} else { ... }
return target;
}
}
In your case, your customization would do some I/O to set target.password.
Related
I am working in a project using Spring, Spring Data JPA, Spring Security, Primefaces...
I was following this tutorial about dynamic datasource routing with spring.
In this tutorial, you can only achieve dynamic datasource switching between a pre-defined datasources.
Here is a snippet of my code :
springContext-jpa.xml
<bean id="dsCgWeb1" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName.Cargest_web}"></property>
<property name="url" value="${jdbc.url.Cargest_web}"></property>
<property name="username" value="${jdbc.username.Cargest_web}"></property>
<property name="password" value="${jdbc.password.Cargest_web}"></property>
</bean>
<bean id="dsCgWeb2" class="org.apache.commons.dbcp.BasicDataSource">
// same properties, different values ..
</bean>
<!-- Generic Datasource [Default : dsCargestWeb1] -->
<bean id="dsCgWeb" class="com.cargest.custom.CargestRoutingDataSource">
<property name="targetDataSources">
<map>
<entry key="1" value-ref="dsCgWeb1" />
<entry key="2" value-ref="dsCgWeb2" />
</map>
</property>
<property name="defaultTargetDataSource" ref="dsCgWeb1" />
</bean>
What i want to do is to make the targetDataSources map dynamic same as its elements too.
In other words, i want to fetch a certain database table, use properties stored in that table to create my datasources then put them in a map like targetDataSources.
Is there a way to do this ?
Nothing in AbstractRoutingDataSource forces you to use a static map of DataSourceS. It is up to you to contruct a bean implementing Map<Object, Object>, where key is what you use to select the DataSource, and value is a DataSource or (by default) a String referencing a JNDI defined data source. You can even modify it dynamically since, as the map is stored in memory, AbstractRoutingDataSource does no caching.
I have no full example code. But here is what I can imagine. In a web application, you have one database per client, all with same structure - ok, it would be a strange design, say it is just for the example. At login time, the application creates the datasource for the client and stores it in a map indexed by sessionId - The map is a bean in root context named dataSources
#Autowired
#Qualifier("dataSources");
Map<String, DataSource> sources;
// I assume url, user and password have been found from connected user
// I use DriverManagerDataSource for the example because it is simple to setup
DataSource dataSource = new DriverManagerDataSource(url, user, password);
sources.put(request.getSession.getId(), dataSource);
You also need a session listener to cleanup dataSources in its destroy method
#Autowired
#Qualifier("dataSources");
Map<String, DataSource> sources;
public void sessionDestroyed(HttpSessionEvent se) {
// eventually cleanup the DataSource if appropriate (nothing to do for DriverManagerDataSource ...)
sources.remove(se.getSession.getId());
}
The routing datasource could be like :
public class SessionRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
return request.getSession().getId();
}
#Autowired
#Qualifier("dataSources")
public void setDataSources(Map<String, DataSource> dataSources) {
setTargetDataSources(dataSources);
}
I have not tested anything because it would be a lot of work to setting the different database, but I thing that it should be Ok. In real world there would not be a different data source per session but one per user with a count of session per user but as I said it is an over simplified example.
The datasource used by a thread might change from time to time.
Should pay attention to concurrency, applications might get concurrency issues in concurrent environment.
thread-bound AbstractRoutingDataSource sample
It can be achieved with AbstractRoutingDataSource and keeping the information in the thread-local Variable. Here is a beautiful working example you can refer to:
Multi-tenancy: Managing multiple datasources with Spring Data JPA
I have an xml bean configuration as follows:
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
<property name="dataSource" ref="dataSource" />
<property name="jpaDialect" ref="openJPADialect" />
</bean>
I want to add a new property defaultTimeout, however I don't want to hard code it.
I want to instead put some class that will retrieve the value of this property from some in memory cache (doesn't matter from where actually)
I've heard and used before - org.springframework.beans.factory.config.PropertyPlaceholderConfigurer However it retrieves values from a property file, which is not exactly what I need.
Could you please advise my direction?
I want to put instead of this property some class that will retrieve value of this property from some in memory cache (doesn't matter from where actually)
How about injecting your txManager into this some class and set the defaultTimeout there?
Try looking into the com.typesafe.config library https://www.javadoc.io/doc/com.typesafe/config/1.2.1. This allows you to load configuration files.
Use this library to create a bean of type config. Something like this. This is a java configuration, but could be adapted to an XML implementation.
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.your.package")
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Bean
public Config properties() throws Exception {
String path = ""; // path to properties file
Config conf = ConfigFactory.parseFile(new File(path));
return conf;
}
}
Then in your component classes, you can autowire the bean and use the properties stored in the in memory bean.
#Autowired
private Config properties;
...
properties.getString("your property key");
Currently,I am storing database details in a property file and then creating an datasource using
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>${driverClassName}</value>
</property>
<property name="url">
<value>${url}</value>
</property>
<property name="username">
<value>${username}</value>
</property>
<property name="password">
<value>${password}</value>
</property>
</bean>
My Client asked us to place a config database and this DB will store all the i18keys and the main database values.
So I need to create one two datasources one is for Configs and other is the main database.
I can create the config data sources using the same. But How I can create an second datasource as all the database details are stored in config database.
can you pointers will be very helpful.
You might take a look into the Java-configuration for Spring. You can combine that with your current XML-configuration using <context:component-scan base-package="..."/>.
The general approach would be to configure the first datasource for configuration (like in your current setup) using XML. The XML should also refer to a 'configuration class'.
That is a special class, annotated with #Configuration, which gets the first datasource injected (or maybe some DAO), and then defines a method like so:
#Bean
public DataSource secondDataSource() {
// Construct the second datasource using the configuration
// retrieved from the first datasource.
return new BasicDataSource();
}
Note that you might want to add a qualifier to either (or even both) datasources so you can distinguish between the two datasources when you want to have them injected into other beans using #Injector #Autowired.
I'm using Spring with DBCP and need to refresh my datasource when some configuration on operation environment changes, without restart all application.
If I do it with no use of DBCP, I force this refresh closing current opened datasource in use and Start a new instance of DataSource.
Using DBCP+Spring, I can't do that.
Somebody knows if it is possible?
I don't think there is such a support in plain DBCP, mostly because database connection properties are very rarely changing during the lifetime of the application. Also you will have to consider transition time, when some connections served by the old data source are still opened while others are already served from the new (refreshed) one.
Decorator/proxy approach
I would suggest you to write custom implementation of DataSource leveraging Decorator/Proxy design pattern. Your implementation would simply call target data source (created by DBCP), most of the time doing nothing more. But when you call some sort of refresh() method, your decorator will close previously created data source and create new one with fresh configuration. Remember about multi-threading!
#Service
public class RefreshableDataSource implements DataSource {
private AtomicReference<DataSource> target = new AtomicReference<DataSource>();
#PostConstruct
public void refresh() {
target.set(createDsManuallyUsingSomeExternalConfigurationSource());
}
#Override
public Connection getConnection() throws SQLException {
return target.get().getConnection();
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
return target.get().getConnection(username, password);
}
//Rest of DataSource methods
}
The createDsManuallyUsingSomeExternalConfigurationSource() method might look like this:
private DataSource createDsManuallyUsingSomeExternalConfigurationSource() {
DataSource ds = new org.apache.commons.dbcp.BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl(/*New database URL*/);
ds.setUsername(/*New username*/);
ds.setPassword(/*New password*/);
return ds;
}
This is a rough equivalent of Spring bean:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
You can't just inject such a target bean into your proxy/decorator RefreshableDataSource as you want data source configuration to be dynamic/refreshable, while Spring only allows you to inject static properties. This means that it is your responsibility to create an instance of target BasicDataSource, but as you can see, it is nothing scary.
Actually, I have a second thought: Spring SpEL AFAIK allows you to call other beans' methods from XML configuration. But this is a very wide topic.
JNDI approach
Another approach might be to use JNDI to fetch DataSource and use hot-deployment (it works with JBoss and its *-ds.xml files.
I have a spring context xml file with this
<context:property-placeholder location="classpath:cacheConfig.properties"/>
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="cacheManagerName" value="cacheName"/>
<property name="shared" value="false"/>
<property name="configLocation" value="classpath:cacheConfig.xml"/>
</bean>
the goal is to allow the customer to edit the properties file, like this
cache.maxMemoryElements="2000"
and then in the actual cacheConfig.xml file have this
<cache name="someCacheName"
maxElementsInMemory="${cache.maxMemoryElements}" ... />
so that items we do not want the customer to change are not exposed. Of course the above details are only partially detailed and NOT working. Currently I see this in the log file
Invocation of init method failed; nested exception is net.sf.ehcache.CacheException: Error configuring from input stream. Initial cause was null:149: Could not set attribute "maxElementsInMemory".
Thanks in advance...
Your example uses EhCacheManagerFactoryBean to expose a reference to the CacheManager, with caches defined in the external cacheConfig.xml file. As #ChssPly76 pointed out, Spring's property resolver only works within Spring's own bean definition files.
However, you don't have to define the individual caches in the external file, you can define them right within the Spring bean definition file, using EhCacheFactoryBean:
FactoryBean that creates a named
EHCache Cache instance... If the
specified named cache is not
configured in the cache configuration
descriptor, this FactoryBean will
construct an instance of a Cache with
the provided name and the specified
cache properties and add it to the
CacheManager for later retrieval.
In other words, if you use EhCacheFactoryBean to refer to a named cache that isn't already defined in cacheConfig.xml, then Spring will create and configure a new cache instance and register it with the CacheManager at runtime. That includes specifying things like maxElementsInMemory, and because this would be specified in the Spring bean definition file, you get full support of the property resolver:
<context:property-placeholder location="classpath:cacheConfig.properties"/>
<bean id="myCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager"/>
<property name="maxElementsInMemory" value="${cache.maxMemoryElements}"/>
</bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="shared" value="false"/>
<property name="configLocation" value="classpath:cacheConfig.xml"/>
</bean>
This is not how PropertyPlaceholderConfigurer works. It can be used to replace values within context, but not within arbitrary external files. And cacheConfig.xml is an external file - it's just being passed by Spring to EH Cache.
If you are using Maven or Ant, both offer the ability to do filtering of tokens in resource files.
For Maven, you could do something like
<cache name="someCacheName"
maxElementsInMemory="${cache.maxMemoryElements}" ... />
And in a filter file, or in the POM itself, have
cache.maxMemoryElements = 200
Resource Filtering in Maven: The Definitive Guide
With Ant, you do this with FilterSets and the <copy> task.
For anyone who needs to modify the diskstore path which cannot be set as the ehcache javadoc states that the diskstore parameter is ignored, you could create your own implementation of a EhCacheManagerFactoryBean, which allows you to inject the diskstore path; you basically need to intercept the creation of the CacheManager and modify the configuration passed along using your diskstore property, e.g.:
private String diskStorePath;
...getter/setter
public void afterPropertiesSet() throws IOException, CacheException {
if (this.shared) {
// Shared CacheManager singleton at the VM level.
if (this.configLocation != null) {
this.cacheManager = CacheManager.create(this.createConfig());
}
else {
this.cacheManager = CacheManager.create();
}
}
else {
// Independent CacheManager instance (the default).
if (this.configLocation != null) {
this.cacheManager = new CacheManager(this.createConfig());
}
else {
this.cacheManager = new CacheManager();
}
}
if (this.cacheManagerName != null) {
this.cacheManager.setName(this.cacheManagerName);
}
}
private Configuration createConfig() throws CacheException, IOException {
Configuration config = ConfigurationFactory.parseConfiguration(this.configLocation.getInputStream());
DiskStoreConfiguration diskStoreConfiguration = config.getDiskStoreConfiguration();
if (diskStoreConfiguration == null) {
DiskStoreConfiguration diskStoreConfigurationParameter = new DiskStoreConfiguration();
diskStoreConfigurationParameter.setPath(getDiskStorePath());
config.addDiskStore(diskStoreConfigurationParameter);
} else {
diskStoreConfiguration.setPath(getDiskStorePath());
}
return config;
}
The Spring config then looks like this:
<bean id="cacheManager" class="com.yourcompany.package.MyEhCacheManagerFactoryBean" depends-on="placeholderConfig">
<property name="diskStorePath" value="${diskstore.path}"/>
<property name="configLocation" value="classpath:ehcache.xml" />
</bean>