On staying DRY with Spring config

This article shows that using java to configure spring is much more flexible than
to use xml. The article gives an example of how one could proceed to setup a lot
of configuration once and for all (i.e. for all future projects).


1. Introduction


Traditionally the configuration of the spring framework was done by creating at least one – but usually more – xml files. With the advent of spring 3, users of the framework have the opportunity to write the spring configuration in java.
Here we will explore the benefits of java configuration.

1.1. Xml configuration for the application


A typical spring context file for a web application using wicket, some database and ORM framework hibernate, tied together with spring, would look like this.

Listing 1: appcontext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd">

<context:annotation-config/>
<context:component-scan base-package="dao"/>
<context:component-scan base-package="services"/>

<bean id="wicketApplication" class="web.WicketApplication"/>

<tx:annotation-driven transaction-manager="transactionManager"/>

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/tpDataSource"/>
<jee:jndi-lookup id="hibernate_dialect" jndi-name="hibernate_dialect" expected-type="java.lang.String"/>
<jee:jndi-lookup id="hibernate_format_sql" jndi-name="hibernate_format_sql" expected-type="java.lang.String"/>
<jee:jndi-lookup id="hibernate_show_sql" jndi-name="hibernate_show_sql" expected-type="java.lang.Boolean"/>
<jee:jndi-lookup id="hibernate_use_sql_comments" jndi-name="hibernate_use_sql_comments" expected-type="java.lang.String"/>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.dialect" value-ref="hibernate_dialect"/>
<entry key="hibernate.hbm2ddl.auto" value="validate"/>
<entry key="hibernate.format_sql" value-ref="hibernate_format_sql"/>
<entry key="hibernate.use_sql_comments" value-ref="hibernate_use_sql_comments"/>
</map>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" ref="hibernate_show_sql"/>
</bean>
</property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

We see that certain configuration for the database is taken from JNDI in order to be able to easily set up database configuration for several environments. JNDI entries can be configured in a servlet container xml file and/or in the web.xml. Listings 2 and 3 show the relevant parts of jetty-env.xml (a configuration for jetty servlet container) and web.xml.

Listing 2: jetty-env.xml


<?xml version="1.0" encoding="utf-8"?>
<Configure id="wapp" class="org.eclipse.jetty.webapp.WebAppContext">
<New id="tpDataSource" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg>jdbc/tpDataSource</Arg>
<Arg>
<New class="com.mchange.v2.c3p0.ComboPooledDataSource">
<Set name="driverClass">org.postgresql.Driver</Set>
<Set name="jdbcUrl">jdbc:postgresql://localhost/tp</Set>
<Set name="user">tpowner</Set>
<Set name="password">tpowner</Set>
<Set name="minPoolSize">2</Set>
<Set name="maxPoolSize">20</Set>
<Set name="maxIdleTime">3600</Set>
<Set name="idleConnectionTestPeriod">300</Set>
<Set name="automaticTestTable">jdbc_pool_check</Set>
<Set name="checkoutTimeout">20000</Set>
</New>
</Arg>
</New>

<New class="org.eclipse.jetty.plus.jndi.EnvEntry">
<Arg>hibernate_dialect</Arg>
<Arg type="java.lang.String">org.hibernate.dialect.PostgreSQLDialect</Arg>
</New>

<New class="org.eclipse.jetty.plus.jndi.EnvEntry">
<Arg>hibernate_format_sql</Arg>
<Arg type="java.lang.String">false</Arg>
</New>

<New class="org.eclipse.jetty.plus.jndi.EnvEntry">
<Arg>hibernate_show_sql</Arg>
<Arg type="java.lang.Boolean">true</Arg>
</New>

<New class="org.eclipse.jetty.plus.jndi.EnvEntry">
<Arg>hibernate_use_sql_comments</Arg>
<Arg type="java.lang.String">true</Arg>
</New>
</Configure>


Listing 3: Part of web.xml


<resource-ref>
<res-ref-name>jdbc/tpDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:appcontext.xml</param-value>
</context-param>

1.2. Xml configuration for testing


When running tests there is no web-application nor is there a servlet container, so we need a slightly different version of the spring context file.

