Code tricks.

Proxy classes ( poor's man AOP ) in conjunction with iBatis framework do the trick:

Lets compare implementations of the same class xflow.server.controller.WorkflowP in Xflow2 and in original Xflow (API and functionality are exactly the same)

XFlow 2
Xflow
~200 lines of code ~620 lines of code
Typical code looks like this Typical code looks like this
149    public  void abortWorkflow (final Integer workflowId)
   throws SQLException { 
150      Map params = new Hashtable(); 
151      params.put("workflowId",workflowId ); 
152      params.put("timeEnded", new Date() ); 
153      Persistence.getThreadSqlMapSession()
 .update( "abortWorkflow",params  ); 
154    } 
	
 
284      public static void abortWorkflow (WorkflowId workflowId) 
throws XflowException { 
285   
286          int wfId = workflowId.getValue(); 
287          Connection con = null; 
288          Statement s = null; 
289          try { 
290              con = Persistence.getConnection(); 
291              s = con.createStatement(); 
292   
293              String timeEnded = DateUtil.getTimestamp(); 
294              String sql = 
"update workflow set isActive = false, timeEnded = '" 
+ timeEnded + 
295      "', status = 'ABORTED' where workflowid = " + wfId;  
296              log.info (sql); 
297              s.execute (sql); 
298          } catch (Exception e) { 
299              e.printStackTrace(); 
300              throw new XflowException ("Failed to abort workflow in database"); 
301          } finally { 
302              if (s!=null) { 
303                  try { 
304                     s.close(); 
305                  } catch (Exception e) { 
306                     e.printStackTrace(); 
307                  } 
308              } 
309              if (con != null) { 
310                  try { 
311                     con.close(); 
312                  } catch (Exception e) { 
313                  } 
314              } 
315          } 
316      } 	
	
 

As we can see Xflow2 code is much smaller and does not have all that plumbing code to get a connection and handle possible exceptions. All that is possible because all those aspects are handled by proxy class that gets created at runtime. That is it: at runtime callers of the class above in reality deal with WorkflowP Proxy class. It is achived by using Factory pattern. Persistence class has methods to enhance DAO classes.

261    public static WorkflowP getWorkflowP() { 
262      synchronized( guard ){ 
263        if( workflowP == null ){ 
264          workflowP = (WorkflowP) enhanceInstanceOfClass( WorkflowP.class ); 
265        } 
266        return workflowP; 
267      } 
268   
269    } 

Enhancer creates Proxy class that looks exactly as given, but every method is wrapped by interceptor, that handles all the plumbing:


xflow1.2.1/src/xflow/util/IBatisMethodInterceptor.java
1 package xflow.util; 2 3 import net.sf.cglib.proxy.MethodProxy; 4 import net.sf.cglib.proxy.MethodInterceptor; 5 6 import java.lang.reflect.Method; 7 8 import com.ibatis.sqlmap.client.SqlMapClient; 9 10 11 /** 12 * User: kosta 13 * Date: Jul 18, 2004 14 * Time: 9:00:05 PM 15 */ 16 public class IBatisMethodInterceptor implements MethodInterceptor{ 17 18 int c = 0; 19 20 private void inc(){ 21 c++; 22 System.out.println("inc c = " + c); 23 } 24 25 private void dec(){ 26 c--; 27 System.out.println("dec c = " + c); 28 } 29 30 public Object intercept( Object o, Method method, Object[] parameters, MethodProxy methodProxy ) throws Throwable { 31 boolean close = true; 32 Object res; 33 try { 34 // inc(); 35 if( Persistence.threadSqlMap.get() == null ){ 36 SqlMapClient ss = Persistence.getSqlMap(); 37 Persistence.threadSqlMap.set( ss ); 38 Persistence.log.debug("START TRANSACTION-" + method.getName() + " of " + o.getClass() ); 39 ss.startTransaction(); 40 }else{ 41 close = false; 42 } 43 44 res = methodProxy.invokeSuper( o, parameters ); 45 46 if( close ){ 47 if( Persistence.threadSqlMap.get() != null ){ 48 Persistence.log.debug("COMMIT TRANSACTION-" + method.getName()); 49 Persistence.getThreadSqlMapSession().commitTransaction(); 50 Persistence.getThreadSqlMapSession().flushDataCache(); 51 // getThreadSqlMapSession().endTransaction(); 52 // getThreadSqlMapSession().close(); 53 Persistence.threadSqlMap.set( null ); 54 } 55 } 56 return res; 57 } catch (Throwable e) { 58 if( Persistence.threadSqlMap.get() != null ){ 59 Persistence.log.debug("ROLLBACK TRANSACTION-" + method.getName()); 60 Persistence.getThreadSqlMapSession().endTransaction(); 61 Persistence.getThreadSqlMapSession().flushDataCache(); 62 // getThreadSqlMapSession().close(); 63 } 64 Persistence.threadSqlMap.set( null ); 65 throw e; 66 } finally{ 67 // dec(); 68 } 69 70 } 71 72 } 73 74 75