001: /*
002: * This file or a portion of this file is licensed under the terms of
003: * the Globus Toolkit Public License, found in file GTPL, or at
004: * http://www.globus.org/toolkit/download/license.html. This notice must
005: * appear in redistributions of this file, with or without modification.
006: *
007: * Redistributions of this Software, with or without modification, must
008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
009: * some other similar material which is provided with the Software (if
010: * any).
011: *
012: * Copyright 1999-2004 University of Chicago and The University of
013: * Southern California. All rights reserved.
014: */
015:
016: package org.griphyn.vdl.util;
017:
018: import org.griphyn.vdl.util.*;
019: import java.util.*;
020: import java.io.*;
021:
022: /**
023: * This class implements file locking using explicit lock files. Some
024: * effort was taken to permit some NFS alleviation of problems. However,
025: * all locking is exclusive, and may result in termination for failure
026: * to obtain a lock due to the presence of a lock file.<p>
027: *
028: * All access to the files must go through the respective open and close
029: * methods provided by this class in order to guarantee proper locking!
030: *
031: * @author Jens-S. Vöckler
032: * @author Yong Zhao
033: * @version $Revision: 50 $
034: *
035: * @see java.io.File
036: * @see LockHelper
037: */
038: public class LockFileLock extends FileHelper {
039: /**
040: * file locking helper (could be static for all I care)
041: */
042: private LockHelper m_lock;
043:
044: /**
045: * maintainer of reads and writes for this instance. Parallel
046: * instances might still try to access in parallel, the reason
047: * lock files are employed.
048: */
049: private int m_state = 0;
050: private int m_refCount = 0;
051:
052: /**
053: * state collector of the streams that are currently open.
054: */
055: private HashMap m_streams;
056:
057: /**
058: * Primary ctor: obtain access to a database cycle via basename.
059: * @param basename is the name of the database without digit suffix.
060: */
061: public LockFileLock(String basename) {
062: super (basename);
063:
064: this .m_lock = new LockHelper();
065: this .m_streams = new HashMap();
066: }
067:
068: /**
069: * Opens a reader for the basename, adjusting the database cycles.
070: * The access can be shared with other simultaneous readers.
071: *
072: * @return a reader opened to the basename, or null for failure.
073: * @see #closeReader( File )
074: */
075: public synchronized File openReader() {
076: // check, if any writer is already open. Parallel readers are allowed.
077: if (this .m_state > 1)
078: return null;
079:
080: int number = -1;
081: if (this .m_number.exists()) {
082: // if the number file does not exist, DON'T create it
083: // FIXME: we still create a lock file for this (nonexisting) file
084: // read which database is the current one
085: if (this .m_lock.lockFile(this .m_number.getPath())) {
086: number = readCount();
087: // keep locked until database is opened
088: }
089: } else {
090: // if the number file does not exit, DO NOT create it (yet)
091: Logging.instance().log(
092: "lock",
093: 2,
094: "number file " + m_number.getPath()
095: + " does not exist, ignoring lock");
096: }
097: // postcondition: number points to the original database to read from
098:
099: // database file
100: File database = new File(number == -1 ? m_database : m_database
101: + '.' + Integer.toString(number));
102:
103: // lock and open database
104: File result = null;
105: if (this .m_lock.lockFile(database.getPath())) {
106: result = database;
107: this .m_state |= 1; // mark reader as active
108: this .m_refCount++; // and count readers
109: this .m_streams.put(result, new Integer(number));
110: }
111:
112: // release lock on number file in any case. Once it is opened,
113: // assume that it is protected by the OS.
114: this .m_lock.unlockFile(this .m_number.getPath());
115:
116: // exit condition: Only the database file is locked, or in case of
117: // failure it is unlocked. The number file is always unlocked at this
118: // stage.
119: return result;
120: }
121:
122: /**
123: * Opens a writer for the basename, adjusting the database cycles
124: * The access is exclusive, and cannot be shared with readers nor
125: * writers.
126: *
127: * @return a writer opened for the basename, or null for failure.
128: * @see #closeWriter( File )
129: */
130: public synchronized File openWriter() {
131: // check, if any reader or a writer is already open
132: if (this .m_state > 0)
133: return null;
134:
135: int number = -1;
136: if (!this .m_number.exists()) {
137: // if the number file does not exist, DO NOT create it (yet)
138: // FIXME: we still create a lock file for this (nonexisting) file
139: number = 0;
140: } else {
141: // read which database is the current one
142: if (this .m_lock.lockFile(this .m_number.getPath())) {
143: number = readCount();
144: // keep file locked!
145: }
146:
147: // generate next file
148: number = (number + 1) % 10;
149: }
150: // postcondition: number is the new database to write to
151:
152: // database file
153: File database = new File(this .m_database + '.'
154: + Integer.toString(number));
155:
156: // lock and open database
157: File result = null;
158: if (this .m_lock.lockFile(database.getPath())) {
159: result = database;
160: this .m_state |= 2; // mark writer as active
161: this .m_streams.put(result, new Integer(number));
162: }
163:
164: if (result == null) {
165: // failure, release lock on number file
166: this .m_lock.unlockFile(this .m_number.getPath());
167: }
168:
169: // exit condition: database file and number file are both locked, or
170: // in case of failure: both unlocked.
171: return result;
172: }
173:
174: /**
175: * Closes a previously obtained reader, and releases internal
176: * locking resources. Only if the reader was found in the internal
177: * state, any closing of the stream will be attempted.
178: *
179: * @param r is the reader that was created by {@link #openReader()}.
180: * @return true, if unlocking went smoothly, or false in the presence
181: * of an error. The only error that can happen it to use a File
182: * instance which was not returned by this instance.
183: * @see #openReader()
184: */
185: public synchronized boolean closeReader(File r) {
186: boolean result = false;
187: Integer number = (Integer) this .m_streams.get(r);
188: if (number != null) {
189: // deactivate reader refcount
190: if (--this .m_refCount == 0)
191: this .m_state &= ~1;
192:
193: // remove lock from database file in any case
194: this .m_streams.remove(r);
195: this .m_lock.unlockFile(r.getPath());
196:
197: // everything is smooth
198: result = true;
199: }
200: return result;
201: }
202:
203: /**
204: * Closes a previously obtained writer, and releases internal
205: * locking resources. Error conditions can be either a missing
206: * instance that passed, or the inability to update the cursor file.
207: *
208: * @param w is the instance that was returned by {@link #openWriter()}.
209: * @return true, if the closing went smoothly, false in the presence
210: * of an error.
211: * @see #openWriter()
212: */
213: public synchronized boolean closeWriter(File w) {
214: boolean result = false;
215: Integer number = (Integer) this .m_streams.get(w);
216: if (number != null) {
217: // Since the cursor could not be modified due to being locked,
218: // we can update it now with the new version. NOW create it.
219: result = writeCount(number.intValue());
220:
221: // deactivate writer
222: this .m_state &= ~2;
223:
224: // remove locks from database and cursor file.
225: this.m_streams.remove(w);
226: this.m_lock.unlockFile(w.getPath());
227: this.m_lock.unlockFile(this.m_number.getPath());
228: }
229: return result;
230: }
231:
232: }
|