/* * 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.statement.xa11; import java.io.*; import java.nio.*; // Java 2 standard packages import java.util.*; // Third party packages import org.apache.log4j.Logger; // Locally written packages import org.mulgara.query.*; import org.mulgara.store.nodepool.*; import org.mulgara.store.statement.*; import org.mulgara.store.statement.xa.TripleAVLFile; import org.mulgara.store.tuples.StoreTuples; import org.mulgara.store.tuples.TuplesOperations; import org.mulgara.store.xa.AbstractBlockFile; import org.mulgara.store.xa.Block; import org.mulgara.store.xa.BlockFile; import org.mulgara.store.xa.LockFile; import org.mulgara.store.xa.PersistableMetaRoot; import org.mulgara.store.xa.SimpleXAResourceException; import org.mulgara.store.xa.XAStatementStore; import org.mulgara.store.xa.XAUtils; import org.mulgara.util.Constants; /** * An implementation of {@link StatementStore}. * * @created 2008-09-30 * @author Paula Gearon * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * @copyright ©2001-2004 <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 XA11StatementStoreImpl implements XAStatementStore { /** Logger. */ private final static Logger logger = Logger.getLogger(XA11StatementStoreImpl.class); /** The value of the invalid gNode */ final static long NONE = NodePool.NONE; /** The subject/predicate/object index */ final static int TI_3012 = 0; /** The predicate/object/subject index */ final static int TI_3120 = 1; /** The object/subject/predicate index */ final static int TI_3201 = 2; /** The number of indexes */ final static int NR_INDEXES = 3; /** The ordering of indexes, as indexed by the TI_ values */ private final static int[][] orders = { {3, 0, 1, 2}, // TI_3012 {3, 1, 2, 0}, // TI_3120 {3, 2, 0, 1} // TI_3201 }; private final static int[] selectIndex = { /* 3XXX */ TI_3012, /* 3XX0 */ TI_3012, /* 3X1X */ TI_3120, /* 3X10 */ TI_3012, /* 32XX */ TI_3201, /* 32X0 */ TI_3201, /* 321X */ TI_3120, /* 3210 */ TI_3012 }; /** A number to identify the correct file type */ private final static int FILE_MAGIC = 0xa5e7f21e; /** The version of file format */ private final static int FILE_VERSION = 9; /** 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_INTS = 4; /** The size of the header of a metaroot in longs. */ private final static int HEADER_SIZE_LONGS = (HEADER_SIZE_INTS + 1) / 2; /** The size of a metaroot in longs. */ private final static int METAROOT_SIZE = HEADER_SIZE_LONGS + Phase.RECORD_SIZE; /** The number of metaroots in the metaroot file. */ private final static int NR_METAROOTS = 2; /** The mask for a bound Subject */ private final static int MASK0 = 1; /** The mask for a bound Predicate */ private final static int MASK1 = 2; /** The mask for a bound Object */ private final static int MASK2 = 4; /** The mask for a bound Graph. This must always be set. */ private final static int MASK3 = 8; /** The node number for the system graph. Globalized to <#>. */ private long systemGraphNode = NONE; /** The node number for <rdf:type>. */ private long rdfTypeNode = NONE; /** The node number for the graph class <mulgara:ModelType>. */ private long graphTypeNode = NONE; /** The name of the triple store which forms the base name for the graph files. */ private String fileName; /** The LockFile that protects the graph 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]; /** An error flag that is set during file initialization if the file version is incorrect */ private boolean wrongFileVersion = false; /** The files containing indexed triples */ private TripleAVLFile[] tripleAVLFiles = new TripleAVLFile[NR_INDEXES]; /** The current read/write phase. Only the latest phase can write. */ private Phase currentPhase = null; /** * Determines if modifications can be performed without creating a new * (in-memory) phase. If dirty is false and the current phase is in use (by * unclosed Tupleses) then a new phase must be created to protect the existing * Tupleses before any further modifications are made. */ private boolean dirty = true; /** * The index of the phase in the metaroot. May be 0 or 1 as the commited phase swaps * between the two metaroots. */ private int phaseIndex = 0; /** The number of the current phase */ private int phaseNumber = 0; /** A reference token for keeping the commited phase available until we no longer need it */ private Phase.Token committedPhaseToken = null; /** A synchronization object for locking access to the committed phase */ private Object committedPhaseLock = new Object(); /** A reference token for keeping the recording phase available until we no longer need it */ private Phase.Token recordingPhaseToken = null; /** * This flag indicates that the current object has been fully written, and may be considered * as committed when the rest of the system is ready. */ private boolean prepared = false; /** A set of objects to be informed when nodes are released. */ private List<ReleaseNodeListener> releaseNodeListeners = new ArrayList<ReleaseNodeListener>(); /** * Creates a statement store using a base filename. * * @param fileName The base filename to operate from. * @throws IOException The mass storage could not be accessed. */ public XA11StatementStoreImpl(String fileName) throws IOException { this.fileName = fileName; lockFile = LockFile.createLockFile(fileName + ".g.lock"); try { // Check that the metaroot file was created with a compatible version of the triplestore. RandomAccessFile metarootRAF = null; try { metarootRAF = new RandomAccessFile(fileName + ".g", "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(); } for (int i = 0; i < NR_INDEXES; ++i) { String suffix = ".g_" + orders[i][0] + orders[i][1] + orders[i][2] + orders[i][3]; tripleAVLFiles[i] = new TripleAVLFile(fileName + suffix, orders[i]); } } catch (IOException ex) { try { close(); } catch (StatementStoreException ex2) { logger.info("Exception closing failed XA11StatementStoreImpl", ex2); } throw ex; } } /** * Returns <code>true</code> if there are no triples in the graph * @return <code>true</code> if there are no triples in the graph */ public synchronized boolean isEmpty() { checkInitialized(); return currentPhase.isEmpty(); } /** * Returns a count of the number of triples in the graph * @return a count of the number of triples in the graph */ public synchronized long getNrTriples() { checkInitialized(); return currentPhase.getNrTriples(); } /** * Gets the PhaseNumber attribute of the XAGraphImpl object * @return The PhaseNumber value */ public synchronized int getPhaseNumber() { checkInitialized(); return phaseNumber; } /** * Adds a feature to the ReleaseNodeListener attribute of the XAGraphImpl object * @param l The feature to be added to the ReleaseNodeListener attribute */ public synchronized void addReleaseNodeListener(ReleaseNodeListener l) { if (!releaseNodeListeners.contains(l)) releaseNodeListeners.add(l); } /** * Removes a release node listener. * @param l The listener to remove. */ public synchronized void removeReleaseNodeListener(ReleaseNodeListener l) { releaseNodeListeners.remove(l); } /** * Adds a new triple to the graph if it doesn't already exist. * @param node0 the first element of the new triple * @param node1 the second element of the new triple * @param node2 the third element of the new triple * @param node3 the fourth element of the new triple * @throws StatementStoreException Due to structural or IO errors. */ public synchronized void addTriple(long node0, long node1, long node2, long node3) throws StatementStoreException { checkInitialized(); if ( node0 < NodePool.MIN_NODE || node1 < NodePool.MIN_NODE || node2 < NodePool.MIN_NODE || node3 < NodePool.MIN_NODE ) { throw new StatementStoreException( "Attempt to add a triple with node number out of range: " + node0 + " " + node1 + " " + node2 + " " + node3 ); } if (!dirty && currentPhase.isInUse()) { try { new Phase(true); } catch (IOException ex) { throw new StatementStoreException("I/O error", ex); } } currentPhase.addTriple(node0, node1, node2, node3); } /** * Removes all triples matching the given specification. * @param node0 the value for the first element of the triples * @param node1 the value for the second element of the triples * @param node2 the value for the third element of the triples * @param node3 the value for the fourth element of the triples * @throws StatementStoreException if something exceptional happens */ public synchronized void removeTriples(long node0, long node1, long node2, long node3) throws StatementStoreException { checkInitialized(); if (node0 != NONE && node1 != NONE && node2 != NONE && node3 != NONE) { if (!dirty && currentPhase.isInUse()) { try { new Phase(true); } catch (IOException ex) { throw new StatementStoreException("I/O error", ex); } } // Remove the triple. currentPhase.removeTriple(node0, node1, node2, node3); } else { // Find all the tuples matching the specification and remove them. StoreTuples tuples = currentPhase.findTuples(node0, node1, node2, node3); try { try { if (!tuples.isEmpty()) { // There is at least one triple to remove so protect the // Tuples as we make changes to the triplestore. try { new Phase(true); } catch (IOException ex) { throw new StatementStoreException("I/O error", ex); } long[] triple = new long[] { node0, node1, node2, node3 }; int[] columnMap = tuples.getColumnOrder(); int nrColumns = columnMap.length; tuples.beforeFirst(); while (tuples.next()) { // Copy the row data over to the triple. for (int col = 0; col < nrColumns; ++col) { triple[columnMap[col]] = tuples.getColumnValue(col); } currentPhase.removeTriple(triple[0], triple[1], triple[2], triple[3]); } } } finally { tuples.close(); } } catch (TuplesException ex) { throw new StatementStoreException("Exception while iterating over temporary Tuples.", ex); } } } /** * Finds triples matching the given specification. * @param node0 The 0 node of the triple to find. * @param node1 The 1 node of the triple to find. * @param node2 The 2 node of the triple to find. * @param node3 The 3 node of the triple to find. * @return A set of all the triples which match the search. * @throws StatementStoreException Due to a structural or IO error. */ public synchronized StoreTuples findTuples(long node0, long node1, long node2, long node3) throws StatementStoreException { checkInitialized(); dirty = false; return currentPhase.findTuples(node0, node1, node2, node3); } /** * Finds triples matching the given specification and index mask. * @param mask The mask of the index to use. This is only allowable for 3 variables * and a given graph. * @param node0 The 0 node of the triple to find. * @param node1 The 1 node of the triple to find. * @param node2 The 2 node of the triple to find. * @param node3 The 3 node of the triple to find. * @return A set of all the triples which match the search. * @throws StatementStoreException Due to a structural or IO error. */ public synchronized StoreTuples findTuples( int mask, long node0, long node1, long node2, long node3 ) throws StatementStoreException { checkInitialized(); dirty = false; if (!checkMask(mask, node0, node1, node2, node3)) throw new StatementStoreException("Bad explicit index selection for given node pattern."); return currentPhase.findTuples(mask, node0, node1, node2, node3); } /** * Tests a mask for consistency against the nodes it will be used to find. * @param mask The mask to test. * @param node0 The 0 node of the triple to find. * @param node1 The 1 node of the triple to find. * @param node2 The 2 node of the triple to find. * @param node3 The 3 node of the triple to find. Must not be NONE. * @return <code>true</code> if the mask is consistent with the given nodes. */ private static boolean checkMask(int mask, long node0, long node1, long node2, long node3) { // The graph must be bound if (node3 != NONE) return false; if (node0 != NONE && 0 == (mask & MASK0)) return false; if (node1 != NONE && 0 == (mask & MASK1)) return false; if (node2 != NONE && 0 == (mask & MASK2)) return false; return true; } /** * Returns a StoreTuples which contains all triples in the store. The * parameters provide a hint about how the StoreTuples will be used. This * information is used to select the index from which the StoreTuples will be * obtained. * @param node0Bound specifies that node0 will be bound * @param node1Bound specifies that node1 will be bound * @param node2Bound specifies that node2 will be bound * @return the {@link StoreTuples} * @throws StatementStoreException if something exceptional happens */ public synchronized StoreTuples findTuples(boolean node0Bound, boolean node1Bound, boolean node2Bound, boolean node3Bound) throws StatementStoreException { checkInitialized(); dirty = false; return currentPhase.findTuples(node0Bound, node1Bound, node2Bound, node3Bound); } /** * Returns <code>true</code> if any triples match the given specification. * Allows wild cards StatementStore.NONE for any of the node numbers except node3. * @param node0 The 0 node of the triple to find. * @param node1 The 1 node of the triple to find. * @param node2 The 2 node of the triple to find. * @param node3 The 3 node of the triple to find. * @return <code>true</code> if any matching triples exist in the graph. * @throws StatementStoreException Due to a structural or IO error. */ public synchronized boolean existsTriples(long node0, long node1, long node2, long node3) throws StatementStoreException { checkInitialized(); return currentPhase.existsTriples(node0, node1, node2, node3); } public XAStatementStore newReadOnlyStatementStore() { return new ReadOnlyGraph(); } public XAStatementStore newWritableStatementStore() { return this; } /** * Close all files, removing empty space from the ends as required. * @throws StatementStoreException if an error occurs while truncating, * flushing or closing one of the three files. */ public synchronized void close() throws StatementStoreException { try { unmap(); } finally { try { IOException savedEx = null; for (int i = 0; i < NR_INDEXES; ++i) { try { if (tripleAVLFiles[i] != null) tripleAVLFiles[i].close(); } catch (IOException ex) { savedEx = ex; } } if (metarootFile != null) { try { metarootFile.close(); } catch (IOException ex) { savedEx = ex; } } if (savedEx != null) throw new StatementStoreException("I/O error closing graph.", savedEx); } finally { if (lockFile != null) { lockFile.release(); lockFile = null; } } } } /** * Close this graph, if it is currently open, and remove all files associated with it. * @throws StatementStoreException Due to an IO error. */ public synchronized void delete() throws StatementStoreException { currentPhase = null; try { unmap(); } finally { try { IOException savedEx = null; for (int i = 0; i < NR_INDEXES; ++i) { try { if (tripleAVLFiles[i] != null) tripleAVLFiles[i].delete(); } catch (IOException ex) { savedEx = ex; } } if (metarootFile != null) { try { metarootFile.delete(); } catch (IOException ex) { savedEx = ex; } } if (savedEx != null) throw new StatementStoreException("I/O error deleting graph.", savedEx); } finally { for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFiles[i] = null; metarootFile = null; if (lockFile != null) { lockFile.release(); lockFile = null; } } } } /** * Try to safely close the store if this was not done explicitly. */ protected void finalize() throws Throwable { try { close(); } catch (Throwable t) { logger.warn("Exception in finalize while trying to close the statement store.", t); } finally { super.finalize(); } } /** * A manually tracked reference to this object was released. Does nothing. */ public void release() { if (logger.isDebugEnabled()) logger.debug("Release " + this.getClass() + ":" + System.identityHashCode(this)); } /** * This in called in response to the resource being manually refreshed. * This implementation does nothing here. */ public void refresh() { if (logger.isDebugEnabled()) { logger.debug("Refresh " + this.getClass() + ":" + System.identityHashCode(this)); } } // // Methods from SimpleXAResource. // /** * Clears this store to a fresh state. * @param phaseNumber The phase number to set to. * @throws IOException Error with file access * @throws SimpleXAResourceException Error with the data structures. */ public synchronized void clear(int phaseNumber) throws IOException, SimpleXAResourceException { if (logger.isDebugEnabled()) { logger.debug("Clear(" + phaseNumber + ") " + this.getClass() + ":" + System.identityHashCode(this)); } if (currentPhase != null) throw new IllegalStateException("Graph already has a current phase."); openMetarootFile(true); synchronized (committedPhaseLock) { committedPhaseToken = new Phase(true).use(); } this.phaseNumber = phaseNumber; phaseIndex = 1; for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFiles[i].clear(); new Phase(true); } /** * Clear the state of the database. * @throws IOException Filesystem error * @throws SimpleXAResourceException Error in the data structures. */ public synchronized void clear() throws IOException, SimpleXAResourceException { if (logger.isDebugEnabled()) logger.debug("Clear " + this.getClass() + ":" + System.identityHashCode(this)); if (currentPhase == null) clear(0); // could throw an exception if clear() is called after any other // operations are performed. Calling clear() multiple times should be // permitted. } /** * Perform all the operations for a commit and return when all the data structures are in place. * @throws SimpleXAResourceException Due to a bad transaction state, or an IO error while preparing. */ public synchronized void prepare() throws SimpleXAResourceException { if (logger.isDebugEnabled()) logger.debug("Prepare " + this.getClass() + ":" + System.identityHashCode(this)); checkInitialized(); // check that prepare() was not caleld twice if (prepared) throw new SimpleXAResourceException("prepare() called twice."); Phase newCurrent = null; try { // Perform a prepare. recordingPhaseToken = currentPhase.use(); Phase recordingPhase = currentPhase; // new Phase() has a side effect of setting the current phase, but we'll keep a local copy anyway newCurrent = new Phase(false); // could not set up the committed graphs yet, so send them in after the fact newCurrent.graphNodes = new LinkedHashSet<Long>(recordingPhase.graphNodes); if (logger.isDebugEnabled()) { logger.debug("Set phase graph nodes from recording phase in prepare(): " + newCurrent.graphNodes); } // Ensure that all data associated with the phase is on disk. for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFiles[i].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 graph metaroot for phase: " + newPhaseNumber); recordingPhase.writeToBlock(block, HEADER_SIZE_LONGS); 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. An exception is on its way out logger.error("Prepare failed."); if (recordingPhaseToken != null) { recordingPhaseToken.release(); recordingPhaseToken = null; } try { if (newCurrent != null) newCurrent.graphNodes = newCurrent.scanForGraphs(); } catch (Exception e) { logger.error("Error reading graphs while handling exception from phase.prepare", e); } } } } /** * Update the metadata to point to the prepared data structures. * @throws SimpleXAResourceException Due to a bad transaction state, or an IO error. */ public synchronized void commit() throws SimpleXAResourceException { if (logger.isDebugEnabled()) logger.debug("Commit " + this.getClass() + ":" + System.identityHashCode(this)); // check that prepare has been called if (!prepared) 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! An exception is on its way out recordingPhaseToken.release(); recordingPhaseToken = null; logger.error("Commit failed. Calling close()."); try { close(); } catch (Throwable t) { logger.error("Exception on forced close()", t); } } } } /** * Read the state from the metaroot file and use it to set up this object * @return An array of 0, 1, or 2 valid phases that can be selected as the last committed phase. * @throws SimpleXAResourceException Due to an IO error, or a data error in the metaroot file. */ public synchronized int[] recover() throws SimpleXAResourceException { if (logger.isDebugEnabled()) logger.debug("Recover " + this.getClass() + ":" + System.identityHashCode(this)); 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; } /** * Choose a phase from the metaroot file to use * @param phaseNumber The number of the phase to select. This must be one of the valid * phases present in the metaroot file. * @throws IOException Due to an error on the filesystem * @throws SimpleXAResourceException If the file structures are incorrect. */ public synchronized void selectPhase(int phaseNumber) throws IOException, SimpleXAResourceException { if (logger.isDebugEnabled()) { logger.debug("SelectPhase(" + phaseNumber + ") " + this.getClass() + ":" + System.identityHashCode(this)); } if (currentPhase != null) throw new SimpleXAResourceException("selectPhase() called on initialized Graph."); if (metarootFile == null) throw new SimpleXAResourceException("Graph 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 = new Phase(metarootBlocks[phaseIndex], HEADER_SIZE_LONGS).use(); } this.phaseNumber = phaseNumber; } catch (IllegalStateException ex) { throw new SimpleXAResourceException("Cannot construct initial phase.", ex); } new Phase(true); // 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(); } /** * Return to the data structure state from the beginning of the transaction. * @throws SimpleXAResourceException Due to an IO error. */ public synchronized void rollback() throws SimpleXAResourceException { if (logger.isDebugEnabled()) logger.debug("Rollback " + this.getClass() + ":" + System.identityHashCode(this)); checkInitialized(); 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(); } } catch (IOException ex) { throw new SimpleXAResourceException("I/O error while performing rollback (invalidating metaroot)", ex); } finally { try { new Phase(committedPhaseToken.getPhase()); } catch (IOException ex) { throw new SimpleXAResourceException("I/O error while performing rollback (new committed phase)", ex); } } } /** * Get a string representation of the current state of the graph. * @return A string representing the current state */ public synchronized String toString() { if (currentPhase == null) return "Uninitialized Graph."; return currentPhase.toString(); } /** * Attempt to cleanly close all mapped files. */ public synchronized void unmap() { if (committedPhaseToken != null) { recordingPhaseToken = null; prepared = false; try { 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 (tripleAVLFiles != null) { for (int i = 0; i < NR_INDEXES; ++i) { if (tripleAVLFiles[i] != null) tripleAVLFiles[i].unmap(); } } if (metarootFile != null) { if (metarootBlocks[0] != null) metarootBlocks[0] = null; if (metarootBlocks[1] != null) metarootBlocks[1] = null; metarootFile.unmap(); } } /** * Check that the data structures are valid * @return The number of triples in the database */ synchronized long checkIntegrity() { checkInitialized(); return currentPhase.checkIntegrity(); } /** * Open the metaroot file and read in the contents * @param clear If <code>true</code> then the file will be reset to empty. * @throws IOException Due to a filesystem error. * @throws SimpleXAResourceException If the data structures are inconsistent. */ private void openMetarootFile(boolean clear) throws IOException, SimpleXAResourceException { if (metarootFile == null) { metarootFile = AbstractBlockFile.openBlockFile(fileName + ".g", METAROOT_SIZE * Constants.SIZEOF_LONG, BlockFile.IOType.EXPLICIT); long nrBlocks = metarootFile.getNrBlocks(); if (nrBlocks != NR_METAROOTS) { if (nrBlocks > 0) { logger.info("Graph 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(); } } /** * Tests that the current object has been initialized. * @throws IllegalStateException Throws this unchecked exception if the object is not initialized. */ private void checkInitialized() { if (currentPhase == null) throw new IllegalStateException("No current phase. Graph has not been initialized or has been closed."); } final class ReadOnlyGraph implements XAStatementStore { private Phase phase = null; private Phase.Token token = null; /** * Create a read-only graph attached to the current outer database */ ReadOnlyGraph() { synchronized (committedPhaseLock) { if (committedPhaseToken == null) { throw new IllegalStateException("Cannot create read only view of uninitialized Graph."); } } } public synchronized boolean isEmpty() { return phase.isEmpty(); } /** * Returns a count of the number of triples in the graph * @return a count of the number of triples in the graph */ public synchronized long getNrTriples() { return phase.getNrTriples(); } /** * Adds a triple to the graph. * @param node0 The 0 node of the triple. * @param node1 The 1 node of the triple. * @param node2 The 2 node of the triple. * @param node3 The 3 node of the triple. */ public void addTriple(long node0, long node1, long node2, long node3) throws StatementStoreException { throw new UnsupportedOperationException("Trying to modify a read-only graph."); } /** * Removes all triples matching the given specification. * @param node0 the value for the first element of the triples * @param node1 the value for the second element of the triples * @param node2 the value for the third element of the triples * @param node3 the value for the fourth element of the triples */ public void removeTriples(long node0, long node1, long node2, long node3) throws StatementStoreException { throw new UnsupportedOperationException("Trying to modify a read-only graph."); } /** * Finds triples matching the given specification. * @param node0 The 0 node of the triple to find. * @param node1 The 1 node of the triple to find. * @param node2 The 2 node of the triple to find. * @param node3 The 3 node of the triple to find. * @return A StoreTuples which contains the triples which match the search. */ public synchronized StoreTuples findTuples(long node0, long node1, long node2, long node3) throws StatementStoreException { return phase.findTuples(node0, node1, node2, node3); } /** * Finds triples matching the given specification. * @param mask The mask of the index to use. This is only allowable for 3 variables * and a given graph. * @param node0 The 0 node of the triple to find. * @param node1 The 1 node of the triple to find. * @param node2 The 2 node of the triple to find. * @param node3 The 3 node of the triple to find. * @return A StoreTuples which contains the triples which match the search. * @throws StatementStoreException A structural or IO error */ public synchronized StoreTuples findTuples(int mask, long node0, long node1, long node2, long node3) throws StatementStoreException { if (!checkMask(mask, node0, node1, node2, node3)) throw new StatementStoreException("Bad explicit index selection for given node pattern."); return phase.findTuples(mask, node0, node1, node2, node3); } /** * Returns a StoreTuples which contains all triples in the store. The * parameters provide a hint about how the StoreTuples will be used. This * information is used to select the index from which the StoreTuples will * be obtained. * @param node0Bound specifies that node0 will be bound * @param node1Bound specifies that node1 will be bound * @param node2Bound specifies that node2 will be bound * @return the {@link StoreTuples} * @throws StatementStoreException if something exceptional happens */ public synchronized StoreTuples findTuples(boolean node0Bound, boolean node1Bound, boolean node2Bound, boolean node3Bound) throws StatementStoreException { return phase.findTuples(node0Bound, node1Bound, node2Bound, node3Bound); } public synchronized boolean existsTriples(long node0, long node1, long node2, long node3) throws StatementStoreException { return phase.existsTriples(node0, node1, node2, node3); } public XAStatementStore newReadOnlyStatementStore() { throw new UnsupportedOperationException(); } public XAStatementStore newWritableStatementStore() { throw new UnsupportedOperationException(); } public void close() { throw new UnsupportedOperationException("Trying to close a read-only graph."); } public void delete() { throw new UnsupportedOperationException("Trying to delete a read-only graph."); } /** * Release the phase. */ public synchronized void release() { if (logger.isDebugEnabled()) logger.debug("Releasing " + this.getClass() + ":" + System.identityHashCode(this)); try { if (token != null) token.release(); } finally { phase = null; token = null; } } public synchronized void refresh() { if (logger.isDebugEnabled()) logger.debug("Refreshing " + this.getClass() + ":" + System.identityHashCode(this)); synchronized (committedPhaseLock) { Phase committedPhase = committedPhaseToken.getPhase(); if (phase != committedPhase) { if (token != null) token.release(); phase = committedPhase; token = phase.use(); } } } public void addReleaseNodeListener(ReleaseNodeListener l) { throw new UnsupportedOperationException(); } public void removeReleaseNodeListener(ReleaseNodeListener l) { throw new UnsupportedOperationException(); } public void prepare() { if (logger.isDebugEnabled()) logger.debug("Preparing " + this.getClass() + ":" + System.identityHashCode(this)); } public void commit() { if (logger.isDebugEnabled()) logger.debug("Commit " + this.getClass() + ":" + System.identityHashCode(this)); } public void rollback() { if (logger.isDebugEnabled()) logger.debug("Rollback " + this.getClass() + ":" + System.identityHashCode(this)); } public void clear() { if (logger.isDebugEnabled()) logger.debug("Clearing " + this.getClass() + ":" + System.identityHashCode(this)); } public void clear(int phaseNumber) { if (logger.isDebugEnabled()) logger.debug("Clearing (" + phaseNumber + ") " + this.getClass() + ":" + System.identityHashCode(this)); } public int[] recover() { if (logger.isDebugEnabled()) logger.debug("Recovering " + this.getClass() + ":" + System.identityHashCode(this)); throw new UnsupportedOperationException("Attempting to recover ReadOnlyGraph"); } public void selectPhase(int phaseNumber) { if (logger.isDebugEnabled()) logger.debug("Selecting Phase " + this.getClass() + ":" + System.identityHashCode(this)); throw new UnsupportedOperationException("Attempting to selectPhase of ReadOnlyGraph"); } public int getPhaseNumber() { return phaseNumber; } /** * Not used on a read-only graph */ public void initializeSystemNodes(long systemGraphNode, long rdfTypeNode, long systemGraphTypeNode) { // do nothing } } /** * This class represents the state of the the database at a particular time. Only the most * recent phase can be written to. */ final class Phase implements PersistableMetaRoot { /** The size of the data this object stores in the metaroot */ final static int RECORD_SIZE = TripleAVLFile.Phase.RECORD_SIZE * NR_INDEXES; /** Maintaines parallel structural phases between all of the parallel tree data structures */ private TripleAVLFile.Phase[] tripleAVLFilePhases = new TripleAVLFile.Phase[NR_INDEXES]; /** The list of graphs valid in this phase. */ private LinkedHashSet<Long> graphNodes = null; /** * Creates a new phase based on the current state of the database. * This sets the latest phase on the outer statement store. * @param initializeGraphs scan for graphs to initialize the graphs list. * @throws IOException Error on the filesystem. */ Phase(boolean initializeGraphs) throws IOException { if (logger.isDebugEnabled()) logger.debug("Phase(boolean), initializeGraphs = " + initializeGraphs); for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFilePhases[i] = tripleAVLFiles[i].new Phase(); currentPhase = this; dirty = true; if (initializeGraphs) { try { graphNodes = scanForGraphs(); } catch (StatementStoreException e) { throw new IOException("Unable to get metadata for phase: " + e.getMessage()); } } } /** * A copy constructor for duplicating a phase structure. This sets the latest phase * on the outer statement store. * @throws IOException Error on the filesystem. */ Phase(Phase p) throws IOException { assert p != null; for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFilePhases[i] = tripleAVLFiles[i].new Phase(p.tripleAVLFilePhases[i]); currentPhase = this; dirty = true; graphNodes = new LinkedHashSet<Long>(p.graphNodes); if (logger.isDebugEnabled()) { logger.debug("Initializing graph nodes from previous phase in constructor: " + graphNodes); } } /** * Create a phase based on information found in a buffer that came from a metaroot * @param b The buffer containing the phase information. * @param offset The start of the phase information in the buffer * @throws IOException A filesystem error occurred while accessing the buffer. */ Phase(Block b, int offset) throws IOException { for (int i = 0; i < NR_INDEXES; ++i) { tripleAVLFilePhases[i] = tripleAVLFiles[i].new Phase(b, offset); offset += TripleAVLFile.Phase.RECORD_SIZE; } currentPhase = this; dirty = false; try { graphNodes = scanForGraphs(); } catch (StatementStoreException sse) { throw new IOException("Error accessing graph data during initialization"); } } /** * Writes this PersistableMetaRoot to the specified Block. The ints are * written at the specified offset. * @param b The metaroot Block to write this object to. * @param offset The start within the buffer of where the phase information should be written to. */ public void writeToBlock(Block b, int offset) { for (int i = 0; i < NR_INDEXES; ++i) { tripleAVLFilePhases[i].writeToBlock(b, offset); offset += TripleAVLFile.Phase.RECORD_SIZE; } } /** * Create a string representation of the current phase. * @return A string representing this phase */ public String toString() { StringBuffer sb = new StringBuffer(); for (int i = 0; i < NR_INDEXES; ++i) { StoreTuples ts = tripleAVLFilePhases[i].allTuples(); try { sb.append(ts).append('\n'); } finally { try { ts.close(); } catch (TuplesException ex) { logger.warn("TuplesException while closing Tuples", ex); return ex.toString(); } } } return sb.toString(); } /** * Tests if any part of this phase has a reference being kept to it * @return <code>true</code> if any part of this phase is being used. */ boolean isInUse() { for (int i = 0; i < NR_INDEXES; ++i) { if (tripleAVLFilePhases[i].isInUse()) return true; } return false; } /** * Tests if the phase contains any triples * @return <code>true</code> if the phase contains 1 or more triples */ boolean isEmpty() { return tripleAVLFilePhases[TI_3012].isEmpty(); } /** * Gets the number of triples in the phase * @return The number of triples in this phase */ long getNrTriples() { return tripleAVLFilePhases[TI_3012].getNrTriples(); } /** * Adds a new triple to the graph if it doesn't already exist. * @param node0 the first element of the new triple * @param node1 the second element of the new triple * @param node2 the third element of the new triple * @param node3 the fourth element of the new triple * @throws StatementStoreException An IO or data structure error */ void addTriple(long node0, long node1, long node2, long node3) throws StatementStoreException { assert node0 >= NodePool.MIN_NODE; assert node1 >= NodePool.MIN_NODE; assert node2 >= NodePool.MIN_NODE; assert node3 >= NodePool.MIN_NODE; //if ( // DEBUG && nodePool != null && // !nodePool.isValid(node0) && !nodePool.isValid(node1) && // !nodePool.isValid(node2) && !nodePool.isValid(node3) //) throw new AssertionError( // "Attempt to add a triple with an invalid node" //); long[] triple = new long[]{node0, node1, node2, node3}; for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFilePhases[i].asyncAddTriple(triple); if (node1 == rdfTypeNode && node2 == graphTypeNode && node3 == systemGraphNode) { if (logger.isDebugEnabled()) logger.debug("Adding new graph node: " + node0); graphNodes.add(node0); } } /** * Removes the specified triple. * @param node0 the value for the first element of the triple * @param node1 the value for the second element of the triple * @param node2 the value for the third element of the triple * @param node3 the value for the fourth element of the triple * @throws StatementStoreException An IO or structural error */ void removeTriple(long node0, long node1, long node2, long node3) throws StatementStoreException { if ( node0 < NodePool.MIN_NODE || node1 < NodePool.MIN_NODE || node2 < NodePool.MIN_NODE || node3 < NodePool.MIN_NODE ) { throw new StatementStoreException("Attempt to remove a triple with node number out of range: " + node0 + " " + node1 + " " + node2 + " " + node3); } try { for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFilePhases[i].removeTriple(node0, node1, node2, node3); // removeTriple listeners can be informed here } catch (IOException e) { throw new StatementStoreException("I/O error", e); } if (node1 == rdfTypeNode && node2 == graphTypeNode && node3 == systemGraphNode) { if (logger.isDebugEnabled()) logger.debug("Removing graph node: " + node0); graphNodes.remove(node0); } } /** * Finds triples matching the given specification. * @param variableMask the mask used to indicate the desired index. * @param node0 The 0 node of the triple to find. * @param node1 The 1 node of the triple to find. * @param node2 The 2 node of the triple to find. * @param node3 The 3 node of the triple to find. * @return A StoreTuples containing all the triples which match the search. * @throws StatementStoreException An IO or structural error */ StoreTuples findTuples(int variableMask, long node0, long node1, long node2, long node3) throws StatementStoreException { if ( node0 < NodePool.NONE || node1 < NodePool.NONE || node2 < NodePool.NONE || node3 < NodePool.NONE ) { // There is at least one query node. Return an empty StoreTuples. return TuplesOperations.empty(); } if (0 == (variableMask & MASK3)) throw new StatementStoreException("This version of find is for re-ordering graphs, based on a given mask."); try { switch (variableMask) { case MASK3: return tripleAVLFilePhases[TI_3012].findTuples(node3); case MASK0 | MASK3: return tripleAVLFilePhases[TI_3012].findTuples(node3); case MASK1 | MASK3: return tripleAVLFilePhases[TI_3120].findTuples(node3); case MASK0 | MASK1 | MASK3: return tripleAVLFilePhases[TI_3012].findTuples(node3); case MASK2 | MASK3: return tripleAVLFilePhases[TI_3201].findTuples(node3); case MASK0 | MASK2 | MASK3: return tripleAVLFilePhases[TI_3201].findTuples(node3); case MASK1 | MASK2 | MASK3: return tripleAVLFilePhases[TI_3120].findTuples(node3); case MASK0 | MASK1 | MASK2 | MASK3: return tripleAVLFilePhases[TI_3012].findTuples(node3); default: throw new AssertionError(); } } catch (IOException ex) { throw new StatementStoreException("I/O error", ex); } } /** * Finds triples matching the given specification. * @param node0 The 0 node of the triple to find. * @param node1 The 1 node of the triple to find. * @param node2 The 2 node of the triple to find. * @param node3 The 3 node of the triple to find. * @return A StoreTuples containing all the triples which match the search. * @throws StatementStoreException An IO or structural error */ StoreTuples findTuples(long node0, long node1, long node2, long node3) throws StatementStoreException { if ( node0 < NodePool.NONE || node1 < NodePool.NONE || node2 < NodePool.NONE || node3 < NodePool.NONE ) { // There is at least one query node. Return an empty StoreTuples. return TuplesOperations.empty(); } int variableMask = (node0 != NONE ? MASK0 : 0) | (node1 != NONE ? MASK1 : 0) | (node2 != NONE ? MASK2 : 0) | (node3 != NONE ? MASK3 : 0); if (node3 == NONE && variableMask != 0) { return joinGraphedTuples(variableMask, node0, node1, node2); } try { switch (variableMask) { case 0: return tripleAVLFilePhases[TI_3012].allTuples(); case MASK3: return tripleAVLFilePhases[TI_3012].findTuples(node3); case MASK0 | MASK3: return tripleAVLFilePhases[TI_3012].findTuples(node3, node0); case MASK1 | MASK3: return tripleAVLFilePhases[TI_3120].findTuples(node3, node1); case MASK0 | MASK1 | MASK3: return tripleAVLFilePhases[TI_3012].findTuples(node3, node0, node1); case MASK2 | MASK3: return tripleAVLFilePhases[TI_3201].findTuples(node3, node2); case MASK0 | MASK2 | MASK3: return tripleAVLFilePhases[TI_3201].findTuples(node3, node2, node0); case MASK1 | MASK2 | MASK3: return tripleAVLFilePhases[TI_3120].findTuples(node3, node1, node2); case MASK0 | MASK1 | MASK2 | MASK3: if (tripleAVLFilePhases[TI_3012].existsTriple(node3, node0, node1, node2)) { return TuplesOperations.unconstrained(); } return TuplesOperations.empty(); default: throw new AssertionError("Search structure incorrectly calculated"); } } catch (IOException ex) { throw new StatementStoreException("I/O error", ex); } } StoreTuples findTuples(boolean node0Bound, boolean node1Bound, boolean node2Bound, boolean node3Bound) throws StatementStoreException { // The variable mask does not need MASK3, as this has been taken into account in selectIndex[] int variableMask = (node0Bound ? MASK0 : 0) | (node1Bound ? MASK1 : 0) | (node2Bound ? MASK2 : 0); if (variableMask == 0 || node3Bound) { return tripleAVLFilePhases[selectIndex[variableMask]].allTuples(); } else { return joinGraphedTuples(variableMask); } } /** * Iterates over all graphs, finding requested tuples, and joining all the results together into a single tuples. * @param variableMask Pre-calculated from the bound node parameters. * @param node0 The bound value for node0, or < 0 if not bound. * @param node1 The bound value for node1, or < 0 if not bound. * @param node2 The bound value for node2, or < 0 if not bound. * @return A StoreTuples with all the intermediate tuples appended. * @throws StatementStoreException On an error accessing the store. */ StoreTuples joinGraphedTuples(int variableMask, long node0, long node1, long node2) throws StatementStoreException { try { assert (variableMask & MASK3) == 0 : "Must not be asking to join on multiple graphs unless graph is variable."; // get the graphNodes if not already configured if (graphNodes.isEmpty()) throw new IllegalStateException("Unable to query for variable graphs until graphs are initialized"); if (variableMask == (MASK0 | MASK1 | MASK2)) { LiteralGraphTuples result = new LiteralGraphTuples(false); for (long graphNode: graphNodes) { if (tripleAVLFilePhases[TI_3012].existsTriple(graphNode, node0, node1, node2)) { result.appendTuple(new long[] { graphNode }); } } return result; } ArrayList<StoreTuples> graphedTuples = new ArrayList<StoreTuples>(); for (long graphNode: graphNodes) { StoreTuples partialResult = null; switch (variableMask) { case 0: partialResult = tripleAVLFilePhases[TI_3012].findTuplesForMeta(graphNode); break; case MASK0: partialResult = tripleAVLFilePhases[TI_3012].findTuplesForMeta(graphNode, node0); break; case MASK1: partialResult = tripleAVLFilePhases[TI_3120].findTuplesForMeta(graphNode, node1); break; case MASK0 | MASK1: partialResult = tripleAVLFilePhases[TI_3012].findTuplesForMeta(graphNode, node0, node1); break; case MASK2: partialResult = tripleAVLFilePhases[TI_3201].findTuplesForMeta(graphNode, node2); break; case MASK0 | MASK2: partialResult = tripleAVLFilePhases[TI_3201].findTuplesForMeta(graphNode, node2, node0); break; case MASK1 | MASK2: partialResult = tripleAVLFilePhases[TI_3120].findTuplesForMeta(graphNode, node1, node2); break; default: throw new AssertionError("Search structure incorrectly calculated"); } graphedTuples.add(partialResult); } return TuplesOperations.appendCompatible(graphedTuples); } catch (TuplesException te) { throw new StatementStoreException("Error accessing Tuples", te); } catch (IOException ex) { throw new StatementStoreException("I/O error", ex); } } /** * Iterates over all graphs, getting all tuples in the requested order, * and joining all the results together into a single tuples. * @param variableMask Determines the required ordering of the data. * @return A StoreTuples with all the intermediate tuples appended. * @throws StatementStoreException On an error accessing the store. */ StoreTuples joinGraphedTuples(int variableMask) throws StatementStoreException { try { assert (variableMask & MASK3) == 0 : "Must not be asking to join on multiple graphs unless graph is variable."; // get the graphNodes if not already configured if (graphNodes.isEmpty()) throw new IllegalStateException("Unable to query for variable graphs until graphs are initialized"); ArrayList<StoreTuples> graphedTuples = new ArrayList<StoreTuples>(); for (long graphNode: graphNodes) { int phaseIndex; switch (variableMask) { case 0: case MASK0: case MASK0 | MASK1: case MASK0 | MASK1 | MASK2: phaseIndex = TI_3012; break; case MASK1: case MASK1 | MASK2: phaseIndex = TI_3120; break; case MASK2: case MASK0 | MASK2: phaseIndex = TI_3201; break; default: throw new AssertionError("Search structure incorrectly calculated"); } StoreTuples partialResult = tripleAVLFilePhases[phaseIndex].findTuplesForMeta(graphNode); graphedTuples.add(partialResult); } return TuplesOperations.appendCompatible(graphedTuples); } catch (TuplesException te) { throw new StatementStoreException("Error accessing Tuples", te); } catch (IOException ex) { throw new StatementStoreException("I/O error", ex); } } /** * Test is there exist triples according to a given pattern * @param node0 A subject gNode, or NONE * @param node1 A predicate gNode, or NONE * @param node2 A object gNode, or NONE * @param node3 A subject gNode. May not be NONE * @return <code>true</code> if there exist triples that match the pattern * @throws StatementStoreException A structural or IO exception */ boolean existsTriples(long node0, long node1, long node2, long node3) throws StatementStoreException { if (node3 == NONE) throw new IllegalStateException("Graph must be specified"); if ( node0 < NodePool.NONE || node1 < NodePool.NONE || node2 < NodePool.NONE || node3 < NodePool.NONE ) { // There is at least one query node (comes from the query, but not in the data pool). // Return an empty StoreTuples. return false; } int variableMask = (node0 != NONE ? MASK0 : 0) | (node1 != NONE ? MASK1 : 0) | (node2 != NONE ? MASK2 : 0) | MASK3; try { switch (variableMask) { case MASK3: return tripleAVLFilePhases[TI_3012].existsTriples(node3); case MASK0 | MASK3: return tripleAVLFilePhases[TI_3012].existsTriples(node3, node0); case MASK1 | MASK3: return tripleAVLFilePhases[TI_3120].existsTriples(node3, node1); case MASK0 | MASK1 | MASK3: return tripleAVLFilePhases[TI_3012].existsTriples(node3, node0, node1); case MASK2 | MASK3: return tripleAVLFilePhases[TI_3201].existsTriples(node3, node2); case MASK0 | MASK2 | MASK3: return tripleAVLFilePhases[TI_3201].existsTriples(node3, node2, node0); case MASK1 | MASK2 | MASK3: return tripleAVLFilePhases[TI_3120].existsTriples(node3, node1, node2); case MASK0 | MASK1 | MASK2 | MASK3: return tripleAVLFilePhases[TI_3012].existsTriple(node3, node0, node1, node2); default: throw new AssertionError("Search structure incorrectly calculated"); } } catch (IOException ex) { throw new StatementStoreException("I/O error", ex); } } /** * Check that each index contains the same number of triples * @throws AssertionError if the indexes contain a differing number of triples */ long checkIntegrity() { long nrTriples[] = new long[NR_INDEXES]; for (int i = 0; i < NR_INDEXES; ++i) nrTriples[i] = tripleAVLFilePhases[i].checkIntegrity(); for (int i = 1; i < NR_INDEXES; ++i) { if (nrTriples[0] != nrTriples[i]) { StringBuffer sb = new StringBuffer("tripleAVLFiles disagree on the number of triples:"); for (int j = 0; j < NR_INDEXES; ++j) sb.append(' ').append(nrTriples[j]); throw new AssertionError(sb.toString()); } } return nrTriples[0]; } /** * Ask the system for all the known graphs. * @return All the known graph nodes. */ LinkedHashSet<Long> scanForGraphs() throws StatementStoreException, IOException { LinkedHashSet<Long> nodeList = new LinkedHashSet<Long>(); if (systemGraphNode == NONE || rdfTypeNode == NONE || graphTypeNode == NONE) { if (logger.isDebugEnabled()) { logger.debug("Unable to scan for graphs, nodes not initialized. systemGraphNode = " + systemGraphNode + ", rdfTypeNode = " + rdfTypeNode + ", graphTypeNode = " + graphTypeNode); } return nodeList; } StoreTuples graphTuples = tripleAVLFilePhases[TI_3120].findTuples(systemGraphNode, rdfTypeNode, graphTypeNode); assert graphTuples.getNumberOfVariables() == 1; try { graphTuples.beforeFirst(); while (graphTuples.next()) nodeList.add(graphTuples.getColumnValue(0)); } catch (TuplesException e) { throw new StatementStoreException("Unable to construct a result containing all graphs.", e); } if (logger.isDebugEnabled()) logger.debug("Initializing graph nodes from statement indexes: " + nodeList); return nodeList; } /** * Increment the reference count on this object. * @return A new token representing the reference. */ Token use() { return new Token(); } /** * A token to reference the phase, incrementing the reference count from the perpective * of the garbage collector. */ final class Token { /** A list of tokens from the underlying indexes */ private TripleAVLFile.Phase.Token[] tripleAVLFileTokens = new TripleAVLFile.Phase.Token[NR_INDEXES]; /** The phase being referenced */ private Phase phase = Phase.this; /** * Creates a token. This creates tokens for the underlying objects as well. */ Token() { for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFileTokens[i] = tripleAVLFilePhases[i].use(); } /** * Get the phase that this token represents. * @return The phase referenced by this token. */ public Phase getPhase() { assert tripleAVLFileTokens != null : "Invalid Token"; return phase; } /** * Reduce the reference count on the referenced phase by releasing this token. * The token may not be used after being released. */ public void release() { assert tripleAVLFileTokens != null : "Invalid Token"; for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFileTokens[i].release(); tripleAVLFileTokens = null; phase = null; } } } /** * @see org.mulgara.store.xa.XAStatementStore#initializeSystemNodes(long, long, long) * Set the various system graph nodes. These may only be set once, but will allow duplicate calls if the values are the same. * @param systemGraphNode The new system graph node. * @param rdfTypeNode The node for rdf:graph. * @param systemGraphTypeNode The node for the system graph type. * @throws StatementStoreException */ public void initializeSystemNodes(long systemGraphNode, long rdfTypeNode, long systemGraphTypeNode) throws StatementStoreException { if (this.systemGraphNode != NONE && systemGraphNode != this.systemGraphNode) { throw new IllegalStateException("Cannot set system graph again. Was: " + this.systemGraphNode + ", now: " + systemGraphNode); } if (systemGraphNode < 0) throw new IllegalArgumentException("Attempt to set invalid system graph node"); this.systemGraphNode = systemGraphNode; if (this.rdfTypeNode != NONE && rdfTypeNode != this.rdfTypeNode) { throw new IllegalStateException("Cannot set the rdf:type node again. Was: " + this.rdfTypeNode + ", now: " + rdfTypeNode); } if (rdfTypeNode < 0) throw new IllegalArgumentException("Attempt to set invalid rdf:type node"); this.rdfTypeNode = rdfTypeNode; if (this.graphTypeNode != NONE && systemGraphTypeNode != this.graphTypeNode) { throw new IllegalStateException("Cannot set graph type again. Was: " + this.graphTypeNode + ", now: " + systemGraphTypeNode); } if (systemGraphTypeNode < 0) throw new IllegalArgumentException("Attempt to set invalid graph type node"); this.graphTypeNode = systemGraphTypeNode; if (currentPhase != null) { try { currentPhase.graphNodes = currentPhase.scanForGraphs(); } catch (IOException e) { throw new StatementStoreException("Error while scanning for graph nodes", e); } } } }