package org.ektorp; import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; import java.io.InputStreamReader; import java.io.Serializable; import java.util.Iterator; import java.util.NoSuchElementException; import com.fasterxml.jackson.databind.*; import org.ektorp.ViewResult.Row; import org.ektorp.http.HttpResponse; /** * * @author Sverre Kristian ValskrÄ */ public class StreamingViewResult implements Serializable, Iterable<Row>, Closeable{ private static final String OFFSET_FIELD_NAME = "offset"; private static final String TOTAL_ROWS_FIELD_NAME = "total_rows"; private static final long serialVersionUID = 4750290767936801714L; private int totalRows = -1; private int offset = -1; @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="SE_BAD_FIELD") private final BufferedReader reader; private final ObjectMapper objectMapper; private boolean iteratorCalled; private final boolean ignoreNotFound; @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="SE_BAD_FIELD") private final HttpResponse httpResponse; @edu.umd.cs.findbugs.annotations.SuppressWarnings({"DM_DEFAULT_ENCODING", "NP_DEREFERENCE_OF_READLINE_VALUE"}) public StreamingViewResult(ObjectMapper objectMapper, HttpResponse httpResponse, boolean ignoreNotFound) { this.objectMapper = objectMapper; this.httpResponse = httpResponse; this.ignoreNotFound = ignoreNotFound; reader = new BufferedReader(new InputStreamReader(httpResponse.getContent())); try{ String info = reader.readLine(); totalRows = getFieldValue(info, TOTAL_ROWS_FIELD_NAME); offset = getFieldValue(info, OFFSET_FIELD_NAME); }catch(IOException e) { throw new DbAccessException(e); } } /** * * @return -1 if result did not contain an offset field */ public int getOffset() { return offset; } /** * * @return -1 if result did not contain a total_rows field */ public int getTotalRows() { return totalRows; } public Iterator<ViewResult.Row> iterator() { if (iteratorCalled) { throw new IllegalStateException("Iterator can only be called once!"); } iteratorCalled = true; return new StreamingViewResultIterator(); } public void abort() { httpResponse.abort(); } public void close() { try { reader.close(); } catch (IOException e) { } } private int getFieldValue(String line, String key) { int index = line.indexOf(key); if (index == -1) { return -1; } int fromIndex = index + key.length() + 2; return Integer.parseInt(line.substring(fromIndex, line.indexOf(",", fromIndex))); } private class StreamingViewResultIterator implements Iterator<Row>{ private Row row; private boolean closed = false; public boolean hasNext() { if (closed) { // The BufferedReader is closed. There can't be any more rows. return false; } if (row != null) { // We still already have an 'uncollected' row from last time. return true; } try { JsonNode node; do { String doc = reader.readLine(); if (doc == null || doc.equals("]}")) { reader.close(); closed = true; return false; } if (doc.endsWith(",")) { doc = doc.substring(0, doc.length() -1); } node = objectMapper.readTree(doc); }while(ignoreNotFound && node.has(Row.ERROR_FIELD_NAME)); row = new ViewResult.Row(node); return true; } catch (IOException e) { throw new DbAccessException(e); } } public Row next() { if (!hasNext()) { throw new NoSuchElementException("Attempt to iterate beyond the result set"); } Row toReturn = row; row = null; return toReturn; } public void remove() { throw new UnsupportedOperationException(); } } }