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. *
- * // 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 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 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 If the object doesn't have an
+ * If the object doesn't have an 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 The object must have the correct
+ * The object must have the correct 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.
* 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 (/).
* 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
*/
_id value, the code will assign a UUID as the document id.
+ * _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.
- * _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.
- * _id and _rev values.
+ * Removes a document from the database.
+ * _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
* 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.
- *
* 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.
- * >(){}.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