/*
* 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.sparql.engine.iterator;
import java.util.NoSuchElementException ;
import org.apache.jena.atlas.lib.Lib ;
import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.query.QueryCancelledException ;
import org.apache.jena.query.QueryException ;
import org.apache.jena.query.QueryFatalException ;
import org.apache.jena.sparql.engine.QueryIterator ;
import org.apache.jena.sparql.engine.binding.Binding ;
import org.apache.jena.sparql.util.PrintSerializableBase ;
/**
* This class provides the general machinary for iterators. This includes:
* <ul>
* <li>autoclose when the iterator runs out</li>
* <li>ensuring query iterators only contain Bindings</li>
* </ul>
*/
public abstract class QueryIteratorBase
extends PrintSerializableBase
implements QueryIterator
{
public static boolean traceIterators = false ;
private boolean finished = false ;
// === Cancellation
// .cancel() can be called asynchronously with iterator execution.
// It causes notification to cancellation to be made, once, by calling .requestCancel()
// which is called synchronously with .cancel() and asynchronously with iterator execution.
// ONLY the requestingCancel variable needs to be volatile. The abortIterator is guaranteed to
// be visible because it is written to before requestingCancel, and read from after.
private volatile boolean requestingCancel = false;
/* If set, any hasNext/next throws QueryAbortedException
* In normal operation, this is the same setting as requestingCancel.
* Non-compliant behaviour can result otherwise.
* Accessed through cancelAllowContinue()
*/
private boolean abortIterator = false ;
private Object cancelLock = new Object();
private Throwable stackTrace = null ;
public QueryIteratorBase()
{
if ( traceIterators )
stackTrace = new Throwable() ;
}
// -------- The contract with the subclasses
/** Implement this, not hasNext() */
protected abstract boolean hasNextBinding() ;
/** Implement this, not next() or nextBinding()
Returning null is turned into NoSuchElementException
Does not need to call hasNext (can presume it is true) */
protected abstract Binding moveToNextBinding() ;
/** Close the iterator. */
protected abstract void closeIterator() ;
/** Propagates the cancellation request - called asynchronously with the iterator itself */
protected abstract void requestCancel();
/* package */ boolean getRequestingCancel() {
return requestingCancel ;
}
// -------- The contract with the subclasses
protected boolean isFinished() { return finished ; }
/** final - subclasses implement hasNextBinding() */
@Override
public final boolean hasNext()
{
if ( finished )
// Even if aborted. Finished is finished.
return false ;
if ( requestingCancel && abortIterator )
{
// Try to close first to release resources (in case the user
// doesn't have a close() call in a finally block)
close() ;
throw new QueryCancelledException() ;
}
// Handles exceptions
boolean r = hasNextBinding() ;
if ( r == false )
try {
close() ;
} catch (QueryFatalException ex)
{
Log.fatal(this, "Fatal exception: "+ex.getMessage() ) ;
throw ex ; // And pass on up the exception.
}
return r ;
}
/** final - autoclose and registration relies on it - implement moveToNextBinding() */
@Override
public final Binding next()
{
return nextBinding() ;
}
/** final - subclasses implement moveToNextBinding() */
@Override
public final Binding nextBinding()
{
try {
// Need to make sure to only read this once per iteration
boolean shouldCancel = requestingCancel;
if ( shouldCancel && abortIterator )
{
// Try to close first to release resources (in case the user
// doesn't have a close() call in a finally block)
close() ;
throw new QueryCancelledException() ;
}
if ( finished )
throw new NoSuchElementException(Lib.className(this)) ;
if ( ! hasNextBinding() )
throw new NoSuchElementException(Lib.className(this)) ;
Binding obj = moveToNextBinding() ;
if ( obj == null )
throw new NoSuchElementException(Lib.className(this)) ;
if ( shouldCancel && ! finished )
{
// But .cancel sets both requestingCancel and abortIterator
// This only happens with a continuing iterator.
close() ;
}
return obj ;
} catch (QueryFatalException ex)
{
Log.fatal(this, "QueryFatalException", ex) ;
throw ex ;
}
}
@Override
public final void remove()
{
Log.warn(this, "Call to QueryIterator.remove() : "+Lib.className(this)+".remove") ;
throw new UnsupportedOperationException(Lib.className(this)+".remove") ;
}
@Override
public void close()
{
if ( finished )
return ;
try { closeIterator() ; }
catch (QueryException ex)
{ Log.warn(this, "QueryException in close()", ex) ; }
finished = true ;
}
/** Cancel this iterator */
@Override
public final void cancel()
{
// Call requestCancel() once.
synchronized (cancelLock)
{
if (!this.requestingCancel)
{
// Need to set the flags before allowing subclasses to handle requestCancel() in order
// to prevent a race condition. We want to be sure that calls to hasNext()/nextBinding()
// will definitely throw a QueryCancelledException in this class and not allow a
// situation in which a subclass component thinks it is cancelled, while this class does not.
this.abortIterator = true ;
this.requestingCancel = true;
this.requestCancel() ;
}
}
}
/** Cancel this iterator but allow it to continue servicing hasNext/next.
* Wrong answers are possible (e.g. partial ORDER BY and LIMIT).
*/
private final void cancelAllowContinue()
{
// Call requestCancel() once.
synchronized (cancelLock)
{
if (!this.requestingCancel)
{
//this.abortIterator = true ;
this.requestingCancel = true;
this.requestCancel() ;
}
}
}
/** close an iterator */
protected static void performClose(QueryIterator iter)
{
if ( iter == null ) return ;
iter.close() ;
}
/** cancel an iterator */
protected static void performRequestCancel(QueryIterator iter)
{
if ( iter == null ) return ;
iter.cancel() ;
}
public String debug()
{
String s = "" ;
if ( stackTrace != null )
{
for ( int i = 0 ; i < stackTrace.getStackTrace().length ; i++ )
{
StackTraceElement e = stackTrace.getStackTrace()[i] ;
// <init> or <clinit>
// Find first non-constructor
if ( e.getMethodName().equals("<init>") )
continue ;
// Use this so Eclipse can find the code
s = s + e.toString() ;
// Looks like:
//s = s + e.getClassName()+"."+e.getMethodName()+"("+e.getFileName()+":"+e.getLineNumber()+")" ;
// Too short for Eclipse.
//s = s +"("+e.getFileName()+":"+e.getLineNumber()+")" ;
break ;
}
}
return s ;
}
}