/*
* This file or a portion of this file is licensed under the terms of
* the Globus Toolkit Public License, found in file GTPL, or at
* http://www.globus.org/toolkit/download/license.html. This notice must
* appear in redistributions of this file, with or without modification.
*
* Redistributions of this Software, with or without modification, must
* reproduce the GTPL in: (1) the Software, or (2) the Documentation or
* some other similar material which is provided with the Software (if
* any).
*
* Copyright 1999-2004 University of Chicago and The University of
* Southern California. All rights reserved.
*/
package org.griphyn.vdl.util;
import org.griphyn.vdl.util.*;
import java.util.*;
import java.io.*;
/**
* This class implements file locking using explicit lock files. Some
* effort was taken to permit some NFS alleviation of problems. However,
* all locking is exclusive, and may result in termination for failure
* to obtain a lock due to the presence of a lock file.<p>
*
* All access to the files must go through the respective open and close
* methods provided by this class in order to guarantee proper locking!
*
* @author Jens-S. Vöckler
* @author Yong Zhao
* @version $Revision$
*
* @see java.io.File
* @see LockHelper
*/
public class LockFileLock extends FileHelper
{
/**
* file locking helper (could be static for all I care)
*/
private LockHelper m_lock;
/**
* maintainer of reads and writes for this instance. Parallel
* instances might still try to access in parallel, the reason
* lock files are employed.
*/
private int m_state = 0;
private int m_refCount = 0;
/**
* state collector of the streams that are currently open.
*/
private HashMap m_streams;
/**
* Primary ctor: obtain access to a database cycle via basename.
* @param basename is the name of the database without digit suffix.
*/
public LockFileLock( String basename )
{
super( basename );
this.m_lock = new LockHelper();
this.m_streams = new HashMap();
}
/**
* Opens a reader for the basename, adjusting the database cycles.
* The access can be shared with other simultaneous readers.
*
* @return a reader opened to the basename, or null for failure.
* @see #closeReader( File )
*/
public synchronized File openReader()
{
// check, if any writer is already open. Parallel readers are allowed.
if ( this.m_state > 1 ) return null;
int number = -1;
if ( this.m_number.exists() ) {
// if the number file does not exist, DON'T create it
// FIXME: we still create a lock file for this (nonexisting) file
// read which database is the current one
if ( this.m_lock.lockFile( this.m_number.getPath() ) ) {
number = readCount();
// keep locked until database is opened
}
} else {
// if the number file does not exit, DO NOT create it (yet)
Logging.instance().log( "lock", 2, "number file " + m_number.getPath() +
" does not exist, ignoring lock" );
}
// postcondition: number points to the original database to read from
// database file
File database = new File( number == -1 ?
m_database :
m_database + '.' + Integer.toString(number) );
// lock and open database
File result = null;
if ( this.m_lock.lockFile( database.getPath() ) ) {
result = database;
this.m_state |= 1; // mark reader as active
this.m_refCount++; // and count readers
this.m_streams.put( result, new Integer(number) );
}
// release lock on number file in any case. Once it is opened,
// assume that it is protected by the OS.
this.m_lock.unlockFile( this.m_number.getPath() );
// exit condition: Only the database file is locked, or in case of
// failure it is unlocked. The number file is always unlocked at this
// stage.
return result;
}
/**
* Opens a writer for the basename, adjusting the database cycles
* The access is exclusive, and cannot be shared with readers nor
* writers.
*
* @return a writer opened for the basename, or null for failure.
* @see #closeWriter( File )
*/
public synchronized File openWriter()
{
// check, if any reader or a writer is already open
if ( this.m_state > 0 ) return null;
int number = -1;
if ( ! this.m_number.exists() ) {
// if the number file does not exist, DO NOT create it (yet)
// FIXME: we still create a lock file for this (nonexisting) file
number = 0;
} else {
// read which database is the current one
if ( this.m_lock.lockFile( this.m_number.getPath() ) ) {
number = readCount();
// keep file locked!
}
// generate next file
number = (number + 1) % 10;
}
// postcondition: number is the new database to write to
// database file
File database =
new File( this.m_database + '.' + Integer.toString(number) );
// lock and open database
File result = null;
if ( this.m_lock.lockFile( database.getPath() ) ) {
result = database;
this.m_state |= 2; // mark writer as active
this.m_streams.put( result, new Integer(number) );
}
if ( result == null ) {
// failure, release lock on number file
this.m_lock.unlockFile( this.m_number.getPath() );
}
// exit condition: database file and number file are both locked, or
// in case of failure: both unlocked.
return result;
}
/**
* Closes a previously obtained reader, and releases internal
* locking resources. Only if the reader was found in the internal
* state, any closing of the stream will be attempted.
*
* @param r is the reader that was created by {@link #openReader()}.
* @return true, if unlocking went smoothly, or false in the presence
* of an error. The only error that can happen it to use a File
* instance which was not returned by this instance.
* @see #openReader()
*/
public synchronized boolean closeReader( File r )
{
boolean result = false;
Integer number = (Integer) this.m_streams.get(r);
if ( number != null ) {
// deactivate reader refcount
if ( --this.m_refCount == 0 ) this.m_state &= ~1;
// remove lock from database file in any case
this.m_streams.remove( r );
this.m_lock.unlockFile( r.getPath() );
// everything is smooth
result = true;
}
return result;
}
/**
* Closes a previously obtained writer, and releases internal
* locking resources. Error conditions can be either a missing
* instance that passed, or the inability to update the cursor file.
*
* @param w is the instance that was returned by {@link #openWriter()}.
* @return true, if the closing went smoothly, false in the presence
* of an error.
* @see #openWriter()
*/
public synchronized boolean closeWriter( File w )
{
boolean result = false;
Integer number = (Integer) this.m_streams.get(w);
if ( number != null ) {
// Since the cursor could not be modified due to being locked,
// we can update it now with the new version. NOW create it.
result = writeCount(number.intValue());
// deactivate writer
this.m_state &= ~2;
// remove locks from database and cursor file.
this.m_streams.remove( w );
this.m_lock.unlockFile( w.getPath() );
this.m_lock.unlockFile( this.m_number.getPath() );
}
return result;
}
}