Listing 4: testcontext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">

<context:annotation-config/>
<context:component-scan base-package="dao"/>
<context:component-scan base-package="services"/>

<tx:annotation-driven transaction-manager="transactionManager"/>

<jdbc:embedded-database id="dataSource">
<jdbc:script location="file:environments/hsqldb/init.sql"/>
</jdbc:embedded-database>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">validate</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.use_sql_comments">true</prop>
</props>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
</bean>
</property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

Our aim will be to rewrite our spring configuration in java: we will replace the files appcontext.xml and testcontext.xml by a single file called Appcontext.java.

2. Rewriting spring context configuration in java


Let’s start with the JNDI part. As noted, values to configure the entity manager factory are taken from JNDI to make it easy to configure it for different kinds of databases. This suggests that in java we want to have an interface-class to supply these values, because this makes it easy to switch between different implementations.

So the five jee:jndi-lookup elements in appcontext.xml give rise to the following interface.

Listing 5: IDatabaseVariables.java


package commons.config;

import javax.sql.DataSource;

public interface IDatabaseVariables {
DataSource getDataSource ();
String getHibernateDialect ();
String getHibernateFormatSql ();
boolean getHibernateShowSql ();
String getHibernateUseSqlComments ();
}

The interface doesn’t say anything about JNDI of course, it depends on the implementation whether to do a JNDI-lookup or not. This makes our java-configuration more flexible than its xml-counterpart, because when we do not want to use JNDI to look up these values – perhaps because sometimes there is no JNDI service available, which is the case when we run our tests – using xml-configuration we need to create another configuration file for our tests (testcontext.xml), but when using java-configuration we only need to supply our Appcontext.java with a different implementation of the interface.

Later we will see how we actually perform a JNDI-lookup.

The bean elements in appcontext.xml all describe the construction of an instance of some class, specified by the class attribute of the element. Each of these elements can be replaced by a method that creates and initialises an object of the appropriate type. To make spring treat these instances as beans, the methods returning them should be annotated with @Bean. These instances can be autowired.

The tx:annotation-driven becomes an @EnableTransactionManagement on the Appcontext class. The context: elements also translate into annotations on the Appcontext class. Below the listing of Appcontext.java that makes both appcontext.xml and testcontext.xml obsolete.

Listing 6: Appcontext.java (Initial version)


package config;

import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManagerFactory;

import org.apache.wicket.protocol.http.WebApplication;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import web.WicketApplication;

import commons.config.IDatabaseVariables;

@Configuration
@EnableTransactionManagement
@ComponentScan (basePackages = {"config", "dao", "services"})
public class Appcontext {
@Autowired
private IDatabaseVariables variables;
@Autowired
private FactoryBean <EntityManagerFactory> factoryBean;

@Bean
public FactoryBean <EntityManagerFactory> entityManagerFactory () {
Map <String, String> jpaPropertiesMap = new HashMap ();
jpaPropertiesMap.put ("hibernate.hbm2ddl.auto", "validate");
jpaPropertiesMap.put ("hibernate.dialect", variables.getHibernateDialect ());
jpaPropertiesMap.put ("hibernate.format_sql", variables.getHibernateFormatSql ());
jpaPropertiesMap.put ("hibernate.use_sql_comments", variables.getHibernateUseSqlComments ());

HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter ();
jpaVendorAdapter.setShowSql (variables.getHibernateShowSql ());

LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean ();
factoryBean.setJpaVendorAdapter (jpaVendorAdapter);
factoryBean.setJpaPropertyMap (jpaPropertiesMap);
factoryBean.setDataSource (variables.getDataSource ());
return factoryBean;
}

@Bean
public PlatformTransactionManager transactionManager () {
JpaTransactionManager transactionManager = new JpaTransactionManager ();
transactionManager.setDataSource (variables.getDataSource ());
try {
transactionManager.setEntityManagerFactory (factoryBean.getObject ());
} catch (Exception x) {
throw new RuntimeException (x);
}
return transactionManager;
}

@Bean
public WebApplication webApplication () {
return new WicketApplication ();
}
}

