/*
* 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: StateManager.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.arjuna;
import com.arjuna.ats.arjuna.coordinator.*;
import com.arjuna.ats.arjuna.objectstore.ObjectStore;
import com.arjuna.ats.arjuna.common.*;
import com.arjuna.ats.arjuna.state.*;
import com.arjuna.ats.arjuna.gandiva.*;
import com.arjuna.ats.arjuna.utils.Utility;
import com.arjuna.ats.arjuna.exceptions.FatalError;
import com.arjuna.ats.internal.arjuna.template.*;
import java.io.PrintWriter;
import java.util.*;
import com.arjuna.common.util.logging.*;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.logging.FacilityCode;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
import java.io.IOException;
/**
* The root of the Arjuna class hierarchy. This class provides
* state management facilities than can be automatically used by
* other classes by inheritance.
*
* @author Mark Little (mark@arjuna.com)
* @version $Id: StateManager.java 2342 2006-03-30 13:06:17Z $
* @since JTS 1.0.
*
* @message com.arjuna.ats.arjuna.StateManager_1 [com.arjuna.ats.arjuna.StateManager_1] - StateManager::terminate() should be invoked in every destructor
* @message com.arjuna.ats.arjuna.StateManager_2 [com.arjuna.ats.arjuna.StateManager_2] - Activate of object with id = {0} and type {1} unexpectedly failed"
* @message com.arjuna.ats.arjuna.StateManager_3 [com.arjuna.ats.arjuna.StateManager_3] - StateManager::deactivate - object store error
* @message com.arjuna.ats.arjuna.StateManager_4 [com.arjuna.ats.arjuna.StateManager_4] - StateManager::deactivate - save_state error
* @message com.arjuna.ats.arjuna.StateManager_5 [com.arjuna.ats.arjuna.StateManager_5] - StateManager::destroy for object-id {0}
* @message com.arjuna.ats.arjuna.StateManager_6 [com.arjuna.ats.arjuna.StateManager_6] - StateManager.destroy - failed to add abstract record.
* @message com.arjuna.ats.arjuna.StateManager_7 [com.arjuna.ats.arjuna.StateManager_7] - StateManager.destroy - caught object store exception: {0}
* @message com.arjuna.ats.arjuna.StateManager_8 [com.arjuna.ats.arjuna.StateManager_8] - StateManager.destroy - called on non-persistent or new object!
* @message com.arjuna.ats.arjuna.StateManager_9 [com.arjuna.ats.arjuna.StateManager_9] - StateManager.restore_state - could not find StateManager state in object state!
* @message com.arjuna.ats.arjuna.StateManager_10 [com.arjuna.ats.arjuna.StateManager_10] - StateManager::modified() invocation on an object whose state has not been restored - activating object
* @message com.arjuna.ats.arjuna.StateManager_11 [com.arjuna.ats.arjuna.StateManager_11] - Delete called on object with uid {0} and type {1} within atomic action.
* @message com.arjuna.ats.arjuna.StateManager_12 [com.arjuna.ats.arjuna.StateManager_12] - StateManager.cleanup - could not save_state from terminate!
* @message com.arjuna.ats.arjuna.StateManager_13 [com.arjuna.ats.arjuna.StateManager_13] - Attempt to use volatile store.
* @message com.arjuna.ats.arjuna.StateManager_14 [com.arjuna.ats.arjuna.StateManager_14] - Volatile store not implemented!
* @message com.arjuna.ats.arjuna.StateManager_15 [com.arjuna.ats.arjuna.StateManager_15] - Invalid object state.
*/
public class StateManager
{
/**
* These methods must be used by a derived class. They
* are responsible for packing and unpacking an object's state
* to/from a state buffer. StateManager calls them at appropriate
* times during the lifetime of the object, and may then pass the
* buffer to a persistent object store for saving.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
public boolean save_state (OutputObjectState os, int ot)
{
/*
* Only pack additional information if this is for a
* persistent state modification.
*/
if (ot == ObjectType.ANDPERSISTENT)
{
try
{
BasicAction action = BasicAction.Current();
if (action == null)
packHeader(os, null, Utility.getProcessUid());
else
packHeader(os, action.get_uid(), Utility.getProcessUid());
}
catch (IOException e)
{
return false;
}
}
return true;
}
/**
* These methods must be provided by a derived class. They
* are responsible for packing and unpacking an object's state
* to/from a state buffer. StateManager calls them at appropriate
* times during the lifetime of the object, and may then pass the
* buffer to a persistent object store for saving.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
public boolean restore_state (InputObjectState os, int ot)
{
if (ot == ObjectType.ANDPERSISTENT)
{
try
{
Uid txId = new Uid(Uid.nullUid());
Uid processUid = new Uid(Uid.nullUid());
unpackHeader(os, txId, processUid);
}
catch (IOException e)
{
return false;
}
}
return true;
}
/**
* Destructor.
*/
public void finalize () throws Throwable
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.DESTRUCTORS, VisibilityLevel.VIS_PUBLIC,
FacilityCode.FAC_STATE_MAN, "StateManager.finalize() for object-id "+get_uid() + " type " + type());
}
if (currentStatus == ObjectStatus.ACTIVE_NEW)
{
BasicAction action = BasicAction.Current();
if ((action != null) && (action.status() == ActionStatus.RUNNING))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_1");
cleanup(false);
}
}
}
/**
* This operation activates an object. Activation of an object may
* entail loading its passive state from the object store and unpacking
* it into the memory resident form, or it may simply be a no-op.
* Full activation is only necessary if the object is currently marked
* as being PASSIVE (that is, the object was constructed as being of
* type ANDPERSISTENT with an existing uid and has not already been
* activated).
*
* Objects that are not of type ANDPERSISTENT or are persistent but
* have not yet been saved in an object store (so-called new persistent
* objects) are unaffected by this function.
* Returns false if PASSIVE object cannot be loaded from object store,
* true otherwise.
*
* The root of the object store is taken as <code>null</code>.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
* @see com.arjuna.ats.arjuna.objectstore.ObjectStore
*/
public boolean activate ()
{
return activate(null);
}
/**
* This operation activates an object. Activation of an object may
* entail loading its passive state from the object store and unpacking
* it into the memory resident form, or it may simply be a no-op.
* Full activation is only necessary if the object is currently marked
* as being PASSIVE (that is, the object was constructed as being of
* type ANDPERSISTENT with an existing uid and has not already been
* activated).
*
* Objects that are not of type ANDPERSISTENT or are persistent but
* have not yet been saved in an object store (so-called new persistent
* objects) are unaffected by this function.
* Returns false if PASSIVE object cannot be loaded from object store,
* true otherwise.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
* @see com.arjuna.ats.arjuna.objectstore.ObjectStore
*/
public synchronized boolean activate (String rootName)
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_STATE_MAN,
"StateManager::activate( "+((rootName != null) ? rootName : "null")+") for object-id "+objectUid);
}
if (myType == ObjectType.NEITHER)
{
return true;
}
if (currentStatus == ObjectStatus.DESTROYED)
return false;
BasicAction action = null;
int oldStatus = currentStatus;
boolean result = true; /* assume 'succeeds' */
boolean forceAR = false;
/*
* Check if this action has logged its presence before. If not we force
* creation of an ActivationRecord so that each thread/action tree has
* an ActivationRecord in it. This allows us to passivate the object
* when the last thread has finished with it, i.e., when the last
* ActivationRecord is gone.
*/
action = BasicAction.Current();
if ((action != null) && (action.status() == ActionStatus.RUNNING))
{
/*
* Only check for top-level action. This is sufficient because
* activation records are propagated to the parent on nested
* transaction commit, and dropped when the (nested) action
* aborts. Thus, an object remains active as long as a single
* ActivationRecord is being used, and we don't need to create a
* new record for each transaction in the same hierarchy. Once
* activated, the object remains active until the action commits
* or aborts (at which time it may be passivated, and then
* reactivated later by the creation of a new ActivationRecord.)
*/
synchronized (usingActions)
{
if (usingActions.get(action.topLevelAction().get_uid()) == null)
{
usingActions.put(action.topLevelAction().get_uid(), action.topLevelAction());
forceAR = true;
}
}
}
if (forceAR || (currentStatus == ObjectStatus.PASSIVE) ||
(currentStatus == ObjectStatus.PASSIVE_NEW))
{
/*
* If object is recoverable only, then no need to set up
* the object store.
*/
if (loadObjectState())
{
setupStore(rootName);
}
/* Only really activate if object is PASSIVE */
if (currentStatus == ObjectStatus.PASSIVE)
{
/*
* If the object is shared between different processes, then we
* must load the state each time a top-level action accesses
* it. Otherwise we can continue to use the last state as in
* Dharma/ArjunaII.
*/
if (loadObjectState())
{
InputObjectState oldState = null;
try
{
oldState = objectStore.read_committed(objectUid, type());
}
catch (ObjectStoreException e)
{
oldState = null;
}
if (oldState != null)
{
if ((result = restore_state(oldState, ObjectType.ANDPERSISTENT)))
{
currentStatus = ObjectStatus.ACTIVE;
}
oldState = null;
}
else
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_2",
new Object[] {objectUid, type()});
}
return false;
}
}
else
{
if (currentStatus == ObjectStatus.PASSIVE_NEW)
currentStatus = ObjectStatus.ACTIVE_NEW;
else
currentStatus = ObjectStatus.ACTIVE;
}
}
else
{
if (currentStatus == ObjectStatus.PASSIVE_NEW)
currentStatus = ObjectStatus.ACTIVE_NEW;
else
currentStatus = ObjectStatus.ACTIVE;
}
/*
* Create ActivationRecord if status changed Passive->Active or if
* object is a new persistent object.
*/
if (forceAR || ((currentStatus == ObjectStatus.ACTIVE) ||
(currentStatus == ObjectStatus.PASSIVE_NEW)) && (action != null))
{
int arStatus = AddOutcome.AR_ADDED;
ActivationRecord ar = new ActivationRecord(oldStatus, this, action.topLevelAction());
if ((arStatus = action.add(ar)) != AddOutcome.AR_ADDED)
{
ar = null;
if (forceAR)
{
synchronized (usingActions)
{
usingActions.remove(action.topLevelAction().get_uid());
}
}
if (arStatus == AddOutcome.AR_REJECTED)
result = false;
}
else
{
/*
* We never reset activated, so we can optimise state
* loading/unloading in the case of SINGLE object model
*/
currentlyActivated = activated = true;
}
}
}
return result;
}
/**
* This operation deactivates a persistent object.
* It behaves in a similar manner to the activate operation, but has an
* extra argument which defines whether the object's state should be
* committed or written as a shadow.
*
* The root of the object store is <code>null</code>. It is assumed
* that this is being called during a transaction commit.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
public boolean deactivate ()
{
return deactivate(null, true);
}
/**
* This operation deactivates a persistent object.
* It behaves in a similar manner to the activate operation, but has an
* extra argument which defines whether the object's state should be
* commited now or not.
*
* It is assumed that this is being called during a transaction commit.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
public boolean deactivate (String rootName)
{
return deactivate(rootName, true);
}
/**
* This operation deactivates a persistent object.
* It behaves in a similar manner to the activate operation, but has an
* extra argument which defines whether the object's state should be
* commited now or not.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
public synchronized boolean deactivate (String rootName, boolean commit)
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_STATE_MAN,
"StateManager::deactivate("+((rootName != null) ? rootName : "null")+", "+commit+") for object-id " +objectUid);
}
boolean result = false;
if ((currentlyActivated && (myType == ObjectType.ANDPERSISTENT)) || loadObjectState())
{
setupStore(rootName);
if ((currentStatus == ObjectStatus.ACTIVE_NEW) || (currentStatus == ObjectStatus.ACTIVE))
{
String tn = type();
OutputObjectState newState = new OutputObjectState(objectUid, tn);
/*
* Call save_state again to possibly get a persistent
* representation of the object.
*/
if (save_state(newState, myType))
{
try
{
if (commit)
result = objectStore.write_committed(objectUid, tn, newState);
else
result = objectStore.write_uncommitted(objectUid, tn, newState);
}
catch (ObjectStoreException e)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_3",e);
result = false;
}
}
else
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_4");
}
/*
* Not needed any more because activation record does this when
* all actions are forgotten.
*
* if (result)
* currentStatus = ObjectStatus.PASSIVE;
*
*/
}
}
else
{
result = true;
}
return result;
}
/**
* @return the object's current status (active, passive, ...)
*/
public synchronized int status ()
{
return currentStatus;
}
/**
* @return the type of the object (persistent, recoverable, ...)
*/
public synchronized int ObjectType ()
{
return myType;
}
/**
* @return the object's unique identifier.
*/
public final Uid get_uid ()
{
return objectUid;
}
/**
* Destroy the object (e.g., remove its state from the persistent
* store.)
*
* Calls to destroy for volatile objects (ones not maintained within the
* volatile object store) are ignored, and FALSE is returned.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
public synchronized boolean destroy ()
{
if (tsLogger.arjLoggerI18N.debugAllowed())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
FacilityCode.FAC_STATE_MAN,
"com.arjuna.ats.arjuna.StateManager_5", new Object[]{objectUid});
}
boolean result = false;
if (objectStore != null)
{
BasicAction action = BasicAction.Current();
if (action != null) // add will fail if the status is wrong!
{
DisposeRecord dr = new DisposeRecord(objectStore, this);
if (action.add(dr) != AddOutcome.AR_ADDED)
{
dr = null;
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_6");
}
}
else
result = true;
}
else
{
try
{
result = objectStore.remove_committed(get_uid(), type());
/*
* Once destroyed, we can never use the object again.
*/
if (result)
destroyed();
}
catch (Exception e)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_7", e);
result = false;
}
}
}
else
{
/*
* Not a persistent object!
*/
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_8");
}
}
return result;
}
/**
* The following function disables recovery for an object by setting
* the ObjectType to NEITHER (RECOVERABLE or ANDPERSISTENT).
* The value of this variable is checked in the modified operation
* so that no recovery information is created if myType is set to NEITHER.
*/
public synchronized void disable ()
{
myType = ObjectType.NEITHER;
}
/**
* Print out information about the object.
*/
public void print (PrintWriter strm)
{
strm.println("Uid: "+objectUid);
strm.println("Type: "+type());
}
/**
* The object's type. Derived classes should override this to
* reflect their type structure. Typically this string is used for
* locating the object state in an object store, and reflects the
* hierarchy structure of the object.
*/
public String type ()
{
return "/StateManager";
}
/**
* @return the root of the object store this instance will
* use if it has to save the state of the object to storage.
*/
public final String getStoreRoot ()
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
FacilityCode.FAC_STATE_MAN, "StateManager::getStoreRoot ()");
}
return storeRoot;
}
/**
* @return the object store this instance will used if it has
* to save the state of the object to storage.
*/
public ObjectStore getStore ()
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
FacilityCode.FAC_STATE_MAN, "StateManager::getStore ()");
}
if (objectStore == null)
setupStore();
return objectStore;
}
/**
* @return object specific attributes (e.g., object store type).
*/
public Object attributes ()
{
return smAttributes;
}
/**
* Pack the necessary information for crash recovery.
*
* @since JTS 2.1.
*/
protected void packHeader (OutputObjectState os, Uid txId,
Uid processUid) throws IOException
{
/*
* If there is a transaction present than pack the process Uid of
* this JVM and the tx id. Otherwise pack a null Uid.
*/
try
{
// pack the marker first.
os.packString(StateManager.marker);
/*
* Only pack something if there is a transaction. Otherwise
* the application is driving this object manually, and all
* bets are off!
*/
if (txId != null)
{
txId.pack(os);
processUid.pack(os);
}
else
Uid.nullUid().pack(os);
}
catch (IOException ex)
{
throw ex;
}
catch (Exception e)
{
throw new IOException(e.toString());
}
}
/**
* Unpack the crash recovery state header information and return it.
*
* @since JTS 2.1.
* @param txId the identity of the transaction that last caused the
* state to be written to the object store.
* @return the <code>Uid</code> of the process that last managed this
* state.
*/
protected void unpackHeader (InputObjectState os, Uid txId,
Uid processUid) throws IOException
{
try
{
String myState = os.unpackString();
if (myState.equals(StateManager.marker))
{
txId.unpack(os);
/*
* Is there going to be a Uid to unpack?
*/
if (!txId.equals(Uid.nullUid()))
processUid.unpack(os);
}
else
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_9");
}
throw new IOException(tsLogger.log_mesg.getString("com.arjuna.ats.arjuna.StateManager_15"));
}
}
catch (IOException ex)
{
throw ex;
}
catch (Exception e)
{
throw new IOException(e.toString());
}
}
/**
* The following function checks to see if the object is going out of
* scope while an action is still running.
*/
protected void terminate ()
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN,
"StateManager::terminate() for object-id "+get_uid());
}
cleanup(true);
}
protected final synchronized void setStatus (int s)
{
currentStatus = s;
}
/**
* Create object with specific uid. This constructor
* is primarily used when recreating an existing object. The object type
* is set to 'ANDPERSISTENT' this is equivalent to invoking
* persist in the object constructor.
*/
protected StateManager (Uid objUid)
{
this(objUid, ObjectType.ANDPERSISTENT, null);
}
protected StateManager (Uid objUid, ObjectName attr)
{
this(objUid, ObjectType.ANDPERSISTENT, attr);
}
protected StateManager (Uid objUid, int ot)
{
this(objUid, ot, null);
}
protected StateManager (Uid objUid, int ot, ObjectName objName)
{
objectName = objName;
parseObjectName();
if (ot == ObjectType.NEITHER)
{
modifyingActions = null;
usingActions = null;
}
else
{
modifyingActions = new Hashtable();
usingActions = new Hashtable();
}
activated = false;
currentlyActivated = false;
currentStatus = ObjectStatus.PASSIVE;
initialStatus = ObjectStatus.PASSIVE;
myType = ot;
objectStore = null;
storeRoot = null;
objectUid = objUid;
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN, "StateManager::StateManager( "+get_uid()+" )");
}
}
protected StateManager ()
{
this(ObjectType.RECOVERABLE, null);
}
protected StateManager (int ot)
{
this(ot, null);
}
protected StateManager (int ot, ObjectName objName)
{
objectName = objName;
parseObjectName();
if (ot == ObjectType.NEITHER)
{
modifyingActions = null;
usingActions = null;
}
else
{
modifyingActions = new Hashtable();
usingActions = new Hashtable();
}
activated = false;
currentlyActivated = false;
currentStatus = (((smAttributes.objectModel == ObjectModel.SINGLE) && (ot == ObjectType.RECOVERABLE)) ? ObjectStatus.ACTIVE : ObjectStatus.PASSIVE_NEW);
initialStatus = currentStatus;
myType = ot;
objectStore = null;
storeRoot = null;
objectUid = new Uid();
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN, "StateManager::StateManager( "+ot+" )");
}
}
protected StateManager (ObjectName objName)
{
objectName = objName;
parseObjectName();
if (myType == ObjectType.NEITHER)
{
modifyingActions = null;
usingActions = null;
}
else
{
modifyingActions = new Hashtable();
usingActions = new Hashtable();
}
activated = false;
currentlyActivated = false;
currentStatus = ObjectStatus.PASSIVE;
initialStatus = ObjectStatus.PASSIVE;
objectStore = null;
storeRoot = null;
if (objectUid == null)
objectUid = new Uid();
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN, "StateManager::StateManager( "+objName+" )");
}
}
/*
* Protected non-virtual functions.
*/
/**
* The object's state is about to be modified, and StateManager should
* take a snapshot of the state if the object is being used within
* a transaction.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
protected synchronized boolean modified ()
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN, "StateManager::modified() for object-id "+get_uid());
}
BasicAction action = BasicAction.Current();
RecoveryRecord record = null;
if ((myType == ObjectType.NEITHER) || (currentStatus == ObjectStatus.DESTROYED)) /* NEITHER => no recovery info */
{
return true;
}
if (currentStatus == ObjectStatus.PASSIVE)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_10");
activate();
}
/*
* Need not have gone through active if new object.
*/
if (currentStatus == ObjectStatus.PASSIVE_NEW)
currentStatus = ObjectStatus.ACTIVE_NEW;
if (action != null)
{
/*
* Check if this is the first call to modified in this action.
* BasicList insert returns FALSE if the entry is already
* present.
*/
synchronized (modifyingActions)
{
if ((modifyingActions.size() > 0) &&
(modifyingActions.get(action.get_uid()) != null))
{
return true;
}
else
modifyingActions.put(action.get_uid(), action);
}
/* If here then its a new action */
OutputObjectState state = new OutputObjectState(objectUid, type());
int rStatus = AddOutcome.AR_ADDED;
if (save_state(state, ObjectType.RECOVERABLE))
{
if ((myType == ObjectType.RECOVERABLE) && (smAttributes.objectModel == ObjectModel.SINGLE))
{
record = new RecoveryRecord(state, this);
}
else
record = new PersistenceRecord(state, objectStore, this);
if ((rStatus = action.add(record)) != AddOutcome.AR_ADDED)
{
synchronized(modifyingActions)
{
modifyingActions.remove(action.get_uid()); // remember to unregister with action
}
record = null;
return false;
}
}
else
return false;
}
return true;
}
/**
* The persist function changes the type of the object from RECOVERABLE
* to ANDPERSISTENT.
*
* No changes are made unless the status of the object is ACTIVE, so it
* is not possible to change the type of the object if it has been
* modified.
*/
protected final synchronized void persist ()
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN, "StateManager::persist() for object-id "+get_uid());
}
if (currentStatus == ObjectStatus.ACTIVE)
{
currentStatus = ObjectStatus.PASSIVE_NEW;
myType = ObjectType.ANDPERSISTENT;
}
}
/**
* Object cleanup.
* Attempt sane cleanup when object is deleted. Handle perverse cases
* where multiple actions are still active as object dies.
*
* @param fromTerminate indicates whether this method is being called
* from the <code>terminate</code> method, or from elsewhere.
* @see StateManager#terminate
*/
protected final synchronized void cleanup (boolean fromTerminate)
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN, "StateManager::cleanup() for object-id "+get_uid());
}
if (myType == ObjectType.NEITHER)
return;
BasicAction action = null;
synchronized (usingActions)
{
if (usingActions != null)
{
Enumeration e = usingActions.keys();
while (e.hasMoreElements())
{
action = (BasicAction) usingActions.remove(e.nextElement());
if (action != null)
{
/*
* Pop actions off using list.
*
* Don't check if action is running below so that
* cadavers can be created in commit protocol too.
*/
AbstractRecord record = null;
int rStatus = AddOutcome.AR_ADDED;
if ((currentStatus == ObjectStatus.ACTIVE_NEW) ||
(currentStatus == ObjectStatus.ACTIVE))
{
OutputObjectState state = null;
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_11",
new Object[]{objectUid, type()});
}
/*
* If we get here via terminate its ok to do
* a save_state.
*/
if (fromTerminate)
{
state = new OutputObjectState(objectUid, type());
if (!save_state(state, myType))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_12");
/* force action abort */
action.preventCommit();
}
}
else
{
/* otherwise force action abort */
action.preventCommit();
}
/*
* This should be unnecessary - but just in
* case.
*/
setupStore(storeRoot);
record = new CadaverRecord(state, objectStore, this);
if ((rStatus = action.add(record)) != AddOutcome.AR_ADDED)
record = null;
}
if (currentlyActivated && (currentStatus != ObjectStatus.DESTROYED))
{
record = new CadaverActivationRecord(this);
if ((rStatus = action.add(record)) == AddOutcome.AR_ADDED)
{
currentStatus = ObjectStatus.PASSIVE;
}
else
record = null;
}
}
}
}
}
/*
* Here the object must be either RECOVERABLE or PERSISTENT.
* Whether or not an action exists we still need to reset the
* object status to avoid possible later confusion
* What it gets set to is not important really as long as it gets
* changed from ACTIVE_NEW which might cause any running action to
* abort.
*/
if (currentStatus == ObjectStatus.ACTIVE_NEW)
{
if ((myType == ObjectType.RECOVERABLE) && (smAttributes.objectModel == ObjectModel.SINGLE))
{
currentStatus = ObjectStatus.ACTIVE;
}
else
{
currentStatus = ObjectStatus.PASSIVE;
}
}
currentlyActivated = false;
}
/**
* Make sure the object store is set up, if required.
* The root of the object store is assumed to be <code>null</code>.
*/
protected final void setupStore ()
{
setupStore(null);
}
/**
* Make sure the object store is set up, if required.
*
* @param rootName indicates the root of the object store.
*/
protected synchronized void setupStore (String rootName)
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_STATE_MAN,
"StateManager::setupStore ( "+((rootName != null) ? rootName : "null")+" )");
}
if (!loadObjectState())
return;
/*
* Already setup?
* Assume type will not change once object is created.
*/
if (objectStore != null)
return;
if (rootName == null)
{
if ( smAttributes.objectStoreRoot != null )
{
rootName = smAttributes.objectStoreRoot;
}
else
{
rootName = "";
}
}
/* Check if we have a store */
if (storeRoot != null)
{
/* Attempting to reuse it ? */
if ((rootName == null) || (rootName.compareTo("") == 0) ||
(rootName.compareTo(storeRoot) == 0))
{
return;
}
/* No - destroy old store and create new */
objectStore = null;
}
if (rootName == null)
{
rootName = "";
}
/* Create store now */
storeRoot = new String(rootName);
if ((myType == ObjectType.ANDPERSISTENT) || (myType == ObjectType.NEITHER))
{
/*
* If null, default object store type is obtained by the
* interface.
*/
int sharedStatus = ((smAttributes.objectModel == ObjectModel.SINGLE) ? ObjectStore.OS_UNSHARED : ObjectStore.OS_SHARED);
ObjectName osObjName = null;
if (objectName != null)
{
try
{
osObjName = objectName.getObjectNameAttribute(ArjunaNames.ObjectStore_implementationObjectName());
}
catch (Exception ex)
{
osObjName = null;
}
}
if (osObjName == null)
{
objectStore = new ObjectStore(smAttributes.objectStoreType, storeRoot, sharedStatus);
}
else
{
objectStore = new ObjectStore(smAttributes.objectStoreType, osObjName);
}
}
else
{
/*
* Currently we should never get here!
* However, since Arjuna supports a volatile (in memory)
* object store we will also eventually, probably through a set
* of native methods.
*/
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.StateManager_13");
throw new FatalError(tsLogger.log_mesg.getString("com.arjuna.ats.arjuna.StateManager_14"));
// objectStore = new ObjectStore(ArjunaNames.Implementation_ObjectStore_VolatileStore(), storeRoot);
}
/*
* Do any work needed to initialise the object store.
* Really only makes sense for replicated object store where
* we attempt to do early binding to the replicas.
*/
objectStore.initialise(get_uid(), type());
}
/**
* Do we need to load the object's state?
*
* @return <code>true</code> if the object state should be loaded,
* <code>false</code> otherwise.
*/
protected final boolean loadObjectState ()
{
boolean load = (smAttributes.objectModel != ObjectModel.SINGLE);
/*
* MULTIPLE object model requires loading of state every
* time, even if we are RECOVERABLE - we use the volatile
* store.
*/
if (!load)
{
/*
* Must be SINGLE object model. So, is this the first
* time? If so, load state.
*/
if ((myType != ObjectType.RECOVERABLE) && (!activated))
load = true;
}
return load;
}
/*
* Called ONLY by ActivationRecords!
*/
/**
* Remove action from list of using actions. If the action list empties
* reset state to PASSIVE.
* The second param tells why the action should be forgotten.
* This aids in resetting the state correctly.
*/
protected final synchronized boolean forgetAction (BasicAction action,
boolean committed,
int recordType)
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN, "StateManager::forgetAction("
+((action != null) ? action.get_uid() : Uid.nullUid())+")"
+" for object-id "+objectUid);
}
synchronized (modifyingActions)
{
modifyingActions.remove(action.get_uid());
}
if (recordType != RecordType.RECOVERY)
{
synchronized (usingActions)
{
if (usingActions != null)
{
usingActions.remove(action.get_uid());
if (usingActions.size() == 0)
{
if (committed)
{
if ((myType == ObjectType.RECOVERABLE) && (smAttributes.objectModel == ObjectModel.SINGLE))
{
initialStatus = currentStatus = ObjectStatus.ACTIVE;
}
else
{
initialStatus = currentStatus = ObjectStatus.PASSIVE;
}
}
else
currentStatus = initialStatus;
}
}
}
}
return true;
}
/**
* Remember that the specified transaction is using the object.
*/
protected final synchronized boolean rememberAction (BasicAction action, int recordType)
{
if (tsLogger.arjLogger.debugAllowed())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED,
FacilityCode.FAC_STATE_MAN, "StateManager::rememberAction("
+((action != null) ? action.get_uid() : Uid.nullUid())+")"
+" for object-id "+objectUid);
}
boolean result = false;
if (recordType != RecordType.RECOVERY)
{
if ((action != null) && (action.status() == ActionStatus.RUNNING))
{
synchronized (usingActions)
{
if (usingActions.get(action.get_uid()) == null)
usingActions.put(action.get_uid(), action);
}
}
}
result = true;
return result;
}
/**
* @return the mutex object used to lock this object.
*
* @since JTS 2.1.
*/
protected final Mutex getMutex ()
{
return mutex;
}
/**
* @return the result of the attempt to lock this object.
*
* @since JTS 2.1.
*/
protected final boolean lockMutex ()
{
if (mutex.lock() == Mutex.LOCKED)
return true;
else
return false;
}
/**
* @return the result of the attempt to unlock this object.
*
* @since JTS 2.1.
*/
protected final boolean unlockMutex ()
{
if (mutex.unlock() == Mutex.UNLOCKED)
return true;
else
return false;
}
/**
* @return <code>true</code> if the object was locked,
* <code>false</code> if the attempt would cause the thread to block.
*
* @since JTS 2.1.
*/
protected final boolean tryLockMutex ()
{
if (mutex.tryLock() == Mutex.LOCKED)
return true;
else
return false;
}
/*
* Package scope.
*/
/*
* Set the status of the object to destroyed so that we can no
* longer use it.
*/
synchronized final void destroyed ()
{
currentStatus = ObjectStatus.DESTROYED;
}
/*
* Private functions
*/
private void parseObjectName ()
{
smAttributes = new StateManagerAttribute();
if (objectName != null)
{
try
{
objectUid = objectName.getUidAttribute(ArjunaNames.StateManager_uid());
}
catch (Exception e)
{
// assume not present
objectUid = null;
}
try
{
myType = (int) objectName.getLongAttribute(ArjunaNames.StateManager_objectType());
}
catch (Exception e)
{
myType = ObjectType.ANDPERSISTENT;
}
try
{
smAttributes.objectModel = (int) objectName.getLongAttribute(ArjunaNames.StateManager_objectModel());
}
catch (Exception e)
{
// assume not present.
}
try
{
/*
* For uniformity use the same attribute name as the
* environment name.
*/
smAttributes.objectStoreType = objectName.getClassNameAttribute(com.arjuna.ats.arjuna.common.Environment.OBJECTSTORE_TYPE);
}
catch (Exception e)
{
// assume not present.
}
try
{
smAttributes.objectStoreRoot = objectName.getStringAttribute(ArjunaNames.StateManager_objectStoreRoot());
}
catch (Exception e)
{
// assume not present.
}
}
else
{
objectUid = null;
}
}
protected StateManagerAttribute smAttributes;
protected ObjectName objectName;
protected Hashtable modifyingActions;
protected Hashtable usingActions;
protected Uid objectUid;
private boolean activated;
private boolean currentlyActivated;
private int currentStatus;
private int initialStatus;
private int myType;
private ObjectStore objectStore;
private String storeRoot;
private Mutex mutex = new Mutex();
private static final String marker = "#ARJUNA#";
}