/* * Copyright (C) 2011 lightcouch.org * * Licensed 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.lightcouch; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import org.apache.commons.codec.Charsets; import org.apache.http.client.methods.HttpGet; import org.lightcouch.ChangesResult.Row; import com.google.gson.Gson; /** * <p>Contains the Change Notifications API, supports <i>normal</i> and <i>continuous</i> feed Changes. * <h3>Usage Example:</h3> * <pre> * // feed type normal * String since = dbClient.context().info().getUpdateSeq(); // latest update seq * ChangesResult changeResult = dbClient.changes() * .since(since) * .limit(10) * .filter("example/filter") * .getChanges(); * * for (ChangesResult.Row row : changeResult.getResults()) { * String docId = row.getId() * JsonObject doc = row.getDoc(); * } * * // feed type continuous * Changes changes = dbClient.changes() * .includeDocs(true) * .heartBeat(30000) * .continuousChanges(); * * while (changes.hasNext()) { * ChangesResult.Row feed = changes.next(); * String docId = feed.getId(); * JsonObject doc = feed.getDoc(); * // changes.stop(); // stop continuous feed * } * </pre> * @see ChangesResult * @since 0.0.2 * @author Ahmed Yehia */ public class Changes { private BufferedReader reader; private HttpGet httpGet; private Row nextRow; private boolean stop; private CouchDbClientBase dbc; private Gson gson; private URIBuilder uriBuilder; Changes(CouchDbClientBase dbc) { this.dbc = dbc; this.gson = dbc.getGson(); this.uriBuilder = URIBuilder.buildUri(dbc.getDBUri()).path("_changes"); } /** * Requests Change notifications of feed type continuous. * <p>Feed notifications are accessed in an <i>iterator</i> style. */ public Changes continuousChanges() { final URI uri = uriBuilder.query("feed", "continuous").build(); httpGet = new HttpGet(uri); final InputStream in = dbc.get(httpGet); final InputStreamReader is = new InputStreamReader(in, Charsets.UTF_8); setReader(new BufferedReader(is)); return this; } /** * Checks whether a feed is available in the continuous stream, blocking * until a feed is received. */ public boolean hasNext() { return readNextRow(); } /** * @return The next feed in the stream. */ public Row next() { return getNextRow(); } /** * Stops a running continuous feed. */ public void stop() { stop = true; } /** * Requests Change notifications of feed type normal. */ public ChangesResult getChanges() { final URI uri = uriBuilder.query("feed", "normal").build(); return dbc.get(uri, ChangesResult.class); } // Query Params public Changes since(String since) { uriBuilder.query("since", since); return this; } public Changes limit(int limit) { uriBuilder.query("limit", limit); return this; } public Changes heartBeat(long heartBeat) { uriBuilder.query("heartbeat", heartBeat); return this; } public Changes timeout(long timeout) { uriBuilder.query("timeout", timeout); return this; } public Changes filter(String filter) { uriBuilder.query("filter", filter); return this; } public Changes includeDocs(boolean includeDocs) { uriBuilder.query("include_docs", includeDocs); return this; } public Changes style(String style) { uriBuilder.query("style", style); return this; } // Helper /** * Reads and sets the next feed in the stream. */ private boolean readNextRow() { boolean hasNext = false; try { if(!stop) { String row = ""; do { row = getReader().readLine(); } while(row.length() == 0); if(!row.startsWith("{\"last_seq\":")) { setNextRow(gson.fromJson(row, Row.class)); hasNext = true; } } } catch (Exception e) { terminate(); throw new CouchDbException("Error reading continuous stream.", e); } if(!hasNext) terminate(); return hasNext; } private BufferedReader getReader() { return reader; } private void setReader(BufferedReader reader) { this.reader = reader; } private Row getNextRow() { return nextRow; } private void setNextRow(Row nextRow) { this.nextRow = nextRow; } private void terminate() { httpGet.abort(); CouchDbUtil.close(getReader()); } }