Install this theme
Configuring Spring based Web Application from database or other external data source

The most common usecase to configure Spring’s application context using external properties is a combination of a property file and a PropertyPlaceholderConfigurer bean. If you deploy your application in multiple environments (e.g. development, pre-production, production..) it is a good idea to externalize this configuration file and to bring the application to load this file from external location on startup.

This post demonstrates how to replace the property file by other external data source (especially database) and make it possible to configure a Spring based web application on startup from it.

First, let’s define a database table t_dbconf_app_props containing some dummy properties we will use in this example.

create table t_dbconf_app_props(key varchar2 (100) not null, value varchar2(1000) not null) ;

insert into t_dbconf_app_props (key, value) values ('app.name', 'dbconf');
insert into t_dbconf_app_props (key, value) values ('app.version', '0.0.1');
insert into t_dbconf_app_props (key, value) values ('app.logback.config', '<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
		<resetJUL>true</resetJUL>
	</contextListener>
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
		</encoder>
	</appender>
	<root level="info">
		<appender-ref ref="console" />
	</root>
</configuration>');

image

As you can see, there is a Logback configuration saved in the database as property too. This is just to demonstrate how to configure other libraries / frameworks not controlled by Spring on startup.

Now create an ApplicationProperties class holding our properties. As example it contains additional getLogbackConfiguration getter to access the internal app.logback.config property. Surely you can add getters for every particular  property to access them in a comfortable way from within the application.

public final class ApplicationProperties extends Properties {

	private static final long serialVersionUID = 1L;

	private static ApplicationProperties instance;

	private ApplicationProperties() {
	}

	public String getLogbackConfiguration() {
		return this.getProperty("app.logback.config");
	}

	public static final ApplicationProperties get() {
		if (instance == null)
			instance = new ApplicationProperties();

		return instance;
	}
}

Next create an abstract ServletContextListener implementation offering properties loading from some external data source:

public abstract class ApplicationPropertiesInitializer implements
		ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		final ServletContext ctx = sce.getServletContext();
		ctx.log("*************************************************************");
		ctx.log("**                    app startup                          **");
		ctx.log("*************************************************************");

		ApplicationProperties.get().putAll(loadProperties());
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		final ServletContext ctx = sce.getServletContext();
		ctx.log("*************************************************************");
		ctx.log("**                    app shutdown                         **");
		ctx.log("*************************************************************");
	}

	/**
	 * Loads properties from the desired data source (e.g. database, LDAP,...)
	 * 
	 * @return initialised properties instance
	 */
	protected abstract Properties loadProperties();

}

Now we have to write a concrete implementation providing properties loading from the above database table. The only thing we need to know within our implementation is a data source (database connection). In this post we’ll assume the name of the data source is specified by the JVM’s system property dbconf.app.ds. 