What remains to be done is to provide the implementations we need for IDatabaseVariables.

Before we continue, however, let’s have a look at what we have accomplished. Are we able to rewrite things a bit more, such that most of the configuration done can be used over and over again in every project in which spring and hibernate are used?

3. Towards minimal project configuration


Because we are now using java to write our configuration, we should take advantage from the fact that we are programming again – even though it’s configuration – and have great flexibility in setting up a solution. Glueing all the frameworks together that usually accompany java-web-app programming is the most dull and stupid part of it. Besides, it is a very time consuming and frustrating process indeed, provided you are not slightly modifying a copy-pasted version of some other project. Even then, you probably still need the internet to find out e.g. namespace schema urls or yet another fully qualified name of a class that might have been used already in some other project-configuration file, but which just not happend to be the one you used to copy-paste your own base configuration for the new project.

Needless to say, this situation is far from desirable.

The situation we do want to have, is one in which we do not need other example projects or the internet to start and work on a new project1. Things that we do need to figure out, are preferably done beforehand, but most importantly – only once.

4. Developing a configuration java-archive


For the simple and small configuration discussed in this article an example of the code needed to create such a reusable archive will be developed and concisely discussed.

All classes that are meant to be put into the archive will be in the package commons.config or one of its subpackages2.

4.1. Supported databases


First of all we want an easy to remember list of all of the supported databases: these names should hide uninteresting fully qualified names of classes that are hard to remember, especially when there are a lot of them. It should contain those data which we want to look up only once3.

Listing 7: DatabaseId.java


package commons.config;

import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hsqldb.jdbcDriver;
import org.postgresql.Driver;

import commons.Strings;

public enum DatabaseId {
HSQL (HSQLDialect.class.getName (),
jdbcDriver.class.getName ()),
POSTGRES (PostgreSQLDialect.class.getName (),
Driver.class.getName ());

public static DatabaseId forName (String name) {
if (Strings.isBlank (name)) {
throw new IllegalArgumentException ("No `name' specified.");
}
try {
return Enum.valueOf (DatabaseId.class, name);
} catch (Exception x) {
throw new IllegalArgumentException ("Unknown name: ".concat (name));
}
}

private final String jdbcDriver;
private final String hibernateDialect;

private DatabaseId (String hibernateDialect, String jdbcDriver) {
this.hibernateDialect = hibernateDialect;
this.jdbcDriver = jdbcDriver;
}

public String getHibernateDialect () {
return hibernateDialect;
}

public String getJdbcDriver () {
return jdbcDriver;
}
}

4.2. On data source configuration


Now let’s compare the configuration of the javax.sql.DataSource in the application context to the one in test context. The xml configuration for both is repeated here as listing 8 and listing 9.

Listing 8: DataSource configuration for the application


<New class="com.mchange.v2.c3p0.ComboPooledDataSource">
<Set name="driverClass">org.postgresql.Driver</Set>
<Set name="jdbcUrl">jdbc:postgresql://localhost/tp</Set>
<Set name="user">tpowner</Set>
<Set name="password">tpowner</Set>
<Set name="minPoolSize">2</Set>
<Set name="maxPoolSize">20</Set>
<Set name="maxIdleTime">3600</Set>
<Set name="idleConnectionTestPeriod">300</Set>
<Set name="automaticTestTable">jdbc_pool_check</Set>
<Set name="checkoutTimeout">20000</Set>
</New>


Listing 9: DataSource configuration for testing


<jdbc:embedded-database id="dataSource">
<jdbc:script location="file:environments/hsqldb/init.sql"/>
</jdbc:embedded-database>

We see that both are quite different from one another. Both are instances of different classes4 and both need different data to be initialised. For example, the Hypersonic in-memory database needs to be initialised (creating role, tables, initial data, etc.) at startup, so it needs to be supplied with an initialisation script. PostgreSQL on the other hand, can be (and is expected to be) initialised before startup, so it does not need an initialisation script, but it does need a username and password. It would be convenient to have a single java object that contains all data for intialising the data source in all circumstances. Listing 10 shows the definition of a class that can contain all of this information and that returns default values in case some of the lesser important properties have not been specified.

