As promised, this is the second of a three part blog entry discussing the propagation of middle-tier users to the database. This post will mainly concentrate on the Java side of things. I will show how to use Spring-framework’s excellent transactional support using AOP to add an additional advice, relying on ThreadLocal to pass application user identities from the web tier all the way to the database, and using annotations on the service layer to specify the module and action.

I strongly advise downloading the linked zipped source code at the end of the post if you’re planning on seriously reviewing the code.

OK, let us get down to the gory details:
Our domain object will be the Context that will represent the propagated data.

package com.slaviks_blog.domain;
/**
* The <code>Context</code> class contains context data retrieved from the database.
* @author slavik
*
*/
public class Context
{

private final String action;
private final String module;
private final String clientIdentifier;
public Context(String action, String module, String clientIdentifier)
{
this.action = action;
this.module = module;
this.clientIdentifier = clientIdentifier;
}
public String getAction()
{
return action;
}
public String getClientIdentifier()
{
return clientIdentifier;
}
public String getModule()
{
return module;
}
public String toString()
{
StringBuilder sb = new StringBuilder(“Context – “);
sb.append(“{Action [“).append(action).append(“], Module [“).append(
module).append(“]”).append(“Client Identifier [“).append(
clientIdentifier).append(“]}”);
return sb.toString();
}
}

Next, let?s define the data layer we are going to use in our application. For simplicity, we?ll keep the transactions on this layer.

package com.slaviks_blog.dao;
import com.slaviks_blog.auth.ModuleAndAction;
import com.slaviks_blog.domain.Context;
public interface TestDao
{
@ModuleAndAction(action = “getContext”, module = “Test”)
Context getContext();
}

package com.slaviks_blog.dao.jdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.transaction.annotation.Transactional;
import com.slaviks_blog.dao.TestDao;
import com.slaviks_blog.domain.Context;
public class TestDaoJdbc extends JdbcDaoSupport implements TestDao
{
private static final String GET_CONTEXT_SQL = “SELECT sys_context(‘USERENV’,’ACTION’) action, ”
+ “sys_context(‘USERENV’,’MODULE’) module, ”
+ “sys_context(‘USERENV’,’CLIENT_IDENTIFIER’) client_identifier ”
+ “FROM dual”;
static class ContextRowMapper implements ParameterizedRowMapper<Context>
{
public Context mapRow(ResultSet rs, int rowNum) throws SQLException
{
String action = rs.getString(“action”);
String module = rs.getString(“module”);
String clientIdentifier = rs.getString(“client_identifier”);
return new Context(action, module, clientIdentifier);
}
}
@Transactional(readOnly = true)
public Context getContext()
{
return (Context) getJdbcTemplate().queryForObject(GET_CONTEXT_SQL,
new ContextRowMapper());
}
}

Aside from the standard Spring database plumbing stuff, notice that the implementation method is annotated with the Transactional annotation and the interface is annotated with our own ModuleAndAction annotation. I believe that this is the correct way, as transactions are an implementation detail while module and action are not.

Next, let?s create the annotation, the container for client identifiers and the actual class that will do our work ? the advisor to intercept our annotation and set the correct propagation on the connection.

package com.slaviks_blog.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The annotation to add metadata of action and module to methods inside
* transactions.
* @author slavik
*
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ModuleAndAction
{
public String action();
public String module();
}

package com.slaviks_blog.auth;
/**
* The <code>AuthContainer</code> will contain the actual user details
* coming from various application tiers. It will be used in the transactional
* database tier to set the client identifier in the database.
* @author slavik
*
*/
public class AuthContainer
{
/**
* The currently thread bound user – for simplicity, only a string
*/
private static ThreadLocal<String> currentUser = new ThreadLocal<String>();
public static String getCurrentUser()
{
return currentUser.get();
}
public static void setCurrentUser(String user)
{
if (user == null)
{
currentUser.remove();
}
else
{
currentUser.set(user);
}
}
}

