com.couchbase.lite.replicator.BulkDownloader Maven / Gradle / Ivy
package com.couchbase.lite.replicator;
import com.couchbase.lite.Database;
import com.couchbase.lite.Manager;
import com.couchbase.lite.internal.InterfaceAudience;
import com.couchbase.lite.internal.RevisionInternal;
import com.couchbase.lite.support.HttpClientFactory;
import com.couchbase.lite.support.MultipartDocumentReader;
import com.couchbase.lite.support.MultipartReader;
import com.couchbase.lite.support.MultipartReaderDelegate;
import com.couchbase.lite.support.RemoteRequest;
import com.couchbase.lite.support.RemoteRequestCompletionBlock;
import com.couchbase.lite.util.CollectionUtils;
import com.couchbase.lite.util.Log;
import com.couchbase.lite.util.Utils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.GZIPInputStream;
/**
* A special type of RemoteRequest that knows how to call the _bulk_get endpoint.
*
* @exclude
*/
@InterfaceAudience.Private
public class BulkDownloader extends RemoteRequest implements MultipartReaderDelegate {
public static final String TAG = Log.TAG_SYNC;
private Database db;
private MultipartReader _topReader;
private MultipartDocumentReader _docReader;
private BulkDownloaderDocumentBlock _onDocument;
public BulkDownloader(ScheduledExecutorService workExecutor,
HttpClientFactory clientFactory,
URL dbURL,
List revs,
Database database,
Map requestHeaders,
BulkDownloaderDocumentBlock onDocument,
RemoteRequestCompletionBlock onCompletion) throws Exception {
super(workExecutor,
clientFactory,
"POST",
new URL(buildRelativeURLString(dbURL, "/_bulk_get?revs=true&attachments=true")),
helperMethod(revs, database),
database,
requestHeaders,
onCompletion);
db = database;
_onDocument = onDocument;
}
@Override
public void run() {
HttpClient httpClient = clientFactory.getHttpClient();
try {
preemptivelySetAuthCredentials(httpClient);
request.addHeader("Content-Type", "application/json");
request.addHeader("Accept", "multipart/related");
request.addHeader("User-Agent", Manager.getUserAgent());
request.addHeader("X-Accept-Part-Encoding", "gzip");
request.addHeader("Accept-Encoding", "gzip, deflate");
addRequestHeaders(request);
setBody(request);
executeRequest(httpClient, request);
} finally {
// shutdown connection manager (close all connections)
if (httpClient != null && httpClient.getConnectionManager() != null)
httpClient.getConnectionManager().shutdown();
}
}
public String toString() {
return this.getClass().getName() + '[' + url.getPath() + ']';
}
private static final int BUF_LEN = 1024;
@Override
protected void executeRequest(HttpClient httpClient, HttpUriRequest request) {
Object fullBody = null;
Throwable error = null;
HttpResponse response = null;
try {
if (request.isAborted()) {
respondWithResult(fullBody, new Exception(
String.format("%s: Request %s has been aborted", this, request)), response);
return;
}
response = httpClient.execute(request);
try {
// add in cookies to global store
if (httpClient instanceof DefaultHttpClient) {
DefaultHttpClient defaultHttpClient = (DefaultHttpClient) httpClient;
clientFactory.addCookies(defaultHttpClient.getCookieStore().getCookies());
}
} catch (Exception e) {
Log.e(Log.TAG_REMOTE_REQUEST, "Unable to add in cookies to global store", e);
}
StatusLine status = response.getStatusLine();
if (status.getStatusCode() >= 300) {
try {
// conflict could be happen. So ERROR prints make developer confused.
if (status.getStatusCode() == 409)
Log.w(Log.TAG_REMOTE_REQUEST, "Got error status: %d for %s. Reason: %s",
status.getStatusCode(), request, status.getReasonPhrase());
else
Log.e(Log.TAG_REMOTE_REQUEST, "Got error status: %d for %s. Reason: %s",
status.getStatusCode(), request, status.getReasonPhrase());
error = new HttpResponseException(status.getStatusCode(),
status.getReasonPhrase());
respondWithResult(fullBody, error, response);
return;
} finally {
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
entity.consumeContent();
} catch (IOException e) {
}
}
}
} else {
HttpEntity entity = response.getEntity();
try {
if (entity != null) {
InputStream inputStream = entity.getContent();
try {
// decompress if contentEncoding is gzip
if (Utils.isGzip(entity))
inputStream = new GZIPInputStream(inputStream);
Header contentTypeHeader = entity.getContentType();
if (contentTypeHeader != null) {
// multipart
if (contentTypeHeader.getValue().contains("multipart/")) {
Log.v(TAG, "contentTypeHeader = %s",
contentTypeHeader.getValue());
_topReader = new MultipartReader(
contentTypeHeader.getValue(), this);
byte[] buffer = new byte[BUF_LEN];
int nBytesRead = 0;
while ((nBytesRead = inputStream.read(buffer)) != -1) {
_topReader.appendData(buffer, 0, nBytesRead);
}
_topReader.finished();
}
// non-multipart
else {
Log.v(TAG, "contentTypeHeader is not multipart = %s",
contentTypeHeader.getValue());
fullBody = Manager.getObjectMapper().readValue(
inputStream, Object.class);
}
respondWithResult(fullBody, error, response);
return;
}
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
}
}
}
} finally {
if (entity != null) {
try {
entity.consumeContent();
} catch (IOException e) {
}
}
}
}
} catch (Exception e) {
Log.e(Log.TAG_REMOTE_REQUEST, "%s: executeRequest() Exception: ", e, this);
error = e;
} finally {
Log.v(TAG, "%s: BulkDownloader finally block. url: %s", this, url);
}
// error scenario
respondWithResult(fullBody, error, response);
}
/**
* This method is called when a part's headers have been parsed, before its data is parsed.
*/
@Override
public void startedPart(Map headers) {
if (_docReader != null) {
throw new IllegalStateException("_docReader is already defined");
}
Log.v(TAG, "%s: Starting new document; headers =%s", this, headers);
Log.v(TAG, "%s: Starting new document; ID=%s", this, headers.get("X-Doc-Id"));
_docReader = new MultipartDocumentReader(db);
_docReader.setHeaders(headers);
_docReader.startedPart(headers);
}
/**
* This method is called to append data to a part's body.
*/
@Override
public void appendToPart(byte[] data) {
appendToPart(data, 0, data.length);
}
@Override
public void appendToPart(final byte[] data, int off, int len) {
if (_docReader == null) {
throw new IllegalStateException("_docReader is not defined");
}
_docReader.appendData(data, off, len);
}
/**
* This method is called when a part is complete.
*/
@Override
public void finishedPart() {
Log.v(TAG, "%s: Finished document", this);
if (_docReader == null) {
throw new IllegalStateException("_docReader is not defined");
}
_docReader.finish();
_onDocument.onDocument(_docReader.getDocumentProperties());
_docReader = null;
}
/**
* @exclude
*/
@InterfaceAudience.Private
public interface BulkDownloaderDocumentBlock {
public void onDocument(Map props);
}
private static Map helperMethod(List revs, final Database database) {
// Build up a JSON body describing what revisions we want:
Collection
© 2015 - 2025 Weber Informatics LLC | Privacy Policy