Listing 10: DatabaseConfigInfo.java


package commons.config;

public class DatabaseConfigInfo {
private String automaticTestTable;
private Integer checkoutTimeout;
private String databaseId;
private Integer idleConnectionTestPeriod;
private String jdbcUrl;
private Integer maxIdleTime;
private Integer maxPoolSize;
private Integer minPoolSize;
private String password;
private String sqlInitFile;
private String user;

public String getAutomaticTestTable () {
return automaticTestTable != null ? automaticTestTable : "jdbc_pool_check";
}

public int getCheckoutTimeout () {
return checkoutTimeout != null ? checkoutTimeout.intValue () : 20000;
}

public String getDatabaseId () {
return databaseId;
}

public int getIdleConnectionTestPeriod () {
return idleConnectionTestPeriod != null ? idleConnectionTestPeriod.intValue () : 300;
}

public String getJdbcUrl () {
return jdbcUrl;
}

public int getMaxIdleTime () {
return maxIdleTime != null ? maxIdleTime.intValue () : 3600;
}

public int getMaxPoolSize () {
return maxPoolSize != null ? maxPoolSize.intValue () : 20;
}

public int getMinPoolSize () {
return minPoolSize != null ? minPoolSize.intValue () : 2;
}

public String getPassword () {
return password;
}

public String getSqlInitFile () {
return sqlInitFile;
}

public String getUser () {
return user;
}

public void setAutomaticTestTable (String val) {
automaticTestTable = val;
}

public void setCheckoutTimeout (int val) {
checkoutTimeout = Integer.valueOf (val);
}

public void setDatabaseId (String val) {
databaseId = val;
}

public void setIdleConnectionTestPeriod (int val) {
idleConnectionTestPeriod = Integer.valueOf (val);
}

public void setJdbcUrl (String val) {
jdbcUrl = val;
}

public void setMaxIdleTime (int val) {
maxIdleTime = Integer.valueOf (val);
}

public void setMaxPoolSize (int val) {
maxPoolSize = Integer.valueOf (val);
}

public void setMinPoolSize (int val) {
minPoolSize = Integer.valueOf (val);
}

public void setPassword (String val) {
password = val;
}

public void setSqlInitFile (String val) {
sqlInitFile = val;
}

public void setUser (String val) {
user = val;
}
}

Now we can simplify JNDI configuration a bit.

Listing 11: DataSource configuration for Postgres


<Configure id="wapp" class="org.eclipse.jetty.webapp.WebAppContext">
<New class="org.eclipse.jetty.plus.jndi.Resource">
<Arg>databaseConfigInfo</Arg>
<Arg>
<New class="commons.config.DatabaseConfigInfo">
<Set name="databaseId">POSTGRES</Set>
<Set name="jdbcUrl">jdbc:postgresql://localhost/tp</Set>
<Set name="user">tpowner</Set>
<Set name="password">tpowner</Set>
</New>
</Arg>
</New>
</Configure>


Listing 12: DataSource configuration for HSQL


<Configure id="wapp" class="org.eclipse.jetty.webapp.WebAppContext">
<New class="org.eclipse.jetty.plus.jndi.Resource">
<Arg>databaseConfigInfo</Arg>
<Arg>
<New class="commons.config.DatabaseConfigInfo">
<Set name="databaseId">HSQL</Set>
<Set name="sqlInitFile">file:environments/hsqldb/init.sql</Set>
</New>
</Arg>
</New>
</Configure>

4.3. A basic spring configuration file


When we take another look at our Appcontext.java (Listing 6) we can see that actually only the wicket-application bean is project dependent. The rest of it can be reused in other projects. This suggests the creation of the abstract class Springcontext.java.

Listing 13: A basic spring configuration file


package commons.config;

