/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.store.xa;
// Java 2 standard packages
import java.io.*;
// Third party packages
import org.apache.log4j.Logger;
import org.mulgara.util.StackTrace;
/**
* A class that implements efficient copy-on-write semantics for a BlockFile.
* This is the lowest level of the phased writing implementation, and wraps
* the implementations of {@link AbstractBlockFile}.
*
* @created 2001-09-20
*
* @author David Makepeace
*
* @version $Revision: 1.11 $
*
* @modified $Date: 2005/07/05 04:23:54 $
*
* @maintenanceAuthor $Author: pgearon $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright ©2001 <a href="http://www.pisoftware.com/">Plugged In
* Software Pty Ltd</a>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public final class ManagedBlockFile {
/** Logger. */
private final static Logger logger = Logger.getLogger(ManagedBlockFile.class);
/** File extension for the FreeList file. */
private final static String FREELIST_EXT = "_fl";
/** The name of the BlockFile. */
private File file;
/** The open BlockFile. */
private BlockFile blockFile;
/**
* The multi-phase FreeList used for allocating and freeing blocks in the
* BlockFile.
*/
private FreeList freeList;
/** The current (writable) phase. No other phase is writable. */
private Phase currentPhase;
/** True if the ManagedBlockFile has not been closed. */
private boolean isOpen;
/**
* Constructs a ManagedBlockFile with the specified file.
*
* @param file the name of the BlockFile.
* @param blockSize the block size in bytes.
* @param ioType the type of I/O mechanism to use.
* @throws IOException if an I/O error occurs.
*/
public ManagedBlockFile(File file, int blockSize,BlockFile.IOType ioType) throws IOException {
this.file = file;
File freeListFile = new File(file + FREELIST_EXT);
if (file.exists() != freeListFile.exists()) {
logger.error("ERROR: inconsistency between Block file and Free List file");
}
blockFile = AbstractBlockFile.openBlockFile(file, blockSize, ioType);
freeList = FreeList.openFreeList(freeListFile);
isOpen = true;
}
/**
* Constructs a ManagedBlockFile with the specified file name.
*
* @param fileName the name of the BlockFile.
* @param blockSize the block size in bytes.
* @param ioType the type of I/O mechanism to use.
* @throws IOException if an I/O error occurs.
*/
public ManagedBlockFile(String fileName, int blockSize, BlockFile.IOType ioType) throws IOException {
this(new File(fileName), blockSize, ioType);
}
/**
* Truncates the file to zero length.
*
* @throws IOException if an I/O error occurs.
*/
public void clear() throws IOException {
blockFile.clear();
freeList.clear();
}
/**
* Ensures that all data for this BlockFile is stored in persistent storage
* before returning.
*
* @throws IOException if an I/O error occurs.
*/
public void force() throws IOException {
blockFile.force();
freeList.force();
}
/**
* Nullifies references to MappedByteBuffers.
*/
public void unmap() {
if (blockFile != null) {
blockFile.unmap();
}
if (freeList != null) {
freeList.unmap();
}
}
/**
* Closes the block file.
*
* @throws IOException if an I/O error occurs.
*/
public synchronized void close() throws IOException {
close(false);
}
/**
* Closes and deletes the block file.
*
* @throws IOException if an I/O error occurs.
*/
public synchronized void delete() throws IOException {
try {
close(true);
} finally {
blockFile = null;
freeList = null;
}
}
/**
* Closes the block file.
*
* @param deleteFiles true if both the block file and the free list file
* should be deleted after they are closed.
* @throws IOException if an I/O error occurs.
*/
private void close(boolean deleteFiles) throws IOException {
boolean success = false;
try {
if (blockFile != null) {
try {
if (
!deleteFiles && isOpen &&
freeList != null && currentPhase != null
) {
long blockNr = freeList.getNextItem();
long currentBlocks = blockFile.getNrBlocks();
if (currentBlocks < blockNr) {
logger.error("Block file smaller than it should be. Currently: " + currentBlocks + ". Should be >" + blockNr);
} else {
blockFile.setNrBlocks(blockNr);
}
}
} finally {
isOpen = false;
if (deleteFiles) {
blockFile.delete();
} else {
blockFile.close();
}
}
}
success = true;
} finally {
try {
if (freeList != null) {
if (deleteFiles) {
freeList.delete();
} else {
freeList.close();
}
}
} catch (IOException e) {
if (success) throw e; // Worked up to now; re-throw this exception.
else logger.info("Suppressing I/O exception while closing failed resource", e); // Something else already failed.
}
}
}
/**
* Provides phased access to a {@link BlockFile}. These phases are instantiated directly
* by the object needing file access.
*/
public final class Phase implements BlockFile, PersistableMetaRoot {
/** The size of a free-list record. */
public final static int RECORD_SIZE = FreeList.Phase.RECORD_SIZE;
/** The phase for the free list associated with this phase. */
private FreeList.Phase freeListPhase;
/**
* Creates a new writing phase for this ManagedBlockFile.
*
* @throws IOException If an I/O error occurred on the free list.
*/
public Phase() throws IOException {
freeListPhase = freeList.new Phase();
currentPhase = this;
}
/**
* Creates a new phase, duplicating another existing phase.
*
* @throws IOException If an I/O error occurred on the free list.
*/
public Phase(Phase p) throws IOException {
assert p != null;
freeListPhase = freeList.new Phase(p.freeListPhase);
currentPhase = this;
}
/**
* Creates a new phase, using a freelist block.
*
* @param b The block in the freelist to start the new phase in.
* @param offset The offset in the block to find the start of the freelist block.
* @throws IOException If an I/O error occurred on the free list.
*/
public Phase(Block b, int offset) throws IOException {
freeListPhase = freeList.new Phase(b, offset);
currentPhase = this;
check();
}
/**
* Indicates if there are any remaining references to the current phase.
*
* @return <code>true</code> if the phase on the free list is still being referenced.
*/
public boolean isInUse() {
return freeListPhase.isInUse();
}
/**
* Writes this PersistableMetaRoot to the specified Block. The ints are
* written at the specified offset.
*
* @param b the Block.
* @param offset writes the free list block data at this offset in the block.
*/
public void writeToBlock(Block b, int offset) {
freeListPhase.writeToBlock(b, offset);
}
public Token use() {
return new Token();
}
/**
* Establishes that the structure of the blocks is what it should be.
*
* @throws Error When the file is shorter than the fields in the file indicate.
*/
private void check() throws IOException {
// Initial phase.
// Check that the block file has the correct size.
long nrValidBlocks = freeList.getNextItem();
long nrBlocks = getNrBlocks();
if (nrBlocks != nrValidBlocks) {
if (nrBlocks < nrValidBlocks) {
logger.error("File " + file + " may have been truncated.");
throw new Error("File " + file + " may have been truncated.");
}
if (logger.isInfoEnabled()) {
logger.info("File " + file +
" may not have been closed properly on shutdown.\n nrBlocks=" + nrBlocks +
" nrValidBlocks=" + nrValidBlocks + "\n" + new StackTrace());
}
blockFile.setNrBlocks(nrValidBlocks);
}
}
/**
* Sets the length of the file in blocks. Not supported.
*
* @param nrBlocks the length of the file in blocks.
* @throws UnsupportedOperationException is always thrown.
*/
public void setNrBlocks(long nrBlocks) {
throw new UnsupportedOperationException(
"Cannot change number of blocks on a ManagedBlockFile.");
}
/**
* Gets the current length of the BlockFile in blocks. An implementation
* may allow the actual disk file to be larger or smaller than this size
* while it is open but will set the file to this size when it is closed.
*
* @return the current length of the BlockFile in blocks.
*/
public long getNrBlocks() {
return blockFile.getNrBlocks();
}
/**
* Truncates the file to zero length.
*
* @throws IOException if an I/O error occurs.
*/
public void clear() throws IOException {
throw new UnsupportedOperationException(
"ManagedBlockFile.Phase.clear() not supported. " +
"Call ManagedBlockFile.clear() instead.");
}
/**
* Ensures that all data for this BlockFile is stored in persistent storage
* before returning.
*
* @throws IOException if an I/O error occurs.
*/
public void force() throws IOException {
throw new UnsupportedOperationException(
"ManagedBlockFile.Phase.force() not supported. " +
"Call ManagedBlockFile.force() instead.");
}
/**
* Allocates a ByteBuffer to be used for writing to the specified block.
* The contents of the ByteBuffer are undefined. The method {@link
* #writeBlock} should be called to write the buffer to the block but,
* depending on the BlockFile implementation, changes to the ByteBuffer may
* take effect even if {@link #writeBlock} is never called.
*
* @param blockId The ID of the block that this buffer will be written to.
* @return a ByteBuffer to be used for writing to the specified block.
*/
public Block allocateBlock(long blockId) {
throw new UnsupportedOperationException("Cannot allocate a specific block of a ManagedBlockFile.");
}
/**
* Allocates a ByteBuffer which is filled with the contents of the
* specified block. If the buffer is modified then the method {@link
* #writeBlock} should be called to write the buffer back to the file but,
* depending on the BlockFile implementation, changes to the ByteBuffer may
* take effect even if {@link #writeBlock} is never called.
*
* @param blockId the block to read into the ByteBuffer.
* @return The read block.
* @throws IOException if an I/O error occurs.
*/
public Block readBlock(long blockId) throws IOException {
Block block = blockFile.readBlock(blockId);
block.setBlockFile(this);
return block;
}
/**
* Writes a buffer that was allocated by calling either {@link
* #allocateBlock} or {@link #readBlock} to the specified block. The buffer
* may only be written to the same block as was specified when the buffer
* was allocated.
*
* @param block the buffer to write to the file.
* @throws IOException if an I/O error occurs.
*/
public void writeBlock(Block block) throws IOException {
assert this == currentPhase;
blockFile.writeBlock(block);
}
/**
* Changes the block ID of the specified Block. This method is called
* copyBlock because a call to copyBlock() followed by a call to
* writeBlock() can be used to copy the contents of a block to a new
* location in the block file.
*
* @param block the Block to be copied.
* @param dstBlockId the ID of the block to which the Block will be written
* when writeBlock() is called.
* @throws IOException if an I/O error occurs.
*/
public void copyBlock(Block block, long dstBlockId) throws IOException {
assert this == currentPhase;
assert block.getBlockId() != dstBlockId;
if (freeList.isSharedItem(dstBlockId)) {
// Choose an alternative destination block since this one is shared
// with another phase.
freeList.free(dstBlockId);
dstBlockId = freeList.allocate();
blockFile.setNrBlocks(freeList.getNextItem());
}
blockFile.copyBlock(block, dstBlockId);
}
/**
* ManagedBlockFile effectively re-uses in the read
*/
public Block recycleBlock(long blockId, Block block) throws IOException {
return readBlock(blockId);
}
/**
* Used to unmap a file. This is supported on the enclosing class rather than here.
*/
public void unmap() {
throw new UnsupportedOperationException(
"ManagedBlockFile.Phase.unmap() not supported. " +
"Call ManagedBlockFile.unmap() instead.");
}
/**
* Closes the block file.
*/
public void close() throws IOException {
throw new UnsupportedOperationException(
"ManagedBlockFile.Phase.close() not supported. " +
"Call ManagedBlockFile.close() instead.");
}
/**
* Closes and deletes the block file.
*/
public void delete() throws IOException {
throw new UnsupportedOperationException(
"ManagedBlockFile.Phase.delete() not supported. " +
"Call ManagedBlockFile.delete() instead.");
}
/**
* Allocates a ByteBuffer to be used for writing to the specified block. The
* contents of the ByteBuffer are undefined. The method {@link #writeBlock}
* should be called to write the buffer to the block but, depending on the
* BlockFile implementation, changes to the ByteBuffer may take effect even if
* {@link #writeBlock} is never called.
*
* @return a ByteBuffer to be used for writing to the specified block.
* @throws IOException if an I/O error occurs.
*/
public Block allocateBlock() throws IOException {
assert this == currentPhase;
long blockId = freeList.allocate();
blockFile.setNrBlocks(freeList.getNextItem());
Block block = blockFile.allocateBlock(blockId);
block.setBlockFile(this);
return block;
}
/**
* Gets a new block from the file to write block data to.
* The block data to write is a modified version of an existing block.
*
* @param block The block to be copied into the new "modified" block.
* @throws IOException if an I/O error occurs.
*/
public void modifyBlock(Block block) throws IOException {
assert this == currentPhase;
if (freeList.isSharedItem(block.getBlockId())) {
// Allocate a new block and copy block to it.
long newBlockId = freeList.allocate();
blockFile.setNrBlocks(freeList.getNextItem());
// Although we use block after the block is freed, the block can't
// be reused (by a later phase) until the current phase is committed.
freeList.free(block.getBlockId());
blockFile.copyBlock(block, newBlockId);
}
}
/**
* Release a block ID back into the free list.
*
* @param blockId The ID of the block to release.
* @throws IOException if an I/O error occurs.
*/
public void freeBlock(long blockId) throws IOException {
assert this == currentPhase;
freeList.free(blockId);
}
/**
* Instances of this class represent a reference to the enclosing phase.
*/
public final class Token {
/** A reference to the underlying token for the free list phase. */
private FreeList.Phase.Token freeListToken;
/**
* Create a new token for the encapsulating phase.
*/
Token() {
freeListToken = freeListPhase.use();
}
/**
* Get the encapsulating phase.
*
* @return The encapsulating phase.
*/
public Phase getPhase() {
assert freeListToken != null : "Invalid Token";
return Phase.this;
}
/**
* Release this reference to the encapsulating phase.
*/
public void release() {
assert freeListToken != null : "Invalid Token";
freeListToken.release();
freeListToken = null;
}
}
}
}