/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package org.codice.ddf.commands.util;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import ddf.catalog.CatalogFramework;
import ddf.catalog.data.Result;
import ddf.catalog.federation.FederationException;
import ddf.catalog.operation.QueryRequest;
import ddf.catalog.operation.SourceResponse;
import ddf.catalog.source.SourceUnavailableException;
import ddf.catalog.source.UnsupportedQueryException;
/**
* Effectively a cursor over the results of a filter that automatically pages through all results
* <br/>
* Throws a {@link CatalogCommandRuntimeException} if anything goes wrong during iteration or
* querying
*/
public class QueryResultIterable implements Iterable<Result> {
private final CatalogFramework catalog;
private final Function<Integer, QueryRequest> filter;
private final int pageSize;
/**
* For paging through a single filter with a default pageSize of 64
*
* @param catalog catalog to query
* @param filter A dynamic supplier of a filter that takes the current index such that
* the caller can control iteration based on their own logic
*/
public QueryResultIterable(CatalogFramework catalog, Function<Integer, QueryRequest> filter) {
this(catalog, filter, 64);
}
/**
* For paging through a single filter.
*
* @param catalog catalog to query
* @param filter A dynamic supplier of a filter that takes the current index such that
* the caller can control iteration based on their own logic
* @param pageSize How many results should each page hold
*/
public QueryResultIterable(CatalogFramework catalog, Function<Integer, QueryRequest> filter,
int pageSize) {
this.catalog = catalog;
this.filter = filter;
this.pageSize = pageSize;
}
@Override
public Iterator<Result> iterator() {
return new ResultQueryIterator();
}
@Override
public Spliterator<Result> spliterator() {
int characteristics = Spliterator.DISTINCT;
return Spliterators.spliteratorUnknownSize(this.iterator(), characteristics);
}
public Stream<Result> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
class ResultQueryIterator implements Iterator<Result> {
private int pageIndex = 1;
private boolean finished = false;
private SourceResponse response = null;
private Iterator<Result> results = null;
ResultQueryIterator() {
if (pageSize <= 0) {
this.finished = true;
}
}
@Override
public boolean hasNext() {
ensureInitialized();
if (results.hasNext()) {
return true;
}
if (finished) {
return false;
}
pageIndex += pageSize;
queryNext(pageIndex);
return hasNext();
}
@Override
public Result next() {
ensureInitialized();
if (!hasNext()) {
throw new NoSuchElementException();
}
return results.next();
}
private void queryNext(int index) {
try {
Map<String, Serializable> props = new HashMap<>();
// Avoid caching all results while dumping with native query mode
props.put("mode", "native");
response = catalog.query(filter.apply(index));
} catch (UnsupportedQueryException | SourceUnavailableException | FederationException e) {
throw new CatalogCommandRuntimeException(e);
}
List<Result> queryResults = response.getResults();
this.results = queryResults.iterator();
int size = queryResults.size();
if (size == 0 || size < pageSize) {
finished = true;
}
}
private void ensureInitialized() throws CatalogCommandRuntimeException {
if (response != null || results != null) {
return;
}
queryNext(pageIndex);
}
}
}