fr.dyade.aaa.util.JTransaction Maven / Gradle / Ivy
Show all versions of a3-rt Show documentation
/*
* Copyright (C) 2001 - 2023 ScalAgent Distributed Technologies
* Copyright (C) 1996 - 2000 BULL
* Copyright (C) 1996 - 2000 INRIA
*
* 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.
*/
package fr.dyade.aaa.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.NotActiveException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import org.objectweb.util.monolog.api.BasicLevel;
/**
* The JTransaction class implements a transactionnal storage.
* This implementation is simple but inefficient, its main advantage is
* the compatibility with old JDK.
*
* @see Transaction
*/
@Deprecated
public final class JTransaction extends BaseTransaction implements JTransactionMBean {
protected long startTime = 0L;
/**
* Returns the starting time.
*
* @return The starting time.
*/
public long getStartTime() {
return startTime;
}
/**
* Number of commit operation since starting up.
*/
private int commitCount = 0;
/**
* Returns the number of commit operation since starting up.
*
* @return The number of commit operation.
*/
public final int getCommitCount() {
return commitCount;
}
public static final String EMPTY_STRING = new String();
/**
* Number of pooled operation, by default 100.
* This value can be adjusted for a particular server by setting
* LogThresholdOperation
specific property.
*
* These property can be fixed either from java
launching
* command, or in a3servers.xml
configuration file.
*/
static int LogThresholdOperation = 100;
private File dir = null;
static private final String LOG = "log";
private RandomAccessFile logFile = null;
private Hashtable log = null;
public JTransaction() {}
/**
* Tests if the Transaction component is persistent.
*
* @return true.
*/
public boolean isPersistent() {
return true;
}
public void init(String path) throws IOException {
phase = INIT;
dir = new File(path);
if (!dir.exists()) dir.mkdir();
if (!dir.isDirectory())
throw new FileNotFoundException(path + " is not a directory.");
// Saves the transaction classname in order to prevent use of a
// different one after restart (see AgentServer.init).
DataOutputStream dos = null;
try {
File tfc = new File(dir, "TFC");
if (! tfc.exists()) {
dos = new DataOutputStream(new FileOutputStream(tfc));
dos.writeUTF(getClass().getName());
dos.flush();
}
} finally {
if (dos != null) dos.close();
}
loadProperties(dir);
LogThresholdOperation = getInteger("LogThresholdOperation", LogThresholdOperation).intValue();
Operation.initPool(LogThresholdOperation);
saveProperties(dir);
// Read the log, then...
int oldPhase = FREE;
logFile = new RandomAccessFile(new File(dir, LOG), "rw");
log = new Hashtable();
if (logFile.length() != 0) {
logFile.seek(0L);
oldPhase = logFile.readInt();
// Test the stop status then complete commit or rollback if needed
if (oldPhase == COMMIT) {
int op;
String name;
while (!(name = logFile.readUTF()).equals("")) {
String dirName = logFile.readUTF();
if (dirName.length() == 0) dirName = null;
Object key = OperationKey.newKey(dirName, name);
op = logFile.read();
if (op == Operation.SAVE) {
byte buf[] = new byte[logFile.readInt()];
logFile.readFully(buf);
log.put(key, Operation.alloc(Operation.SAVE, dirName, name, buf));
} else {
log.put(key, Operation.alloc(op, dirName, name));
}
}
}
_commit();
}
startTime = System.currentTimeMillis();
setPhase(FREE);
}
public File getDir() {
return dir;
}
/**
* Returns the path of persistence directory.
*
* @return The path of persistence directory.
*/
public String getPersistenceDir() {
return dir.getPath();
}
// State of the transaction monitor.
protected int phase;
public final int getPhase() {
return phase;
}
public final String getPhaseInfo() {
return PhaseInfo[phase];
}
protected void setPhase(int newPhase) throws IOException {
logFile.seek(0L);
logFile.writeInt(newPhase);
logFile.getFD().sync();
phase = newPhase;
}
public final synchronized void begin() throws IOException {
while (phase != FREE) {
try {
wait();
} catch (InterruptedException exc) {
}
}
// Change the transaction state.
setPhase(RUN);
}
public String[] getList(String prefix) {
return dir.list(new StartWithFilter(prefix));
}
public final void create(Serializable obj, String name) throws IOException {
save(obj, null, name, true);
}
public final void create(Serializable obj,
String dirName, String name) throws IOException {
save(obj, dirName, name, true);
}
public final void save(Serializable obj, String name) throws IOException {
save(obj, null, name, false);
}
public final void save(Serializable obj,
String dirName, String name) throws IOException {
save(obj, dirName, name, false);
}
public void save(Serializable obj,
String dirName, String name,
boolean first) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
saveByteArray(bos.toByteArray(), dirName, name);
}
public final void createByteArray(byte[] buf, String name) throws IOException {
saveByteArray(buf, null, name, true, true);
}
public final void createByteArray(byte[] buf,
String dirName, String name) throws IOException {
saveByteArray(buf, dirName, name, true, true);
}
public final void saveByteArray(byte[] buf, String name) throws IOException {
saveByteArray(buf, null, name, true, false);
}
public final void saveByteArray(byte[] buf,
String dirName, String name) throws IOException {
saveByteArray(buf, dirName, name, true, false);
}
public void saveByteArray(byte[] buf,
String dirName, String name,
boolean copy,
boolean first) throws IOException {
if (phase == RUN) {
// We are during a transaction put the new state in the log.
Object key = OperationKey.newKey(dirName, name);
log.put(key, Operation.alloc(Operation.SAVE, dirName, name, buf));
} else {
// Save the new state on the disk.
File file;
if (dirName == null) {
file = new File(dir, name);
} else {
File parentDir = new File(dir, dirName);
if (!parentDir.exists()) {
parentDir.mkdirs();
}
file = new File(parentDir, name);
}
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(buf);
}
}
}
public final Object load(String name) throws IOException, ClassNotFoundException {
return load(null, name);
}
public final Object load(String dirName, String name) throws IOException, ClassNotFoundException {
byte[] buf = loadByteArray(dirName, name);
if (buf != null) {
ByteArrayInputStream bis = new ByteArrayInputStream(buf);
ObjectInputStream ois = new ObjectInputStream(bis);
try {
return ois.readObject();
} finally {
ois.close();
bis.close();
}
}
return null;
}
/**
* Returns false, this Transaction implementation does not implement an optimized loadAll method.
*
* @return false.
*/
@Override
public boolean useLoadAll() {
return false;
}
/**
* Fills the map with all objects of the component whose name begins with the prefix.
*
* @param prefix The prefix of searched objects.
* @param map The map of corresponding objects.
*/
@Override
public void loadAll(String prefix, Map map) {
if (logmon.isLoggable(BasicLevel.DEBUG))
logmon.log(BasicLevel.DEBUG, "JTransaction, loadAll(" + prefix + ")");
// TODO (AF): To be implemented
return;
}
public final byte[] loadByteArray(String name) throws IOException {
return loadByteArray(null, name);
}
public byte[] loadByteArray(String dirName, String name) throws IOException {
if (phase == RUN) {
// first search in the log a new value for the object.
Object key = OperationKey.newKey(dirName, name);
Operation op = (Operation) log.get(key);
if (op != null) {
if (op.type == Operation.SAVE) {
return op.value;
} else if (op.type == Operation.DELETE) {
// The object is no longer alive
return null;
}
}
}
try {
File file;
if (dirName == null) {
file = new File(dir, name);
} else {
File parentDir = new File(dir, dirName);
file = new File(parentDir, name);
}
byte[] buf = null;
try (FileInputStream fis = new FileInputStream(file)) {
buf = new byte[(int) file.length()];
for (int nb=0; nb this.dir.getAbsolutePath().length()) {
deleteDir(dir.getParentFile());
}
}
}
public synchronized void commit(boolean release) throws IOException {
if (phase != RUN)
throw new NotActiveException("Can not commit inexistent transaction.");
// Save the log to disk
logFile.seek(4L);
for (Enumeration e = log.elements(); e.hasMoreElements(); ) {
Operation op = (Operation) e.nextElement();
logFile.writeUTF(op.name);
if (op.dirName != null) {
logFile.writeUTF(op.dirName);
} else {
logFile.writeUTF(EMPTY_STRING);
}
logFile.writeByte(op.type);
if (op.type == Operation.SAVE) {
logFile.writeInt(op.value.length);
logFile.write(op.value);
}
}
logFile.writeUTF("");
setPhase(COMMIT);
_commit();
commitCount += 1;
log.clear();
if (release) {
// Change the transaction state and save it.
setPhase(FREE);
notify();
}
}
private void _commit() throws IOException {
for (Enumeration e = log.elements(); e.hasMoreElements(); ) {
Operation op = (Operation) e.nextElement();
if (op.type == Operation.SAVE) {
File file;
if (op.dirName == null) {
file = new File(dir, op.name);
} else {
File parentDir = new File(dir, op.dirName);
if (!parentDir.exists()) {
parentDir.mkdirs();
}
file = new File(parentDir, op.name);
}
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(op.value);
fos.getFD().sync();
}
} else if (op.type == Operation.DELETE) {
File file;
if (op.dirName == null) {
file = new File(dir, op.name);
file.delete();
} else {
File parentDir = new File(dir, op.dirName);
file = new File(parentDir, op.name);
file.delete();
deleteDir(parentDir);
}
} else {
throw new InvalidObjectException("Unknow object in log.");
}
}
}
public synchronized void release() throws IOException {
if ((phase != RUN) && (phase != COMMIT) && (phase != ROLLBACK))
throw new IllegalStateException("Can not release transaction.");
// Change the transaction state.
setPhase(FREE);
// wake-up an eventually user's thread in begin
notify();
}
/**
* Stops the transaction module.
* It waits all transactions termination, then the module is kept
* in a FREE 'ready to use' state.
*/
public synchronized void stop() {
while (phase != FREE) {
try {
// Wait for the transaction subsystem to be free
wait();
} catch (InterruptedException exc) {
}
}
}
/**
* Close the transaction module.
* It waits all transactions termination, the module will be initialized
* anew before reusing it.
*/
public synchronized void close() {
stop();
try {
setPhase(FINALIZE);
logFile.close();
setPhase(INIT);
} catch (IOException exc) {
}
}
/**
* Indicates whether some operations have been done in
* this transaction.
*/
public boolean containsOperations() {
return log.size() > 0;
}
/**
* Returns the number of operations to be committed.
* @return the number of operations to be committed.
*/
public int getOperationCount() {
return log.size();
}
/**
* Backups the content of Transaction module.
* Produces a generic Backup/Restore file containing all living objects.
* Currently not implemented, throws an UnsupportedOperationException.
*
* @param path Directory path to store the backup.
* @return the name of created backup file.
* @throws Exception An error occurs during backup.
*/
@Override
public String backup(String path) throws Exception {
// /!\ Be careful, should be synchronized!!
throw new UnsupportedOperationException("Backup operation not already implemented.");
}
}