Monday, June 1, 2009

Obtaining a write lock on a file in java over multiple JVMs

Why didn't we use the FileChannel & FileLock classes? This methodology locks the file also for read access. Which is kinda ugly.
I made a singleton class which handles the file that needs this sort of control, in our case it was the dirlist files. Why a singleton class? So that there is only one instance of the handler in the whole jvm and multiple threads in the same jvm can have synchronized access to this file. How does it obtain the write lock? By writing a .lck file in the same folder as this file. Below is the code we wrote for the handler:
package com.oracle.orion.fupv5.common;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
* This class must be the only means to access with the dirlist files for reading or
* for writing.
*/
public class DirlistFileHandler {
private static DirlistFileHandler dfHandler;
private String dirlistFilepath;
private boolean locked = false;
private final String dirlistLockFile="FUPDirlistLock.lck";

private DirlistFileHandler(String filepath) {
dirlistFilepath = filepath;
}

/**
* This is the only method to get an instance of this class.
*
* @param filepath The absolute filepath to the dirlist file.
*/
public static DirlistFileHandler getInstance(String filepath) {
if (dfHandler == null) {
dfHandler = new DirlistFileHandler(filepath);
}
return dfHandler;
}

/**
* This method will return a BufferedReader instance to the dirlist file.
* One can then use this reader to access every line of the code by using
* the method readLine() of the BufferedReader class. Please close this
* reader instance after you are done with it.
*/
public BufferedReader getReader() throws FileNotFoundException {
BufferedReader in = new BufferedReader(new FileReader(dirlistFilepath));
return in;
}

/**
* This method will return a FileWriter instance to the dirlist file.
* One can then use this writer to write a new line at the end of file.
* Please close this writer instance after you are done with it. And
* call the releaseWriter() method of this class when you are closing
* the writer.
When the caller successfully gets a writer to the dirlist

* file then the dirlist file is write locked until the releaseWriter() method is
* called.
*/
public FileWriter getWriter() throws FUPException, IOException {
if (isFileLocked()) {
throw new FUPException(ErrorConstants.DIRLIST_FILE_LOCKED);
}
synchronized(this) {
writeLockFile();
return new FileWriter(dirlistFilepath);
}
}

/**
* Use this method to release the write lock held over the dirlist file.
*/
public void releaseWriter() {
synchronized(this) {
removeLockFile();
}
}

/**
* It is adviced to use this method to ascertain that there is no write
* lock over the dirlist file before writing a new line to it, rather than
* trying to get the writer to it directly because you will need to handle
* an excpetion if it is locked. Instead use this method to check a lock
* and if a lock is present sleep for sometime and try to check it again.
*/
public boolean isFileLocked() {
File f = new File(dirlistLockFile);
if (f.exists()) {
return true;
}
else {
return false;
}
}

/**
* This method creates a new lock file in the same folder as the dirlist file.
*/
protected void writeLockFile() throws FileNotFoundException, IOException {
File df = new File(dirlistFilepath);
FileOutputStream fos = new FileOutputStream(df.getParentFile().getAbsolutePath()+"/"+dirlistLockFile);
fos.write((System.currentTimeMillis()+"").getBytes());
fos.flush();
fos.close();
}

/**
* This method removes the lock file.
*/
protected boolean removeLockFile() {
File f = new File(dirlistLockFile);
f.delete();
}
}

How to do an "around" logging Spring AOP Advice on all methods of a class?

Define a LoggingInterceptor in this way :

package ro.vodafone.search.admin.common;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import org.apache.log4j.Logger;

public class LogInterceptor implements MethodInterceptor{

public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result = null;
Logger logger = Logger.getLogger(methodInvocation.getMethod().getDeclaringClass());
try {

logger.info(methodInvocation.getMethod().getDeclaringClass()+ "."+ methodInvocation.getMethod().getName()+ " entered with parameters: " + methodInvocation.getArguments());
result = methodInvocation.proceed();
if (methodInvocation.getMethod().getReturnType() != null && result != null)
logger.info(methodInvocation.getMethod().getDeclaringClass() + "." + methodInvocation.getMethod().getName()+ " exitting with parameters: " + result);
else
logger.info(methodInvocation.getMethod().getDeclaringClass() + "." + methodInvocation.getMethod().getName()+" exitting");
} catch (Throwable ex) {
logger.error("Error while executing the method:"+methodInvocation.getMethod().getName(), ex);
throw ex;
}

return result;
}
}


Then in the spring app context file define the interceptors over the target in this way:

<bean id="logInterceptor" class="ro.vodafone.search.admin.common.LogInterceptor"/>
<bean id="searchAdminService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="service"/>
</property>
<property name="interceptorNames">
<list>
<value>logInterceptor</value>
</list>
</property>
</bean>