/*

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

   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.iapi.services.io.FormatIdUtil;
import org.apache.derby.iapi.services.io.StoredFormatIds;
import org.apache.derby.shared.common.sanity.SanityManager;

import org.apache.derby.iapi.store.raw.Compensation;
import org.apache.derby.iapi.store.raw.ContainerHandle;
import org.apache.derby.iapi.store.raw.LockingPolicy;
import org.apache.derby.iapi.store.raw.Transaction;
import org.apache.derby.iapi.store.raw.Undoable;

import org.apache.derby.iapi.store.raw.xact.RawTransaction;
import org.apache.derby.iapi.store.raw.data.RawContainerHandle;
import org.apache.derby.iapi.store.raw.log.LogInstant;

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

import org.apache.derby.iapi.util.ByteArray;

import java.io.ObjectOutput;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.IOException;
import org.apache.derby.iapi.services.io.LimitObjectInput;

/**
	Log operation to create, drop or remove a container.

	Both the doMe or the undoMe of a create actually caused the container
	header to be modified and flushed before the log record is flushed.  This
	is necessary for 2 reasons, one is that of ensuring enough disk space, and
	the other is because unlike any other operation, the log record create
	container is in the log stream before the container is in the container
	cache.  What this mean is that if a checkpoint started after the container
	operation but before the container is kept or is dirtied in the container
	cache, then checkpoint will not know to wait for the container to be kept
	or cleaned.  The checkpoint will erroneous assume that the operation does
	not need to be redone since its log instant is before the checkpoint but in
	fact the change has not been flushed to disk.

	A drop or remove container does not have this problem.  The container exist
	and is in kept state when the operation is logged so the checkpoint will
	not overlook it and it doesn't need to flush the container header.  In the
	case of remove, the stub is flushed for a different reason - that of
	ensuring disk space.

*/
public class ContainerOperation extends ContainerBasicOperation implements Undoable
{
	protected byte operation;		// create, drop, or remove

	// in previous version of contianerOperation, there may not
	// be a createByteArray
	transient protected boolean hasCreateByteArray = true;

	protected ByteArray createByteArray;	// information necessary to
															// recreate the container 

	protected static final byte CREATE = (byte)1;
	protected static final byte DROP = (byte)2;
	protected static final byte REMOVE = (byte)4;

	protected ContainerOperation(RawContainerHandle hdl, byte operation) 
		 throws StandardException
	{
		super(hdl);
		this.operation = operation;
	}

	/*
	 * Formatable methods
	 */

	// no-arg constructor, required by Formatable 
	public ContainerOperation() { super(); }

	public void writeExternal(ObjectOutput out) throws IOException  
	{
		super.writeExternal(out);
		out.writeByte(operation);

		if (operation == CREATE)
		{
			try
			{
				createByteArray = containerHdl.logCreateContainerInfo();
			}
			catch (StandardException se)
			{
				throw new IOException(se.toString());
			}
					
			createByteArray.writeExternal(out);
		}
	}

	/**
		@exception IOException cannot read log record from log stream
		@exception ClassNotFoundException cannot read ByteArray object
	 */
	public void readExternal(ObjectInput in)
		 throws IOException, ClassNotFoundException 
	{
		super.readExternal(in);
		operation = in.readByte();

		if (operation == CREATE && hasCreateByteArray)
		{
			createByteArray = new ByteArray();
			createByteArray.readExternal(in);
		}
	}

	/**
		Return my format identifier.
	*/
	public int getTypeFormatId() {
		return StoredFormatIds.LOGOP_CONTAINER;
	}


