/* * 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.nodepool.xa; import java.io.*; import java.nio.*; // Java 2 standard packages import java.util.*; // Third-party packages import org.apache.log4j.Logger; // log4j classes // Locally written packages import org.mulgara.store.nodepool.*; import org.mulgara.store.xa.*; import org.mulgara.util.Constants; import org.mulgara.util.IntFile; import org.mulgara.util.LongMapper; /** * A NodePool implementation which supports data integrity. * * @created 2001-09-10 * * @author David Makepeace * @author Michael Judd * * @version $Revision: 1.1 $ * * @modified $Date: 2005/02/22 08:16:45 $ by $Author: newmana $ * * @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 XANodePoolImpl implements XANodePool { /** * Logger. */ private final static Logger logger = Logger.getLogger(XANodePoolImpl.class); /** * Description of the Field */ private final static int FILE_MAGIC = 0xa5eeefe4; /** * Description of the Field */ private final static int FILE_VERSION = 5; /** * Index of the file magic number within each of the two on-disk metaroots. */ private final static int IDX_MAGIC = 0; /** * Index of the file version number within each of the two on-disk metaroots. */ private final static int IDX_VERSION = 1; /** * Index of the valid flag (in ints) within each of the two on-disk metaroots. */ private final static int IDX_VALID = 2; /** * The index of the phase number in the on-disk phase. */ private final static int IDX_PHASE_NUMBER = 3; /** * The size of the header of a metaroot in ints. */ private final static int HEADER_SIZE_I = 4; /** * The size of the header of a metaroot in longs. */ private final static int HEADER_SIZE = (HEADER_SIZE_I + 1) / 2; /** * The size of a metaroot in longs. */ private final static int METAROOT_SIZE = HEADER_SIZE + FreeList.Phase.RECORD_SIZE; /** * Description of the Field */ private final static int NR_METAROOTS = 2; /** * The name of the triple store which forms the base name for the node pool * data files. */ private String fileName; /** * The LockFile that protects the node pool from being opened twice. */ private LockFile lockFile; /** * The BlockFile for the node pool metaroot file. */ private BlockFile metarootFile = null; /** * The metaroot blocks of the metaroot file. */ private Block[] metarootBlocks = new Block[NR_METAROOTS]; /** * Description of the Field */ private boolean wrongFileVersion = false; /** * The node ID free list. */ private FreeList freeList = null; /** * The current phase. This is the phase used for allocating and freeing nodes. */ private FreeList.Phase currentPhase = null; /** * The index of the on-disk metaroot where the current phase will be stored * when {@link #prepare} is called. */ private int phaseIndex = 0; /** * Description of the Field */ private int phaseNumber = 0; /** * The token used to prevent the blocks used by the committed phase from being * reused. */ private FreeList.Phase.Token committedPhaseToken = null; private Object committedPhaseLock = new Object(); /** * The token used to prevent the blocks used by the phase which is being (or * has) forced to disk by {@link #prepare} from being reused. */ private FreeList.Phase.Token recordingPhaseToken = null; /** * <code>true</code> if a phase has been prepared but not yet committed. */ private boolean prepared = false; /** * Description of the Field */ private Set<NewNodeListener> newNodeListeners = new HashSet<NewNodeListener>(); /** * Instanciates a NodePoolImpl for the specified triple store. Either {@link * #clear} or {@link #recover} should be called after constructing a * NodePoolImpl and before any other method is called. * * @param fileName the name of the triple store which forms the base name of * the node pool data files. * @throws IOException if an I/O error occurs. */ public XANodePoolImpl(String fileName) throws IOException { this.fileName = fileName; lockFile = LockFile.createLockFile(fileName + ".np.lock"); try { // Open the metaroot file. RandomAccessFile metarootRAF = null; try { metarootRAF = new RandomAccessFile(fileName + ".np", "r"); if (metarootRAF.length() >= 2 * Constants.SIZEOF_INT) { int fileMagic = metarootRAF.readInt(); int fileVersion = metarootRAF.readInt(); if (AbstractBlockFile.byteOrder != ByteOrder.BIG_ENDIAN) { fileMagic = XAUtils.bswap(fileMagic); fileVersion = XAUtils.bswap(fileVersion); } wrongFileVersion = fileMagic != FILE_MAGIC || fileVersion != FILE_VERSION; } else { wrongFileVersion = false; } } catch (FileNotFoundException ex) { wrongFileVersion = false; } finally { if (metarootRAF != null) { metarootRAF.close(); } } freeList = FreeList.openFreeList(fileName + ".np_fl"); } catch (IOException ex) { try { close(); } catch (NodePoolException ex2) { // NO-OP } throw ex; } } /** * Gets the NrValidNodes attribute of the XANodePoolImpl object * * @return The NrValidNodes value */ public synchronized long getNrValidNodes() { checkInitialized(); return currentPhase.getNrValidItems(); } /** * Gets the PhaseNumber attribute of the XANodePoolImpl object * * @return The PhaseNumber value * @throws SimpleXAResourceException EXCEPTION TO DO */ public synchronized int getPhaseNumber() throws SimpleXAResourceException { checkInitialized(); return phaseNumber; } /** * Adds a feature to the NewNodeListener attribute of the XANodePoolImpl * object * * @param l The feature to be added to the NewNodeListener attribute */ public synchronized void addNewNodeListener(NewNodeListener l) { newNodeListeners.add(l); } /** * METHOD TO DO * * @param l PARAMETER TO DO */ public synchronized void removeNewNodeListener(NewNodeListener l) { newNodeListeners.remove(l); } // // Methods from NodePool // /** * METHOD TO DO * * @return RETURNED VALUE TO DO * @throws NodePoolException EXCEPTION TO DO */ public synchronized long newNode() throws NodePoolException { checkInitialized(); try { long node = freeList.allocate(); // Notify all the NewNodeListeners. try { Iterator<NewNodeListener> it = newNodeListeners.iterator(); while (it.hasNext()) { it.next().newNode(node); } } catch (Exception ex) { throw new NodePoolException("Call to NewNodeListener failed.", ex); } return node; } catch (NullPointerException ex) { throw new NodePoolException("Node pool not open."); } catch (IOException ex) { throw new NodePoolException("Failed to allocate new node.", ex); } catch (IllegalStateException ex) { throw new NodePoolException("Node pool already initialized.", ex); } } /** * METHOD TO DO * * @param node PARAMETER TO DO * @throws NodePoolException EXCEPTION TO DO * @throws NoSuchNodeException EXCEPTION TO DO */ public synchronized void releaseNode( long node ) throws NodePoolException, NoSuchNodeException { checkInitialized(); try { if (node < MIN_NODE || node >= freeList.getNextItem()) { throw new NoSuchNodeException(node, "Invalid node ID: " + node); } freeList.free(node); } catch (NullPointerException ex) { throw new NodePoolException("Node pool not open."); } catch (IOException ex) { throw new NodePoolException("Failed to free node.", ex); } catch (IllegalStateException ex) { throw new NodePoolException("Node pool already initialized.", ex); } } public XANodePool newReadOnlyNodePool() { return new ReadOnlyNodePool(); } public XANodePool newWritableNodePool() { return this; } /** * METHOD TO DO * * @throws NodePoolException EXCEPTION TO DO */ public synchronized void close() throws NodePoolException { try { unmap(); } finally { try { try { if (metarootFile != null) metarootFile.close(); } finally { if (freeList != null) freeList.close(); } } catch (IOException ex) { throw new NodePoolException("I/O error closing node pool.", ex); } finally { if (lockFile != null) { lockFile.release(); lockFile = null; } } } } /** * METHOD TO DO * * @throws NodePoolException EXCEPTION TO DO */ public synchronized void delete() throws NodePoolException { currentPhase = null; try { unmap(); } finally { try { try { if (metarootFile != null) metarootFile.delete(); } finally { if (freeList != null) freeList.delete(); } } catch (IOException ex) { throw new NodePoolException("I/O error deleting node pool.", ex); } finally { metarootFile = null; freeList = null; if (lockFile != null) { lockFile.release(); lockFile = null; } } } } protected void finalize() throws Throwable { // close the node pool if it has not already been closed explicitly. try { close(); } catch (Throwable t) { logger.warn( "Exception in finalize while trying to close the node pool.", t ); throw t; } finally { super.finalize(); } } // // Methods from SimpleXAResource. // /** * Reinitializes the NodePool so that it is empty. This should be called after * constructing a NodePoolImpl when creating a new triple store. * * @param phaseNumber the initial phase number. * @throws IOException if an I/O error occurs. * @throws SimpleXAResourceException if it is inappropriate to call clear() at * this time. */ public synchronized void clear( int phaseNumber ) throws IOException, SimpleXAResourceException { if (currentPhase != null) { throw new IllegalStateException( "NodePool already has a current phase." ); } openMetarootFile(true); try { synchronized (committedPhaseLock) { committedPhaseToken = freeList.new Phase(MIN_NODE).use(); } this.phaseNumber = phaseNumber; phaseIndex = 1; freeList.clear(); } catch (IllegalStateException ex) { throw new SimpleXAResourceException("Cannot initialize free list.", ex); } currentPhase = freeList.new Phase(); } /** * Reinitializes the NodePool so that it is empty. This should be called * after constructing a NodePoolImpl when creating a new triple store. * * @throws IOException if an I/O error occurs. * @throws SimpleXAResourceException if it is inappropriate to call clear() * at this time. */ public synchronized void clear( ) throws IOException, SimpleXAResourceException { if (currentPhase == null) { clear(0); } // TODO - should throw an exception if clear() is called after any other // operations are performed. Calling clear() multiple times should be // permitted. } /** * METHOD TO DO * * @throws SimpleXAResourceException EXCEPTION TO DO */ public synchronized void prepare() throws SimpleXAResourceException { checkInitialized(); if (prepared) { // prepare already performed. throw new SimpleXAResourceException("prepare() called twice."); } try { // Perform a prepare. recordingPhaseToken = currentPhase.use(); FreeList.Phase recordingPhase = currentPhase; currentPhase = freeList.new Phase(); // Ensure that all data associated with the phase is on disk. freeList.force(); // Write the metaroot. int newPhaseIndex = 1 - phaseIndex; int newPhaseNumber = phaseNumber + 1; Block block = metarootBlocks[newPhaseIndex]; block.putInt(IDX_VALID, 0); // should already be invalid. block.putInt(IDX_PHASE_NUMBER, newPhaseNumber); logger.debug("Writing node pool metaroot for phase: " + newPhaseNumber); recordingPhase.writeToBlock(block, HEADER_SIZE); block.write(); metarootFile.force(); block.putInt(IDX_VALID, 1); block.write(); metarootFile.force(); phaseIndex = newPhaseIndex; phaseNumber = newPhaseNumber; prepared = true; } catch (IOException ex) { logger.error("I/O error while performing prepare.", ex); throw new SimpleXAResourceException( "I/O error while performing prepare.", ex ); } finally { if (!prepared) { // Something went wrong! logger.error("Prepare failed."); if (recordingPhaseToken != null) { recordingPhaseToken.release(); recordingPhaseToken = null; } } } } /** * METHOD TO DO * * @throws SimpleXAResourceException EXCEPTION TO DO */ public synchronized void commit() throws SimpleXAResourceException { if (!prepared) { // commit without prepare. throw new SimpleXAResourceException( "commit() called without previous prepare()." ); } // Perform a commit. try { // Invalidate the metaroot of the old phase. Block block = metarootBlocks[1 - phaseIndex]; block.putInt(IDX_VALID, 0); block.write(); metarootFile.force(); // Release the token for the previously committed phase. synchronized (committedPhaseLock) { if (committedPhaseToken != null) { committedPhaseToken.release(); } committedPhaseToken = recordingPhaseToken; } recordingPhaseToken = null; } catch (IOException ex) { logger.fatal("I/O error while performing commit.", ex); throw new SimpleXAResourceException( "I/O error while performing commit.", ex ); } finally { prepared = false; if (recordingPhaseToken != null) { // Something went wrong! recordingPhaseToken.release(); recordingPhaseToken = null; logger.error("Commit failed. Calling close()."); try { close(); } catch (Throwable t) { logger.error("Exception on forced close()", t); } } } } /** * METHOD TO DO * * @return RETURNED VALUE TO DO * @throws SimpleXAResourceException EXCEPTION TO DO */ public synchronized int[] recover() throws SimpleXAResourceException { if (currentPhase != null) { return new int[0]; } if (wrongFileVersion) { throw new SimpleXAResourceException("Wrong metaroot file version."); } try { openMetarootFile(false); } catch (IOException ex) { throw new SimpleXAResourceException("I/O error", ex); } // Count the number of valid phases. int phaseCount = 0; if (metarootBlocks[0].getInt(IDX_VALID) != 0) { ++phaseCount; } if (metarootBlocks[1].getInt(IDX_VALID) != 0) { ++phaseCount; } // Read the phase numbers. int[] phaseNumbers = new int[phaseCount]; int index = 0; if (metarootBlocks[0].getInt(IDX_VALID) != 0) { phaseNumbers[index++] = metarootBlocks[0].getInt(IDX_PHASE_NUMBER); } if (metarootBlocks[1].getInt(IDX_VALID) != 0) { phaseNumbers[index++] = metarootBlocks[1].getInt(IDX_PHASE_NUMBER); } return phaseNumbers; } /** * @param phaseNumber PARAMETER TO DO * @throws IOException EXCEPTION TO DO * @throws SimpleXAResourceException EXCEPTION TO DO */ public synchronized void selectPhase( int phaseNumber ) throws IOException, SimpleXAResourceException { if (currentPhase != null) { throw new SimpleXAResourceException( "selectPhase() called on initialized NodePoolImpl." ); } if (metarootFile == null) { throw new SimpleXAResourceException( "Node pool metaroot file is not open." ); } // Locate the metaroot corresponding to the given phase number. if ( metarootBlocks[0].getInt(IDX_VALID) != 0 && metarootBlocks[0].getInt(IDX_PHASE_NUMBER) == phaseNumber ) { phaseIndex = 0; // A new phase will be saved in the other metaroot. } else if ( metarootBlocks[1].getInt(IDX_VALID) != 0 && metarootBlocks[1].getInt(IDX_PHASE_NUMBER) == phaseNumber ) { phaseIndex = 1; // A new phase will be saved in the other metaroot. } else { throw new SimpleXAResourceException( "Invalid phase number: " + phaseNumber ); } // Load a duplicate of the selected phase. The duplicate will have a // phase number which is one higher than the original phase. try { synchronized (committedPhaseLock) { committedPhaseToken = freeList.new Phase( metarootBlocks[phaseIndex], HEADER_SIZE ).use(); } this.phaseNumber = phaseNumber; } catch (IllegalStateException ex) { throw new SimpleXAResourceException( "Cannot construct initial phase for free list.", ex ); } currentPhase = freeList.new Phase(); // Invalidate the on-disk metaroot that the new phase will be saved to. Block block = metarootBlocks[1 - phaseIndex]; block.putInt(IDX_VALID, 0); block.write(); metarootFile.force(); } /** * METHOD TO DO * * @throws SimpleXAResourceException EXCEPTION TO DO */ public synchronized void rollback() throws SimpleXAResourceException { checkInitialized(); boolean success = false; try { if (prepared) { // Restore phaseIndex and phaseNumber to their previous values. phaseIndex = 1 - phaseIndex; --phaseNumber; recordingPhaseToken = null; prepared = false; // Invalidate the metaroot of the other phase. Block block = metarootBlocks[1 - phaseIndex]; block.putInt(IDX_VALID, 0); block.write(); metarootFile.force(); } success = true; } catch (IOException ex) { throw new SimpleXAResourceException( "I/O error while performing rollback (invalidating metaroot)", ex ); } finally { try { currentPhase = freeList.new Phase(committedPhaseToken.getPhase()); } catch (IOException ex) { String msg = "I/O error while performing rollback (new committed phase)"; if (success) throw new SimpleXAResourceException(msg, ex); // new exception, need to re-throw. else logger.info(msg, ex); // already had a different exception, suppress this one and log it. } } } /** * METHOD TO DO */ public void release() { // NO-OP } /** * METHOD TO DO */ public void refresh() { // NO-OP } /** * Gets the Valid attribute of the XANodePoolImpl object * * @param node PARAMETER TO DO * @return The Valid value */ public boolean isValid(long node) { return freeList.isValid(node); } /** * METHOD TO DO */ public synchronized void unmap() { if (committedPhaseToken != null) { recordingPhaseToken = null; prepared = false; try { freeList.new Phase(committedPhaseToken.getPhase()); } catch (Throwable t) { logger.warn("Exception while rolling back in unmap()", t); } currentPhase = null; synchronized (committedPhaseLock) { committedPhaseToken.release(); committedPhaseToken = null; } } if (metarootFile != null) { if (metarootBlocks[0] != null) metarootBlocks[0] = null; if (metarootBlocks[1] != null) metarootBlocks[1] = null; metarootFile.unmap(); } if (freeList != null) freeList.unmap(); } /** @see org.mulgara.store.xa.XANodePool#getNodeMapper() */ public LongMapper getNodeMapper() throws Exception { return IntFile.newTempIntFile("n2n"); } /** * METHOD TO DO * * @param clear PARAMETER TO DO * @throws IOException EXCEPTION TO DO * @throws SimpleXAResourceException EXCEPTION TO DO */ private void openMetarootFile(boolean clear) throws IOException, SimpleXAResourceException { if (metarootFile == null) { metarootFile = AbstractBlockFile.openBlockFile( fileName + ".np", METAROOT_SIZE * Constants.SIZEOF_LONG, BlockFile.IOType.EXPLICIT ); long nrBlocks = metarootFile.getNrBlocks(); if (nrBlocks != NR_METAROOTS) { if (nrBlocks > 0) { logger.info( "Node pool metaroot file for triple store \"" + fileName + "\" has invalid number of blocks: " + nrBlocks ); if (nrBlocks < NR_METAROOTS) { clear = true; metarootFile.clear(); } } else { // Perform initialization on empty file. clear = true; } metarootFile.setNrBlocks(NR_METAROOTS); } metarootBlocks[0] = metarootFile.readBlock(0); metarootBlocks[1] = metarootFile.readBlock(1); } if (clear) { // Invalidate the metaroots on disk. metarootBlocks[0].putInt(IDX_MAGIC, FILE_MAGIC); metarootBlocks[0].putInt(IDX_VERSION, FILE_VERSION); metarootBlocks[0].putInt(IDX_VALID, 0); metarootBlocks[0].write(); metarootBlocks[1].putInt(IDX_MAGIC, 0); metarootBlocks[1].putInt(IDX_VERSION, 0); metarootBlocks[1].putInt(IDX_VALID, 0); metarootBlocks[1].write(); metarootFile.force(); } } /** * METHOD TO DO */ private void checkInitialized() { if (currentPhase == null) { throw new IllegalStateException( "No current phase. NodePool has not been initialized or has been closed." ); } } final class ReadOnlyNodePool implements XANodePool { private FreeList.Phase phase = null; private FreeList.Phase.Token token = null; /** * CONSTRUCTOR ReadOnlyNodePool TO DO */ ReadOnlyNodePool() { synchronized (committedPhaseLock) { if (committedPhaseToken == null) { throw new IllegalStateException( "Cannot create read only view of uninitialized NodePool." ); } } } public synchronized long getNrValidNodes() { return phase.getNrValidItems(); } public long newNode() { throw new UnsupportedOperationException("Read-only node pool."); } public void releaseNode(long node) { throw new UnsupportedOperationException("Read-only node pool."); } public XANodePool newReadOnlyNodePool() { throw new UnsupportedOperationException(); } public XANodePool newWritableNodePool() { throw new UnsupportedOperationException(); } public void close() { throw new UnsupportedOperationException("Read-only node pool."); } public void delete() { throw new UnsupportedOperationException("Read-only node pool."); } public synchronized void release() { try { if (token != null) { token.release(); } } finally { phase = null; token = null; } } public synchronized void refresh() { synchronized (committedPhaseLock) { FreeList.Phase committedPhase = committedPhaseToken.getPhase(); if (phase != committedPhase) { if (token != null) { token.release(); } phase = committedPhase; token = phase.use(); } } } public void addNewNodeListener(NewNodeListener l) { throw new UnsupportedOperationException(); } public void removeNewNodeListener(NewNodeListener l) { throw new UnsupportedOperationException(); } public void prepare() { } public void commit() { } public void rollback() { } public void clear() { } public void clear(int phaseNumber) { } public int[] recover() { throw new UnsupportedOperationException("Attempting to recover ReadOnlyNodePool"); } public void selectPhase(int phaseNumber) { throw new UnsupportedOperationException("Attempting to selectPhase of ReadOnlyNodePool"); } public int getPhaseNumber() { return phaseNumber; } public LongMapper getNodeMapper() throws Exception { throw new UnsupportedOperationException("Attempting to map newly allocated nodes of ReadOnlyNodePool"); } } }