public class DatabaseConfigInfo {
private String automaticTestTable;
private Integer checkoutTimeout;
private String databaseId;
private Integer idleConnectionTestPeriod;
private String jdbcUrl;
private Integer maxIdleTime;
private Integer maxPoolSize;
private Integer minPoolSize;
private String password;
private String sqlInitFile;
private String user;

public String getAutomaticTestTable () {
return automaticTestTable != null ? automaticTestTable : "jdbc_pool_check";
}

public int getCheckoutTimeout () {
return checkoutTimeout != null ? checkoutTimeout.intValue () : 20000;
}

public String getDatabaseId () {
return databaseId;
}

public int getIdleConnectionTestPeriod () {
return idleConnectionTestPeriod != null ? idleConnectionTestPeriod.intValue () : 300;
}

public String getJdbcUrl () {
return jdbcUrl;
}

public int getMaxIdleTime () {
return maxIdleTime != null ? maxIdleTime.intValue () : 3600;
}

public int getMaxPoolSize () {
return maxPoolSize != null ? maxPoolSize.intValue () : 20;
}

public int getMinPoolSize () {
return minPoolSize != null ? minPoolSize.intValue () : 2;
}

public String getPassword () {
return password;
}

public String getSqlInitFile () {
return sqlInitFile;
}

public String getUser () {
return user;
}

public void setAutomaticTestTable (String val) {
automaticTestTable = val;
}

public void setCheckoutTimeout (int val) {
checkoutTimeout = Integer.valueOf (val);
}

public void setDatabaseId (String val) {
databaseId = val;
}

public void setIdleConnectionTestPeriod (int val) {
idleConnectionTestPeriod = Integer.valueOf (val);
}

public void setJdbcUrl (String val) {
jdbcUrl = val;
}

public void setMaxIdleTime (int val) {
maxIdleTime = Integer.valueOf (val);
}

public void setMaxPoolSize (int val) {
maxPoolSize = Integer.valueOf (val);
}

public void setMinPoolSize (int val) {
minPoolSize = Integer.valueOf (val);
}

public void setPassword (String val) {
password = val;
}

public void setSqlInitFile (String val) {
sqlInitFile = val;
}

public void setUser (String val) {
user = val;
}
}

Now the spring configuration we need to do in our project has been reduced to the one shown in Listing 14.

Listing 14: Appcontext.java (Final version)


package config;

import org.apache.wicket.protocol.http.WebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import web.WicketApplication;

import commons.config.Springcontext;

@Configuration
@EnableTransactionManagement
@ComponentScan (basePackages = {"commons.config", "config", "dao", "services"})
public class Appcontext extends Springcontext {
@Bean
public WebApplication webApplication () {
return new WicketApplication ();
}
}

Still the only thing missing is the implementation of the IDatabaseVariables. Because this implementation is so simple, it will be given here.

Listing 15: ProductionDatabaseVariables.java


package config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import commons.config.jndi.JndiDatabaseVariables;

@Configuration
@Profile ("production")
public class ProductionDatabaseVariables extends JndiDatabaseVariables {
public ProductionDatabaseVariables () {
}
}

4.4. Adding a package for JNDI support



Listing 16: Jndi.java


package commons.config.jndi;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public final class Jndi {
private Jndi () {}

private static final Context CONTEXT;

static {
try {
CONTEXT = (Context) new InitialContext ().lookup ("java:comp/env");
} catch (NamingException x) {
throw new RuntimeException (x);
}
}

public static Object lookup (String key) {
Object value;
try {
value = CONTEXT.lookup (key);
} catch (NamingException x) {
value = null;
}
return value;
}

public static Object lookup (String key, Object defaultValue) {
Object value = lookup (key);
return value != null ? value : defaultValue;
}

public static Object lookupNotNull (String key) {
Object value = lookup (key);
if (value == null) {
throw new RuntimeException ("No Jndi-entry defined for `".concat (key).concat ("'."));
}
return value;
}
}


Listing 17: JndiDataSourceSupply.java


package commons.config.jndi;

import java.beans.PropertyVetoException;

import javax.sql.DataSource;

import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import commons.Strings;
import commons.config.DatabaseConfigInfo;
import commons.config.DatabaseId;

