/** * 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.query.text ; import java.util.Iterator ; import java.util.List ; import org.apache.jena.graph.Graph ; import org.apache.jena.graph.Node ; import org.apache.jena.query.ReadWrite ; import org.apache.jena.sparql.core.* ; import org.apache.lucene.queryparser.classic.QueryParserBase ; import org.slf4j.Logger ; import org.slf4j.LoggerFactory ; public class DatasetGraphText extends DatasetGraphMonitor implements Transactional { private static Logger log = LoggerFactory.getLogger(DatasetGraphText.class) ; private final TextIndex textIndex ; private final Graph dftGraph ; private final boolean closeIndexOnClose; // If we are going to implement Transactional, then we are going to have to do as DatasetGraphWithLock and // TDB's DatasetGraphTransaction do and track transaction state in a ThreadLocal private final ThreadLocal<ReadWrite> readWriteMode = new ThreadLocal<ReadWrite>(); public DatasetGraphText(DatasetGraph dsg, TextIndex index, TextDocProducer producer) { this(dsg, index, producer, false); } public DatasetGraphText(DatasetGraph dsg, TextIndex index, TextDocProducer producer, boolean closeIndexOnClose) { super(dsg, producer) ; this.textIndex = index ; dftGraph = GraphView.createDefaultGraph(this) ; this.closeIndexOnClose = closeIndexOnClose; } // ---- Intercept these and force the use of views. @Override public Graph getDefaultGraph() { return dftGraph ; } @Override public Graph getGraph(Node graphNode) { return GraphView.createNamedGraph(this, graphNode) ; } // ---- public TextIndex getTextIndex() { return textIndex ; } /** Search the text index on the default text field */ public Iterator<TextHit> search(String queryString) { return search(queryString, null) ; } /** Search the text index on the text field associated with the predicate */ public Iterator<TextHit> search(String queryString, Node predicate) { return search(queryString, predicate, -1) ; } /** Search the text index on the default text field */ public Iterator<TextHit> search(String queryString, int limit) { return search(queryString, null, limit) ; } /** Search the text index on the text field associated with the predicate */ public Iterator<TextHit> search(String queryString, Node predicate, int limit) { queryString = QueryParserBase.escape(queryString) ; if ( predicate != null ) { String f = textIndex.getDocDef().getField(predicate) ; queryString = f + ":" + queryString ; } List<TextHit> results = textIndex.query(predicate, queryString, limit) ; return results.iterator() ; } @Override public void begin(ReadWrite readWrite) { readWriteMode.set(readWrite); super.begin(readWrite) ; super.getMonitor().start() ; } /** * Rollback all changes, discarding any exceptions that occur. */ @Override public void abort() { // Roll back all both objects, discarding any exceptions that occur try { super.abort(); } catch (Throwable t) { log.warn("Exception in abort: " + t.getMessage(), t); } try { textIndex.rollback(); } catch (Throwable t) { log.warn("Exception in abort: " + t.getMessage(), t); } readWriteMode.set(null) ; super.getMonitor().finish() ; } /** * Perform a 2-phase commit by first calling prepareCommit() on the TextIndex * followed by committing the Transaction object, and then calling commit() * on the TextIndex(). * <p> * If either of the objects fail on either the preparation or actual commit, * it terminates and calls {@link #abort()} on both of them. * <p> * <b>NOTE:</b> it may happen that the TextIndex fails to commit, after the * Transactional has already successfully committed. A rollback instruction will * still be issued, but depending on the implementation, it may not have any effect. */ @Override public void commit() { super.getMonitor().finish() ; // Phase 1 if (readWriteMode.get() == ReadWrite.WRITE) { try { textIndex.prepareCommit(); } catch (Throwable t) { log.error("Exception in prepareCommit: " + t.getMessage(), t) ; abort(); throw new TextIndexException(t); } } // Phase 2 try { super.commit(); if (readWriteMode.get() == ReadWrite.WRITE) { textIndex.commit(); } } catch (Throwable t) { log.error("Exception in commit: " + t.getMessage(), t) ; abort(); throw new TextIndexException(t); } readWriteMode.set(null); } @Override public boolean isInTransaction() { return readWriteMode.get() != null; } @Override public void end() { // If we are still in a write transaction at this point, then commit was never called, so rollback the TextIndex if (readWriteMode.get() == ReadWrite.WRITE) { try { textIndex.rollback(); } catch (Throwable t) { log.warn("Exception in end: " + t.getMessage(), t) ; } } try { super.end() ; } catch (Throwable t) { log.warn("Exception in end: " + t.getMessage(), t) ; } readWriteMode.set(null) ; super.getMonitor().finish() ; } @Override public boolean supportsTransactions() { return super.supportsTransactions() ; } /** Declare whether {@link #abort} is supported. * This goes along with clearing up after exceptions inside application transaction code. */ @Override public boolean supportsTransactionAbort() { return super.supportsTransactionAbort() ; } @Override public void close() { super.close(); if (closeIndexOnClose) { textIndex.close(); } } }