fr.dyade.aaa.util.NTransaction Maven / Gradle / Ivy
/*
* Copyright (C) 2004 - 2012 ScalAgent Distributed Technologies
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*
* Initial developer(s): ScalAgent Distributed Technologies
* Contributor(s):
*/
package fr.dyade.aaa.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Timer;
import java.util.TimerTask;
import org.objectweb.util.monolog.api.BasicLevel;
import fr.dyade.aaa.common.Debug;
/**
* The NTransaction class implements a transactional storage.
* For efficiency it uses a file for its transaction journal, the final
* storage is provided through the Repository interface on filesystem or
* database.
*
* Be Careful, the configuration properties don't work for the transaction component:
* these properties are saved in the transaction repository so they can not be used to
* configure it.
*
* @see Transaction
* @see Repository
* @see FileRepository
* @see DBRepository
* @see MySqlDBRepository
*/
public final class NTransaction extends AbstractTransaction implements NTransactionMBean {
/**
* Global in memory log initial capacity, by default 4096.
* This value can be adjusted for a particular server by setting
* NTLogMemoryCapacity
specific property.
*
* This property can be set only at first launching.
*/
static int LogMemoryCapacity = 4096;
/**
* Returns the initial capacity of global in memory log (by default 4096).
*
* @return The initial capacity of global in memory log.
*/
public final int getLogMemoryCapacity() {
return LogMemoryCapacity;
}
/**
* Maximum size of memory log, by default 2048Kb.
* This value can be adjusted (Kb) for a particular server by setting
* NTLogMemorySize
specific property.
*
* This property can be set only at first launching.
*/
static int MaxLogMemorySize = 2048 * Kb;
/**
* Returns the maximum size of memory log in Kb, by default 2048Kb.
*
* @return The maximum size of memory log in Kb.
*/
public final int getMaxLogMemorySize() {
return MaxLogMemorySize / Kb;
}
/**
* Sets the maximum size of memory log in Kb.
*
* @param size The maximum size of memory log in Kb.
*/
public final void setMaxLogMemorySize(int size) {
if (size > 0)
MaxLogMemorySize = size * Kb;
}
/**
* Returns the size of memory log in bytes.
*
* @return The size of memory log in bytes.
*/
public final int getLogMemorySize() {
return logFile.logMemorySize;
}
/**
* Size of disk log in Mb, by default 16Mb.
* This value can be adjusted (Mb) for a particular server by setting
* NTLogFileSize
specific property.
*
* This property can be set only at first launching.
*/
static int MaxLogFileSize = 16 * Mb;
/**
* Returns the maximum size of disk log in Mb, by default 16Mb.
*
* @return The maximum size of disk log in Mb.
*/
public final int getMaxLogFileSize() {
return MaxLogFileSize/Mb;
}
/**
* Sets the maximum size of disk log in Mb.
*
* @param size The maximum size of disk log in Mb.
*/
public final void setMaxLogFileSize(int size) {
if (size > 0) MaxLogFileSize = size *Mb;
}
/**
* Returns the size of disk log in Kb.
*
* @return The size of disk log in Kb.
*/
public final int getLogFileSize() {
return (logFile.getLogFileSize() /Kb);
}
/**
* If true every write in the log file is synced to disk, by default
* false. This value can be adjusted for a particular server by setting
* Transaction.SyncOnWrite
specific property.
*
* This property can be set only at first launching.
*/
boolean syncOnWrite = false;
/**
* @return the syncOnWrite
*/
public boolean isSyncOnWrite() {
return syncOnWrite;
}
/**
* If true use a lock file to avoid multiples activation of Transaction
* component. This value can be adjusted for a particular server by setting
* Transaction.UseLockFile
specific property.
*
* This property can be set only at first launching.
*/
boolean useLockFile = true;
/**
* Number of pooled operation, by default 1000.
* This value can be adjusted for a particular server by setting
* NTLogThresholdOperation
specific property.
*
* This property can be set only at first launching.
*/
static int LogThresholdOperation = 1000;
/**
* Returns the pool size for operation
objects, by default 1000.
*
* @return The pool size for operation
objects.
*/
public final int getLogThresholdOperation() {
return LogThresholdOperation;
}
/**
* Returns the number of commit operation since starting up.
*
* @return The number of commit operation.
*/
public final int getCommitCount() {
return logFile.commitCount;
}
/**
* Returns the number of garbage operation since starting up.
*
* @return The number of garbage operation.
*/
public final int getGarbageCount() {
return logFile.garbageCount;
}
/**
* Returns the maximum time between two garbages, 0 if disable.
*
* @return The maximum time between two garbages.
*/
public final int getGarbageDelay() {
return (int) (logFile.garbageTimeOut /1000L);
}
/**
* Sets the maximum time between two garbages, 0 to disable the
* asynchronous garbage mechanism.
*
* @param timeout The maximum time between two garbages.
*/
public final void setGarbageDelay(int timeout) {
logFile.garbageTimeOut = timeout *1000L;
}
/**
* Returns the status of the garbage thread.
*
* @return The status of the garbage thread.
*/
public final boolean isGarbageRunning() {
// Currently there is no asynchronous garbage.
return false;
}
private Timer timer = null;
private GarbageTask task = null;
/**
* Sets asynchronous garbage.
*
* @param async If true activates the asynchronous garbage,
* deactivates otherwise.
*/
public void garbageAsync(boolean async) {
if (async) {
if (task == null) {
task = new GarbageTask();
}
} else {
if (task != null) task.cancel();
task = null;
if (timer != null) timer.cancel();
timer = null;
}
}
private class GarbageTask extends TimerTask {
private GarbageTask() {
if (NTransaction.this.timer == null)
NTransaction.this.timer = new Timer();
if (logFile.garbageTimeOut > 0) {
try {
NTransaction.this.timer.schedule(this, logFile.garbageTimeOut, logFile.garbageTimeOut);
} catch (Exception exc) {
logmon.log(BasicLevel.ERROR,
"NTransaction, cannot schedule garbage task ", exc);
}
}
}
/** Method called when the timer expires. */
public void run() {
if (logFile.garbageTimeOut > 0) {
if (System.currentTimeMillis() > (logFile.lastGarbageTime + logFile.garbageTimeOut)) {
garbage();
}
}
}
}
/**
* Returns the cumulated time of garbage operations since starting up.
*
* @return The cumulated time of garbage operations since starting up.
*/
public long getGarbageTime() {
return logFile.garbageTime;
}
/**
* Returns the ratio of garbage operations since starting up.
*
* @return The ratio of garbage operations since starting up.
*/
public int getGarbageRatio() {
return (int) ((logFile.garbageTime *100) / (System.currentTimeMillis() - startTime));
}
/**
* The Repository classname implementation.
* This value can be set for a particular server by setting the
* NTRepositoryImpl
specific property. By default its value
* is "fr.dyade.aaa.util.FileRepository".
*
* This property can be set only at first launching.
*/
String repositoryImpl = "fr.dyade.aaa.util.FileRepository";
/**
* Returns the Repository classname implementation.
*
* @return The Repository classname implementation.
*/
public String getRepositoryImpl() {
return repositoryImpl;
}
/**
* Returns the number of save operation to repository.
*
* @return The number of save operation to repository.
*/
public int getNbSavedObjects() {
return repository.getNbSavedObjects();
}
/**
* Returns the number of delete operation on repository.
*
* @return The number of delete operation on repository.
*/
public int getNbDeletedObjects() {
return repository.getNbDeletedObjects();
}
/**
* Returns the number of useless delete operation on repository.
*
* @return The number of useless delete operation on repository.
*/
public int getNbBadDeletedObjects() {
return repository.getNbBadDeletedObjects();
}
/**
* Returns the number of load operation from repository.
*
* @return The number of load operation from repository.
*/
public int getNbLoadedObjects() {
return repository.getNbLoadedObjects();
}
LogFile logFile = null;
Repository repository = null;
static final boolean debug = false;
public NTransaction() {}
/**
* Tests if the Transaction component is persistent.
*
* @return true.
*/
public boolean isPersistent() {
return true;
}
public final void initRepository() throws IOException {
LogMemoryCapacity = getInteger("NTLogMemoryCapacity", LogMemoryCapacity).intValue();
MaxLogFileSize = getInteger("NTLogFileSize", MaxLogFileSize / Mb).intValue() * Mb;
MaxLogMemorySize = getInteger("NTLogMemorySize", MaxLogMemorySize / Kb).intValue() * Kb;
LogThresholdOperation = getInteger("NTLogThresholdOperation", LogThresholdOperation).intValue();
Operation.initPool(LogThresholdOperation);
syncOnWrite = getBoolean("Transaction.SyncOnWrite");
try {
repositoryImpl = System.getProperty("NTRepositoryImpl", repositoryImpl);
repository = (Repository) Class.forName(repositoryImpl).newInstance();
repository.init(this, dir);
} catch (ClassNotFoundException exc) {
logmon.log(BasicLevel.FATAL,
"NTransaction, cannot initializes the repository ", exc);
throw new IOException(exc.getMessage());
} catch (InstantiationException exc) {
logmon.log(BasicLevel.FATAL,
"NTransaction, cannot initializes the repository ", exc);
throw new IOException(exc.getMessage());
} catch (IllegalAccessException exc) {
logmon.log(BasicLevel.FATAL,
"NTransaction, cannot initializes the repository ", exc);
throw new IOException(exc.getMessage());
}
if (getBoolean("NTNoLockFile"))
logmon.log(BasicLevel.ERROR,
"NTransaction, no longer use NTNoLockFile property, use Transaction.UseLockFile.");
useLockFile = getBoolean("Transaction.UseLockFile");
logFile = new LogFile(dir, repository, useLockFile, syncOnWrite);
// Be careful, setGarbageDelay and garbageAsync use logFile !!
setGarbageDelay(getInteger("NTGarbageDelay", getGarbageDelay()).intValue());
garbageAsync(getBoolean("NTAsyncGarbage"));
}
/**
* Returns the path of persistence directory.
*
* @return The path of persistence directory.
*/
public String getPersistenceDir() {
return dir.getPath();
}
protected final void setPhase(int newPhase) {
phase = newPhase;
}
/**
* Returns an array of strings naming the persistent objects denoted by
* a name that satisfy the specified prefix. Each string is an object name.
*
* @param prefix the prefix
* @return An array of strings naming the persistent objects
* denoted by a name that satisfy the specified prefix. The
* array will be empty if no names match.
*/
public synchronized String[] getList(String prefix) {
String[] list1 = null;
try {
list1 = repository.list(prefix);
} catch (IOException exc) {
// AF: TODO
}
if (list1 == null) list1 = new String[0];
Object[] list2 = logFile.log.keySet().toArray();
int nb = list1.length;
for (int i=0; i=0; i--) {
if (list1[i] != null) list[--nb] = list1[i];
}
for (int i=list2.length-1; i>=0; i--) {
if (list2[i] != null) list[--nb] = (String) list2[i];
}
return list;
}
/**
* Save an object state already serialized. The byte array in parameter
* may be modified so we must duplicate it.
*/
protected final void saveInLog(byte[] buf,
String dirName, String name,
Hashtable log,
boolean copy,
boolean first) throws IOException {
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG,
"NTransaction, saveInLog(" + dirName + '/' + name + ", " + copy + ", " + first + ")");
Object key = OperationKey.newKey(dirName, name);
Operation op = null;
if (first)
op = Operation.alloc(Operation.CREATE, dirName, name, buf);
else
op = Operation.alloc(Operation.SAVE, dirName, name, buf);
Operation old = (Operation) log.put(key, op);
if (copy) {
if ((old != null) &&
(old.type == Operation.SAVE) &&
(old.value.length == buf.length)) {
// reuse old buffer
op.value = old.value;
} else {
// alloc a new one
op.value = new byte[buf.length];
}
System.arraycopy(buf, 0, op.value, 0, buf.length);
}
if (old != null) old.free();
}
private final byte[] getFromLog(Hashtable log, Object key) throws IOException {
// Searches in the log a new value for the object.
Operation op = (Operation) log.get(key);
if (op != null) {
if ((op.type == Operation.SAVE) || (op.type == Operation.CREATE)) {
return op.value;
} else if (op.type == Operation.DELETE) {
// The object was deleted.
throw new FileNotFoundException();
}
}
return null;
}
private final byte[] getFromLog(String dirName, String name) throws IOException {
// First searches in the logs a new value for the object.
Object key = OperationKey.newKey(dirName, name);
byte[] buf = getFromLog(perThreadContext.get().getLog(), key);
if (buf != null) return buf;
if ((buf = getFromLog(logFile.log, key)) != null) {
return buf;
}
return null;
}
public byte[] loadByteArray(String dirName, String name) throws IOException {
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG,
"NTransaction, loadByteArray(" + dirName + '/' + name + ")");
// First searches in the logs a new value for the object.
try {
byte[] buf = getFromLog(dirName, name);
if (buf != null) return buf;
// Gets it from disk.
return repository.load(dirName, name);
} catch (FileNotFoundException exc) {
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG,
"NTransaction, loadByteArray(" + dirName + '/' + name + ") not found");
return null;
}
}
public final void delete(String dirName, String name) {
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG,
"NTransaction, delete(" + dirName + ", " + name + ")");
Object key = OperationKey.newKey(dirName, name);
Hashtable