/*

   Derby - Class org.apache.derby.impl.store.raw.data.OverflowInputStream

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.impl.store.raw.data;

import org.apache.derby.shared.common.error.StandardException;
import org.apache.derby.shared.common.reference.SQLState;

import org.apache.derby.iapi.services.io.CloneableStream;
import org.apache.derby.shared.common.i18n.MessageService;

import org.apache.derby.iapi.store.raw.RecordHandle;

import org.apache.derby.iapi.types.Resetable;

import org.apache.derby.iapi.store.raw.LockingPolicy;
import org.apache.derby.iapi.store.access.TransactionController;

import org.apache.derby.shared.common.reference.MessageId;

import java.io.EOFException;
import java.io.InputStream;
import java.io.IOException;

/**
A OverflowInputStream is used by store to turn a long column into an 
InputStream.
<p>
Any time store fetches a long column, the value is returned as a stream.
A long column is any column that at some point was longer than a page, so
a long column in one table may not be long in another depending on page size.
<p>
When the column is fetched a new OverflowInputStream is created and then
the datatype's stream is set using:
 ((StreamStorable)sColumn).setStream(OverflowInputStream);

**/

public class OverflowInputStream
extends BufferedByteHolderInputStream
implements Resetable, CloneableStream
{
    /**************************************************************************
     * Fields of the class
     **************************************************************************
     */
    protected BaseContainerHandle   owner;

    // tracks the next overflow page and id on that page to read
    protected long                  overflowPage;
    protected int                   overflowId;

    // remember first page and id for reset
    protected long                  firstOverflowPage;
    protected int                   firstOverflowId;

    // the row to lock for Blobs/Clobs
    protected RecordHandle          recordToLock;

    // Make sure record is only locked once.
    private boolean initialized = false;


    /**************************************************************************
     * Constructors for This class:
     **************************************************************************
     */

    /**
     * Constructor for OverflowInputStream
     * <p>
     * It is up to the caller to allocate the ByteHolder for this stream,
     * and pass it in.
     *
     * @param bh            ByteHolder to hold buffers of bytes as the stream is
     *                      walked.  Expected usage is that a page worth of 
     *                      data is held in memory at a time, filled by a 
     *                      calls to restorePortionLongColumn(). 
     *
     * @param owner         BaseContainerHandle used to read pages from the 
     *                      container.  Note this handle is closed automatically
     *                      on commit.
     *
     * @param overflowPage  The first overflow page of this long column.  
     *
     * @param overflowId    The record id of 1st segment of the long column on
     *                      overflowPage.
     *
     * @param recordToLock  RecordHandle of the owning record of the long long
     *                      column, this is the row level lock to get.
     **/
    public OverflowInputStream(
    ByteHolder          bh, 
    BaseContainerHandle owner,
    long                overflowPage, 
    int                 overflowId, 
    RecordHandle        recordToLock) {
        super(bh);
        this.owner              = owner;
        this.overflowPage       = overflowPage;
        this.overflowId         = overflowId;
        this.firstOverflowPage  = overflowPage;
        this.firstOverflowId    = overflowId;
        this.recordToLock       = recordToLock;
    }

    /**************************************************************************
     * Public Methods of This class:
     **************************************************************************
     */

    /**
     * If bytes remain in stream, insure the current buffer is not empty.
     * <p>
     * If there are bytes in current buffer than no more work necessary,
     * else if there are no bytes available in current buffer and there are 
     * still more overflow segments then get the next buffer's worth of
     * data.
     *
     * @exception  IOException
     **/
    public void fillByteHolder() 
        throws IOException
    {
        if ((this.bh.available() == 0) && (this.overflowPage != -1))
        {
            this.bh.clear();

            try
            {
                // fill the byte holder with data from the page.
                BasePage columnOverflowPage = 
                    ((BasePage) this.owner.getPage(overflowPage));

                if (columnOverflowPage != null)
                {
                    columnOverflowPage.restorePortionLongColumn(this);
                    columnOverflowPage.unlatch();
                    columnOverflowPage = null;
                } else {
                    // An overflow page was specified, but we failed to get it.
                    // Probably the value got deleted under our feet.
                    throw new EOFException(MessageService.getTextMessage(
                            MessageId.STORE_STREAM_OVERFLOW_PAGE_NOT_FOUND));
                }

            }
            catch (StandardException se)
            {
                throw new IOException(se);
            }

            this.bh.startReading();
        }
    }

    /**
     * Set the next overflow page of the long column.
     * <p>
     * Used by StorePage.restorePortionLongColumn() as part of the call back
     * process to save the state of the scan of the pieces of the long column.
     * StorePage.restorePortionLongColumn() is called by fillByteHolder() to
     * get the next page worth into a buffer, and in turn after those bytes
     * are read the state of this stream is updated with then next overflow
     * page.
     *
     * @param overflowPage  Page number containing the next segment of the
     *                      long column.  -1 if there are no more segments.
     *
     **/
    public void setOverflowPage(long overflowPage) 
    {
        this.overflowPage = overflowPage;
    }

    /**
     * Set the next overflow page of the long column.
     * <p>
     * Used by StorePage.restorePortionLongColumn() as part of the call back
     * process to save the state of the scan of the pieces of the long column.
     * StorePage.restorePortionLongColumn() is called by fillByteHolder() to
     * get the next page worth into a buffer, and in turn after those bytes
     * are read the state of this stream is updated with then next overflow
     * page.
     *
     * @param overflowId    Page number containing the next segment of the
     *                      long column.  -1 if there are no more segments.
     *
     **/
    public void setOverflowId(int overflowId) 
    {
        this.overflowId = overflowId;
    }

    public long getOverflowPage() 
    {
        return this.overflowPage;
    }

    public int getOverflowId() 
    {
        return this.overflowId;
    }

    /**************************************************************************
     * Public Methods of Resetable Interface
     **************************************************************************
     */

    /**
     * Initialize a Resetable stream.
     * <p>
     * InitStream() must be called first before using any other of the 
     * Resetable interfaces.
     * <p>
     * Reopens the container.  This gets a separate intent shared locked on
     * the table and a read lock on the appropriate row.  These locks remain
     * until the enclosing blob/clob object is closed, or until the end of
     * the transaction in which initStream() was first called.  This locking
     * behavior protects the row while the stream is being accessed.  Otherwise
     * for instance in the case of read committed the original row lock on 
     * the row would be released when the scan went to the next row, and there
     * would be nothing to stop another transaction from deleting the row while
     * the client read through the stream.
     *
     * @exception  StandardException  Standard exception policy.
     **/

    public void initStream() throws StandardException
    {
        // only one initStream() required.
        if (initialized) 
            return;

        // it is possible that the transaction in which the stream was
        // created is committed and no longer valid dont want to get NPE but 
        // instead throw error that container was not opened
        if (owner.getTransaction() == null)
        {
            throw StandardException.newException(
                    SQLState.DATA_CONTAINER_CLOSED);
        }

        // Use isolation level READ_COMMITTED and reopen the container to 
        // get a new container handle to use for locking.  This way, the lock 
        // will be freed when the container handle is closed. This will 
        // happen in closeStream() or when the transaction commits. 
        // Hence, locks will be released before the end of transaction if 
        // blobs/clobs are explicitly released.
        LockingPolicy lp = 
            owner.getTransaction().newLockingPolicy(
                LockingPolicy.MODE_RECORD, 
                TransactionController.ISOLATION_READ_COMMITTED, 
                true);

        // reopen the container
        owner = (BaseContainerHandle) owner.getTransaction().openContainer(
                    owner.getId(), lp, owner.getMode());

        // get a read lock on the appropriate row this will wait until either 
        // the lock is granted or an exception is thrown
        owner.getLockingPolicy().lockRecordForRead(
            owner.getTransaction(), owner, recordToLock, true, false);

        initialized = true;
    }

    /**
     * Reset the stream back to beginning of the long column.
     * <p>
     * Also fills in the first buffer from the stream.
     * <p>
     * Throws exception if the underlying open container has been closed,
     * for example automatically by a commit().
     *
     * @exception  StandardException  Standard exception policy.
     **/
    public void resetStream() throws IOException, StandardException
    {
        // check the container is open, this is needed to make sure the
        // container closed exception is thrown as a StandardException and not
        // as an IOException
        owner.checkOpen();

        // return to the original overflow page and id
        this.overflowPage   = firstOverflowPage;
        this.overflowId     = firstOverflowId;

        // completely clear the byte holder
        this.bh.clear();
        this.bh.startReading();
    }

    /**
     * Close the Resetable stream.
     * <p>
     * Close the container associated with this stream.  (This will also free 
     * the associated IS table lock and the associated S row lock.)
     **/
    public void closeStream()
    {
        owner.close();
        initialized = false;
    }

    /**************************************************************************
     * Public Methods of CloneableStream Interface
     **************************************************************************/

    /**
     * Clone this object.
     * <p>
     * Creates a deep copy of this object. The returned stream has its own
     * working buffers and can be initialized, reset and read independently
     * from this stream.
     * <p>
     * The cloned stream is set back to the beginning of stream, no matter
     * where the current stream happens to be positioned.
     *
     * @return Copy of this stream which can be used independently.
     */
    public InputStream cloneStream() {
        OverflowInputStream ret_stream = 
            new OverflowInputStream(
                bh.cloneEmpty(),
                owner, 
                firstOverflowPage, 
                firstOverflowId, 
                recordToLock);

        return(ret_stream);
    }
}
