diff --git a/src/main/java/org/lightcouch/Changes.java b/src/main/java/org/lightcouch/Changes.java index 0e54f72..5c5a0b2 100644 --- a/src/main/java/org/lightcouch/Changes.java +++ b/src/main/java/org/lightcouch/Changes.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.lightcouch; import java.io.BufferedReader; @@ -26,15 +25,18 @@ import org.lightcouch.ChangesResult.Row; import com.google.gson.Gson; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; /** - *

Contains the Change Notifications API, supports normal and continuous feed Changes. + *

+ * Contains the Change Notifications API, supports normal and continuous feed Changes. *

Usage Example:

*
- * // feed type normal 
+ * // feed type normal
  * String since = dbClient.context().info().getUpdateSeq(); // latest update seq
  * ChangesResult changeResult = dbClient.changes()
- *	.since(since) 
+ *	.since(since)
  *	.limit(10)
  *	.filter("example/filter")
  *	.getChanges();
@@ -46,32 +48,35 @@
  *
  * // feed type continuous
  * Changes changes = dbClient.changes()
- *	.includeDocs(true) 
+ *	.includeDocs(true)
  *	.heartBeat(30000)
- *	.continuousChanges(); 
- * 
- * while (changes.hasNext()) { 
+ *	.continuousChanges();
+ *
+ * while (changes.hasNext()) {
  *	ChangesResult.Row feed = changes.next();
  *  String docId = feed.getId();
  *  JsonObject doc = feed.getDoc();
  *	// changes.stop(); // stop continuous feed
  * }
  * 
+ * * @see ChangesResult * @since 0.0.2 * @author Ahmed Yehia */ public class Changes { - + private BufferedReader reader; - private HttpGet httpGet; + private HttpRequestBase httpRequest; private Row nextRow; private boolean stop; - + private CouchDbClientBase dbc; private Gson gson; private URIBuilder uriBuilder; - + + private String selector; + Changes(CouchDbClientBase dbc) { this.dbc = dbc; this.gson = dbc.getGson(); @@ -80,22 +85,39 @@ public class Changes { /** * Requests Change notifications of feed type continuous. - *

Feed notifications are accessed in an iterator style. + *

+ * Feed notifications are accessed in an iterator 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)); + if (selector == null) { + final HttpGet get = new HttpGet(uri); + httpRequest = get; + final InputStream in = dbc.get(get); + final InputStreamReader is = new InputStreamReader(in, Charsets.UTF_8); + setReader(new BufferedReader(is)); + } else { + final HttpPost post = new HttpPost(uri); + httpRequest = post; + final InputStream in = dbc.post(post, selector); + final InputStreamReader is = new InputStreamReader(in, Charsets.UTF_8); + setReader(new BufferedReader(is)); + } + + return this; + } + + // Query Params + public Changes selector(String json) { + uriBuilder.query("filter", "_selector"); + this.selector = json; return this; } /** - * Checks whether a feed is available in the continuous stream, blocking - * until a feed is received. + * Checks whether a feed is available in the continuous stream, blocking until a feed is received. */ - public boolean hasNext() { + public boolean hasNext() { return readNextRow(); } @@ -118,26 +140,29 @@ public void stop() { */ public ChangesResult getChanges() { final URI uri = uriBuilder.query("feed", "normal").build(); - return dbc.get(uri, ChangesResult.class); + if (selector == null) { + return dbc.get(uri, ChangesResult.class); + } else { + return dbc.post(uri, selector, 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; @@ -147,42 +172,42 @@ 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 + // Helper /** * Reads and sets the next feed in the stream. */ private boolean readNextRow() { boolean hasNext = false; try { - if(!stop) { - String row = ""; + if (!stop) { + String row = ""; do { - row = getReader().readLine(); - } while(row.length() == 0); - - if(!row.startsWith("{\"last_seq\":")) { + 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) + } + if (!hasNext) { terminate(); + } return hasNext; } @@ -201,9 +226,9 @@ private Row getNextRow() { private void setNextRow(Row nextRow) { this.nextRow = nextRow; } - + private void terminate() { - httpGet.abort(); + httpRequest.abort(); CouchDbUtil.close(getReader()); } } diff --git a/src/main/java/org/lightcouch/CouchDbClientBase.java b/src/main/java/org/lightcouch/CouchDbClientBase.java index 08208b7..b9cb662 100644 --- a/src/main/java/org/lightcouch/CouchDbClientBase.java +++ b/src/main/java/org/lightcouch/CouchDbClientBase.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.lightcouch; import static org.lightcouch.CouchDbUtil.assertNotEmpty; @@ -69,6 +68,7 @@ /** * Contains a client Public API implementation. + * * @see CouchDbClient * @see CouchDbClientAndroid * @author Ahmed Yehia @@ -79,37 +79,36 @@ public abstract class CouchDbClientBase { private URI baseURI; private URI dbURI; - private Gson gson; + private Gson gson; private CouchDbContext context; private CouchDbDesign design; final HttpClient httpClient; final HttpHost host; - + CouchDbClientBase() { this(new CouchDbConfig()); } - + CouchDbClientBase(CouchDbConfig config) { final CouchDbProperties props = config.getProperties(); this.httpClient = createHttpClient(props); this.gson = initGson(new GsonBuilder()); this.host = new HttpHost(props.getHost(), props.getPort(), props.getProtocol()); - + final String path = props.getPath() != null ? props.getPath() : ""; - this.baseURI = buildUri().scheme(props.getProtocol()).host(props.getHost()).port(props.getPort()).path("/").path(path).build(); - this.dbURI = buildUri(baseURI).path(props.getDbName()).path("/").build(); - - this.context = new CouchDbContext(this, props); + this.baseURI = buildUri().scheme(props.getProtocol()).host(props.getHost()).port(props.getPort()).path("/").path(path).build(); + this.dbURI = buildUri(baseURI).path(props.getDbName()).path("/").build(); + + this.context = new CouchDbContext(this, props); this.design = new CouchDbDesign(this); } - + // Client(s) provided implementation - /** * @return {@link HttpClient} instance for HTTP request execution. */ abstract HttpClient createHttpClient(CouchDbProperties properties); - + /** * @return {@link HttpContext} instance for HTTP request execution. */ @@ -119,27 +118,29 @@ public abstract class CouchDbClientBase { * Shuts down the connection manager used by this client instance. */ abstract void shutdown(); - + // Public API - /** * Provides access to DB server APIs. + * * @see CouchDbContext */ public CouchDbContext context() { return context; } - + /** * Provides access to CouchDB Design Documents. + * * @see CouchDbDesign */ public CouchDbDesign design() { return design; } - + /** * Provides access to CouchDB View APIs. + * * @see View */ public View view(String viewId) { @@ -148,30 +149,34 @@ public View view(String viewId) { /** * Provides access to CouchDB replication APIs. + * * @see Replication */ public Replication replication() { return new Replication(this); } - + /** * Provides access to the replicator database. + * * @see Replicator */ public Replicator replicator() { return new Replicator(this); } - + /** * Provides access to Change Notifications API. + * * @see Changes */ public Changes changes() { return new Changes(this); } - + /** * Finds an Object of the specified type. + * * @param Object type. * @param classType The class of type T. * @param id The document id. @@ -184,9 +189,10 @@ public T find(Class classType, String id) { final URI uri = buildUri(getDBUri()).path(id, true).build(); return get(uri, classType); } - + /** * Finds an Object of the specified type. + * * @param Object type. * @param classType The class of type T. * @param id The document id. @@ -200,9 +206,10 @@ public T find(Class classType, String id, Params params) { final URI uri = buildUri(getDBUri()).path(id, true).query(params).build(); return get(uri, classType); } - + /** * Finds an Object of the specified type. + * * @param Object type. * @param classType The class of type T. * @param id The document _id field. @@ -217,10 +224,12 @@ public T find(Class classType, String id, String rev) { final URI uri = buildUri(getDBUri()).path(id, true).query("rev", rev).build(); return get(uri, classType); } - + /** * This method finds any document given a URI. - *

The URI must be URI-encoded. + *

+ * The URI must be URI-encoded. + * * @param classType The class of type T. * @param uri The URI as string. * @return An object of type T. @@ -230,10 +239,12 @@ public T findAny(Class classType, String uri) { assertNotEmpty(uri, "uri"); return get(URI.create(uri), classType); } - + /** * Finds a document and return the result as {@link InputStream}. - *

Note: The stream must be closed after use to release the connection. + *

+ * Note: The stream must be closed after use to release the connection. + * * @param id The document _id field. * @return The result as {@link InputStream} * @throws NoDocumentException If the document is not found in the database. @@ -243,10 +254,12 @@ public InputStream find(String id) { assertNotEmpty(id, "id"); return get(buildUri(getDBUri()).path(id).build()); } - + /** * Finds a document given id and revision and returns the result as {@link InputStream}. - *

Note: The stream must be closed after use to release the connection. + *

+ * Note: The stream must be closed after use to release the connection. + * * @param id The document _id field. * @param rev The document _rev field. * @return The result as {@link InputStream} @@ -258,9 +271,10 @@ public InputStream find(String id, String rev) { final URI uri = buildUri(getDBUri()).path(id).query("rev", rev).build(); return get(uri); } - + /** * Find documents using a declarative JSON querying syntax. + * * @param jsonQuery The JSON query string. * @param classOfT The class of type T. * @return The result of the query as a {@code List } @@ -273,25 +287,26 @@ public List findDocs(String jsonQuery, Class classOfT) { response = post(buildUri(getDBUri()).path("_find").build(), jsonQuery); Reader reader = new InputStreamReader(getStream(response), Charsets.UTF_8); JsonArray jsonArray = new JsonParser().parse(reader) - .getAsJsonObject().getAsJsonArray("docs"); + .getAsJsonObject().getAsJsonArray("docs"); List list = new ArrayList(); for (JsonElement jsonElem : jsonArray) { JsonElement elem = jsonElem.getAsJsonObject(); T t = this.gson.fromJson(elem, classOfT); list.add(t); } - return list; + return list; } finally { close(response); } } - + /** * Checks if a document exist in the database. + * * @param id The document _id field. * @return true If the document is found, false otherwise. */ - public boolean contains(String id) { + public boolean contains(String id) { assertNotEmpty(id, "id"); HttpResponse response = null; try { @@ -303,10 +318,12 @@ public boolean contains(String id) { } return true; } - + /** * Saves an object in the database, using HTTP PUT request. - *

If the object doesn't have an _id value, the code will assign a UUID as the document id. + *

+ * If the object doesn't have an _id value, the code will assign a UUID as the document id. + * * @param object The object to save * @throws DocumentConflictException If a conflict is detected during the save. * @return {@link Response} @@ -314,17 +331,19 @@ public boolean contains(String id) { public Response save(Object object) { return put(getDBUri(), object, true); } - + /** * Saves an object in the database using HTTP POST request. - *

The database will be responsible for generating the document id. + *

+ * The database will be responsible for generating the document id. + * * @param object The object to save * @return {@link Response} */ public Response post(Object object) { assertNotEmpty(object, "object"); HttpResponse response = null; - try { + try { URI uri = buildUri(getDBUri()).build(); response = post(uri, getGson().toJson(object)); return getResponse(response); @@ -332,24 +351,26 @@ public Response post(Object object) { close(response); } } - + /** * Saves a document with batch=ok query param. + * * @param object The object to save. */ public void batch(Object object) { assertNotEmpty(object, "object"); HttpResponse response = null; - try { + try { URI uri = buildUri(getDBUri()).query("batch", "ok").build(); response = post(uri, getGson().toJson(object)); } finally { close(response); } } - + /** * Updates an object in the database, the object must have the correct _id and _rev values. + * * @param object The object to update * @throws DocumentConflictException If a conflict is detected during the update. * @return {@link Response} @@ -357,10 +378,12 @@ public void batch(Object object) { public Response update(Object object) { return put(getDBUri(), object, false); } - + /** - * Removes a document from the database. - *

The object must have the correct _id and _rev values. + * Removes a document from the database. + *

+ * The object must have the correct _id and _rev values. + * * @param object The document to remove as object. * @throws NoDocumentException If the document is not found in the database. * @return {@link Response} @@ -372,9 +395,10 @@ public Response remove(Object object) { final String rev = getAsString(jsonObject, "_rev"); return remove(id, rev); } - + /** * Removes a document from the database given both a document _id and _rev values. + * * @param id The document _id field. * @param rev The document _rev field. * @throws NoDocumentException If the document is not found in the database. @@ -386,9 +410,10 @@ public Response remove(String id, String rev) { final URI uri = buildUri(getDBUri()).path(id, true).query("rev", rev).build(); return delete(uri); } - + /** * Performs a Bulk Documents insert request. + * * @param objects The {@link List} of objects. * @param allOrNothing Indicates whether the request has all-or-nothing semantics. * @return {@code List} Containing the resulted entries. @@ -396,7 +421,7 @@ public Response remove(String id, String rev) { public List bulk(List objects, boolean allOrNothing) { assertNotEmpty(objects, "objects"); HttpResponse response = null; - try { + try { final String allOrNothingVal = allOrNothing ? "\"all_or_nothing\": true, " : ""; final URI uri = buildUri(getDBUri()).path("_bulk_docs").build(); final String json = String.format("{%s%s%s}", allOrNothingVal, "\"docs\": ", getGson().toJson(objects)); @@ -406,10 +431,12 @@ public List bulk(List objects, boolean allOrNothing) { close(response); } } - + /** * Saves an attachment to a new document with a generated UUID as the document id. - *

To retrieve an attachment, see {@link #find(String)}. + *

+ * To retrieve an attachment, see {@link #find(String)}. + * * @param instream The {@link InputStream} holding the binary data. * @param name The attachment name. * @param contentType The attachment "Content-Type". @@ -422,17 +449,19 @@ public Response saveAttachment(InputStream in, String name, String contentType) final URI uri = buildUri(getDBUri()).path(generateUUID()).path("/").path(name).build(); return put(uri, in, contentType); } - + /** - * Saves an attachment to an existing document given both a document id - * and revision, or save to a new document given only the id, and rev as {@code null}. - *

To retrieve an attachment, see {@link #find(String)}. + * Saves an attachment to an existing document given both a document id and revision, or save to a new document given + * only the id, and rev as {@code null}. + *

+ * To retrieve an attachment, see {@link #find(String)}. + * * @param instream The {@link InputStream} holding the binary data. * @param name The attachment name. * @param contentType The attachment "Content-Type". * @param docId The document id to save the attachment under, or {@code null} to save under a new document. * @param docRev The document revision to save the attachment under, or {@code null} when saving to a new document. - * @throws DocumentConflictException + * @throws DocumentConflictException * @return {@link Response} */ public Response saveAttachment(InputStream in, String name, String contentType, String docId, String docRev) { @@ -443,13 +472,14 @@ public Response saveAttachment(InputStream in, String name, String contentType, final URI uri = buildUri(getDBUri()).path(docId, true).path("/").path(name).query("rev", docRev).build(); return put(uri, in, contentType); } - + /** * Invokes an Update Handler. *

 	 * String query = "field=foo&value=bar";
 	 * String output = dbClient.invokeUpdateHandler("designDoc/update1", "docId", query);
 	 * 
+ * * @param updateHandlerUri The Update Handler URI, in the format: designDoc/update1 * @param docId The document id to update. * @param query The query string parameters, e.g, field=field1&value=value1 @@ -464,16 +494,18 @@ public String invokeUpdateHandler(String updateHandlerUri, String docId, String final HttpResponse response = executeRequest(new HttpPut(uri)); return streamToString(getStream(response)); } - + /** * Invokes an Update Handler. - *

Use this method in particular when the docId contain special characters such as slashes (/). + *

+ * Use this method in particular when the docId contain special characters such as slashes (/). *

 	 * Params params = new Params()
 	 *	.addParam("field", "foo")
-	 *	.addParam("value", "bar"); 
+	 *	.addParam("value", "bar");
 	 * String output = dbClient.invokeUpdateHandler("designDoc/update1", "docId", params);
 	 * 
+ * * @param updateHandlerUri The Update Handler URI, in the format: designDoc/update1 * @param docId The document id to update. * @param query The query parameters as {@link Params}. @@ -488,81 +520,86 @@ public String invokeUpdateHandler(String updateHandlerUri, String docId, Params final HttpResponse response = executeRequest(new HttpPut(uri)); return streamToString(getStream(response)); } - + /** * Executes a HTTP request. - *

Note: The response must be closed after use to release the connection. + *

+ * Note: The response must be closed after use to release the connection. + * * @param request The HTTP request to execute. * @return {@link HttpResponse} */ public HttpResponse executeRequest(HttpRequestBase request) { try { - return httpClient.execute(host, request, createContext()); + return httpClient.execute(host, request, createContext()); } catch (IOException e) { request.abort(); throw new CouchDbException("Error executing request. ", e); - } + } } - + /** * Synchronize all design documents with the database. */ public void syncDesignDocsWithDb() { design().synchronizeAllWithDb(); } - + /** * Sets a {@link GsonBuilder} to create {@link Gson} instance. - *

Useful for registering custom serializers/deserializers, such as JodaTime classes. + *

+ * Useful for registering custom serializers/deserializers, such as JodaTime classes. */ public void setGsonBuilder(GsonBuilder gsonBuilder) { this.gson = initGson(gsonBuilder); } - + /** * @return The base URI. */ public URI getBaseUri() { return baseURI; } - + /** * @return The database URI. */ public URI getDBUri() { return dbURI; } - + /** * @return The Gson instance. */ public Gson getGson() { return gson; } - + // End - Public API - /** - * Performs a HTTP GET request. - * @return {@link InputStream} + * Performs a HTTP GET request. + * + * @return {@link InputStream} */ InputStream get(HttpGet httpGet) { - HttpResponse response = executeRequest(httpGet); + HttpResponse response = executeRequest(httpGet); return getStream(response); } - + /** - * Performs a HTTP GET request. - * @return {@link InputStream} + * Performs a HTTP GET request. + * + * @return {@link InputStream} */ InputStream get(URI uri) { HttpGet get = new HttpGet(uri); get.addHeader("Accept", "application/json"); return get(get); } - + /** - * Performs a HTTP GET request. + * Performs a HTTP GET request. + * * @return An object of type T */ T get(URI uri, Class classType) { @@ -576,27 +613,29 @@ T get(URI uri, Class classType) { close(in); } } - + /** - * Performs a HTTP HEAD request. + * Performs a HTTP HEAD request. + * * @return {@link HttpResponse} */ HttpResponse head(URI uri) { return executeRequest(new HttpHead(uri)); } - + /** * Performs a HTTP PUT request, saves or updates a document. + * * @return {@link Response} */ Response put(URI uri, Object object, boolean newEntity) { assertNotEmpty(object, "object"); HttpResponse response = null; - try { + try { final JsonObject json = getGson().toJsonTree(object).getAsJsonObject(); String id = getAsString(json, "_id"); String rev = getAsString(json, "_rev"); - if(newEntity) { // save + if (newEntity) { // save assertNull(rev, "rev"); id = (id == null) ? generateUUID() : id; } else { // update @@ -605,15 +644,16 @@ Response put(URI uri, Object object, boolean newEntity) { } final HttpPut put = new HttpPut(buildUri(uri).path(id, true).build()); setEntity(put, json.toString()); - response = executeRequest(put); + response = executeRequest(put); return getResponse(response); } finally { close(response); } } - + /** * Performs a HTTP PUT request, saves an attachment. + * * @return {@link Response} */ Response put(URI uri, InputStream instream, String contentType) { @@ -629,9 +669,10 @@ Response put(URI uri, InputStream instream, String contentType) { close(response); } } - + /** * Performs a HTTP POST request. + * * @return {@link HttpResponse} */ HttpResponse post(URI uri, String json) { @@ -639,47 +680,76 @@ HttpResponse post(URI uri, String json) { setEntity(post, json); return executeRequest(post); } - + + /** + * Performs a HTTP POST request. + * + * @return {@link HttpResponse} + */ + InputStream post(HttpPost post, String json) { + setEntity(post, json); + HttpResponse resp = executeRequest(post); + return getStream(resp); + } + + /** + * Performs a HTTP GET request. + * + * @return An object of type T + */ + T post(URI uri, String json, Class classType) { + InputStream in = null; + try { + in = getStream(post(uri, json)); + return getGson().fromJson(new InputStreamReader(in, "UTF-8"), classType); + } catch (UnsupportedEncodingException e) { + throw new CouchDbException(e); + } finally { + close(in); + } + } + /** * Performs a HTTP DELETE request. + * * @return {@link Response} */ Response delete(URI uri) { HttpResponse response = null; try { HttpDelete delete = new HttpDelete(uri); - response = executeRequest(delete); + response = executeRequest(delete); return getResponse(response); } finally { close(response); } } - + // Helpers - /** * Validates a HTTP response; on error cases logs status and throws relevant exceptions. + * * @param response The HTTP response. */ void validate(HttpResponse response) throws IOException { final int code = response.getStatusLine().getStatusCode(); - if(code == 200 || code == 201 || code == 202) { // success (ok | created | accepted) + if (code == 200 || code == 201 || code == 202) { // success (ok | created | accepted) return; - } + } String reason = response.getStatusLine().getReasonPhrase(); switch (code) { - case HttpStatus.SC_NOT_FOUND: { - throw new NoDocumentException(reason); - } - case HttpStatus.SC_CONFLICT: { - throw new DocumentConflictException(reason); - } - default: { // other errors: 400 | 401 | 500 etc. - throw new CouchDbException(reason += EntityUtils.toString(response.getEntity())); - } + case HttpStatus.SC_NOT_FOUND: { + throw new NoDocumentException(reason); + } + case HttpStatus.SC_CONFLICT: { + throw new DocumentConflictException(reason); + } + default: { // other errors: 400 | 401 | 500 etc. + throw new CouchDbException(reason += EntityUtils.toString(response.getEntity())); + } } } - + /** * @param response The {@link HttpResponse} * @return {@link Response} @@ -688,7 +758,7 @@ private Response getResponse(HttpResponse response) throws CouchDbException { InputStreamReader reader = new InputStreamReader(getStream(response), Charsets.UTF_8); return getGson().fromJson(reader, Response.class); } - + /** * @param response The {@link HttpResponse} * @return {@link Response} @@ -696,11 +766,13 @@ private Response getResponse(HttpResponse response) throws CouchDbException { private List getResponseList(HttpResponse response) throws CouchDbException { InputStream instream = getStream(response); Reader reader = new InputStreamReader(instream, Charsets.UTF_8); - return getGson().fromJson(reader, new TypeToken>(){}.getType()); + return getGson().fromJson(reader, new TypeToken>() { + }.getType()); } - + /** * Sets a JSON String as a request entity. + * * @param httpRequest The request to set entity. * @param json The JSON String to set. */ @@ -709,25 +781,26 @@ private void setEntity(HttpEntityEnclosingRequestBase httpRequest, String json) entity.setContentType("application/json"); httpRequest.setEntity(entity); } - + /** * Builds {@link Gson} and registers any required serializer/deserializer. + * * @return {@link Gson} instance */ private Gson initGson(GsonBuilder gsonBuilder) { gsonBuilder.registerTypeAdapter(JsonObject.class, new JsonDeserializer() { public JsonObject deserialize(JsonElement json, - Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { + Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { return json.getAsJsonObject(); } }); gsonBuilder.registerTypeAdapter(JsonObject.class, new JsonSerializer() { public JsonElement serialize(JsonObject src, Type typeOfSrc, - JsonSerializationContext context) { + JsonSerializationContext context) { return src.getAsJsonObject(); } - + }); return gsonBuilder.create(); } diff --git a/src/test/java/org/lightcouch/tests/ChangeNotificationsTest.java b/src/test/java/org/lightcouch/tests/ChangeNotificationsTest.java index bd3d4c8..7a6ce0d 100644 --- a/src/test/java/org/lightcouch/tests/ChangeNotificationsTest.java +++ b/src/test/java/org/lightcouch/tests/ChangeNotificationsTest.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.lightcouch.tests; import static org.hamcrest.CoreMatchers.is; @@ -36,7 +35,7 @@ import com.google.gson.JsonObject; public class ChangeNotificationsTest { - + private static CouchDbClient dbClient; @BeforeClass @@ -48,43 +47,96 @@ public static void setUpClass() { public static void tearDownClass() { dbClient.shutdown(); } - + @Test public void changes_normalFeed() { - dbClient.save(new Foo()); + dbClient.save(new Foo()); ChangesResult changes = dbClient.changes() - .includeDocs(true) - .limit(1) - .getChanges(); - + .includeDocs(true) + .limit(1) + .getChanges(); + List rows = changes.getResults(); - + for (Row row : rows) { List revs = row.getChanges(); String docId = row.getId(); JsonObject doc = row.getDoc(); - + assertNotNull(revs); assertNotNull(docId); assertNotNull(doc); } - + + assertThat(rows.size(), is(1)); + } + + @Test + public void changes_normalFeed_selector() { + dbClient.save(new Foo()); + + ChangesResult changes = dbClient.changes() + .includeDocs(true) + .limit(1) + .selector("{\"selector\":{\"_id\": {\"$gt\": null}}}") + .getChanges(); + + List rows = changes.getResults(); + + for (Row row : rows) { + List revs = row.getChanges(); + String docId = row.getId(); + JsonObject doc = row.getDoc(); + + assertNotNull(revs); + assertNotNull(docId); + assertNotNull(doc); + } + assertThat(rows.size(), is(1)); } @Test public void changes_continuousFeed() { - dbClient.save(new Foo()); + dbClient.save(new Foo()); + + CouchDbInfo dbInfo = dbClient.context().info(); + String since = dbInfo.getUpdateSeq(); + + Changes changes = dbClient.changes() + .includeDocs(true) + .since(since) + .heartBeat(1000) + .continuousChanges(); + + Response response = dbClient.save(new Foo()); + + while (changes.hasNext()) { + ChangesResult.Row feed = changes.next(); + final JsonObject feedObject = feed.getDoc(); + final String docId = feed.getId(); + + assertEquals(response.getId(), docId); + assertNotNull(feedObject); + + changes.stop(); + } + } + + @Test + public void changes_continuousFeed_selector() { + dbClient.save(new Foo()); CouchDbInfo dbInfo = dbClient.context().info(); String since = dbInfo.getUpdateSeq(); Changes changes = dbClient.changes() - .includeDocs(true) - .since(since) - .heartBeat(30000) - .continuousChanges(); + .includeDocs(true) + .since(since) + .heartBeat(1000) + .selector("{\"selector\":{\"_id\": {\"$gt\": null}}}") + .continuousChanges(); Response response = dbClient.save(new Foo()); @@ -92,10 +144,11 @@ public void changes_continuousFeed() { ChangesResult.Row feed = changes.next(); final JsonObject feedObject = feed.getDoc(); final String docId = feed.getId(); - + System.out.println("next()=" + docId); + assertEquals(response.getId(), docId); assertNotNull(feedObject); - + changes.stop(); } }