/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.tdb.store.nodetable;
import static org.apache.jena.tdb.lib.NodeLib.setHash ;
import java.nio.ByteBuffer ;
import java.util.Iterator ;
import java.util.function.Function;
import org.apache.jena.atlas.iterator.Iter ;
import org.apache.jena.atlas.lib.Pair ;
import org.apache.jena.graph.Node ;
import org.apache.jena.tdb.TDBException ;
import org.apache.jena.tdb.base.objectfile.ObjectFile ;
import org.apache.jena.tdb.base.record.Record ;
import org.apache.jena.tdb.index.Index ;
import org.apache.jena.tdb.lib.NodeLib ;
import org.apache.jena.tdb.store.Hash ;
import org.apache.jena.tdb.store.NodeId ;
/** A concrete NodeTable based on native storage (string file and an index) */
public class NodeTableNative implements NodeTable
{
// TODO Split into a general accessor (get and put (node,NodeId) pairs)
// Abstracts the getAllocateNodeId requirements.
protected ObjectFile objects ;
protected Index nodeHashToId ; // hash -> int
private boolean syncNeeded = false ;
// Delayed construction - must call init explicitly.
protected NodeTableNative() {}
// Combined into one constructor.
public NodeTableNative(Index nodeToId, ObjectFile objectFile)
{
this() ;
init(nodeToId, objectFile) ;
}
protected void init(Index nodeToId, ObjectFile objectFile)
{
this.nodeHashToId = nodeToId ;
this.objects = objectFile;
}
// ---- Public interface for Node <==> NodeId
/** Get the Node for this NodeId, or null if none */
@Override
public Node getNodeForNodeId(NodeId id)
{
return _retrieveNodeByNodeId(id) ;
}
/** Find the NodeId for a node, or return NodeId.NodeDoesNotExist */
@Override
public NodeId getNodeIdForNode(Node node) { return _idForNode(node, false) ; }
/** Find the NodeId for a node, allocating a new NodeId if the Node does not yet have a NodeId */
@Override
public NodeId getAllocateNodeId(Node node) { return _idForNode(node, true) ; }
@Override
public boolean containsNode(Node node) {
NodeId x = getNodeIdForNode(node) ;
return NodeId.isDoesNotExist(x) ;
}
@Override
public boolean containsNodeId(NodeId nodeId) {
Node x = getNodeForNodeId(nodeId) ;
return x == null ;
}
// ---- The worker functions
// Synchronization:
// accessIndex and readNodeFromTable
// Cache around this class further out in NodeTableCache are synchronized
// to maintain cache validatity which indirectly sync access to the NodeTable.
// But to be sure, we provide MRSW guarantees on this class.
// (otherwise if no cache => disaster)
// synchonization happens in accessIndex() and readNodeByNodeId
// NodeId to Node worker.
private Node _retrieveNodeByNodeId(NodeId id)
{
if ( NodeId.isDoesNotExist(id) )
return null ;
if ( NodeId.isAny(id) )
return null ;
Node n = readNodeFromTable(id) ;
return n ;
}
// ----------------
// Node to NodeId worker
// Find a node, possibly placing it in the node file as well
private NodeId _idForNode(Node node, boolean allocate)
{
if ( node == Node.ANY )
return NodeId.NodeIdAny ;
// synchronized in accessIndex
NodeId nodeId = accessIndex(node, allocate) ;
return nodeId ;
}
protected final NodeId accessIndex(Node node, boolean create)
{
Hash hash = new Hash(nodeHashToId.getRecordFactory().keyLength()) ;
setHash(hash, node) ;
byte k[] = hash.getBytes() ;
// Key only.
Record r = nodeHashToId.getRecordFactory().create(k) ;
synchronized (this) // Pair to readNodeFromTable.
{
// Key and value, or null
Record r2 = nodeHashToId.find(r) ;
if ( r2 != null )
{
// Found. Get the NodeId.
NodeId id = NodeId.create(r2.getValue(), 0) ;
return id ;
}
// Not found.
if ( ! create )
return NodeId.NodeDoesNotExist ;
// Write the node, which allocates an id for it.
NodeId id = writeNodeToTable(node) ;
// Update the r record with the new id.
// r.value := id bytes ;
id.toBytes(r.getValue(), 0) ;
// Put in index - may appear because of concurrency
if ( ! nodeHashToId.add(r) )
throw new TDBException("NodeTableBase::nodeToId - record mysteriously appeared") ;
return id ;
}
}
// -------- NodeId<->Node
// Synchronization:
// write: in accessIndex
// read: synchronized here.
// Only places for accessing the StringFile.
private final NodeId writeNodeToTable(Node node)
{
syncNeeded = true ;
// Synchronized in accessIndex
long x = NodeLib.encodeStore(node, getObjects()) ;
return NodeId.create(x);
}
private final Node readNodeFromTable(NodeId id)
{
synchronized (this) // Pair to accessIndex
{
if ( id.getId() >= getObjects().length() )
return null ;
return NodeLib.fetchDecode(id.getId(), getObjects()) ;
}
}
// -------- NodeId<->Node
@Override
public synchronized void close()
{
// Close once. This may be shared (e.g. triples table and quads table).
if ( nodeHashToId != null )
{
nodeHashToId.close() ;
nodeHashToId = null ;
}
if ( getObjects() != null )
{
getObjects().close() ;
objects = null ;
}
}
@Override
public NodeId allocOffset()
{
return NodeId.create(getObjects().length()) ;
}
// Not synchronized
@Override
public Iterator<Pair<NodeId, Node>> all() { return all2() ; }
private Iterator<Pair<NodeId, Node>> all2()
{
Iterator<Pair<Long, ByteBuffer>> objs = objects.all() ;
Function<Pair<Long, ByteBuffer>, Pair<NodeId, Node>> transform = item -> {
NodeId id = NodeId.create(item.car().longValue());
ByteBuffer bb = item.cdr();
Node n = NodeLib.decode(bb);
return new Pair<>(id, n);
};
return Iter.map(objs, transform) ;
}
@Override
public void sync()
{
if ( syncNeeded )
{
if ( nodeHashToId != null )
nodeHashToId.sync() ;
if ( getObjects() != null )
getObjects().sync() ;
syncNeeded = false ;
}
}
public ObjectFile getObjects()
{
return objects;
}
@Override
public String toString() { return objects.getLabel() ; }
@Override
public boolean isEmpty()
{
return getObjects().isEmpty() ;
}
@Override
public NodeTable wrapped() {
return null ;
}
}