Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of the executeQuery method of StdCouchDbConnector, #192

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions org.ektorp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
77 changes: 65 additions & 12 deletions org.ektorp/src/main/java/org/ektorp/ViewQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <a href="https://github.com/helun/Ektorp/issues/165">For ViewQuery.keys(), use GET instead of POST</a>
*/
public ViewQuery keys(Collection<?> keyList) {
reset();
Expand Down Expand Up @@ -567,14 +573,30 @@ 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\":[]}";
}
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;
Expand All @@ -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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="http://cloudant.com/">Cloudant</a>
* where {@code POST} requests are charged more than {@code GET}.
*
*/
*/
public class DefaultQueryExecutor implements QueryExecutor {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <a href="https://github.com/helun/Ektorp/issues/165">Ektorp #165</a>,
* which prefers {@code GET} HTTP method even in case of multiple keys.
* It is more appropriate for hosted services like <a href="http://cloudant.com/">Cloudant</a>
* where {@code POST} requests are charged more than {@code GET}.
*
* <p>However, if the HTTP request length exceeds {@value #MAX_KEYS_LENGTH_FOR_GET} characters,
* it will use {@code POST} HTTP method.
*
* @author Hendy Irawan <[email protected]>
*/
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> T executeQuery(ViewQuery query, ResponseCallback<T> 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;
}

}