package com.slaviks_blog.auth;
import java.sql.Connection;
import javax.sql.DataSource;
import oracle.jdbc.driver.OracleConnection;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* This ‘around’ advice will add the details from the annotation such as action and module as well
* as the user details from the ThreadLocal
* @author slavik
*
*/
public class ModuleAndActionAdvice implements MethodInterceptor
{
private DataSource dataSource;
private PlatformTransactionManager transactionManager;
public Object invoke(MethodInvocation mi) throws Throwable
{
// Make sure we are in a transaction, else throw exception
TransactionDefinition definition =
new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_MANDATORY);
TransactionStatus status = transactionManager.getTransaction(definition);
if (status.isCompleted())
{
throw new IllegalStateException(“Transaction is already completed”);
}
// Get the current connection bound to this transaction.
Connection conn = DataSourceUtils.getConnection(dataSource);
// We can always use a connection extractor if using commons-dbcp, etc.
if (conn instanceof OracleConnection)
{
// A minor hack to get the annotation on the interface.
ModuleAndAction ma = mi.getThis().getClass().getInterfaces()[0]
.getMethod(mi.getMethod().getName(),
mi.getMethod().getParameterTypes()).getAnnotation(
ModuleAndAction.class);
if (ma != null)
{
String metrics[] = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
// The details from the annotation
metrics[OracleConnection.END_TO_END_ACTION_INDEX] = ma.action();
metrics[OracleConnection.END_TO_END_MODULE_INDEX] = ma.module();
// The client identifier from the external tier (be that web or something else)
metrics[OracleConnection.END_TO_END_CLIENTID_INDEX] = AuthContainer.getCurrentUser();
// Set these metrics
((OracleConnection) conn).setEndToEndMetrics(metrics, (short) 0);
}
}
Object rc = null;
try
{
rc = mi.proceed();
}
finally
{
// Clear the metrics
if (conn instanceof OracleConnection)
{
String metrics[] = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
metrics[OracleConnection.END_TO_END_ACTION_INDEX] = null;
metrics[OracleConnection.END_TO_END_MODULE_INDEX] = null;
metrics[OracleConnection.END_TO_END_CLIENTID_INDEX] = null;
((OracleConnection) conn).setEndToEndMetrics(metrics,
Short.MIN_VALUE);
}
}
return rc;
}
public void setTransactionManager(
PlatformTransactionManager transactionManager)
{
this.transactionManager = transactionManager;
}
public void setDataSource(
DataSource dataSource)
{
this.dataSource = dataSource;
}
}

And finally, let’s tie everything with a Spring configuration file:

<?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:aop=”http://www.springframework.org/schema/aop”
xmlns:tx=”http://www.springframework.org/schema/tx”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd”>
<!–================================================================–>
<!– Datasource –>
<!–================================================================–>
<bean id=”dataSource” class=”oracle.jdbc.pool.OracleDataSource”
destroy-method=”close”>
<property name=”URL”
value=”jdbc:oracle:thin:@192.168.17.128:1521:slavdev” />
<property name=”user” value=”scott” />
<property name=”password” value=”tiger” />
<property name=”connectionCachingEnabled” value=”true” />
</bean>
<!–================================================================–>
<!– Transaction Manager for a datasource –>
<!–================================================================–>
<bean id=”transactionManager”
class=”org.springframework.jdbc.datasource.DataSourceTransactionManager”>
<property name=”dataSource” ref=”dataSource” />
</bean>
<tx:annotation-driven transaction-manager=”transactionManager” />
<bean id=”contextAdvice”
class=”com.slaviks_blog.auth.ModuleAndActionAdvice”>
<property name=”dataSource” ref=”dataSource” />
<property name=”transactionManager” ref=”transactionManager” />
</bean>
<bean id=”contextPointcut”
class=”org.springframework.aop.support.annotation.AnnotationMatchingPointcut”>
<constructor-arg type=”java.lang.Class” value=”” />
<constructor-arg type=”java.lang.Class” value=”com.slaviks_blog.auth.ModuleAndAction” />
</bean>
<bean id=”contextAdvisor”
class=”org.springframework.aop.support.DefaultPointcutAdvisor”>
<property name=”advice” ref=”contextAdvice” />
<property name=”pointcut” ref=”contextPointcut” />
</bean>
<!–================================================================–>
<!– Data Access Objects (DAOs) –>
<!–================================================================–>
<bean id=”testDao” class=”com.slaviks_blog.dao.jdbc.TestDaoJdbc”>
<property name=”dataSource”>
<ref local=”dataSource”></ref>
</property>
</bean>
</beans>

The last thing we should do is to provide a simple unit test to test everything we’ve done so far.

package com.slaviks_blog.dao.jdbc;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import com.slaviks_blog.auth.AuthContainer;
import com.slaviks_blog.dao.TestDao;
import com.slaviks_blog.domain.Context;
public class TestDaoJdbcTest extends
AbstractTransactionalDataSourceSpringContextTests
{
private TestDao testDao;
/**
* Specify all Spring config files needed for the test.
*
* @return
*/
protected String[] getConfigLocations()
{
return new String[]
{
“/war/WEB-INF/config/spring/data-access.xml”
};
}
/**
* Override the default implementation to use FileSystem path instead of
* Classpath, because the lookup of the spring config xml files is different
* when running as a web-app, as opposed to running as a test-case.
*
* @param locations
* @return
*/
protected ConfigurableApplicationContext loadContextLocations(
String[] locations)
{
return new FileSystemXmlApplicationContext(locations);
}
public void testContextIsSet()
{
AuthContainer.setCurrentUser(“Slavik”);
Context ctx = testDao.getContext();
assertEquals(“Action was not set”, “getContext”, ctx.getAction());
assertEquals(“Module was not set”, “Test”, ctx.getModule());
assertEquals(“ClientID was not set”, “Slavik”, ctx.getClientIdentifier());
}
public void setTestDao(TestDao testDao)
{
this.testDao = testDao;
}
}


Source files for the example project

In the next blog entry, I will describe a way to set the action, module and client identifier in a Java application for which we have no source code. We will make this magic happen using AspectJ.