Friday, February 4, 2011

Spring AOP - Changing target of a Proxy at runtime

I had a pretty strange usecase where i wanted to change the target object of the proxy and runtime. Spring makes it so simple to use.

My usecase was as follows



ApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:META-INF/spring/aop-poc-module-context.xml");
AccountDAO accountDao = (AccountDAO) ctx.getBean("accountDao");

IAccount account = accountDao.getById(123);

System.out.println("account" + account);

account.setName("sudheer");

System.out.println("account" + account);



Is a property is change on the entity , i want to change the entity object itself , that is , in the above use two sysouts will print different objects.

The use case arose because i was using a cache(infinispan) which did not allow modifying POJOs outside a transaction scope.

I did achieve the same using the follwing



public class AccountDAO {

private Advisor advisor;

//This is get methods of the accountDao

public IAccount getById(long id) {
//creating new account - to mock database behavior
IAccount a = new Account(369, "suji");
//Using spring factory
ProxyFactory pf = new ProxyFactory();
pf.setExposeProxy(true);
pf.addInterface(IAccount.class);
//Using by own Target source to change the Target
pf.setTargetSource(new SwappableTargetSource(a));
//advisor which holds the advice and pointcut
pf.addAdvisor(advisor);
return (IAccount) pf.getProxy();
}

public void setAdvisor(Advisor advisor) {
this.advisor = advisor;
}

}



My advice class



public class SwappableBeforeAdvice implements MethodBeforeAdvice {

@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("Before advice called");

if (!((IAccount) target).isWritable()) {

//Changing the target source based on specific business condition
((SwappableTargetSource) ((Advised) AopContext.currentProxy())
.getTargetSource()).swap(new Account(56, "dan"));
}

}
}



My custom target source



public class SwappableTargetSource implements TargetSource {

private Object target;

public SwappableTargetSource(Object initialTarget) {
this.target = initialTarget;
}

public synchronized Class<?> getTargetClass() {
return this.target.getClass();
}

public final boolean isStatic() {
return false;
}

public synchronized Object getTarget() {
return this.target;
}

public void releaseTarget(Object target) {
// nothing to do
}

public synchronized void swap(Object newTarget)
throws IllegalArgumentException {
this.target = newTarget;
}

@Override
public boolean equals(Object other) {
return (this == other || (other instanceof SwappableTargetSource && this.target
.equals(((SwappableTargetSource) other).target)));
}

@Override
public int hashCode() {
return SwappableTargetSource.class.hashCode();
}

@Override
public String toString() {
return "SwappableTargetSource for target: " + this.target;
}

}



These are the spring xml configuration.



<bean id="accountDao" class="com.test.dao.account.AccountDAO">
<property name="advisor" ref="settersAdvisor" />
</bean>

<bean id="swapableBeforeAdvice" class="com.test.framework.SwappableBeforeAdvice"/>



This is the advice which advise all setter methods on the bean



<bean id="settersAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="swapableBeforeAdvice" />
</property>
<property name="patterns">
<list>
<value>.*set.*</value>
</list>
</property>
</bean>



Thanks to this post in spring forum which helped me do this.

http://forum.springsource.org/showthread.php?t=102784

Unit test framework with Liquibase+HsqlDb+Spring transactions

When looking at options for testing DAO classes I came up with a framework using the following

1. Liquibase - For maintaing data/schema scripts

2. HSQLDB - Inmemory database for test cases

3. Spring transactions - For maintaining clean state between two test cases which make DB updates i .e all data changes made by the test cases will be rolled back by the end of the test case.


I am using spring beans to initialize all the above. I will explain each of them in detail.

1. Initializing the HSQLDB



<context:property-placeholder
location="classpath*:tspex-test-inmemorydatabase.properties" />

<bean id="hsqlDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${database.driverClassName}">
<property name="url" value="${database.url}">
<property name="username" value="${database.username}">
<!-- <property name="password" value="${jdbc.password}"> -->
</bean>



All the values will be picked up from the file mentioned in the classpath.

2. Setting up Liquibase

I am using the following method to initialize Liquibase



public class TestFramework {

private DataSource dataSource;

public void initializeLiquiBase(String changeLogFile) throws Exception {
Connection conn = dataSource.getConnection();
Liquibase liquibase = new Liquibase(changeLogFile,
new ClassLoaderResourceAccessor(), new HsqlConnection(conn));
// Uncomment the following line when testing with mysql database
/*
* Liquibase liquibase = new Liquibase(changeLogFile, new
* ClassLoaderResourceAccessor(), new JdbcConnection(conn));
*/

liquibase.update("");
conn.close();
}

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

}



Corresponding spring configuration.



<bean id="testFramework" class="com.mycompany.framework.unittest.TestFramework">
<property name="dataSource" ref="hsqlDataSource">
</bean>



The changeLogFile will be provided by the user of the frame work , which i will explain shortly.


3. Setting up Spring transactions.



<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="hsqlDataSource">
</bean>

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



These are the only classes/configuration present in my framework

The maven dependencies required are as follows



<dependency>
<groupid>org.hsqldb</groupid>
<artifactid>hsqldb</artifactid>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupid>org.liquibase</groupid>
<artifactid>liquibase-core</artifactid>
<version>2.0.0</version>
</dependency>
<dependency>
<groupid>org.liquibase</groupid>
<artifactid>liquibase-plugin</artifactid>
<version>1.9.5.0</version>
</dependency>
<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.7</version>
</dependency>



Now let me explain how the framework will be used by the classes testing the framework


A typical test class for DAO will be as follows



@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath*:/META-INF/spring/unit-test-framework-module-context.xml",
"classpath*:/META-INF/spring/test-spring-jdbc-module-context.xml" })
public class TestAccountDAOSpringJdbc {

@Autowired
private TestFramework testFramework;

@Autowired
private AccountDAOSpringJdbc accountDao;

@Test
public void setUpBeforeClass() throws Exception {
System.out.println("Setup before class called");
testFramework.initializeLiquiBase("accounts-data-changelog-1.0.xml");


@Test
@Transactional
public void testSaveUpdate() throws Exception {
// Create a new Account
Account account = accountDao.get(1);
account.setUsername("newaccount");
account.setTitle("Mr");

........


}



The important thing to note here are

1. Initializing the framework beans - which contains HSQLDB and spring transaction initialization.

2. Initializing the test framework with liquibase scripts

This ensure that the database tables are created and test data is inserted before test cases run.

Ref: http://www.liquibase.org/

3. Using @Transactional annotation before test cases which updates the Database . This ensures that test cases do not commit anything into DB and all test cases can be independent of each other.

With some infrastructure setup like this , writing test cases for DAO become very easy.

 
Free Domain Names @ .co.nr!