/*
* JBoss, Home of Professional Open Source
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* 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,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006,
* @author JBoss Inc.
*/
/*
* Copyright (C) 1998, 1999, 2000, 2001,
*
* Arjuna Solutions Limited,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id: ShadowingStore.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.internal.arjuna.objectstore;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SyncFailedException;
import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.objectstore.StateStatus;
import com.arjuna.ats.arjuna.objectstore.StateType;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.arjuna.state.OutputObjectState;
import com.arjuna.ats.arjuna.utils.FileLock;
/**
* A shadowing file store implementation. Each version of the object's state is
* maintained in a separate file. So, the original is stored in one file, and
* the shadow (the updated state) is stored in another. When the transaction
* commits, the shadow is made the original. If the transaction rolls back then
* the shadow is simply removed from the object store.
*
* @author Mark Little (mark@arjuna.com)
* @version $Id: ShadowingStore.java 2342 2006-03-30 13:06:17Z $
* @since 1.0
*/
public class ShadowingStore extends FileSystemStore
{
/**
* @return current state of object. Assumes that genPathName allocates
* enough extra space to allow extra chars to be added. State search
* is ordered OS_SHADOW, OS_UNCOMMITTED_HIDDEN, OS_ORIGINAL,
* OS_COMMITTED_HIDDEN.
*/
public int currentState (Uid objUid, String tName)
throws ObjectStoreException
{
int theState = StateStatus.OS_UNKNOWN;
String path = genPathName(objUid, tName, StateType.OS_SHADOW);
if (exists(path))
{
theState = StateStatus.OS_UNCOMMITTED;
}
else
{
path = path + HIDDINGCHAR;
if (exists(path))
{
theState = StateStatus.OS_UNCOMMITTED_HIDDEN;
}
else
{
path = genPathName(objUid, tName, StateType.OS_ORIGINAL);
if (exists(path))
{
theState = StateStatus.OS_COMMITTED;
}
else
{
path = path + HIDDINGCHAR;
if (exists(path))
{
theState = StateStatus.OS_COMMITTED_HIDDEN;
}
}
}
}
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("ShadowingStore.currentState("+objUid+", "+tName+") - returning "+
StateStatus.stateStatusString(theState));
}
return theState;
}
/**
* Commit a previous write_state operation which was made with the SHADOW
* StateType argument. This is achieved by renaming the shadow and removing
* the hidden version.
*/
public boolean commit_state (Uid objUid, String tName)
throws ObjectStoreException
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("ShadowingStore.commit_state(" + objUid + ", " + tName + ")");
}
boolean result = false;
if (tName != null)
{
String shadow = null;
String filename = null;
int state = currentState(objUid, tName);
if ((state == StateStatus.OS_UNCOMMITTED_HIDDEN)
|| (state == StateStatus.OS_UNCOMMITTED))
{
shadow = genPathName(objUid, tName, StateType.OS_SHADOW);
filename = genPathName(objUid, tName, StateType.OS_ORIGINAL);
if (state == StateStatus.OS_UNCOMMITTED_HIDDEN)
{
/* maintain hidden status on rename */
shadow = shadow + HIDDINGCHAR;
filename = filename + HIDDINGCHAR;
}
File shadowState = new File(shadow);
File originalState = new File(filename);
/*
* We need to do this because rename will not overwrite an
* existing file in Windows, as it will in Unix. It is safe to
* do so since we have written the shadow.
*/
result = renameFromTo(shadowState, originalState);
if (!result) {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_2(shadow, filename);
}
else
{
super.addToCache(filename);
super.removeFromCache(shadow);
}
shadowState = null;
originalState = null;
}
else
result = true;
}
else
throw new ObjectStoreException(
"ShadowStore::commit_state - "
+ tsLogger.i18NLogger.get_objectstore_notypenameuid()
+ objUid);
return result;
}
/**
* Hide/reveal an object regardless of state. Hidden objects cannot be read
* but they can be written (Crash recovery needs this).
*/
public boolean hide_state (Uid objUid, String tName)
throws ObjectStoreException
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("ShadowingStore.hide_state(" + objUid + ", " + tName + ")");
}
boolean hiddenOk = true;
int state = currentState(objUid, tName);
String path1 = null;
String path2 = null;
switch (state)
{
case StateStatus.OS_UNCOMMITTED_HIDDEN:
case StateStatus.OS_COMMITTED_HIDDEN:
break;
case StateStatus.OS_COMMITTED:
{
path1 = genPathName(objUid, tName, StateType.OS_ORIGINAL);
path2 = new String(path1) + HIDDINGCHAR;
File newState = new File(path1);
File oldState = new File(path2);
if (renameFromTo(newState, oldState))
{
super.removeFromCache(path1);
super.addToCache(path2);
}
else {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_3(newState.getName(), oldState.getName());
}
newState = null;
oldState = null;
break;
}
case StateStatus.OS_UNCOMMITTED:
{
path1 = genPathName(objUid, tName, StateType.OS_SHADOW);
path2 = new String(path1) + HIDDINGCHAR;
File newState = new File(path1);
File oldState = new File(path2);
if (renameFromTo(newState, oldState))
{
super.removeFromCache(path1);
super.addToCache(path2);
}
else {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_3(newState.getName(), oldState.getName());
}
newState = null;
oldState = null;
break;
}
default:
hiddenOk = false;
}
return hiddenOk;
}
public boolean reveal_state (Uid objUid, String tName)
throws ObjectStoreException
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("ShadowingStore.reveal_state(" + objUid + ", " + tName + ")");
}
boolean revealedOk = true;
int state = currentState(objUid, tName);
String path1 = null;
String path2 = null;
switch (state)
{
case StateStatus.OS_UNCOMMITTED_HIDDEN:
{
path1 = genPathName(objUid, tName, StateType.OS_SHADOW);
path2 = new String(path1) + HIDDINGCHAR;
File newState = new File(path2);
File oldState = new File(path1);
if (renameFromTo(newState, oldState))
{
super.removeFromCache(path2);
super.addToCache(path1);
}
else {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_4(newState.getName(), oldState.getName());
}
newState = null;
oldState = null;
break;
}
case StateStatus.OS_COMMITTED_HIDDEN:
{
path1 = genPathName(objUid, tName, StateType.OS_ORIGINAL);
path2 = new String(path1) + HIDDINGCHAR;
File newState = new File(path2);
File oldState = new File(path1);
if (renameFromTo(newState, oldState))
{
super.removeFromCache(path2);
super.addToCache(path1);
}
else {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_4(newState.getName(), oldState.getName());
}
newState = null;
oldState = null;
break;
}
case StateStatus.OS_COMMITTED:
case StateStatus.OS_UNCOMMITTED:
break;
default:
revealedOk = false;
}
return revealedOk;
}
/**
* @return the file name for the state of the object identified by the Uid
* and TypeName. If the StateType argument is OS_SHADOW then the Uid
* part of the name includes # characters. Builds on lower level
* genPathName which allocates enough slop to accomodate the extra
* chars.
*/
protected String genPathName (Uid objUid, String tName, int ft)
throws ObjectStoreException
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("ShadowingStore.genPathName(" + objUid + ", " + tName + ", " + StateType.stateTypeString(ft) + ")");
}
String fname = super.genPathName(objUid, tName, ft);
if (ft == StateType.OS_SHADOW)
fname = fname + SHADOWCHAR;
return fname;
}
protected String revealedId (String name)
{
int index = name.indexOf(HIDDINGCHAR);
if (index == -1)
index = name.indexOf(SHADOWCHAR);
if (index != -1)
return name.substring(0, index);
else
return name;
}
protected InputObjectState read_state (Uid objUid, String tName, int ft)
throws ObjectStoreException
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("ShadowingStore.read_state(" + objUid + ", " + tName + ", " + StateType.stateTypeString(ft) + ")");
}
InputObjectState new_image = null;
if (tName != null)
{
int state = currentState(objUid, tName);
if ((state == StateStatus.OS_COMMITTED)
|| (state == StateStatus.OS_UNCOMMITTED))
{
/*
* Is the current state the same as that requested?
*/
if (((state == StateStatus.OS_COMMITTED) && (ft != StateType.OS_ORIGINAL))
|| ((state == StateStatus.OS_UNCOMMITTED) && (ft != StateType.OS_SHADOW)))
{
/*
* Print out a warning/info if the state has changed to help explain the null
* value that is returned.
*/
tsLogger.logger.info("Object state "+objUid+" for type "+tName+" has changed on disk from what was expected.");
return null;
}
String fname = genPathName(objUid, tName, ft);
File fd = openAndLock(fname, FileLock.F_RDLCK, false);
if (fd != null)
{
int imageSize = (int) fd.length();
byte[] buffer = new byte[imageSize];
FileInputStream ifile = null;
try
{
ifile = new FileInputStream(fd);
}
catch (FileNotFoundException e)
{
closeAndUnlock(fd, ifile, null);
tsLogger.logger.info("ObjectStore record was deleted during restoration, users should not deleted records manually: " + fd.getAbsolutePath(), e);
return null;
}
/* now try to read the actual image out of the store */
try
{
if ((buffer != null)
&& (ifile.read(buffer, 0, imageSize) == imageSize))
{
new_image = new InputObjectState(objUid, tName,
buffer);
}
else {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_7();
}
}
catch (IOException e)
{
closeAndUnlock(fd, ifile, null);
throw new ObjectStoreException(
"ShadowingStore::read_state failed: " + e, e);
}
if (!closeAndUnlock(fd, ifile, null)) {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_8(fname);
}
}
else
{
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_5(fname);
}
}
else
{
/*
* We tried to read either a committed or uncommitted state but neither was present.
* This may be an error but we can't tell at the level of the ObjectStore and so
* SHOULD NOT issue a warning message by default. The application can figure this out because
* we're going to return a null InputObjectState anyway!
*
* Changing this from a warning to trace so that anyone who needs to check this can do so. But
* it shouldn't be the default action!
*
* https://issues.jboss.org/browse/JBTM-2593
*/
if (tsLogger.logger.isTraceEnabled())
tsLogger.logger.trace("ShadowingStore.read_state could not find committed or uncommitted state for "+objUid+
" instead found state "+StateStatus.stateStatusString(state));
}
}
else
throw new ObjectStoreException(
"ShadowStore::read_state - "
+ tsLogger.i18NLogger.get_objectstore_notypenameuid()
+ objUid);
return new_image;
}
protected boolean remove_state (Uid objUid, String name, int ft)
throws ObjectStoreException
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("ShadowingStore.remove_state(" + objUid + ", " + name + ", " + StateType.stateTypeString(ft) + ")");
}
boolean removeOk = true;
if (name != null)
{
int state = currentState(objUid, name);
if ((state == StateStatus.OS_COMMITTED)
|| (state == StateStatus.OS_UNCOMMITTED))
{
String fname = genPathName(objUid, name, ft);
File fd = openAndLock(fname, FileLock.F_WRLCK, false);
if (fd != null)
{
if (!fd.canWrite())
{
removeOk = false;
if (ft == StateType.OS_ORIGINAL) {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_9(objUid, name);
if (!fd.exists()) {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_10(objUid, name);
}
}
}
else
{
if (!fd.delete())
{
removeOk = false;
if (ft == StateType.OS_ORIGINAL) {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_11(fname);
}
}
}
closeAndUnlock(fd, null, null);
}
else {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_12(objUid);
removeOk = false;
}
if (removeOk)
super.removeFromCache(fname);
}
else
{
removeOk = false;
if (state == StateStatus.OS_UNKNOWN)
tsLogger.i18NLogger.info_objectstore_ShadowingStore_14(objUid, name);
else
tsLogger.i18NLogger.info_objectstore_ShadowingStore_15(objUid, name);
}
}
else {
removeOk = false;
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_17(objUid);
}
return removeOk;
}
/**
* write_state saves the ObjectState in a file named by the type and Uid of
* the ObjectState. If the second argument is SHADOW, then the file name is
* different so that a subsequent commit_state invocation will rename the
* file.
*/
protected boolean write_state (Uid objUid, String tName,
OutputObjectState state, int ft) throws ObjectStoreException
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("ShadowingStore.write_state(" + objUid + ", " + tName + ", " + StateType.stateTypeString(ft) + ")");
}
if (tName != null)
{
String fname = genPathName(objUid, tName, ft);
File fd = openAndLock(fname, FileLock.F_WRLCK, true);
int imageSize = (int) state.length();
if (fd == null) {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_18(fname);
return false;
}
FileOutputStream ofile = null;
if (imageSize > 0)
{
try
{
ofile = new FileOutputStream(fd);
ofile.write(state.buffer(), 0, imageSize);
if (synchronousWrites())
{
// must flush any in-memory buffering prior to sync
ofile.flush();
FileDescriptor fileDesc = ofile.getFD(); // assume it's
// valid!
fileDesc.sync();
}
}
catch (SyncFailedException e)
{
closeAndUnlock(fd, null, ofile);
throw new ObjectStoreException(
"ShadowingStore::write_state() - write failed to sync for "
+ fname, e);
}
catch (FileNotFoundException e)
{
closeAndUnlock(fd, null, ofile);
e.printStackTrace();
throw new ObjectStoreException(
"ShadowingStore::write_state() - write failed to locate file "
+ fname + ": " + e, e);
}
catch (IOException e)
{
closeAndUnlock(fd, null, ofile);
e.printStackTrace();
throw new ObjectStoreException(
"ShadowingStore::write_state() - write failed for "
+ fname + ": " + e, e);
}
}
if (!closeAndUnlock(fd, null, ofile)) {
tsLogger.i18NLogger.warn_objectstore_ShadowingStore_19(fname);
}
super.addToCache(fname);
return true;
}
else
throw new ObjectStoreException(
"ShadowStore::write_state - "
+ tsLogger.i18NLogger.get_objectstore_notypenameuid()
+ objUid);
}
public ShadowingStore(ObjectStoreEnvironmentBean objectStoreEnvironmentBean) throws ObjectStoreException
{
super(objectStoreEnvironmentBean);
}
public static final char HIDDINGCHAR = '#';
public static final char SHADOWCHAR = '!';
}