	/*
	 * override ContainerBasicOperation's findContainerForRedoRecovery
	 */
	/**
        Find container for load tran.
        <p>
		If we are in load tran, and the operation is a create, the container
		may not (should not?) exist yet.  We need to recreate it.

		@exception StandardException Standard Derby error policy.
	 */
	protected RawContainerHandle findContainerForRedoRecovery(
    RawTransaction xact)
		 throws StandardException
	{
		if (SanityManager.DEBUG)
			SanityManager.ASSERT(createByteArray != null,
			"cannot reCreate container in load tran, createByteArray is null");

		long sid = containerId.getSegmentId();
		long cid = containerId.getContainerId();

		xact.reCreateContainerForRedoRecovery(sid, cid, createByteArray);

		// now we should be able to open this container
		return xact.openDroppedContainer(containerId, (LockingPolicy)null);
	}

	/** 
		@exception StandardException Standard Derby error policy
	*/
	public final void doMe(Transaction tran, LogInstant instant, 
						   LimitObjectInput in)
		 throws StandardException
	{

		switch (operation)
		{
		case DROP:
			containerHdl.dropContainer(instant, true);
			//
			// RESOLVE: if it hasn't been stubbified, even at redo time, we will
			// want to earmark this as a post commit work because we know it will
			// not be wasted effort.
			//
			break;

		case REMOVE:
			containerHdl.removeContainer(instant);
			break;

		case CREATE:
			break;
			// nothing to do with create container, it has already been synced to
			// disk.  If the container is subsequently dropped or even removed,
			// that's fine too.  Don't bother to find it.
		}

		releaseResource(tran);
	}


	/**
		Undo of create, drop or remove

		@param tran the transaction that is undoing this operation
		@param hdl the container handle.  This is found here during runtime
		undo - in which case we made the CLR and passed in the containerHdl
		found in generateUndo and it is passed back to this; or it is found in
		the CLR's needsRedo and is passed in and this operation never found the
		container.  Either case, release resource at the end is safe
		@param CLRInstant the log instant of the CLR
		@param in optional data

		@exception StandardException Standard Derby error policy
	*/
	public void undoMe(Transaction tran, RawContainerHandle hdl,
					   LogInstant CLRInstant, LimitObjectInput in)
		 throws StandardException
	{
		switch(operation)
		{
		case DROP:
			if (SanityManager.DEBUG) {
				SanityManager.ASSERT(hdl != null, "container handle is null");
				SanityManager.ASSERT(hdl.getContainerStatus() != RawContainerHandle.COMMITTED_DROP,
									 "Undoing a drop but the container status is not dropped");
			}
			hdl.dropContainer(CLRInstant, false); // not dropped
			break;

		case CREATE: 
			// remove the container
			hdl.removeContainer(CLRInstant);
			break;

		case REMOVE:
			if (SanityManager.DEBUG) {
				SanityManager.THROWASSERT("cannot undo REMOVE, should not have generated a CLR in the first place");
			}
			break;
		}
		releaseResource(tran);

	}

	/**
		@see org.apache.derby.iapi.store.raw.Undoable
		@exception StandardException Standard Derby error policy
	*/
	public Compensation generateUndo(Transaction tran, LimitObjectInput in)
		 throws StandardException
	{
		if (operation == REMOVE)
			return null;		// cannot undo REMOVE
		else
		{
			RawContainerHandle undoContainerHandle = findContainer(tran);
			
			// mark the container as pre-dirtied so that if a checkpoint
			// happens after the log record is sent to the log stream, the
			// cache cleaning will wait for this change.
			//
			// RESOLVE: don't do this now because if undo failed, this
			// container will be "stuck" in the preDirty state and checkpoint
			// will be stuck
			// undoContainerHandle.preDirty(true);
			//

			return new ContainerUndoOperation(undoContainerHandle, this);
		}
	}

	/** debug */
	public String toString()
	{
		if (SanityManager.DEBUG)
		{
			String str = super.toString();
			switch(operation)
			{
			case CREATE: str += " CREATE container " + containerId;
				break;
			case DROP: str += " DROP container " + containerId;
				break;
			case REMOVE: str += " REMOVE container " + containerId;
				break;
			}
			return str;
		}
		else
			return null;
	}


}
