blob: 02c6982e3f7fc31bdfcdc80dd6f61ea389779c6b [file] [log] [blame]
/*
* 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.jackrabbit.oak.jcr.query;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.jackrabbit.oak.api.Result;
import org.apache.jackrabbit.oak.api.Result.SizePrecision;
/**
* An iterator that pre-fetches a number of items in order to calculate the size
* of the result if possible. This iterator loads at least a number of items,
* and then tries to load some more items until the timeout is reached or the
* maximum number of entries are read.
* <p>
* Prefetching is only done when size() is called.
*
* @param <K> the iterator data type
*/
public class PrefetchIterator<K> implements Iterator<K> {
private final Iterator<K> it;
private final long minPrefetch, timeout, maxPrefetch;
private final boolean fastSize;
private final Result fastSizeCallback;
private boolean prefetchDone;
private Iterator<K> prefetchIterator;
private long size, position;
/**
* Create a new iterator.
*
* @param it the base iterator
* @param options the prefetch options to use
*/
PrefetchIterator(Iterator<K> it, PrefetchOptions options) {
this.it = it;
this.minPrefetch = options.min;
this.maxPrefetch = options.max;
this.timeout = options.fastSize ? 0 : options.timeout;
this.fastSize = options.fastSize;
this.size = options.size;
this.fastSizeCallback = options.fastSizeCallback;
}
@Override
public boolean hasNext() {
if (prefetchIterator != null) {
if (prefetchIterator.hasNext()) {
return true;
}
prefetchIterator = null;
}
boolean result = it.hasNext();
if (!result) {
size = position;
}
return result;
}
@Override
public K next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (prefetchIterator != null) {
return prefetchIterator.next();
}
position++;
return it.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Get the size if known. This call might pre-fetch data. The returned value
* is unknown if the actual size is larger than the number of pre-fetched
* elements, unless the end of the iterator has been reached already.
*
* @return the size, or -1 if unknown
*/
public long size() {
if (size != -1) {
return size;
}
if (!fastSize) {
if (prefetchDone || position > maxPrefetch) {
return -1;
}
}
prefetchDone = true;
ArrayList<K> list = new ArrayList<K>();
long end;
if (timeout <= 0) {
end = 0;
} else {
long nanos = System.nanoTime();
end = nanos + timeout * 1000 * 1000;
}
while (true) {
if (!it.hasNext()) {
size = position;
break;
}
if (position > maxPrefetch) {
break;
}
if (position > minPrefetch) {
if (end == 0 || System.nanoTime() > end) {
break;
}
}
position++;
list.add(it.next());
}
if (list.size() > 0) {
prefetchIterator = list.iterator();
position -= list.size();
}
if (size == -1 && fastSize) {
if (fastSizeCallback != null) {
size = fastSizeCallback.getSize(SizePrecision.EXACT, Long.MAX_VALUE);
}
}
return size;
}
/**
* The options to use for prefetching.
*/
public static class PrefetchOptions {
// uses the "simple" named-parameter pattern
// see also http://stackoverflow.com/questions/1988016/named-parameter-idiom-in-java
/**
* The minimum number of rows / nodes to pre-fetch.
*/
long min = 20;
/**
* The maximum number of rows / nodes to pre-fetch.
*/
long max = 100;
/**
* The maximum number of milliseconds to prefetch rows / nodes
* (ignored if fastSize is set).
*/
long timeout = 100;
/**
* The size if known, or -1 if not (prefetching is only required if -1).
*/
long size;
/**
* Whether or not the expected size should be read from the result.
*/
boolean fastSize;
/**
* The result (optional) to get the size from, in case the fast size options is set.
*/
Result fastSizeCallback;
{
String s = System.getProperty("oak.queryMinPrefetch");
if (s != null) {
try {
min = Integer.parseInt(s);
max = Math.max(min, max);
} catch (Exception e) {
// ignore
}
}
}
}
}