public final class JndiDataSourceSupply {
private final DataSource dataSource;
private final DatabaseId databaseId;

public JndiDataSourceSupply () {
DatabaseConfigInfo configInfo = (DatabaseConfigInfo) Jndi.lookupNotNull ("databaseConfigInfo");
databaseId = DatabaseId.forName (configInfo.getDatabaseId ());
switch (databaseId) {
case HSQL: {
String sqlInitFile = validateNonblankString (configInfo.getSqlInitFile (), "sqlInitFile");
dataSource = new EmbeddedDatabaseBuilder ().setName ("testdb").addScript (sqlInitFile).build ();
} break;

case POSTGRES: {
dataSource = newComboPooledDataSource (configInfo);
} break;

default: {
throw new RuntimeException ();
}
}
}

public DatabaseId getDatabaseId () {
return databaseId;
}

public DataSource getDataSource () {
return dataSource;
}

private DataSource newComboPooledDataSource (DatabaseConfigInfo configInfo) {
ComboPooledDataSource ds = new ComboPooledDataSource ();
try {
ds.setDriverClass (databaseId.getJdbcDriver ());
} catch (PropertyVetoException x) {
throw new RuntimeException (x);
}
ds.setJdbcUrl (validateNonblankString (configInfo.getJdbcUrl (), "jdbcUrl"));
ds.setUser (validateNonblankString (configInfo.getUser (), "user"));
ds.setPassword (validateNonblankString (configInfo.getPassword (), "password"));
ds.setAutomaticTestTable (configInfo.getAutomaticTestTable ());
ds.setMinPoolSize (configInfo.getMinPoolSize ());
ds.setMaxPoolSize (configInfo.getMaxPoolSize ());
ds.setMaxIdleTime (configInfo.getMaxIdleTime ());
ds.setIdleConnectionTestPeriod (configInfo.getIdleConnectionTestPeriod ());
ds.setCheckoutTimeout (configInfo.getCheckoutTimeout ());
return ds;
}

private static String validateNonblankString (String str, String name) {
if (Strings.isBlank (str)) {
throw new IllegalStateException ("No `".concat (name).concat ("' specified."));
}
return str;
}
}


Listing 18: JndiDatabaseVariables.java


package commons.config.jndi;

import javax.sql.DataSource;

import commons.config.IDatabaseVariables;

public abstract class JndiDatabaseVariables implements IDatabaseVariables {
private final DataSource dataSource;
private final String hibernateDialect;
private final String hibernateFormatSql;
private final String hibernateUseSqlComments;
private final Boolean hibernateShowSql;

protected JndiDatabaseVariables () {
JndiDataSourceSupply dssupply = new JndiDataSourceSupply ();
dataSource = dssupply.getDataSource ();
hibernateDialect = dssupply.getDatabaseId ().getHibernateDialect ();
hibernateFormatSql = (String) Jndi.lookup ("hibernate_format_sql", Boolean.FALSE.toString ());
hibernateUseSqlComments = (String) Jndi.lookup ("hibernate_use_sql_comments", Boolean.FALSE.toString ());
hibernateShowSql = (Boolean) Jndi.lookup ("hibernate_show_sql", Boolean.FALSE);
}

@Override
public DataSource getDataSource () {
return dataSource;
}

@Override
public String getHibernateDialect () {
return hibernateDialect;
}

@Override
public String getHibernateFormatSql () {
return hibernateFormatSql;
}

@Override
public boolean getHibernateShowSql () {
return hibernateShowSql.booleanValue ();
}

@Override
public String getHibernateUseSqlComments () {
return hibernateUseSqlComments;
}
}

Footnotes


1. Note that this implies to keep the amount of third party jar-dependencies as small as possible.

2. Note that the class IDatabaseVariables (Listing 5) is already in that package.

3. At the time of writing there existed an url enumerating more hibernate dialects: http://www.javabeat.net/qna/163-list-of-hibernate-sql-dialects/. For JDBC-drivers: http://www.devx.com/tips/Tip/28818.

4. The (runtime) class of the data source for testing, not directly visible from the xml-configuration, is org.springframework.jdbc.datasource.embedded.EmbeddedDatabase.

1 comment:

  1. Hello, thanks for that interesting article. I wonder if you'd have the source code somewhere ? I'm a bit puzzled by this element you use in your xml files.

    ReplyDelete