- Add the failsafe transaction utility and its test case

- Now use this utility in MclStorageManager
cusax-fix
Benoit Pradelle 19 years ago
parent a723f66398
commit 7c550c0c7b

@ -90,6 +90,11 @@ public class MclStorageManager
* A reference to the file containing the locally stored meta contact list.
*/
private File contactlistFile = null;
/**
* A reference to the failsafe transaction used with the contactlist file.
*/
private FailSafeTransaction contactlistTrans = null;
/**
* A regerence to the MetaContactListServiceImpl that created and started us.
@ -283,33 +288,14 @@ void start(BundleContext bc,
+". error was:" + ex.getMessage());
}
// try to see if any backup remains from the last execution
try
{
File backup = faService.getPrivatePersistentFile(fileName + ".bak");
// if the backup exists, simply use it as a normal file
if (backup.exists())
{
FileInputStream in = new FileInputStream(backup);
FileOutputStream out = new FileOutputStream(contactlistFile);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0)
{
out.write(buf, 0, len);
}
in.close();
out.close();
backup.delete();
}
}
catch (Exception e)
{
logger.error("Failed to restore the backup contact list file", e);
// create the failsafe transaction and restore the file if needed
try {
contactlistTrans = new FailSafeTransaction(this.contactlistFile);
contactlistTrans.restoreFile();
} catch (NullPointerException e) {
logger.error("the contactlist file is null", e);
} catch (IllegalStateException e) {
logger.error("The contactlist file can't be found", e);
}
try
@ -390,34 +376,12 @@ private void storeContactList0() throws IOException
+ isModified);
if(isStarted())
{
// copy the contact list before write on it to ensure
// a safe modification
File backup = new File (contactlistFile.getAbsolutePath()
+ ".bak.part");
FileInputStream in = new FileInputStream(contactlistFile);
FileOutputStream out = new FileOutputStream(backup);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0)
{
out.write(buf, 0, len);
}
in.close();
out.close();
// note the end of the transfert
File final_backup = new File(contactlistFile.getAbsolutePath()
+ ".bak");
// this should not happen, but if it's the case, the rename
// operation can fail
if (final_backup.exists()) {
final_backup.delete();
// begin a new transaction
try {
contactlistTrans.beginTransaction();
} catch (IllegalStateException e) {
logger.error("the contactlist file is missing", e);
}
backup.renameTo(final_backup);
// really write the modification
OutputStream stream = new FileOutputStream(contactlistFile);
@ -425,8 +389,12 @@ private void storeContactList0() throws IOException
stream);
stream.close();
// once done, delete the backup file
final_backup.delete();
// commit the changes
try {
contactlistTrans.commit();
} catch (IllegalStateException e) {
logger.error("the contactlist file is missing", e);
}
}
}

@ -0,0 +1,199 @@
/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.util;
import java.io.*;
/**
* A failsafe transaction utility class. By failsafe we mean here that the file
* concerned always stays in a coherent state. This class use the transactional
* model.
*
* @author Benoit Pradelle
*/
public class FailSafeTransaction
{
/**
* Original file used by the transaction
*/
private File file;
/**
* Backup file used by the transaction
*/
private File backup;
/**
* Extension of a partial file
*/
private static final String PART_EXT = ".part";
/**
* Extension of a backup copy
*/
private static final String BAK_EXT = ".bak";
/**
* Creates a new transaction.
*
* @param file The file associated with this transaction
*
* @throws NullPointerException if the file is null
*/
public FailSafeTransaction(File file)
throws NullPointerException
{
if (file == null) {
throw new NullPointerException("null file provided");
}
this.file = file;
this.backup = null;
}
/**
* Ensure that the file accessed is in a coherent state. This function is
* useful to do a failsafe read without starting a transaction.
*
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the file restoration
*/
public void restoreFile()
throws IllegalStateException, IOException
{
File back = new File(this.file.getAbsolutePath() + BAK_EXT);
// if a backup copy is still present, simply restore it
if (back.exists()) {
failsafeCopy(back.getAbsolutePath(),
this.file.getAbsolutePath());
back.delete();
}
}
/**
* Begins a new transaction. If a transaction is already active, commits the
* changes and begin a new transaction.
* A transaction can be closed by a commit or rollback operation.
* When the transaction begins, the file is restored to a coherent state if
* needed.
*
* @return The fail-safe file in which every modification must be done.
*
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the transaction
* creation
*/
public void beginTransaction()
throws IllegalStateException, IOException
{
// if the last transaction hasn't been closed, commit it
if (this.backup != null) {
this.commit();
}
// if needed, restore the file in its previous state
restoreFile();
this.backup = new File(this.file.getAbsolutePath() + BAK_EXT);
// else backup the current file
failsafeCopy(this.file.getAbsolutePath(),
this.backup.getAbsolutePath());
}
/**
* Closes the transaction and commit the changes. Everything written in the
* file during the transaction is saved.
*
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the operation
*/
public void commit()
throws IllegalStateException, IOException
{
if (this.backup == null) {
return;
}
// simply delete the backup file
this.backup.delete();
this.backup = null;
}
/**
* Closes the transation and cancel the changes. Everything written in the
* file during the transaction is NOT saved.
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the operation
*/
public void rollback()
throws IllegalStateException, IOException
{
if (this.backup == null) {
return;
}
// restore the backup and delete it
failsafeCopy(this.backup.getAbsolutePath(),
this.file.getAbsolutePath());
this.backup.delete();
this.backup = null;
}
/**
* Copy a file in a fail-safe way. The destination is created in an atomic
* way.
*
* @param from The file to copy
* @param to The copy to create
*
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the operation
*/
private void failsafeCopy(String from, String to)
throws IllegalStateException, IOException
{
FileInputStream in = null;
FileOutputStream out = null;
// to ensure a perfect copy, delete the destination if it exists
File toF = new File(to);
if (toF.exists()) {
toF.delete();
}
File ptoF = new File(to + PART_EXT);
if (ptoF.exists()) {
ptoF.delete();
}
try {
in = new FileInputStream(from);
out = new FileOutputStream(to + PART_EXT);
} catch (FileNotFoundException e) {
throw new IllegalStateException(e.getMessage());
}
// actually copy the file
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0)
{
out.write(buf, 0, len);
}
in.close();
out.close();
// once done, rename the partial file to the final copy
ptoF.renameTo(toF);
}
}

