/* * 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: CadaverRecord.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.internal.arjuna.abstractrecords; import java.io.PrintWriter; import com.arjuna.ats.arjuna.StateManager; import com.arjuna.ats.arjuna.coordinator.AbstractRecord; 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.OutputObjectState; /** * Cadaver records are created whenever a persistent object is deleted while * still in the scope of an atomic action. This ensures that if the * action commits the state of the persistent objects gets properly * reflected back in the object participantStore. For objects that are only * recoverable such work is unnecessary. Cadaver records replace * PersistenceRecords in the record list of an atomic action so they must * be merged with such records to enable both commits and aborts to occur. * * @author Mark Little (mark@arjuna.com) * @version $Id: CadaverRecord.java 2342 2006-03-30 13:06:17Z $ * @since JTS 1.0. */ public class CadaverRecord extends PersistenceRecord { /** * Create a new instance, passing in the object that is being managed. * * @param os the state of the object that is being * removed. * @param participantStore the object participantStore instance used to manipulate the * persistent state. * @param sm the object being removed. */ public CadaverRecord (OutputObjectState os, ParticipantStore participantStore, StateManager sm) { super(os, participantStore, sm); newStateIsValid = ((os != null) ? true : false); oldState = null; oType = RecordType.NONE_RECORD; if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("CadaverRecord::CadaverRecord(" + os + ", " + sm.get_uid() + ")"); } } /** * Override default AbstractRecord method. CadaverRecords are propagated * regardless of the termination condition. * * @return <code>true</code> */ public boolean propagateOnAbort () { return true; } /** * The type of the record. * * @return RecordType.PERSISTENT * @see com.arjuna.ats.arjuna.coordinator.RecordType */ public int typeIs () { return RecordType.PERSISTENCE; } /** * The nested transaction has aborted. The record will invalidate any * new state. */ public int nestedAbort () { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("CadaverRecord::nestedAbort() for " + order()); } if (oldState != null) newStateIsValid = false; if (oType == RecordType.RECOVERY) { tsLogger.i18NLogger.warn_CadaverRecord_1(order(), getTypeOfObject()); } /* * No need to forget the action since this object is * being deleted so it is unlikely to have modified called * on it! */ // super.forgetAction(false); return TwoPhaseOutcome.FINISH_OK; } /** * The nested transaction is preparing. If there is any new state for * the object being removed, and that state is valid, then this record * will call nestedPrepare on the object being removed. * * If we have no new state then we cannot commit and must force an * abort. Do this by failing the prepare phase. */ public int nestedPrepare () { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("CadaverRecord::nestedPrepare() for " + order()); } if (newStateIsValid) return super.nestedPrepare(); else return TwoPhaseOutcome.PREPARE_NOTOK; } /** * The nested transaction has aborted. Invalidate any new state. */ public int topLevelAbort () { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("CadaverRecord::topLevelAbort() for " + order()); } newStateIsValid = false; if (oType == RecordType.RECOVERY) { tsLogger.i18NLogger.warn_CadaverRecord_1(order(), getTypeOfObject()); } // super.forgetAction(false); return TwoPhaseOutcome.FINISH_OK; } /** * At topLevelCommit we commit the uncommitted version already saved * into object participantStore. * Cannot use inherited version since that assumes object is alive * instead talk directly to the object participantStore itself. */ public int topLevelCommit () { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("CadaverRecord::topLevelCommit() for " + order()); } boolean res = true; OutputObjectState oState = super.state; if ((oState != null) && (oType == RecordType.PERSISTENCE)) { if (targetParticipantStore == null) return TwoPhaseOutcome.FINISH_ERROR; try { res = targetParticipantStore.commit_state(oState.stateUid(), oState.type()); } catch (ObjectStoreException e) { res = false; } } // super.forgetAction(false); return ((res) ? TwoPhaseOutcome.FINISH_OK : TwoPhaseOutcome.FINISH_ERROR); } /** * At topLevelPrepare write uncommitted version into object participantStore. * Cannot use inherited version since that assumes object is alive * instead talk directly to the object participantStore itself. */ public int topLevelPrepare () { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("CadaverRecord::topLevelPrepare() for " + order()); } int tlpOk = TwoPhaseOutcome.PREPARE_NOTOK; OutputObjectState oState = (newStateIsValid ? super.state : oldState); if (oState != null) { if (oType == RecordType.PERSISTENCE) { if (targetParticipantStore == null) return TwoPhaseOutcome.PREPARE_NOTOK; try { if (targetParticipantStore.write_uncommitted(oState.stateUid(), oState.type(), oState)) { if (shadowForced()) tlpOk = TwoPhaseOutcome.PREPARE_OK; } } catch (final ObjectStoreException e) { e.printStackTrace(); } } else tlpOk = TwoPhaseOutcome.PREPARE_OK; } return tlpOk; } /** * Override AbstractRecord.print to write specific information to * the specified stream. * * @param strm the stream to use. */ public void print (PrintWriter strm) { strm.println("Cadaver for:"); super.print(strm); } /** * The type of the class - may be used to save information in an * hierarchical manner in the object participantStore. */ public String type() { return "/StateManager/AbstractRecord/RecoveryRecord/PersistenceRecord/CadaverRecord"; } /** * Override the AbstractRecord.doSave. * * @return <code>true</code> if the object being removed is a persistent * object (RecordType.PERSISTENT). <code>false</code> otherwise. * @see com.arjuna.ats.arjuna.coordinator.RecordType */ public boolean doSave () { if (oType == RecordType.PERSISTENCE) return true; else return false; } /** * merge takes the information from the incoming PersistenceRecord and * uses it to initialise the oldState information. This is required * for processing of action aborts since CadaverRecords maintain the * final state of an object normally - which is required if the action * commits. * * @param mergewith The record to merge with. */ public void merge (AbstractRecord mergewith) { /* * Following assumes that value returns a pointer to the * old state maintained in the PersistenceRecord (as an ObjectState). * Here we create a copy of that state allowing the original * to be deleted */ oType = mergewith.typeIs(); if (oldState != null) { if (newStateIsValid) { oldState = null; } else { setValue(oldState); newStateIsValid = true; } } oldState = new OutputObjectState((OutputObjectState)(mergewith.value())); } /** * Overrides AbstractRecord.shouldMerge * * @param ar the record to potentially merge with. * * @return <code>true</code> if this instance and the parameter have the * same id (order()) and the parameter is either persistent or recoverable. * <code>false</code> otherwise. * @see com.arjuna.ats.arjuna.coordinator.RecordType */ public boolean shouldMerge (AbstractRecord ar) { return (((order().equals(ar.order())) && ((ar.typeIs() == RecordType.PERSISTENCE) || (ar.typeIs() == RecordType.RECOVERY))) ? true : false); } /** * Overrides AbstractRecord.shouldReplace * * @param ar the record to potentially replace this * instance. * * @return <code>true</code> if this instance and the parameter have the * same id (order()) and the parameter is either persistent or recoverable. * <code>false</code> otherwise. * @see com.arjuna.ats.arjuna.coordinator.RecordType */ public boolean shouldReplace (AbstractRecord ar) { return (((order().equals(ar.order())) && ((ar.typeIs() == RecordType.PERSISTENCE) || (ar.typeIs() == RecordType.RECOVERY))) ? true : false); } /** * Create a new instance using default values. Typically used during * failure recovery. */ public CadaverRecord () { super(); newStateIsValid = false; oldState = null; oType = RecordType.NONE_RECORD; targetParticipantStore = null; if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("CadaverRecord::CadaverRecord ()"); } } private boolean newStateIsValid; private OutputObjectState oldState; private int oType; }