org.apache.jena.sdb.layout2.LoaderTuplesNodes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jena-sdb Show documentation
Show all versions of jena-sdb Show documentation
SDB is a persistence layer for use with Apache Jena that uses an SQL database to store triples/quads.
/*
* 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.sdb.layout2;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.sdb.SDBException ;
import org.apache.jena.sdb.Store ;
import org.apache.jena.sdb.sql.SDBConnection ;
import org.apache.jena.sdb.sql.SDBConnectionHolder ;
import org.apache.jena.sdb.sql.SDBExceptionSQL ;
import org.apache.jena.sdb.store.StoreLoaderPlus ;
import org.apache.jena.sdb.store.TableDesc ;
import org.apache.jena.sdb.store.TupleLoader ;
import org.apache.jena.sparql.core.Quad ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoaderTuplesNodes extends SDBConnectionHolder implements StoreLoaderPlus
{
private static Logger log = LoggerFactory.getLogger(LoaderTuplesNodes.class);
// Delayed initialization until first bulk load.
private boolean initialized = false ;
boolean threading = true; // Do we want to thread?
Thread commitThread = null ; // The loader thread
final static TupleChange flushSignal = new TupleChange(); // Signal to thread to commit
final static TupleChange finishSignal = new TupleChange(); // Signal to thread to finish
ArrayBlockingQueue queue ; // Pipeline to loader thread
AtomicReference threadException ; // Placeholder for problems thrown in the thread
Object threadFlushing = new Object(); // We lock on this when flushing
Map tupleLoaders;
TupleLoader currentLoader;
int count;
int chunkSize = 20000;
private Class extends TupleLoader> tupleLoaderClass;
private Store store;
public LoaderTuplesNodes(SDBConnection connection, Class extends TupleLoader> tupleLoaderClass) {
super(connection) ;
this.tupleLoaderClass = tupleLoaderClass ;
}
public void setStore(Store store) {
this.store = store;
}
@Override
public void startBulkUpdate() {
init() ;
}
@Override
public void finishBulkUpdate() {
flushTriples() ;
}
/**
* Close this loader and finish the thread (if required)
*
*/
@Override
public void close() {
if ( !initialized )
return ;
try {
if ( threading && commitThread.isAlive() ) {
queue.put(finishSignal) ;
commitThread.join() ;
} else {
flushTriples() ;
}
}
catch (Exception e) {
log.error("Problem closing loader: " + e.getMessage()) ;
throw new SDBException("Problem closing loader", e) ;
}
finally {
for ( TupleLoader loader : this.tupleLoaders.values() )
loader.close() ;
this.initialized = false ;
this.commitThread = null ;
this.queue = null ;
this.tupleLoaderClass = null ;
this.tupleLoaders = null ;
}
}
@Override
public void addTriple(Triple triple) {
addTuple(store.getTripleTableDesc(), triple.getSubject(), triple.getPredicate(), triple.getObject()) ;
}
@Override
public void deleteTriple(Triple triple) {
deleteTuple(store.getTripleTableDesc(), triple.getSubject(), triple.getPredicate(), triple.getObject()) ;
}
@Override
public void addQuad(Node g, Node s, Node p, Node o) {
if ( g == Quad.tripleInQuad || Quad.isDefaultGraph(o) )
addTuple(store.getTripleTableDesc(), s, p, o);
else
addTuple(store.getQuadTableDesc(), g, s, p, o);
}
@Override
public void addTuple(TableDesc t, Node... nodes) {
updateStore(new TupleChange(true, t, nodes));
}
@Override
public void deleteQuad(Node g, Node s, Node p, Node o) {
if ( g == Quad.tripleInQuad || Quad.isDefaultGraph(o) )
deleteTuple(store.getTripleTableDesc(), s, p, o);
else
deleteTuple(store.getQuadTableDesc(), g, s, p, o);
}
@Override
public void deleteTuple(TableDesc t, Node... nodes) {
updateStore(new TupleChange(false, t, nodes));
}
@Override
public void deleteAll() {
updateStore(new TupleChange(false, store.getTripleTableDesc()));
}
@Override
public void deleteAll(Node graph) {
updateStore(new TupleChange(false, store.getQuadTableDesc(), graph));
}
static class TupleChange {
public Node[] tuple;
public boolean toAdd;
public TableDesc table;
public TupleChange(boolean toAdd, TableDesc table, Node... tuple) {
this.tuple = tuple;
this.toAdd = toAdd;
this.table = table;
}
public TupleChange() {
tuple = null;
table = null;
toAdd = false;
}
}
private void updateStore(TupleChange tuple) {
if ( threading ) {
checkThreadStatus() ;
try {
queue.put(tuple) ;
}
catch (InterruptedException e) {
log.error("Issue adding to queue: " + e.getMessage()) ;
throw new SDBException("Issue adding to queue" + e.getMessage(), e) ;
}
} else {
updateOneTuple(tuple) ;
}
}
/**
* Flush remain triples in queue to database. If threading this blocks until flush is complete.
*/
private void flushTriples() {
if ( threading ) {
if ( !commitThread.isAlive() )
throw new SDBException("Thread has died") ;
// finish up threaded load
try {
synchronized (threadFlushing) {
queue.put(flushSignal) ;
threadFlushing.wait() ;
}
}
catch (InterruptedException e) {
log.error("Problem sending flush signal: " + e.getMessage()) ;
throw new SDBException("Problem sending flush signal", e) ;
}
checkThreadStatus() ;
} else {
commitTuples() ;
}
}
private void init() {
if ( initialized )
return ;
tupleLoaders = new HashMap() ;
currentLoader = null ;
count = 0 ;
if ( threading ) {
queue = new ArrayBlockingQueue(chunkSize) ;
threadException = new AtomicReference() ;
threadFlushing = new AtomicBoolean() ;
commitThread = new Thread(new Commiter()) ;
commitThread.setDaemon(true) ;
commitThread.start() ;
log.debug("Threading started") ;
}
initialized = true ;
}
private void checkThreadStatus() {
Throwable e = threadException.getAndSet(null) ;
if ( e != null ) {
if ( e instanceof SQLException )
throw new SDBExceptionSQL("Loader thread exception", (SQLException)e) ;
else if ( e instanceof RuntimeException )
throw (RuntimeException)e ;
else
throw new SDBException("Loader thread exception", e) ;
}
if ( !commitThread.isAlive() )
throw new SDBException("Thread has died") ;
}
// Queue up a triple, committing if we have enough chunks
private void updateOneTuple(TupleChange tuple) {
if (currentLoader == null || !currentLoader.getTableDesc().getTableName().equals(tuple.table.getTableName())) {
commitTuples(); // mode is changing, so commit
currentLoader = tupleLoaders.get(tuple.table.getTableName());
if (currentLoader == null) { // make a new loader
try {
currentLoader =
tupleLoaderClass.getConstructor(SDBConnection.class, TableDesc.class, Integer.TYPE).newInstance(connection(), tuple.table, chunkSize);
} catch (Exception e) {
throw new SDBException("Problem making new tupleloader", e);
}
currentLoader.start();
tupleLoaders.put(tuple.table.getTableName(), currentLoader);
}
}
if (tuple.toAdd) currentLoader.load(tuple.tuple);
else currentLoader.unload(tuple.tuple);
}
private void commitTuples() {
if ( currentLoader != null ) {
currentLoader.finish() ;
}
}
@Override
public void setChunkSize(int chunkSize) { this.chunkSize = chunkSize ; }
@Override
public int getChunkSize() { return this.chunkSize ; }
@Override
public void setUseThreading(boolean useThreading) { this.threading = useThreading ; }
@Override
public boolean getUseThreading() { return this.threading ; }
// ---- Bulk loader
/**
* The (very minimal) thread code
*/
class Commiter implements Runnable {
@Override
public void run() {
log.debug("Running loader thread") ;
threadException.set(null) ;
while (true) {
try {
TupleChange tuple = queue.take() ;
if ( tuple == flushSignal ) {
synchronized (threadFlushing) {
try {
commitTuples() ;
}
catch (Throwable e) {
handleIssue(e) ;
}
threadFlushing.notify() ;
}
} else if ( tuple == finishSignal ) {
try {
commitTuples() ; // force commit
}
catch (Throwable e) {
handleIssue(e) ;
}
break ;
} else {
updateOneTuple(tuple) ;
}
}
catch (Throwable e) {
handleIssue(e) ;
}
}
}
private void handleIssue(Throwable e) {
log.error("Error in thread: " + e.getMessage(), e) ;
threadException.set(e) ;
}
}
}