/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.sail.nativerdf;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import info.aduna.concurrent.locks.ExclusiveLockManager;
import info.aduna.concurrent.locks.Lock;
import info.aduna.concurrent.locks.ReadWriteLockManager;
import info.aduna.concurrent.locks.WritePrefReadWriteLockManager;
import info.aduna.iteration.CloseableIteration;
import info.aduna.iteration.EmptyIteration;
import info.aduna.iteration.UnionIteration;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.sail.SailConnection;
import org.openrdf.sail.SailException;
import org.openrdf.sail.helpers.SailBase;
import org.openrdf.sail.nativerdf.btree.RecordIterator;
import org.openrdf.sail.nativerdf.model.NativeValue;
/**
* A SAIL implementation using B-Tree indexing on disk for storing and querying
* its data.
*
* @author Arjohn Kampman
* @author jeen
*/
public class NativeStore extends SailBase {
/*-----------*
* Variables *
*-----------*/
/**
* Specifies which triple indexes this native store must use.
*/
private String tripleIndexes;
/**
* Flag indicating whether updates should be synced to disk forcefully. This
* may have a severe impact on write performance. By default, this feature is
* disabled.
*/
private boolean forceSync = false;
private TripleStore tripleStore;
private ValueStore valueStore;
private NamespaceStore namespaceStore;
/**
* Lock manager used to synchronize read and write access to the store.
*/
private ReadWriteLockManager storeLockManager;
/**
* Lock manager used to prevent concurrent transactions.
*/
private ExclusiveLockManager txnLockManager;
private boolean trackLocks = false;
/**
* Flag indicating whether the Sail has been initialized.
*/
private boolean initialized;
/*--------------*
* Constructors *
*--------------*/
/**
* Creates a new NativeStore.
*/
public NativeStore() {
initialized = false;
}
public NativeStore(File dataDir) {
this();
setDataDir(dataDir);
}
public NativeStore(File dataDir, String tripleIndexes) {
this(dataDir);
setTripleIndexes(tripleIndexes);
}
/*---------*
* Methods *
*---------*/
/**
* Sets the triple indexes for the native store, must be called before
* initialization.
*
* @param tripleIndexes
* An index strings, e.g. <tt>spoc,posc</tt>.
*/
public void setTripleIndexes(String tripleIndexes) {
if (isInitialized()) {
throw new IllegalStateException("sail has already been intialized");
}
this.tripleIndexes = tripleIndexes;
}
public String getTripleIndexes() {
return tripleIndexes;
}
/**
* Specifiec whether updates should be synced to disk forcefully, must be
* called before initialization. Enabling this feature may prevent corruption
* in case of events like power loss, but can have a severe impact on write
* performance. By default, this feature is disabled.
*/
public void setForceSync(boolean forceSync) {
this.forceSync = forceSync;
}
public boolean getForceSync() {
return forceSync;
}
/**
* Initializes this NativeStore.
*
* @exception SailException
* If this RdfRepository could not be initialized using the
* parameters that have been set.
*/
public void initialize()
throws SailException
{
if (isInitialized()) {
throw new IllegalStateException("sail has already been intialized");
}
logger.debug("Initializing NativeStore...");
storeLockManager = new WritePrefReadWriteLockManager(trackLocks);
txnLockManager = new ExclusiveLockManager(trackLocks);
// Check initialization parameters
File dataDir = getDataDir();
if (dataDir == null) {
throw new SailException("Data dir has not been set");
}
else if (!dataDir.exists()) {
boolean success = dataDir.mkdirs();
if (!success) {
throw new SailException("Unable to create data directory: " + dataDir);
}
}
else if (!dataDir.isDirectory()) {
throw new SailException("The specified path does not denote a directory: " + dataDir);
}
else if (!dataDir.canRead()) {
throw new SailException("Not allowed to read from the specified directory: " + dataDir);
}
logger.debug("Data dir is " + dataDir);
try {
namespaceStore = new NamespaceStore(dataDir);
valueStore = new ValueStore(dataDir, forceSync);
tripleStore = new TripleStore(dataDir, tripleIndexes, forceSync);
}
catch (IOException e) {
throw new SailException(e);
}
initialized = true;
logger.debug("NativeStore initialized");
}
/**
* Checks whether the Sail has been initialized.
*
* @return <tt>true</tt> if the Sail has been initialized, <tt>false</tt>
* otherwise.
*/
protected final boolean isInitialized() {
return initialized;
}
@Override
protected void shutDownInternal()
throws SailException
{
if (isInitialized()) {
logger.debug("Shutting down NativeStore...");
Lock txnLock = getTransactionLock();
try {
Lock writeLock = getWriteLock();
try {
tripleStore.close();
valueStore.close();
namespaceStore.close();
initialized = false;
logger.debug("NativeStore shut down");
}
catch (IOException e) {
throw new SailException(e);
}
finally {
writeLock.release();
}
}
finally {
txnLock.release();
}
}
}
public boolean isWritable() {
return getDataDir().canWrite();
}
@Override
protected SailConnection getConnectionInternal()
throws SailException
{
if (!isInitialized()) {
throw new IllegalStateException("sail not initialized.");
}
try {
return new NativeStoreConnection(this);
}
catch (IOException e) {
throw new SailException(e);
}
}
public ValueFactory getValueFactory() {
return valueStore;
}
protected TripleStore getTripleStore() {
return tripleStore;
}
protected ValueStore getValueStore() {
return valueStore;
}
protected NamespaceStore getNamespaceStore() {
return namespaceStore;
}
protected Lock getReadLock()
throws SailException
{
try {
return storeLockManager.getReadLock();
}
catch (InterruptedException e) {
throw new SailException(e);
}
}
protected Lock getWriteLock()
throws SailException
{
try {
return storeLockManager.getWriteLock();
}
catch (InterruptedException e) {
throw new SailException(e);
}
}
protected Lock getTransactionLock()
throws SailException
{
try {
return txnLockManager.getExclusiveLock();
}
catch (InterruptedException e) {
throw new SailException(e);
}
}
protected List<Integer> getContextIDs(Resource... contexts)
throws IOException
{
assert contexts.length > 0 : "contexts must not be empty";
// Filter duplicates
LinkedHashSet<Resource> contextSet = new LinkedHashSet<Resource>();
Collections.addAll(contextSet, contexts);
// Fetch IDs, filtering unknown resources from the result
List<Integer> contextIDs = new ArrayList<Integer>(contextSet.size());
for (Resource context : contextSet) {
if (context == null) {
contextIDs.add(0);
}
else {
int contextID = valueStore.getID(context);
if (contextID != NativeValue.UNKNOWN_ID) {
contextIDs.add(contextID);
}
}
}
return contextIDs;
}
/**
* Creates a statement iterator based on the supplied pattern.
*
* @param subj
* The subject of the pattern, or <tt>null</tt> to indicate a
* wildcard.
* @param pred
* The predicate of the pattern, or <tt>null</tt> to indicate a
* wildcard.
* @param obj
* The object of the pattern, or <tt>null</tt> to indicate a
* wildcard.
* @param context
* The context of the pattern, or <tt>null</tt> to indicate a
* wildcard
* @return A StatementIterator that can be used to iterate over the
* statements that match the specified pattern.
*/
protected CloseableIteration<? extends Statement, IOException> createStatementIterator(Resource subj,
URI pred, Value obj, boolean includeInferred, boolean readTransaction, Resource... contexts)
throws IOException
{
int subjID = NativeValue.UNKNOWN_ID;
if (subj != null) {
subjID = valueStore.getID(subj);
if (subjID == NativeValue.UNKNOWN_ID) {
return new EmptyIteration<Statement, IOException>();
}
}
int predID = NativeValue.UNKNOWN_ID;
if (pred != null) {
predID = valueStore.getID(pred);
if (predID == NativeValue.UNKNOWN_ID) {
return new EmptyIteration<Statement, IOException>();
}
}
int objID = NativeValue.UNKNOWN_ID;
if (obj != null) {
objID = valueStore.getID(obj);
if (objID == NativeValue.UNKNOWN_ID) {
return new EmptyIteration<Statement, IOException>();
}
}
List<Integer> contextIDList = new ArrayList<Integer>(contexts.length);
if (contexts.length == 0) {
contextIDList.add(NativeValue.UNKNOWN_ID);
}
else {
for (Resource context : contexts) {
if (context == null) {
contextIDList.add(0);
}
else {
int contextID = valueStore.getID(context);
if (contextID != NativeValue.UNKNOWN_ID) {
contextIDList.add(contextID);
}
}
}
}
ArrayList<NativeStatementIterator> perContextIterList = new ArrayList<NativeStatementIterator>(
contextIDList.size());
for (int contextID : contextIDList) {
RecordIterator btreeIter;
if (includeInferred) {
// Get both explicit and inferred statements
btreeIter = tripleStore.getTriples(subjID, predID, objID, contextID, readTransaction);
}
else {
// Only get explicit statements
btreeIter = tripleStore.getTriples(subjID, predID, objID, contextID, true, readTransaction);
}
perContextIterList.add(new NativeStatementIterator(btreeIter, valueStore));
}
return new UnionIteration<Statement, IOException>(perContextIterList);
}
}