Aspect Oriented Web Service Transactions

AOP is definitely a life saver. The project which I’ll leave soon is a J2EE project with JSF, Spring and Hibernate but the Document Management System module is implemented by a subcontractor firm and with .NET. As you may guess they talk by using web services and the basic requirement is uploading a document to the java application and send it to the .NET application to save. Let’s say there is a persistent object called Agreement with documents. The basic code in the service method that implements a service interface is as follows;

In SomeSpringService;

  
    
    

public void saveAgreement(Agreement aggrement) {          
     getAgreementDao().saveAggrement(agreement);
     getDMSService().saveDocs(agreement.getDocs());
}      

Subcontractor firm doing the .NET stuff is a small company and apparently they don’t know much about software development. I’m saying this after seeing methods composed of hundreds of lines. I hate the sound of the mouse wheel they use when they are looking for a code portion in the method. Up and Down. Man somebody has to tell them there is a concept of refactoring in the world. Anyway, a requirement arised after Master Kenan realized that the flow to save a document using their web service is a transactional requirement. Paired with Mert, they have created features to begin, end and rollback web service call transaction methods. After that the code looks like;

  
    
    

public void saveAgreement(Agreement aggrement) {
            try {
                  getDMSService().beginDMSTransaction();
                  getAgreementDao().saveAgreement(aggrement);
                  getDMSService.saveDocs(aggrement.getDocs());
                  getDMSService().commitDMSTransaction();
            }
            catch (Exception e) {
                 getDMSService().rollbackDMSTransaction();
                 throw new DMSException(e);
            }
}      

A problem occurred here, because the new infrastructure caused a core change and the first way was widely used by the developers in the project. This means, all the developers must change their service layer code interacting with the web service which will lead to a lot of workload. At this point I’ve sensed a disturbance in the force. Like Russell Crowe in Beautiful Mind or Tom Hanks in Da Vinci Code, some parts of the whole picture began to shine and I’ve came up with a way that will allow no change in the current code but will also handle the transaction stuff. Spring handles the transactions with AOP why not do the samething here.

I’ve used a commons attribute that defines the method needs a web service transaction. The only thing remaining was to implement the interceptor and the attribute related stuff.

DMSAttribute

  
    
    

package forca.barca;

public class DMSAttribute {

  public final static String TRANSACTION_REQUIRED = “DMS_TRANSACTION_REQUIRED”;
  
  private String type;
  
  public DMSAttribute() {}
  
  public DMSAttribute(String type) {
    this.type = type;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }
}      

DMS Attribute Source

  
    
    

package forca.barca;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;

import org.springframework.aop.support.AopUtils;
import org.springframework.metadata.Attributes;
import org.springframework.util.Assert;

public class DMSAttributeSource {

  private Attributes attributes;
    
    public Attributes getAttributes() {
        Assert.notNull(attributes,“‘Attributes’ cannot be null.”);
        return attributes;
    }
    
    public void setAttributes(Attributes attributes) {
        this.attributes = attributes;
    }
    
    public DMSAttribute getDMSAttribute(Method method, Class targetClass) {
        
        Method specificMethod = AopUtils.getMostSpecificMethod(method,targetClass);
        
        DMSAttribute attr = findDMSAttribute(getAttributes().getAttributes(specificMethod));
        if(attr != null) {
            return attr;
        }
        
        attr = findDMSAttribute(getAttributes().getAttributes(specificMethod.getDeclaringClass()));
        if(attr != null) {
            return attr;
        }
        
        if(specificMethod != method) {
            attr = findDMSAttribute(getAttributes().getAttributes(method));
            if(attr != null) {
                return attr;
            }
            
            attr = findDMSAttribute(getAttributes().getAttributes(method.getDeclaringClass()));
            if(attr != null) {
                return attr;
            }
        }
        
        return null;
    }
    
    private DMSAttribute findDMSAttribute(Collection collection) {
        for (Iterator iter = collection.iterator(); iter.hasNext();) {
            Object attr = (Objectiter.next();
            if(attr instanceof DMSAttribute) {
                return (DMSAttribute)attr;
            }
        }
        return null;
    }
}      

Interceptor;

  
    
    

package forca.barca;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import forca.barca.DMSexception;

public class DMSTransactionInterceptor implements MethodInterceptor {
  
  private final static Log logger = LogFactory.getLog(DMSTransactionInterceptor.class);

  private DMSAttributeSource dmsAttributeSource;

  private IDMSService dmsService;

  public Object invoke(MethodInvocation methodInvocationthrows Throwable {
    DMSAttribute dmsAttribute = getDMSAttributeSource().
                           getDMSAttribute
(methodInvocation.getMethod(),methodInvocation.getThis().getClass());

    if (dmsAttribute != null && dmsAttribute.getType().equals(DMSAttribute.TRANSACTION_REQUIRED)) {
      return handleDMSTransaction(methodInvocation);
    else {
      return methodInvocation.proceed();
    }
  }

  public DMSAttributeSource getDMSAttributeSource() {
    return dmsAttribute;
  }

  public void setDMSAttributeSource(DMSAttributeSource dmsAttribute) {
    this.dmsAttribute = dmsAttribute;
  }

  public IDMSService getDMSService() {
    return dmsService;
  }

  public void setDMSService(IDMSService dmsService) {
    this.dmsService = dmsService;
  }

  private Object handleDMSTransaction(MethodInvocation methodInvocationthrows Throwable {
    try {
      getDMSService().beginDMSTransaction();
      logger.debug(“DMS Transaction started”);
      Object result = methodInvocation.proceed();
      getDMSService().commitDMSTransaction();
      logger.debug(“DMS Transaction commi
tted”
);
      return result;
    catch (Exception e) {
      getDMSService().rollbackDMSTransaction();
      logger.debug(“DMS transaction rolled back”);
      throw new DMSException(e);
    }
  }

}      

By the way dmsService is injected to the interceptor. New usage in the interface of the service method, implementation of the service object remains unchanged;

In ISomeSpringService;

  
    
    

 /**
   * @@forca.barca.DMSAttribute (“DMS_TRANSACTION_REQUIRED”) 
   */
 public void saveAgreement(Agreement agreement);      

The DMSAttribute and DMSAttributeSource are used to get the commons attribute defined for the service method. This is similar to the ones in Aspect Oriented Audit Logging with Spring and Hibernate. In the end my whole idea worked and the project saved from changing a lot of production code. It was fun and I believe this is one of the most useful stuff I’ve done before leaving the project.

Comments are closed.

%d bloggers like this: