Mentawai Web Framework

Transaction Filter

Transaction management is an important task for any web project that makes a lot of access to a database. One simple solution that is usualy enough for many cases is to make your action atomic. And this can be easily done through Mentawai's TransactionFilter.

The org.mentawai.filter.TransactionFilter begins a transaction right before the action is executed. If the action returns SUCCESS the transaction is commited. Otherwise, the transaction is rolledback. Note that you may also define a set of results that will lead to a transaction commit. If you don't define any commit results the default will be SUCCESS. Also note that if the action throws an exception, the transaction is rolledback.

The transaction is expected to be in the action input. Most likely it was placed there by Inversion of Control like we will see in a moment.

The source code of the org.mentawai.filter.TransactionFilter is below:

package org.mentawai.filter;

import java.util.*;

import org.mentawai.core.*;
import org.mentawai.transaction.*;

public class TransactionFilter implements Filter {
    
    private final static String TRANSACTION_KEY = "transaction";
    
    private String transaction_key = TRANSACTION_KEY;
    private List results = new LinkedList();
    
    public TransactionFilter() {
        results.add(Action.SUCCESS);		
    }
    
    public TransactionFilter(String transaction_key) {
        this();
        this.transaction_key = transaction_key;
    }
    
    public TransactionFilter(String [] results) {
        for(int i=0;i<results.length;i++) {
            this.results.add(results[i]);
        }
    }
    
    public TransactionFilter(String transaction_key, String [] results) {
        this(results);
        this.transaction_key = transaction_key;
    }
    
    public String filter(InvocationChain chain) throws Exception {
        Action action = chain.getAction();
        Input input = action.getInput();
        Transaction transaction = (Transaction) input.getValue(transaction_key);
        if (transaction == null) {
            throw new FilterException("Cannot find transaction: " + transaction_key);
        }
        
        try {
            transaction.begin();
            String result = chain.invoke();
            if (results.contains(result)) {
                transaction.commit();
            } else {
                transaction.rollback();
            }
            return result;
        } catch(Exception e) {
            transaction.rollback();
            throw e;
        }
    }
    
    public List getResultsForCommit() { 
        return results;
    }
    
    public void destroy() {	}

}                    
					

The Transaction Interface

Mentawai comes with a transaction abstraction in the org.mentawai.transaction.Transaction interface. Through this interface, you can create any type of transaction.

package org.mentawai.transaction;

public interface Transaction {
    public void begin() throws Exception;
    public void commit() throws Exception;
    public void rollback() throws Exception;
    public boolean isActive();
    public boolean wasCommited();
    public boolean wasRolledBack();
}
		

Mentawai comes with two ready-to-use transactions: org.mentawai.transaction.JdbcTransaction and org.mentawai.transaction.HibernateTransaction. A JdbcTransaction needs a java.sql.Connection in order to work and, as you may expect, a HibernateTransaction needs a org.hibernate.Session in order to work.

How do you dynamicaly set a Connection or a Session inside your transactions? The answer is Dependency Injection through the org.mentawai.filter.DIFilter.


Setting up a Transaction

In order to correctly set up a transaction for your action, you will need some Inversion of Control magic. Below is an example of an action that is bounded by a JdbcTransaction:

public class ApplicationManager extends org.mentawai.core.ApplicationManager {
    
	private Component transaction;
	private ConnectionHandler connHandler;
	
	public void init(Context application) {
	
		// ConnectionPool
		this.connHandler = new C3P0ConnectionHandler("com.mysql.jdbc.Driver", 
						 "jdbc:mysql://mybooks.lohis.com.br/mybooks?autoReconnect=true", 
						 "mybooks", 
						 "mybooks");

		// your transaction will be a JdbcTransaction... here we are defining an IOC component...
		ioc("transaction", JdbcTransaction.class);
	}
    
	public void loadActions() {
	
		// Java-Style
    
		// turn Mentawai into a powerful IoC container...
		addGlobalFilter(new IoCFilter());
		
		// turn on the ConnectionPool for Mentawai...
		addGlobalFilter(new ConnectionFilter(connHandler));
		
		// whoever needs a connection will receive a connection...
		addGlobalFilter(new DIFilter("conn", "connection", Connection.class));
		
		// BookManager.createAndAddToUser
		ActionConfig ac = new ActionConfig("/BookManager", BookManagerAction.class, "createAndAddToUser");
		addActionConfig(ac);
		
		ac.addConsequence(SUCCESS, new Forward("/welcome.jsp"));
		ac.addConsequence(ERROR, new Forward("/bookmanager/createbook.jsp"));
		ac.addConsequence(BookManagerAction.ERROR2, new Forward("/welcome.jsp"));
		
		// make this action atomic !!!!
		// by default this filter will look for a transaction with the name "transaction" in the action input
		// refer above that this is exactly like we defined our IoC component...
		ac.addFilter(new TransactionFilter());
		
		
		// Ruby-Style
		
		// turn Mentawai into a powerful IoC container...
		filter(new IoCFilter());
		
		// turn on the ConnectionPool for Mentawai...
		filter(new ConnectionFilter(connHandler));
		
		// whoever needs a connection will receive a connection...
		filter(new DIFilter("conn", "connection", Connection.class));
		
		// BookManager.createAndAddToUser
		action("/BookManager", BookManagerAction.class, "createAndAddToUser")
			.on(SUCCESS, fwd("/welcome.jsp"))
			.on(ERROR, fwd("/bookmanager/createbook.jsp"))
			.on(BookManagerAction.ERROR2, fwd("/welcome.jsp"))
		
			// make this action atomic !!!!
			// by default this filter will look for a transaction with the name "transaction" in the action input
			// refer above that this is exactly like we defined our IoC component...
			.filter(new TransactionFilter());
		
	}
}            
		

Note that we are creating a new transaction for every request with IoC. This is necessary because each request will have its own instance of Connection placed in the action input by the ConnectionFilter. With the transaction and the connection now in the action input, we use the DIFilter to inject the connection into the transaction. After that our transaction is ready to be used by the TransactionFilter.