package ru.dreamteam.couch.changes;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import ru.dreamteam.couch.Couch;
import ru.dreamteam.couch.HttpCall;
import static ru.dreamteam.couch.CouchConstants.*;
import ru.dreamteam.couch.query.CouchQueryException;
import ru.dreamteam.couch.util.JSONUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
/**
* This class provides an ability to make requests
* to CouchDb /_changes API with different query parameters.
*
* Also, polling functional will be added later to this class.
*
* @author dooman
*/
public class ChangesQuery {
private final Logger log = Logger.getLogger(ChangesQuery.class.getName());
private Couch dbInstance;
private ObjectMapper mapper;
private ObjectReader changesReader;
private Map<String, String> queryParams;
private final String PATH;
/**
* Creates a new changes query for a specific database
* @param dbInstance database instance to get changes
* @param dbName database name
* @throws IllegalArgumentException if dbInstance is {@code null} or dbName is blank.
*/
public ChangesQuery(Couch dbInstance, String dbName) throws IllegalArgumentException {
if (dbInstance == null || dbName == null || dbName.trim().length() == 0) {
throw new IllegalArgumentException();
}
this.dbInstance = dbInstance;
this.PATH = "/" + dbName + "/_changes";
this.mapper = JSONUtils.createMapper();
this.changesReader = mapper.reader(new TypeReference<List<Change>>() {});
this.queryParams = new HashMap<>();
}
/**
* Makes a request to a database and return list of changes.
* @return list of changes.
*/
public List<Change> list() throws CouchQueryException {
return dbInstance.execute(new HttpCall<List<Change>>() {
@Override
public List<Change> doWithResponse(HttpResponse response)
throws IOException {
JsonNode rootNode = mapper.readTree(response.getEntity().getContent());
JsonNode resultsNode = rootNode.get("results");
return changesReader.readValue(resultsNode);
}
@Override
public HttpRequest getRequest() throws URISyntaxException {
URIBuilder builder = new URIBuilder();
builder.setPath(PATH);
for (Map.Entry<String, String> param : queryParams.entrySet()) {
builder.setParameter(param.getKey(), param.getValue());
}
HttpPost request = new HttpPost(builder.build());
StringEntity httpEntity = new StringEntity("{}", ContentType.APPLICATION_JSON);
request.setEntity(httpEntity);
return request;
}
});
}
/**
* Sets a limit for a number of changes that will be returned.
* @param limit number of changes.
* @return current query.
* @throws IllegalArgumentException if {@code limit} <= 0
*/
public ChangesQuery limit(int limit) throws IllegalArgumentException {
if (limit <= 0) {
throw new IllegalArgumentException("Limit must be greater then 0.");
}
queryParams.put(LIMIT, Integer.toString(limit));
return this;
}
/**
* Sets a direction of sorting changes.<br/>
* By default changes sorting ascending by change sequence number.
* @param descending new direction of sorting. {@code true} for descending, {@code false} for ascending.
* @return current query.
*/
public ChangesQuery descending(boolean descending) {
queryParams.put(DESCENDING, Boolean.toString(descending));
return this;
}
/**
* Start the results from the change immediately after the given sequence number.
* @param startSince sequence number of the last change.
* @return current query.
*/
public ChangesQuery since(int startSince) {
queryParams.put(SINCE, Integer.toString(startSince));
return this;
}
/**
* If true, then content of a document will be included to change.<br/>
* By default is false.
* @param includeDocs whether or not to include content of a document.
* @return current query.
*/
public ChangesQuery includeDocs(boolean includeDocs) {
queryParams.put(INCLUDE_DOCS, Boolean.toString(includeDocs));
return this;
}
/**
* Sets a filter to use.
* @param filterName name of the filter function in format DESIGN_DOC/FILTER_FUNC
* @return current query.
*/
public ChangesQuery filter(String filterName) {
queryParams.put(FILTER, filterName);
return this;
}
}