Monday, October 25, 2010

Specify Database Connection via Maven Resource Filtering

This is an example usage of Maven's resource filtering facility. While Paolo Predonzani's excellent article got me pointed in the right direction, it didn't quite work out for me. Here is what I did to get what I needed.

My problem is pretty much the same as Paolo's - I need to specify different database connections depending on the environment. Paolo used explicit Maven profiles to itemize the available connections, but in my case, I additionally wanted to provide some extensibility by supporting on-the-fly provision of database user/password. To simplify things, I'll stick simply with the user and password parts of the database connection, but the same principle can be applied to the URL, runtime parameters, etc.

First, I wanted to log some feedback at runtime reflecting just what the database connection is. While a previous post of mine offers one mechanism for this, here's another one, using a Spring configuration that reads database info from a properties file, creates a datasource and feeds that datasource as an argument to a Java object constructor. The properties file is simple:


user=Connor
password=Caillou

Yes, I have a 4-year old named Connor who watches the Caillou cartoon show. He doesn't know how to hack my database, yet.

That properties file, as you can see, has hardwired the credentials for now - this is what we're looking to change. The properties are fed to a Spring configuration, which uses the information to create an Oracle-based datasource:


<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:database.properties"/>
</bean>

<bean id="datasource" class="oracle.jdbc.pool.OracleDataSource">
<property name="URL" value="jdbc:oracle:thin:@//mydb:1521/uberdb"/>
<property name="user" value="${user}"/>
<property name="password" value="${password}"/>
</bean>

<bean class="com.mybiz.datasource.oracle.Config">
<constructor-arg ref="datasource"/>
</bean>

The Java class exists only to log the information:


public class Config {

private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(getClass());

public Config(oracle.jdbc.pool.OracleDataSource dataSource) throws Exception
{
String dsName = dataSource.getDataSourceName();
String user = dataSource.getUser();
String url = dataSource.getURL();
log.info("\n\n================> Data source: {} for user {} ({})\n",
new Object[]{dsName, user, url});
return;
}
}

When my process starts up, this will faithfully log just where I'm pointing. Without doing anything else, of course, it will point to the default connection. What I want to do is alter the properties file to point to a different connection for each developer; up to now, developers have been editing the deployed properties file before runtime to make this change. This must be done after each build-deploy cycle - cumbersome, at best.

Now I'll provide a build time mechanism to do this with much less effort. That mechanism will consist of a simple alteration to the properties file to support build-time binding, and some Maven profiles that use resource filtering to determine that binding. The new properties file looks like this, replacing the hardwired credentials with some variables:


user=${dbuser}
password=${dbpassword}

I'll specify inclusion of the Maven resources plugin - and note that one thing I found is that this didn't work out as expected with version 2.4.3; I had to use version 2.3:


<build>
<pluginManagement>
....
<plugins>
....
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<!-- using version 2.4.3 appears to compromise resource filtering -->
<version>2.3</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
....
</plugins>
</pluginManagement>
</build>

That configuration is typically done in a parent POM so it can be reused as needed across the project. In either event, I next configure a usage of that plugin for a particular module (this is the part where my approach differed from Paolo's - I needed to supply an execution to get it working):


<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>update-variables</id>
<phase>compile</phase>
<goals>
<goal>resources</goal>
</goals>
<configuration>
<outputDirectory>${project.basedir}/target/dist/config</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>

<resources>
<resource>
<directory>src/main/dist/config</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>

That execution performs resource filtering on files found in ./src/main/dist/config, which just happens to be where my properties file is - and puts the output in ./target/dist/config, which just happens to be where my application will look for the properties at runtime. Finally, I supply the target values to replace the variables with various profiles - these can be used by each developer at build time to specify the database connection credentials:


<profiles>
<!--
Default credentials for database access.
-->
<profile>
<id>prod</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<dbuser>Connor</dbuser>
<dbpassword>Caillou</dbpassword>
</properties>
</profile>
<!--
Sets database credentials for access to Joe's database schema. Activated
as usual for a profile: -PJoe
-->
<profile>
<id>Joe</id>
<properties>
<dbuser>Joe</dbuser>
<dbpassword>Joe</dbpassword>
</properties>
</profile>
<!--
Sets database credentials for access to Sue's database schema. Activated
as usual for a profile: -PSue
-->
<profile>
<id>Sue</id>
<properties>
<dbuser>Sue</dbuser>
<dbpassword>Sue</dbpassword>
</properties>
</profile>
</profiles>

Joe builds an application that connects to his database using this command:


mvn -PJoe clean install

This creates an output properties file like this:


user=Joe
password=Joe

Likewise, Sue. The password strength here is not all that solid, but this is a dev environment, so we keep things simple.

And finally, to provide a general database credentials build mechanism, it turns out that there's nothing much extra that I need to do - since I now have resource filtering in play, if I simply pass the appropriate system properties on the command line, Maven will apply them automagically. Now any new developer (or tester...or installer...etc.) can build an application that connects to previously unknown database schemas without needing to add explicit new profiles:


mvn -Ddbuser=NewGuy -Ddbpassword=NewGuyPassword install

...resulting in an output properties file like this:


user=NewGuy
password=NewGuyPassword

Building without either of the properties, and without any of the other profiles, results in a build with default user and password.

No comments:

Post a Comment