/* * 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, * * Hewlett Packard Arjuna Labs, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: AbstractRecord.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.arjuna.coordinator; import java.io.IOException; import java.io.PrintWriter; 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.logging.tsLogger; import com.arjuna.ats.arjuna.state.InputObjectState; import com.arjuna.ats.arjuna.state.OutputObjectState; import com.arjuna.ats.internal.arjuna.common.UidHelper; /** * Abstract Record Class * * This class provides an abstract template that defines the interface that the * atomic action system uses to notify objects that various state transitions * have occurred as the 2PC protocol executes. Record types derived from this * class manage certain properties of objects such as recovery information, * concurrency control information etc, and all must redefine the operations * defined here as abstract to take appropriate action. * * Many functions are declared pure virtual to force a definition to occur in * any derived class. These are currently all functions dealing with atomic * action coordination as well as the following list management functions: * typeIs: returns the record type of the instance. This is one of the values of * the enumerated type Record_type value: Some arbitrary value associated with * the record instance merge: Used when two records need to merge together. * Currently this is only used by CadaverRecords to merge information from * PersistenceRecords shouldAdd: returns TRUE is the record should be added to * the list FALSE if it should be discarded shouldMerge: returns TRUE is the two * records should be merged into a single record, FALSE if it should be * discarded shouldReplace: returns TRUE if the record should replace an * existing one, FALSE otherwise. * * @author Mark Little (mark@arjuna.com) * @version $Id: AbstractRecord.java 2342 2006-03-30 13:06:17Z $ * @since 1.0. */ public abstract class AbstractRecord extends StateManager { /** * @return <code>RecordType</code> value. */ public abstract int typeIs (); /** * If this abstract record caused a heuristic then it should return an * object which implements <code>HeuristicInformation</code> * * @return <code>Object</code> to be used to order. */ public abstract Object value (); public abstract void setValue (Object o); /** * Atomic action interface - one operation per two-phase commit state. */ /** * A rollback of a nested transaction has occurred. * * @return <code>TwoPhaseOutcome</code> to indicate success/failure. * @see com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome */ public abstract int nestedAbort (); /** * A commit of a nested transaction has occurred. * * @return <code>TwoPhaseOutcome</code> to indicate success/failure. * @see com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome */ public abstract int nestedCommit (); /** * A prepare for a nested transaction has occurred. * * @return <code>TwoPhaseOutcome</code> to indicate success/failure. * @see com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome */ public abstract int nestedPrepare (); /** * A rollback of a top-level transaction has occurred. * * @return <code>TwoPhaseOutcome</code> to indicate success/failure. * @see com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome */ public abstract int topLevelAbort (); /** * A commit of a top-level transaction has occurred. * * @return <code>TwoPhaseOutcome</code> to indicate success/failure. * @see com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome */ public abstract int topLevelCommit (); /** * A prepare for a top-level transaction has occurred. * * @return <code>TwoPhaseOutcome</code> to indicate success/failure. * @see com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome */ public abstract int topLevelPrepare (); /** * Return the Uid of this abstract record so that it can be ordered in the * intentions list. This is also the Uid that the record was saved with in * the object store. * * @return <code>Uid</code> for this instance. * @see com.arjuna.ats.arjuna.common.Uid */ /* * Now that StateManager actually maintains state, we could save this in * StateManager. However, it would affect all StateManager instances (more * state to save). */ public Uid order () { return uidOfObject; } /** * Return the type of the abstract record. Used in ordering the instances in * the intentions list. This is also the type that the record was saved with * in the object store. * * @return <code>String</code> representing type. */ public String getTypeOfObject () { return typeOfObject; } /** * Determine if records are discarded on action abort or must be propagated * to parents. * * @return <code>true</code> if the record should be propagated to the * parent transaction if the current transaction rolls back, * <code>false</code> otherwise. The default is <code>false</code>. */ public boolean propagateOnAbort () { return false; } /** * Determine if records are discarded on action commit or must be propagated * to parents. * * @return <code>true</code> if the record should be propagated to the * parent transaction if the current transaction commits, * <code>false</code> otherwise. The default is <code>true</code>. */ public boolean propagateOnCommit () { return true; } /** * Operators for comparing and sequencing instances of classes derived from * AbstractRecords. Records are ordered primarily based upon the value of * 'order', followed by 'typeIs'. */ /** * Determine if two records are equal in that both are the same type and * have the same order value (determined via 'order()'). * * @return <code>true</code> if equal, <code>false</code> otherwise. */ public final boolean equals (AbstractRecord ar) { return (useAlternativeOrdering ? typeEquals(ar) : orderEquals(ar)); } /** * Determine if two records are less than in that both are the same type and * their Uids are less than. * * @return <code>true</code> if equal, <code>false</code> otherwise. */ public final boolean lessThan (AbstractRecord ar) { return (useAlternativeOrdering ? typeLessThan(ar) : orderLessThan(ar)); } /** * Determine if two records are greater than in that both are the same type * and their Uids are greater than. * * @return <code>true</code> if equal, <code>false</code> otherwise. */ public final boolean greaterThan (AbstractRecord ar) { return (useAlternativeOrdering ? typeGreaterThan(ar) : orderGreaterThan(ar)); } /** * Cleanup is called if a top-level action is detected to be an orphan. * * NOTE nested actions are never orphans since their parents would be * aborted we may as well abort them as well. * * @return <code>TwoPhaseOutcome</code> as default is the same as * topLevelAbort. */ public int topLevelCleanup () { return topLevelAbort(); } /** * Cleanup is called if a nested is detected to be an orphan. * * NOTE nested actions are never orphans since their parents would be * aborted we may as well abort them as well. * * @return <code>TwoPhaseOutcome</code> as default is the same as * nestedAbort. */ public int nestedCleanup () { return nestedAbort(); } /** * Should this record be saved in the intentions list? If the record is * saved, then it may be recovered later in the event of a failure. Note, * however, that the size of the intentions list on disk is critical to the * performance of the system (disk I/O is a bottleneck). * * @return <code>true</code> if it should be saved, <code>false</code> * otherwise. <code>false</code> is the default. */ public boolean doSave () { return false; } /** * Re-implementation of abstract methods inherited from base class. */ public String type () { return "/StateManager/AbstractRecord"; } /** * Write information about this specific instance to the specified stream. * * @param strm the stream on which to output. */ public void print (PrintWriter strm) { strm.println("Uid of Managed Object: " + uidOfObject); strm.println("Type of Managed Object: " + typeOfObject); super.print(strm); } /** * When the transaction is required to make the intentions list persistent, * it scans the list and asks each record whether or not it requires state * to be saved (by calling doSave). If the answer is yes, then save_state is * called and the record instance must save enough information to enable it * to be restored from that state later. The basic AbstractRecord save_state * will save common data that is required by the base class during recovery. * * If a derived class calls super.save_state then it must be called before * packing any other data item. * * @return <code>true</code> if successful, <code>false</code> * otherwise. */ public boolean save_state (OutputObjectState os, int i) { try { UidHelper.packInto(uidOfObject, os); os.packString(typeOfObject); return true; } catch (IOException e) { return false; } } /** * During recovery, the transaction log is given to the recovery system and * it will recreate a transaction instance to perform necessary recovery * actions. This transaction will recreate the intentions list and give each * recreated AbstractRecord the state that that was saved during transaction * persistence. The base class will restore information that it needs from * the log. * * Data items must be unpacked in the same order that they were packed. * * @return <code>true</code> if successful, <code>false</code> * otherwise. */ public boolean restore_state (InputObjectState os, int i) { typeOfObject = null; try { uidOfObject = UidHelper.unpackFrom(os); typeOfObject = os.unpackString(); return true; } catch (IOException e) { return false; } } /** * Forget any heuristic outcome which this implementation may have produced. * * @return <code>true</code> by default. If <code>false</code> is * returned then the instance must be remembered by the transaction * (in the log) in order for recovery to retry later or for a system * administrator to be able to determine which resources have not * been successfully completed. */ public boolean forgetHeuristic () { return true; } /** * Perform a nested one phase commit. * * @return <code>TwoPhaseOutcome</code> to indicate success/failure. * @see com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome */ public int nestedOnePhaseCommit () { int res = nestedPrepare(); switch (res) { case TwoPhaseOutcome.PREPARE_OK: return nestedCommit(); case TwoPhaseOutcome.PREPARE_READONLY: return TwoPhaseOutcome.FINISH_OK; default: return TwoPhaseOutcome.FINISH_ERROR; } } /** * Perform a top-level one phase commit. * * @return <code>TwoPhaseOutcome</code> to indicate success/failure. * @see com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome */ public int topLevelOnePhaseCommit () { int res = topLevelPrepare(); switch (res) { case TwoPhaseOutcome.PREPARE_OK: return topLevelCommit(); case TwoPhaseOutcome.PREPARE_READONLY: return TwoPhaseOutcome.FINISH_OK; case TwoPhaseOutcome.ONE_PHASE_ERROR: return TwoPhaseOutcome.ONE_PHASE_ERROR; default: return TwoPhaseOutcome.FINISH_ERROR; } } @SuppressWarnings("unchecked") public static AbstractRecord create (int type) { try { Class recordClass = RecordType.typeToClass(type); return (AbstractRecord) recordClass.newInstance(); } catch (final NullPointerException ex) { tsLogger.i18NLogger.warn_coordinator_AbstractRecord_npe(Integer.toString(type)); return null; } catch (final Throwable ex) { ex.printStackTrace(); return null; } } /** * Merge the current record with the one presented. * * @param a the record with which to merge. */ public abstract void merge (AbstractRecord a); /** * Alter the current record with the one presented. * * @param a the record with which to alter. */ public abstract void alter (AbstractRecord a); /** * Should we add the record presented to the intentions list? * * @param a The record to try to add. * @return <code>true</code> if the record should be added, * <code>false</code> otherwise. */ public abstract boolean shouldAdd (AbstractRecord a); /** * Should we alter the current record with the one presented? * * @param a The record to try to alter. * @return <code>true</code> if the record should be altered, * <code>false</code> otherwise. */ public abstract boolean shouldAlter (AbstractRecord a); /** * Should we merge the current record with the one presented? * * @param a The record to try to merge. * @return <code>true</code> if the record should be merged, * <code>false</code> otherwise. */ public abstract boolean shouldMerge (AbstractRecord a); /** * Should we replace the record presented with the current record? * * @param a The record to try to replace. * @return <code>true</code> if the record should be replaced, * <code>false</code> otherwise. */ public abstract boolean shouldReplace (AbstractRecord a); /** * The current record is about to replace the one presented. This method is * invoked to give the current record a chance to copy information, for * example, from the record being replaced. * * @param a the record that will replace this instance. */ public void replace (AbstractRecord a) { } /** * These few functions are link manipulation primitives used by the * RecordList processing software to chain instances together. * * @return the previous element in the intentions list, or null. */ protected final AbstractRecord getPrevious () { return previous; } /** * @return the next element in the intentions list, or null. */ protected final AbstractRecord getNext () { return next; } /** * Set the previous element in the list to the specified instance. * * @param ar the instance to become previous. */ protected final void setPrevious (AbstractRecord ar) { previous = ar; } /** * Set the next element in the list to the specified instance. * * @param ar the instance to become next. */ protected final void setNext (AbstractRecord ar) { next = ar; } /** * Create a new instance with the specified parameters. * * @param storeUid the unique id for this instance. * @param objType the type of the instance. * @param otype the ObjectType of the object. * @see com.arjuna.ats.arjuna.ObjectType */ protected AbstractRecord (Uid storeUid, String objType, int otype) { super(otype); next = null; previous = null; uidOfObject = storeUid; typeOfObject = objType; if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("AbstractRecord::AbstractRecord (" + storeUid + ", " + otype + ")"); } } /** * Create a new instance with the specified paramaters. * * @param storeUid the unique id for this instance. */ protected AbstractRecord (Uid storeUid) { super(storeUid); next = null; previous = null; uidOfObject = storeUid; typeOfObject = null; if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("AbstractRecord::AbstractRecord (" + storeUid + ")"); } } /** * Creates a 'blank' abstract record. This is used during crash recovery * when recreating the prepared list of a server atomic action. */ public AbstractRecord () { super(Uid.nullUid()); next = null; previous = null; uidOfObject = new Uid(Uid.nullUid()); typeOfObject = null; if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("AbstractRecord::AbstractRecord () - crash recovery constructor"); } } /** * ensure records of the same type are grouped together in the list, rather * than grouping them by object (i.e. uid) */ private final boolean typeEquals (AbstractRecord ar) { return ((typeIs() == ar.typeIs()) && (order().equals(ar.order()))); } private final boolean typeLessThan (AbstractRecord ar) { return ((typeIs() < ar.typeIs()) || ((typeIs() == ar.typeIs()) && (order().lessThan(ar.order())))); } private final boolean typeGreaterThan (AbstractRecord ar) { return ((typeIs() > ar.typeIs()) || ((typeIs() == ar.typeIs()) && (order().greaterThan(ar.order())))); } private final boolean orderEquals (AbstractRecord ar) { return ((order().equals(ar.order())) && (typeIs() == ar.typeIs())); } private final boolean orderLessThan (AbstractRecord ar) { return ((order().lessThan(ar.order())) || ((order().equals(ar.order())) && (typeIs() < ar.typeIs()))); } private final boolean orderGreaterThan (AbstractRecord ar) { return ((order().greaterThan(ar.order())) || ((order().equals(ar.order())) && (typeIs() > ar.typeIs()))); } private AbstractRecord next; private AbstractRecord previous; private Uid uidOfObject; private String typeOfObject; private static final boolean useAlternativeOrdering = arjPropertyManager.getCoordinatorEnvironmentBean().isAlternativeRecordOrdering(); }