@ -43,6 +43,7 @@ public void start(BundleContext bundleContext) throws Exception
addTestSuite(TestXMLUtils.class);
addTestSuite(TestBase64.class);
addTestSuite(TestFailSafeTransaction.class);
bundleContext.registerService(getClass().getName(), this, properties);
logger.debug("Successfully registered " + getClass().getName());

@ -0,0 +1,229 @@
/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.slick.slickless.util;
import java.io.*;
import net.java.sip.communicator.util.*;
import junit.framework.*;
/**
* Tests for the fail safe transactions
*
* @author Benoit Pradelle
*/
public class TestFailSafeTransaction
extends TestCase
{
/**
* Test data to write in the original file
*/
private static final String origData = "this is a test for the fail safe "
+ "transaction ability in SIP Communicator";
/**
* Test data to add to the file
*/
private static final String addedData = " which is the greatest IM client "
+ "in the world !";
/**
* Test data to never write in the file
*/
private static final String wrongData = "all the file is damaged now !";
/**
* The base for the name of the temp file
*/
private static String tempName = "wzsxedcrfv" + System.currentTimeMillis();
/**
* Tests the commit operation
*/
public void testCommit() {
try {
// setup a temp file
File temp = File.createTempFile(tempName + "a", null);
FileOutputStream out = new FileOutputStream(temp);
out.write(origData.getBytes());
// write a modification during a transaction
FailSafeTransaction trans = new FailSafeTransaction(temp);
trans.beginTransaction();
out.write(addedData.getBytes());
trans.commit();
out.close();
// test if the two writes are ok
// file length
assertEquals("the file hasn't the right size after a commit",
temp.length(),
origData.length() + addedData.length());
FileInputStream in = new FileInputStream(temp);
byte[] buffer = new byte[in.available()];
in.read(buffer);
in.close();
String content = new String(buffer);
// file content
assertEquals("the file content isn't correct",
origData + addedData,
content);
temp.delete();
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
/**
* Tests the rollback operation
*/
public void testRollback() {
try {
// setup a temp file
File temp = File.createTempFile(tempName + "b", null);
FileOutputStream out = new FileOutputStream(temp);
out.write(origData.getBytes());
// write a modification during a transaction
FailSafeTransaction trans = new FailSafeTransaction(temp);
trans.beginTransaction();
out.write(wrongData.getBytes());
trans.rollback();
out.close();
// test if the two writes are ok
// file length
assertEquals("the file hasn't the right size after a commit",
temp.length(),
origData.length());
FileInputStream in = new FileInputStream(temp);
byte[] buffer = new byte[in.available()];
in.read(buffer);
in.close();
String content = new String(buffer);
// file content
assertEquals("the file content isn't correct",
origData,
content);
temp.delete();
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
/**
* Tests if the file is commited when we start a new transaction
*/
public void testCommitOnReOpen() {
try {
// setup a temp file
File temp = File.createTempFile(tempName + "c", null);
FileOutputStream out = new FileOutputStream(temp);
out.write(origData.getBytes());
// write a modification during a transaction
FailSafeTransaction trans = new FailSafeTransaction(temp);
trans.beginTransaction();
out.write(addedData.getBytes());
// this transaction isn't closed, it should commit the changes
trans.beginTransaction();
// just to be sure to clean everything
// the rollback must rollback nothing
trans.rollback();
out.close();
// test if the two writes are ok
// file length
assertEquals("the file hasn't the right size after a commit",
temp.length(),
origData.length() + addedData.length());
FileInputStream in = new FileInputStream(temp);
byte[] buffer = new byte[in.available()];
in.read(buffer);
in.close();
String content = new String(buffer);
// file content
assertEquals("the file content isn't correct",
origData + addedData,
content);
temp.delete();
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
/**
* Tests if the file is rollback-ed if the transaction is never closed
*/
public void testRollbackOnFailure() {
try {
// setup a temp file
File temp = File.createTempFile(tempName + "d", null);
FileOutputStream out = new FileOutputStream(temp);
out.write(origData.getBytes());
// write a modification during a transaction
FailSafeTransaction trans = new FailSafeTransaction(temp);
FailSafeTransaction trans2 = new FailSafeTransaction(temp);
trans.beginTransaction();
out.write(wrongData.getBytes());
// we suppose here that SC crashed without closing properly the
// transaction. When it restarts, the modification must have been
// rollback-ed
trans2.restoreFile();
out.close();
// test if the two writes are ok
// file length
assertEquals("the file hasn't the right size after a commit",
temp.length(),
origData.length());
FileInputStream in = new FileInputStream(temp);
byte[] buffer = new byte[in.available()];
in.read(buffer);
in.close();
String content = new String(buffer);
// file content
assertEquals("the file content isn't correct",
origData,
content);
temp.delete();
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
}
Loading…
Cancel
Save