/*
* 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: PersistenceRecord.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.internal.arjuna.abstractrecords;
import java.io.IOException;
import java.io.PrintWriter;
import com.arjuna.ats.arjuna.ObjectType;
import com.arjuna.ats.arjuna.StateManager;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.common.arjPropertyManager;
import com.arjuna.ats.arjuna.coordinator.RecordType;
import com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.objectstore.ParticipantStore;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.arjuna.state.OutputObjectState;
/**
* A PersistenceRecord is created whenever a persistent object is
* created/read/modified within the scope of a transaction. It is responsible
* for ensuring that state changes are committed or rolled back on behalf of the
* object depending upon the outcome of the transaction.
*
* @author Mark Little (mark@arjuna.com)
* @version $Id: PersistenceRecord.java 2342 2006-03-30 13:06:17Z $
* @since JTS 1.0.
*/
public class PersistenceRecord extends RecoveryRecord
{
/**
* This constructor is used to create a new instance of PersistenceRecord.
*/
public PersistenceRecord (OutputObjectState os, ParticipantStore participantStore, StateManager sm)
{
super(os, sm);
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("PersistenceRecord::PersistenceRecord("
+ os + ", " + sm.get_uid() + ")");
}
shadowMade = false;
this.targetParticipantStore = participantStore;
topLevelState = null;
}
/**
* Redefintions of abstract functions inherited from RecoveryRecord.
*/
public int typeIs ()
{
return RecordType.PERSISTENCE;
}
/**
* topLevelAbort may have to remove the persistent state that was written
* into the object store during the processing of topLevelPrepare. It then
* does the standard abort processing.
*/
public int topLevelAbort ()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("PersistenceRecord::topLevelAbort() for "
+ order());
}
Uid uid = null;
String type = null;
if (shadowMade) // state written by StateManager instance
{
uid = order();
type = getTypeOfObject();
}
else
{
if (topLevelState == null) // hasn't been prepared, so no state
{
return nestedAbort();
}
else
{
uid = topLevelState.stateUid();
type = topLevelState.type();
}
}
try
{
if (!targetParticipantStore.remove_uncommitted(uid, type)) {
tsLogger.i18NLogger.warn_PersistenceRecord_19();
return TwoPhaseOutcome.FINISH_ERROR;
}
}
catch (ObjectStoreException e) {
tsLogger.i18NLogger.warn_PersistenceRecord_20(e);
return TwoPhaseOutcome.FINISH_ERROR;
}
return nestedAbort();
}
/**
* commit the state saved during the prepare phase.
*/
public int topLevelCommit ()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("PersistenceRecord::topLevelCommit() : About to commit state, "+
"uid = "+order()+", ObjType = "+getTypeOfObject());
}
if (tsLogger.logger.isTraceEnabled())
{
if (targetParticipantStore != null) {
tsLogger.logger.trace(", store = "
+ targetParticipantStore + "(" + targetParticipantStore.getClass().getCanonicalName() + ")");
}
else {
tsLogger.logger.trace("");
}
}
boolean result = false;
if (targetParticipantStore != null)
{
try
{
if (shadowMade)
{
result = targetParticipantStore.commit_state(order(), super.getTypeOfObject());
if (!result) {
tsLogger.i18NLogger.warn_PersistenceRecord_2(order());
}
}
else
{
if (topLevelState != null)
{
result = targetParticipantStore.write_committed(order(), super.getTypeOfObject(), topLevelState);
}
else {
tsLogger.i18NLogger.warn_PersistenceRecord_3();
}
}
}
catch (ObjectStoreException e) {
tsLogger.i18NLogger.warn_PersistenceRecord_4(e);
result = false;
}
}
else {
tsLogger.i18NLogger.warn_PersistenceRecord_5();
}
if (!result) {
tsLogger.i18NLogger.warn_PersistenceRecord_6();
}
super.forgetAction(true);
return ((result) ? TwoPhaseOutcome.FINISH_OK
: TwoPhaseOutcome.FINISH_ERROR);
}
/**
* topLevelPrepare attempts to save the object. It will either do this in
* the action intention list or directly in the object store by using the
* 'deactivate' function of the object depending upon the size of the state.
* To ensure that objects are correctly hidden while they are in an
* uncommitted state if we use the abbreviated protocol then we write an
* EMPTY object state as the shadow state - THIS MUST NOT BE COMMITTED.
* Instead we write_committed the one saved in the intention list. If the
* store cannot cope with being given an empty state we revert to the old
* protocol.
*/
public int topLevelPrepare ()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("PersistenceRecord::topLevelPrepare() for "
+ order());
}
int result = TwoPhaseOutcome.PREPARE_NOTOK;
StateManager sm = super.objectAddr;
if ((sm != null) && (targetParticipantStore != null))
{
/*
* Get ready to create our state to be saved. At this stage we're not
* sure if the state will go into its own log or be written into the
* transaction log for improved performance.
*/
topLevelState = new OutputObjectState(sm.get_uid(), sm.type());
if (writeOptimisation
&& (!targetParticipantStore.fullCommitNeeded()
&& (sm.save_state(topLevelState, ObjectType.ANDPERSISTENT)) && (topLevelState.size() <= PersistenceRecord.MAX_OBJECT_SIZE)))
{
/*
* We assume that crash recovery will always run before
* the object can be reactivated!
*/
if (PersistenceRecord.classicPrepare)
{
OutputObjectState dummy = new OutputObjectState(
Uid.nullUid(), null);
/*
* Write an empty shadow state to the store to indicate one
* exists, and to prevent bogus activation in the case where
* crash recovery hasn't run yet.
*/
try
{
if (targetParticipantStore.write_uncommitted(sm.get_uid(), sm.type(), dummy))
result = TwoPhaseOutcome.PREPARE_OK;
else
{
result = TwoPhaseOutcome.PREPARE_NOTOK;
}
}
catch (ObjectStoreException e) {
tsLogger.i18NLogger.warn_PersistenceRecord_21(e);
}
dummy = null;
}
else
{
/*
* Don't write anything as our state will go into the log.
*/
result = TwoPhaseOutcome.PREPARE_OK;
}
}
else
{
if (sm.deactivate(targetParticipantStore.getStoreName(), false))
{
shadowMade = true;
result = TwoPhaseOutcome.PREPARE_OK;
}
else
{
topLevelState = null;
tsLogger.i18NLogger.warn_PersistenceRecord_7();
}
}
}
else {
tsLogger.i18NLogger.warn_PersistenceRecord_8();
}
return result;
}
/**
* topLevelCleanup must leave the persistent state that was written in the
* object store during the processing of topLevelPrepare intact. Crash
* recovery will take care of its resolution
*/
public int topLevelCleanup ()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("PersistenceRecord::topLevelCleanup() for "
+ order());
}
return TwoPhaseOutcome.FINISH_OK;
}
/**
* @return <code>true</code>
*/
public boolean doSave ()
{
return true;
}
public boolean restore_state (InputObjectState os, int ot)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("PersistenceRecord::restore_state() for "
+ order());
}
boolean res = false;
topLevelState = null;
try
{
shadowMade = os.unpackBoolean();
// topLevelState = null;
if (!shadowMade)
{
topLevelState = new OutputObjectState(os);
res = topLevelState.valid();
}
else
res = true;
res = (res && super.restore_state(os, ot));
// Note: we don't persist the targetParticipantStore, instead assuming the
// default one present at recovery time will be equivalent. Changing the
// objectstore config when records exist in the tx store is therefore a Bad Thing.
targetParticipantStore = getStore();
return res;
}
catch (final Exception e) {
tsLogger.i18NLogger.warn_PersistenceRecord_10();
}
return res;
}
public boolean save_state (OutputObjectState os, int ot)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("PersistenceRecord::save_state() for "
+ order());
}
boolean res = true;
if (targetParticipantStore != null)
{
// Note: we don't persist the targetParticipantStore, instead assuming the
// default one present at recovery time will be equivalent. Changing the
// objectstore config when records exist in the tx store is therefore a Bad Thing.
try
{
os.packBoolean(shadowMade);
/*
* If we haven't written a shadow state, then pack the state
* into the transaction log. There MUST be a state at this
* point.
*/
if (!shadowMade)
{
res = (topLevelState != null);
if (res)
topLevelState.packInto(os);
else {
tsLogger.i18NLogger.warn_PersistenceRecord_14();
}
}
}
catch (IOException e) {
res = false;
tsLogger.i18NLogger.warn_PersistenceRecord_15();
}
}
else {
tsLogger.i18NLogger.warn_PersistenceRecord_16();
try {
os.packString(null);
}
catch (IOException e) {
res = false;
}
}
return res && super.save_state(os, ot);
}
public void print (PrintWriter strm)
{
super.print(strm); /* bypass RecoveryRecord */
strm.println("PersistenceRecord with state:\n" + super.state);
}
public String type ()
{
return "/StateManager/AbstractRecord/RecoveryRecord/PersistenceRecord";
}
/**
* Creates a 'blank' persistence record. This is used during crash recovery
* when recreating the prepared list of a server atomic action.
*/
public PersistenceRecord ()
{
super();
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("PersistenceRecord::PersistenceRecord() - crash recovery constructor");
}
shadowMade = false;
targetParticipantStore = null;
topLevelState = null;
}
/**
* Cadaver records force write shadows. This operation supresses to
* abbreviated commit This should never return false
*/
protected boolean shadowForced ()
{
if (topLevelState == null)
{
shadowMade = true;
return true;
}
/* I've already done the abbreviated protocol so its too late */
return false;
}
// this value should really come from the object store implementation!
public static final int MAX_OBJECT_SIZE = 4096; // block size
protected boolean shadowMade;
protected ParticipantStore targetParticipantStore;
protected OutputObjectState topLevelState;
protected static final boolean classicPrepare = arjPropertyManager.getCoordinatorEnvironmentBean().isClassicPrepare();
private static final boolean writeOptimisation = arjPropertyManager.getCoordinatorEnvironmentBean().isWriteOptimisation();
}