diff --git a/org.ektorp/src/main/java/org/ektorp/ViewQuery.java b/org.ektorp/src/main/java/org/ektorp/ViewQuery.java
index 209b7886..5665cfb0 100644
--- a/org.ektorp/src/main/java/org/ektorp/ViewQuery.java
+++ b/org.ektorp/src/main/java/org/ektorp/ViewQuery.java
@@ -8,15 +8,18 @@
import java.util.Map;
import java.util.TreeMap;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
import org.ektorp.http.URI;
+import org.ektorp.impl.DefaultQueryExecutor;
+import org.ektorp.impl.QueryExecutor;
import org.ektorp.impl.StdObjectMapperFactory;
import org.ektorp.util.Assert;
import org.ektorp.util.Exceptions;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
/**
*
* @author henrik lundgren
@@ -263,9 +266,12 @@ public ViewQuery key(Object o) {
return this;
}
/**
- * For multiple-key queries (as of CouchDB 0.9). Keys will be JSON-encoded.
+ * For multiple-key queries (as of CouchDB 0.9), the HTTP method is determined by {@link QueryExecutor}.
+ * Keys will be JSON-encoded.
* @param keyList a list of Object, will be JSON encoded according to each element's type.
* @return the view query for chained calls
+ * @see DefaultQueryExecutor
+ * @see For ViewQuery.keys(), use GET instead of POST
*/
public ViewQuery keys(Collection> keyList) {
reset();
@@ -567,7 +573,12 @@ public Object getKey() {
public boolean hasMultipleKeys() {
return keys != null;
}
-
+
+ /**
+ * Get {@link #keys} as JSON object.
+ * @return
+ * @see #getKeysAsJsonArray()
+ */
public String getKeysAsJson() {
if (keys == null) {
return "{\"keys\":[]}";
@@ -575,6 +586,17 @@ public String getKeysAsJson() {
return keys.toJson(mapper);
}
+ /**
+ * Get {@link #keys} as JSON array.
+ * @return
+ * @see #getKeysAsJson()
+ */
+ public String getKeysAsJsonArray() {
+ if (keys == null) {
+ return "[]";
+ }
+ return keys.toJsonArray(mapper);
+ }
public Object getStartKey() {
return startKey;
@@ -584,23 +606,33 @@ public Object getEndKey() {
return endKey;
}
- public String buildQuery() {
+ /**
+ * Builds the HTTP query.
+ * @param keysAsJsonArray If not {@code null} (typically from {@link #getKeysAsJsonArray()}),
+ * it will be included as {@code keys} HTTP query parameter.
+ * @return
+ */
+ public String buildQuery(String keysAsJsonArray) {
if (cachedQuery != null) {
return cachedQuery;
}
- URI query = buildQueryURI();
+ URI query = buildQueryURI(keysAsJsonArray);
cachedQuery = query.toString();
return cachedQuery;
}
- public URI buildQueryURI() {
+ public URI buildQueryURI(String keysAsJsonArray) {
URI query = buildViewPath();
if (isNotEmpty(key)) {
query.param("key", jsonEncode(key));
}
+
+ if (keysAsJsonArray != null) {
+ query.param("keys", keysAsJsonArray);
+ }
if (isNotEmpty(startKey)) {
query.param("startkey", jsonEncode(startKey));
@@ -664,7 +696,16 @@ public URI buildQueryURI() {
return query;
}
- @edu.umd.cs.findbugs.annotations.SuppressWarnings({"SA_FIELD_SELF_ASSIGNMENT", "CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE"})
+ /**
+ * Builds the HTTP query without including {@link #keys(Collection)}).
+ * @return
+ */
+ public String buildQuery() {
+ return buildQuery(null);
+ }
+
+ @Override
+ @edu.umd.cs.findbugs.annotations.SuppressWarnings({"SA_FIELD_SELF_ASSIGNMENT", "CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE"})
public ViewQuery clone() {
ViewQuery copy = new ViewQuery();
copy.mapper = mapper;
@@ -905,7 +946,8 @@ public List> getValues() {
return Collections.unmodifiableList(keys);
}
- @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE")
+ @Override
+ @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE")
public Keys clone() {
return new Keys(keys);
}
@@ -926,8 +968,19 @@ public String toJson(ObjectMapper mapper) {
throw Exceptions.propagate(e);
}
}
- }
+ public String toJsonArray(ObjectMapper mapper) {
+ ArrayNode keysNode = mapper.createArrayNode();
+ for (Object key : keys) {
+ keysNode.addPOJO(key);
+ }
+ try {
+ return mapper.writeValueAsString(keysNode);
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+ }
}
diff --git a/org.ektorp/src/main/java/org/ektorp/impl/DefaultQueryExecutor.java b/org.ektorp/src/main/java/org/ektorp/impl/DefaultQueryExecutor.java
index 1de83bee..9a864963 100644
--- a/org.ektorp/src/main/java/org/ektorp/impl/DefaultQueryExecutor.java
+++ b/org.ektorp/src/main/java/org/ektorp/impl/DefaultQueryExecutor.java
@@ -10,10 +10,12 @@
* This is the default implementation of the executeQuery method of StdCouchDbConnector,
* as of before the method was delegating to the QueryExecutor strategy interface.
*
- * Be aware that, as stated in https://github.com/helun/Ektorp/issues/165 this implementation is making use of POST HTTP method in case of multiple keys,
- * so that it may not be appropriate for hosted services like Cloudant where POST are more charged that GET.
+ * Be aware that, as stated in https://github.com/helun/Ektorp/issues/165 this
+ * implementation is making use of {@code POST} HTTP method in case of multiple keys,
+ * so that it may not be appropriate for hosted services like Cloudant
+ * where {@code POST} requests are charged more than {@code GET}.
*
-*/
+ */
public class DefaultQueryExecutor implements QueryExecutor {
/**
diff --git a/org.ektorp/src/main/java/org/ektorp/impl/PreferGetQueryExecutor.java b/org.ektorp/src/main/java/org/ektorp/impl/PreferGetQueryExecutor.java
new file mode 100644
index 00000000..961676f5
--- /dev/null
+++ b/org.ektorp/src/main/java/org/ektorp/impl/PreferGetQueryExecutor.java
@@ -0,0 +1,77 @@
+package org.ektorp.impl;
+
+import org.ektorp.ViewQuery;
+import org.ektorp.http.ResponseCallback;
+import org.ektorp.http.RestTemplate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the {@code executeQuery} method of {@link StdCouchDbConnector},
+ * from the discussion in Ektorp #165,
+ * which prefers {@code GET} HTTP method even in case of multiple keys.
+ * It is more appropriate for hosted services like Cloudant
+ * where {@code POST} requests are charged more than {@code GET}.
+ *
+ * However, if the HTTP request length exceeds {@value #MAX_KEYS_LENGTH_FOR_GET} characters,
+ * it will use {@code POST} HTTP method.
+ *
+ * @author Hendy Irawan
+ */
+public class PreferGetQueryExecutor implements QueryExecutor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PreferGetQueryExecutor.class);
+
+ /**
+ * Maximum length of {@link ViewQuery#getKeysAsJsonArray()} for a
+ * {@code GET} HTTP request in {@link #executeQuery(ViewQuery, ResponseCallback)},
+ * otherwise uses {@code POST}.
+ */
+ public static final int MAX_KEYS_LENGTH_FOR_GET = 3000;
+
+ private RestTemplate restTemplate;
+
+ public PreferGetQueryExecutor() {
+ super();
+ }
+
+ public PreferGetQueryExecutor(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+ public RestTemplate getRestTemplate() {
+ return restTemplate;
+ }
+
+ public void setRestTemplate(RestTemplate value) {
+ this.restTemplate = value;
+ }
+
+ @Override
+ public T executeQuery(ViewQuery query, ResponseCallback rh) {
+ LOG.debug("Querying CouchDb view at {}.", query);
+ T result;
+ if (!query.isCacheOk()) {
+ if (query.hasMultipleKeys()) {
+ final String keysAsJsonArray = query.getKeysAsJsonArray();
+ result = keysAsJsonArray.length() > MAX_KEYS_LENGTH_FOR_GET
+ ? restTemplate.postUncached(query.buildQuery(), "{\"keys\":" + keysAsJsonArray + "}", rh)
+ : restTemplate.getUncached(query.buildQuery(keysAsJsonArray), rh);
+ } else {
+ result = restTemplate.getUncached(query.buildQuery(), rh);
+ }
+ } else {
+ if (query.hasMultipleKeys()) {
+ final String keysAsJsonArray = query.getKeysAsJsonArray();
+ result = keysAsJsonArray.length() > MAX_KEYS_LENGTH_FOR_GET
+ ? restTemplate.post(query.buildQuery(), "{\"keys\":" + keysAsJsonArray + "}", rh)
+ : restTemplate.get(query.buildQuery(keysAsJsonArray), rh);
+ } else {
+ result = restTemplate.get(query.buildQuery(), rh);
+ }
+ }
+ LOG.debug("Answer from view query: {}.", result);
+ return result;
+ }
+
+}