/*
 * 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,
 *
 * Arjuna Solutions Limited,
 * Newcastle upon Tyne,
 * Tyne and Wear,
 * UK.  
 *
 * $Id: RecoveryRecord.java 2342 2006-03-30 13:06:17Z  $
 */

package com.arjuna.ats.internal.arjuna.abstractrecords;

import java.io.PrintWriter;

import com.arjuna.ats.arjuna.ObjectType;
import com.arjuna.ats.arjuna.StateManager;
import com.arjuna.ats.arjuna.coordinator.AbstractRecord;
import com.arjuna.ats.arjuna.coordinator.BasicAction;
import com.arjuna.ats.arjuna.coordinator.RecordType;
import com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.arjuna.state.OutputObjectState;

public class RecoveryRecord extends AbstractRecord
{

    /**
     * This constructor is used to create a new instance of a RecoveryRecord.
     */

    public RecoveryRecord(OutputObjectState os, StateManager sm)
    {
        super(sm.get_uid(), sm.type(), ObjectType.ANDPERSISTENT);

        if (tsLogger.logger.isDebugEnabled()) {
            tsLogger.logger.debug("RecoveryRecord::RecoveryRecord(" + os + ", "
                    + sm.get_uid() + ")");
        }

        objectAddr = sm;
        state = os;
        actionHandle = BasicAction.Current();
    }

    public int typeIs ()
    {
        return RecordType.RECOVERY;
    }

    public Object value ()
    {
        return state;
    }

    public void setValue (Object newState)
    {
        if (newState instanceof OutputObjectState)
            state = (OutputObjectState) newState;
        else {
            tsLogger.i18NLogger.warn_RecoveryRecord_1();
        }
    }

    /**
     * nestedAbort causes the restore_state function of the object to be invoked
     * passing it the saved ObjectState.
     */

    public int nestedAbort ()
    {
        if (tsLogger.logger.isDebugEnabled()) {
            tsLogger.logger.debug("RecoveryRecord::nestedAbort() for " + order());
        }

        /*
         * First check that we have a state. We won't have for records created
         * by crash recovery.
         */

        forgetAction(false);

        if (state != null)
        {
            if (state.notempty()) /* anything to restore ? */
            {
                InputObjectState oldState = new InputObjectState(state);

                int result = objectAddr.restore_state(oldState,
                        ObjectType.RECOVERABLE) ? TwoPhaseOutcome.FINISH_OK
                        : TwoPhaseOutcome.FINISH_ERROR;

                if (result == TwoPhaseOutcome.FINISH_ERROR) {
                    tsLogger.i18NLogger.warn_RecoveryRecord_2();
                }

                return result;
            }
        }

        return TwoPhaseOutcome.FINISH_OK;
    }

    /**
     * nestedCommit does nothing since the passing of the state up to the parent
     * action is handled by the record list merging system. In fact since
     * nestedPrepare returns PREPARE_READONLY this function should never
     * actually be called
     */

    public int nestedCommit ()
    {
        if (tsLogger.logger.isDebugEnabled()) {
            tsLogger.logger.debug("RecoveryRecord::nestedCommit() for " + order());
        }

        return TwoPhaseOutcome.FINISH_OK;
    }

    public int nestedPrepare ()
    {
        if (tsLogger.logger.isDebugEnabled()) {
            tsLogger.logger.debug("RecoveryRecord::nestedPrepare() for " + order());
        }

        forgetAction(true);

        return TwoPhaseOutcome.PREPARE_READONLY;
    }

    /**
     * topLevelAbort for Recovery records implies the object state should be
     * restored to the saved state exactly like a nested abort.
     */

    public int topLevelAbort ()
    {
        if (tsLogger.logger.isDebugEnabled()) {
            tsLogger.logger.debug("RecoveryRecord::topLevelAbort() for " + order());
        }

        return nestedAbort(); /* i.e., same as nested case */
    }

    /**
     * topLevelCommit has nothing to do for RecoveryRecords as no changes have
     * been made in the object store. In fact since topLevelPrepare returns
     * PREPARE_READONLY this function should never actually be called
     */

    public int topLevelCommit ()
    {
        if (tsLogger.logger.isDebugEnabled()) {
            tsLogger.logger.debug("RecoveryRecord::topLevelCommit() for " + order());
        }

        forgetAction(true);

        return TwoPhaseOutcome.FINISH_OK;
    }

    /**
     * topLevelPrepare can return PREPARE_READONLY to avoid topLevelCommit being
     * called in the action commit case
     */

    public int topLevelPrepare ()
    {
        if (tsLogger.logger.isDebugEnabled()) {
            tsLogger.logger.debug("RecoveryRecord::topLevelPrepare() for " + order());
        }

        return TwoPhaseOutcome.PREPARE_READONLY;
    }

    /*
     * Saving of RecoveryRecords is only undertaken during the Prepare phase of
     * the top level 2PC. Since the managed objects are only recoverable (not
     * persistent) there is no need to save any information (or restore any
     * either). However, persistence records (derived from recovery records)
     * need to be saved for crash recovery purposes.
     */

    public boolean doSave ()
    {
        return false;
    }

    public boolean restore_state (InputObjectState os, int ot)
    {
        return super.restore_state(os, ot);
    }

    public boolean save_state (OutputObjectState os, int ot)
    {
        return super.save_state(os, ot);
    }

    public void print (PrintWriter strm)
    {
        super.print(strm);
        strm.println("RecoveryRecord with state:\n" + state);
    }

    public String type ()
    {
        return "/StateManager/AbstractRecord/RecoveryRecord";
    }

    public void merge (AbstractRecord a)
    {
    }

    public void alter (AbstractRecord a)
    {
    }

    /*
     * should_merge and should_replace are invoked by the record list manager to
     * determine if two records should be merged togethor or if the 'newer'
     * should replace the older. shouldAdd determines if the new record should
     * be added in addition to the existing record and is currently only invoked
     * if both of should_merge and should_replace return false Default
     * implementations here always return false - ie new records do not override
     * old.
     */

    public boolean shouldAdd (AbstractRecord a)
    {
        return false;
    }

    public boolean shouldAlter (AbstractRecord a)
    {
        return false;
    }

    public boolean shouldMerge (AbstractRecord a)
    {
        return false;
    }

    public boolean shouldReplace (AbstractRecord a)
    {
        return false;
    }

    /*
     * Creates a 'blank' recovery record. This is used during crash recovery
     * when recreating the prepared list of a server atomic action.
     */

    public RecoveryRecord()
    {
        super();

        if (tsLogger.logger.isDebugEnabled()) {
            tsLogger.logger.debug("RecoveryRecord::RecoveryRecord()"
                    + " - crash recovery constructor");
        }

        objectAddr = null;
        state = null;
        actionHandle = null;
    }

    /*
     * Can we use this to force our parent to "remember" us when we commit, and
     * prevent the system from creating another record in that action?
     */

    protected final void forgetAction (boolean commit)
    {
        if ((actionHandle != null) && (objectAddr != null))
        {
            StateManagerFriend.forgetAction(objectAddr, actionHandle, commit, RecordType.RECOVERY);
            actionHandle = null; // only do this once!
        }
    }

    protected StateManager objectAddr;

    protected OutputObjectState state;

    private BasicAction actionHandle;

}