It is also possible to specify the datasource name to be environment dependent in some other way. For example you can specify the datasource name as a context parameter within the web.xml and override it with environment specific value using application server features (in case of Tomcat/JBoss see http://tomcat.apache.org/tomcat-6.0-doc/config/context.html#Context_Parameters, Glassfish supports this by providing special set-web-context-param subcommand, see http://docs.oracle.com/cd/E19226-01/820-7693/giyce/index.html)

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;

public class DatabaseAwareApplicationPropertiesInitializer extends
		ApplicationPropertiesInitializer {

	private static final String DATASOURCE_KEY = "dbconf.app.ds";

	private static final String DATASOURCE_FALLBACK_VALUE = "jdbc/dbconf-app-ds";

	private static final String LOAD_CONFIG_QUERY = "select key, value from t_dbconf_app_props";

	@Override
	protected Properties loadProperties() {
		return loadProperties(resolveDataSourceName());
	}

	/**
	 * Default implementation resolves the name of the data source from the
	 * system property. You can override this method and resolve the name from
	 * other sources (e.g. webapp context parameter, property file, environment,
	 * ...)
	 * 
	 * @return name of the data source to use
	 */
	protected String resolveDataSourceName() {
		return System.getProperty(DATASOURCE_KEY, DATASOURCE_FALLBACK_VALUE);
	}

	/**
	 * Initialises {@link Properties} instance from the given data source.
	 * 
	 * @param dataSourceName
	 *            is a name of the data source to use
	 * @return initialised properties
	 */
	private Properties loadProperties(String dataSourceName) {

		JndiDataSourceLookup lookup = new JndiDataSourceLookup();
		DataSource ds = lookup.getDataSource(dataSourceName);

		final Properties props = new Properties();
		new JdbcTemplate(ds).query(LOAD_CONFIG_QUERY, new RowCallbackHandler() {
			@Override
			public void processRow(ResultSet rs) throws SQLException {
				props.setProperty(rs.getString("key"), rs.getString("value"));
			}
		});

		// additionally we have to add the data source name itself to the properties
		props.setProperty(DATASOURCE_KEY, dataSourceName);
		return props;
	}

}

Note! If you want to use some other external properties data source such as LDAP, JNDI, HTTP, FTP,.., just write an appropriate ApplicationPropertiesInitializer implementation. 

Our listener should run on the web application’s startup, thus define it within web.xml together with other Spring stuff.

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
	<display-name>dbconf sample application</display-name>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:META-INF/applicationContext.xml</param-value>
	</context-param>
	<listener>
		<listener-class>net.javaforge.blog.dbconf.DatabaseAwareApplicationPropertiesInitializer</listener-class>
	</listener>
	<listener>
		<listener-class>net.javaforge.blog.dbconf.LogbackInitializer</listener-class>
	</listener>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
</web-app>

Note! As you can see, web.xml defines one more custom LogbackInitializer context listener. We will come back to it at the end of this post.

Last but not least is to pass the initialized ApplicationProperties instance to the Spring’s application context. For this we will make use of Spring’s Expression Language (SpEL):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:jee="http://www.springframework.org/schema/jee"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
	">
	<!-- set properties using SpEL -->
	<bean
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="properties"
			value="#{T(net.javaforge.blog.dbconf.ApplicationProperties).get()}" />
	</bean>
	<context:annotation-config />
	<context:component-scan base-package="net.javaforge.blog.dbconf.beans" />

	<!-- just an example configuration for some database stuff -->
	<jee:jndi-lookup id="dataSource" jndi-name="${dbconf.app.ds}" />
	<bean id="tx.manager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
	<tx:annotation-driven transaction-manager="tx.manager" proxy-target-class="false" />
</beans>

That’s it!

Demonstration part:

Assuming now we have a Spring bean making use of external properties and a dummy JSP page dumping bean contents to the browser:

@Named("appInfo")
public class AppInfoBean {

	@Value("${app.name}")
	private String appName;

	@Value("${app.version}")
	private String appVersion;

	public String getAppName() {
		return this.appName;
	}

	public String getAppVersion() {
		return this.appVersion;
	}
}
<%@page import="net.javaforge.blog.dbconf.beans.AppInfoBean"%>
<%@page import="org.springframework.web.context.WebApplicationContext"%>
<%@ page
	import="org.springframework.web.context.support.WebApplicationContextUtils"%>
<%
	WebApplicationContext ctx = WebApplicationContextUtils
			.getRequiredWebApplicationContext(request.getSession().getServletContext());
	AppInfoBean bean = (AppInfoBean) ctx.getBean("appInfo");
%>
<!DOCTYPE unspecified PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>dbconf example page</title>
<style type="text/css">
	body {
		font-family: verdana, sans-serif;
		font-size: 10px;
	}
</style>

</head>
<body>
	<h3>AppInfoBean properties loaded as values from the database:</h3>
	<ul>
		<li><strong>Application name:</strong> <%=bean.getAppName()%></li>
		<li><strong>Application version:</strong> <%=bean.getAppVersion()%></li>
	</ul>
</body>
</html>

And the resulting webpage will look like this:

image

As promised one more thing that needs to be explained, is the purpose of the LogbackInitializer within the web.xml. It is a ServletContextListener implementation that takes the Logback configuration saved within the database and configures Logback programmatically on application’s startup:

public class LogbackInitializer implements ServletContextListener {

	private static final Logger LOG = LoggerFactory.getLogger(LogbackInitializer.class);

	@Override
	public void contextInitialized(ServletContextEvent sce) {

		SLF4JBridgeHandler.install();

		String logbackConfig = ApplicationProperties.get().getLogbackConfiguration();

		if (logbackConfig == null || logbackConfig.isEmpty()) {
			sce.getServletContext()
				.log("WARNING! No logback configuration found! "
						+ "Please configure 'app.logback.config' configuration property!");
		} else {

			LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
			try {

				JoranConfigurator configurator = new JoranConfigurator();
				configurator.setContext(context);
				// Call context.reset() to clear any previous configuration,
				// e.g. default configuration. For multi-step configuration,
				// omit calling context.reset().
				context.reset();
				configurator.doConfigure(new ByteArrayInputStream(logbackConfig.getBytes()));

				StatusPrinter.printInCaseOfErrorsOrWarnings(context);
				LOG.info("Logback logger configured successfully...");

			} catch (JoranException e) {
				sce.getServletContext().log("Error configuring logback!", e);
			}
		}
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		LOG.info("Destroying logback logger context...");
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		lc.stop();
	}
}

Enjoy and check out the GitHub repository for the complete project: https://github.com/bigpuritz/javaforge-blog/tree/master/dbconf

 
Blog comments powered by Disqus