/**
* Original iOS version by Jens Alfke
* Ported to Android by Marty Schoch
* <p/>
* Copyright (c) 2012 Couchbase, Inc. All rights reserved.
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
package com.couchbase.lite;
import com.couchbase.lite.internal.AttachmentInternal;
import com.couchbase.lite.internal.RevisionInternal;
import com.couchbase.lite.support.Base64;
import com.couchbase.lite.support.security.SymmetricKeyException;
import com.couchbase.lite.util.Log;
import com.couchbase.lite.util.TextUtils;
import junit.framework.Assert;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPOutputStream;
/**
* Tests ported from DatabaseAttachment_Tests.m
*/
public class DatabaseAttachmentTest extends LiteTestCaseWithDB {
public static final String TAG = "Attachments";
/**
* in DatabaseAttachment_Tests.m
* - (void) test10_Attachments
*/
@SuppressWarnings("unchecked")
public void testAttachments() throws Exception {
BlobStore attachments = database.getAttachmentStore();
Assert.assertEquals(0, attachments.count());
Assert.assertEquals(new HashSet<Object>(), attachments.allKeys());
// Add a revision and an attachment to it:
Status status = new Status(Status.OK);
byte[] attach1 = "This is the body of attach1".getBytes();
Map<String, Object> props = new HashMap<String, Object>();
props.put("foo", 1);
props.put("bar", false);
props.put("_attachments", getAttachmentsDict(attach1, "attach", "text/plain", false));
RevisionInternal rev1 = database.putRevision(new RevisionInternal(props), null, false, status);
Assert.assertEquals(Status.CREATED, status.getCode());
AttachmentInternal att = database.getAttachment(rev1, "attach");
Assert.assertNotNull(att);
Log.i(TAG, new String(att.getContent()));
Assert.assertTrue(Arrays.equals(attach1, att.getContent()));
Assert.assertEquals("text/plain", att.getContentType());
Assert.assertEquals(AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, att.getEncoding());
// Check the attachment dict:
Map<String, Object> itemDict = new HashMap<String, Object>();
itemDict.put("content_type", "text/plain");
itemDict.put("digest", "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE=");
itemDict.put("length", 27);
itemDict.put("stub", true);
itemDict.put("revpos", 1);
Map<String, Object> attachmentDict = new HashMap<String, Object>();
attachmentDict.put("attach", itemDict);
RevisionInternal gotRev1 = database.getDocument(rev1.getDocID(), rev1.getRevID(), true);
Map<String, Object> gotAttachmentDict = (Map<String, Object>) gotRev1.getProperties().get("_attachments");
Assert.assertEquals(attachmentDict, gotAttachmentDict);
// Check the attachment dict, with attachments included:
itemDict.remove("stub");
itemDict.put("data", Base64.encodeBytes(attach1));
gotRev1 = database.getDocument(rev1.getDocID(), rev1.getRevID(), true);
RevisionInternal expandedRev = gotRev1.copy();
Assert.assertTrue(database.expandAttachments(expandedRev, 0, false, true, status));
Assert.assertEquals(attachmentDict, expandedRev.getAttachments());
// Add a second revision that doesn't update the attachment:
props = new HashMap<String, Object>();
props.put("_id", rev1.getDocID());
props.put("foo", 2);
props.put("bazz", false);
props.put("_attachments", getAttachmentsStub("attach"));
RevisionInternal rev2 = database.putRevision(new RevisionInternal(props), rev1.getRevID(), false, status);
Assert.assertEquals(Status.CREATED, status.getCode());
// Add a third revision of the same document:
byte[] attach2 = "<html>And this is attach2</html>".getBytes();
props = new HashMap<String, Object>();
props.put("_id", rev2.getDocID());
props.put("foo", 2);
props.put("bazz", false);
props.put("_attachments", getAttachmentsDict(attach2, "attach", "text/html", false));
RevisionInternal rev3 = database.putRevision(new RevisionInternal(props), rev2.getRevID(), false, status);
Assert.assertEquals(Status.CREATED, status.getCode());
// Check the 2nd revision's attachment:
att = database.getAttachment(rev2, "attach");
Assert.assertNotNull(att);
Assert.assertEquals("text/plain", att.getContentType());
Assert.assertEquals(AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, att.getEncoding());
Assert.assertTrue(Arrays.equals(attach1, att.getContent()));
expandedRev = rev2.copy();
Assert.assertTrue(database.expandAttachments(expandedRev, 2, false, true, status));
attachmentDict = new HashMap<String, Object>();
itemDict = new HashMap<String, Object>();
itemDict.put("stub", true);
itemDict.put("revpos", 1);
attachmentDict.put("attach", itemDict);
Assert.assertEquals(attachmentDict, expandedRev.getAttachments());
// Check the 3rd revision's attachment:
att = database.getAttachment(rev3, "attach");
Assert.assertNotNull(att);
Assert.assertEquals("text/html", att.getContentType());
Assert.assertEquals(AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, att.getEncoding());
Assert.assertTrue(Arrays.equals(attach2, att.getContent()));
expandedRev = rev3.copy();
Assert.assertTrue(database.expandAttachments(expandedRev, 2, false, true, status));
attachmentDict = new HashMap<String, Object>();
itemDict = new HashMap<String, Object>();
itemDict.put("content_type", "text/html");
itemDict.put("data", "PGh0bWw+QW5kIHRoaXMgaXMgYXR0YWNoMjwvaHRtbD4=");
itemDict.put("digest", "sha1-s14XRTXlwvzYfjo1t1u0rjB+ZUA=");
itemDict.put("length", 32);
itemDict.put("revpos", 3);
attachmentDict.put("attach", itemDict);
Map<String, Object> data = expandedRev.getAttachments();
Assert.assertEquals(attachmentDict, expandedRev.getAttachments());
// Examine the attachment store:
Assert.assertEquals(2, attachments.count());
Set<BlobKey> expected = new HashSet<BlobKey>();
expected.add(BlobStore.keyForBlob(attach1));
expected.add(BlobStore.keyForBlob(attach2));
Assert.assertEquals(expected, attachments.allKeys());
database.compact(); // This clears the body of the first revision
Assert.assertEquals(1, attachments.count());
Set<BlobKey> expected2 = new HashSet<BlobKey>();
expected2.add(BlobStore.keyForBlob(attach2));
Assert.assertEquals(expected2, attachments.allKeys());
}
/**
* in DatabaseAttachment_Tests.m
* - (void) test11_PutAttachment
*/
@SuppressWarnings("unchecked")
public void test11_PutAttachment() throws Exception {
// Put a revision that includes an _attachments dict:
RevisionInternal rev1 = putDocWithAttachment(null, "This is the body of attach1", false);
Map<String, Object> itemDict = new HashMap<String, Object>();
itemDict.put("content_type", "text/plain");
itemDict.put("digest", "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE=");
itemDict.put("length", 27);
itemDict.put("stub", true);
itemDict.put("revpos", 1);
Map<String, Object> attachmentDict = new HashMap<String, Object>();
attachmentDict.put("attach", itemDict);
Assert.assertEquals(attachmentDict, rev1.getAttachments());
// Examine the attachment store:
Assert.assertEquals(database.getAttachmentStore().count(), 1);
// Get the revision:
RevisionInternal gotRev1 = database.getDocument(rev1.getDocID(), rev1.getRevID(), true);
Assert.assertEquals(attachmentDict, gotRev1.getAttachments());
// Update the attachment directly:
boolean gotExpectedErrorCode = false;
byte[] attachv2 = "Replaced body of attach".getBytes();
try {
database.updateAttachment("attach",
blobForData(database, attachv2),
"application/foo",
AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone,
rev1.getDocID(),
null,
null);
} catch (CouchbaseLiteException e) {
gotExpectedErrorCode = (e.getCBLStatus().getCode() == Status.CONFLICT);
}
Assert.assertTrue(gotExpectedErrorCode);
gotExpectedErrorCode = false;
try {
database.updateAttachment("attach",
blobForData(database, attachv2),
"application/foo",
AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone,
rev1.getDocID(),
"1-deadbeef",
null);
} catch (CouchbaseLiteException e) {
gotExpectedErrorCode = (e.getCBLStatus().getCode() == Status.CONFLICT);
}
Assert.assertTrue(gotExpectedErrorCode);
RevisionInternal rev2 = database.updateAttachment("attach",
blobForData(database, attachv2),
"application/foo",
AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone,
rev1.getDocID(),
rev1.getRevID(),
null);
Assert.assertNotNull(rev2);
Assert.assertEquals(rev1.getDocID(), rev2.getDocID());
Assert.assertEquals(2, rev2.getGeneration());
// Get the updated revision:
RevisionInternal gotRev2 = database.getDocument(rev2.getDocID(), rev2.getRevID(), true);
itemDict = new HashMap<String, Object>();
itemDict.put("content_type", "application/foo");
itemDict.put("digest", "sha1-mbT3208HI3PZgbG4zYWbDW2HsPk=");
itemDict.put("length", 23);
itemDict.put("stub", true);
itemDict.put("revpos", 2);
attachmentDict = new HashMap<String, Object>();
attachmentDict.put("attach", itemDict);
Assert.assertEquals(attachmentDict, gotRev2.getAttachments());
// Delete the attachment:
gotExpectedErrorCode = false;
try {
database.updateAttachment("nosuchattach",
null,
null,
AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone,
rev2.getDocID(),
rev2.getRevID(),
null);
} catch (CouchbaseLiteException e) {
gotExpectedErrorCode = (e.getCBLStatus().getCode() == Status.NOT_FOUND);
}
Assert.assertTrue(gotExpectedErrorCode);
gotExpectedErrorCode = false;
try {
database.updateAttachment("nosuchattach",
null,
null,
AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone,
"nosuchdoc",
"nosuchrev",
null);
} catch (CouchbaseLiteException e) {
gotExpectedErrorCode = (e.getCBLStatus().getCode() == Status.NOT_FOUND);
}
Assert.assertTrue(gotExpectedErrorCode);
RevisionInternal rev3 = database.updateAttachment("attach",
null,
null,
AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone,
rev2.getDocID(),
rev2.getRevID(),
null);
Assert.assertNotNull(rev2);
Assert.assertEquals(rev2.getDocID(), rev3.getDocID());
Assert.assertEquals(3, rev3.getGeneration());
// Get the updated revision:
RevisionInternal gotRev3 = database.getDocument(rev3.getDocID(), rev3.getRevID(), true);
Assert.assertNull(gotRev3.getAttachments());
}
/**
* - (void) test13_GarbageCollectAttachments
*/
public void test13_GarbageCollectAttachments() throws CouchbaseLiteException {
List<RevisionInternal> revs = new ArrayList<RevisionInternal>();
for (int i = 0; i < 100; i++)
revs.add(this.putDocWithAttachment(String.format(Locale.ENGLISH, "doc-%d", i), String.format(Locale.ENGLISH, "Attachment #%d", i), false));
for (int i = 0; i < 40; i++) {
revs.set(i,
database.updateAttachment(
"attach",
null,
null,
AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone,
revs.get(i).getDocID(),
revs.get(i).getRevID(),
null));
}
database.compact();
assertEquals(60, database.getAttachmentStore().count());
}
public void testAddAndGetAttachment() throws CouchbaseLiteException {
Document document = database.createDocument();
UnsavedRevision rev = document.createRevision();
byte[] attach = "This is the body of attach".getBytes();
;
InputStream in = new ByteArrayInputStream(attach);
rev.setAttachment("attach", "text/plain", in);
assertNotNull(rev.getAttachment("attach"));
assertEquals(1, rev.getAttachments().size());
rev.save();
}
public void testStreamAttachmentBlobStoreWriter() throws Exception {
BlobStore attachments = database.getAttachmentStore();
BlobStoreWriter blobWriter = new com.couchbase.lite.BlobStoreWriter(attachments);
String testBlob = "foo";
blobWriter.appendData(new String(testBlob).getBytes());
blobWriter.finish();
String sha1Base64Digest = "sha1-C+7Hteo/D9vJXQ3UfzxbwnXaijM=";
Assert.assertEquals(blobWriter.sHA1DigestString(), sha1Base64Digest);
Assert.assertEquals(blobWriter.mD5DigestString(), "md5-rL0Y20zC+Fzt72VPzMSk2A==");
// install it
blobWriter.install();
// look it up in blob store and make sure it's there
BlobKey blobKey = new BlobKey(sha1Base64Digest);
byte[] blob = attachments.blobForKey(blobKey);
Assert.assertTrue(Arrays.equals(testBlob.getBytes(Charset.forName("UTF-8")), blob));
}
/**
* https://github.com/couchbase/couchbase-lite-android/issues/134
*/
public void testGetAttachmentBodyUsingPrefetch() throws CouchbaseLiteException, IOException {
// add a doc with an attachment
Document doc = database.createDocument();
UnsavedRevision rev = doc.createRevision();
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("foo", "bar");
rev.setUserProperties(properties);
final byte[] attachBodyBytes = "attach body".getBytes();
Attachment attachment = new Attachment(
new ByteArrayInputStream(attachBodyBytes),
"text/plain"
);
final String attachmentName = "test_attachment.txt";
rev.addAttachment(attachment, attachmentName);
rev.save();
// do query that finds that doc with prefetch
View view = database.getView("aview");
view.setMapReduce(new Mapper() {
@Override
public void map(Map<String, Object> document, Emitter emitter) {
String id = (String) document.get("_id");
emitter.emit(id, null);
}
}, null, "1");
// try to get the attachment
Query query = view.createQuery();
query.setPrefetch(true);
QueryEnumerator results = query.run();
while (results.hasNext()) {
QueryRow row = results.next();
// This returns the revision just fine, but the sequence number
// is set to 0.
SavedRevision revision = row.getDocument().getCurrentRevision();
// This returns an Attachment object which looks ok, except again
// its sequence number is 0. The metadata property knows about
// the length and mime type of the attachment. It also says
// "stub" -> "true".
Attachment attachmentRetrieved = revision.getAttachment(attachmentName);
// This throws a CouchbaseLiteException with Status.NOT_FOUND.
InputStream is = attachmentRetrieved.getContent();
assertNotNull(is);
byte[] attachmentDataRetrieved;
try {
attachmentDataRetrieved = TextUtils.read(is);
} finally {
is.close();
}
String attachmentDataRetrievedString = new String(attachmentDataRetrieved);
String attachBodyString = new String(attachBodyBytes);
assertEquals(attachBodyString, attachmentDataRetrievedString);
}
}
/**
* Regression test for https://github.com/couchbase/couchbase-lite-java-core/issues/218
*/
public void testGetAttachmentAfterItDeleted() throws CouchbaseLiteException, IOException {
// add a doc with an attachment
Document doc = database.createDocument();
UnsavedRevision rev = doc.createRevision();
final byte[] attachBodyBytes = "attach body".getBytes();
Attachment attachment = new Attachment(
new ByteArrayInputStream(attachBodyBytes),
"text/plain"
);
String attachmentName = "test_delete_attachment.txt";
rev.addAttachment(attachment, attachmentName);
rev.save();
UnsavedRevision rev1 = doc.createRevision();
Attachment currentAttachment = rev1.getAttachment(attachmentName);
assertNotNull(currentAttachment);
rev1.removeAttachment(attachmentName);
currentAttachment = rev1.getAttachment(attachmentName);
assertNull(currentAttachment); // otherwise NullPointerException when currentAttachment.getMetadata()
rev1.save();
currentAttachment = doc.getCurrentRevision().getAttachment(attachmentName);
assertNull(currentAttachment); // otherwise NullPointerException when currentAttachment.getMetadata()
}
/**
* Regression test for https://github.com/couchbase/couchbase-lite-android-core/issues/70
*/
public void testAttachmentDisappearsAfterSave() throws CouchbaseLiteException, IOException {
// create a doc with an attachment
Document doc = database.createDocument();
String content = "This is a test attachment!";
ByteArrayInputStream body = new ByteArrayInputStream(content.getBytes());
UnsavedRevision rev = doc.createRevision();
rev.setAttachment("index.html", "text/plain; charset=utf-8", body);
rev.save();
// make sure the doc's latest revision has the attachment
Map<String, Object> attachments = (Map) doc.getCurrentRevision().getProperty("_attachments");
assertNotNull(attachments);
assertEquals(1, attachments.size());
// make sure the rev has the attachment
attachments = (Map) rev.getProperty("_attachments");
assertNotNull(attachments);
assertEquals(1, attachments.size());
// create new properties to add
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("foo", "bar");
// make sure the new rev still has the attachment
UnsavedRevision rev2 = doc.createRevision();
rev2.getProperties().putAll(properties);
rev2.save();
attachments = (Map) rev2.getProperty("_attachments");
assertNotNull(attachments);
assertEquals(1, attachments.size());
}
/**
* attempt to reproduce https://github.com/couchbase/couchbase-lite-android/issues/328 &
* https://github.com/couchbase/couchbase-lite-android/issues/325
*/
public void testSetAttachmentsSequentially() throws CouchbaseLiteException, IOException {
try {
//Create rev1 of document with just properties
Document doc = database.createDocument();
String id = doc.getId();
Map<String, Object> docProperties = new HashMap<String, Object>();
docProperties.put("Iteration", 0);
doc.putProperties(docProperties);
UnsavedRevision rev = null;
//Create a new revision with attachment1
InputStream attachmentStream1 = getAsset("attachment.png");
doc = database.getDocument(id);//not required
byte[] jsonb = Manager.getObjectMapper().writeValueAsBytes(doc.getProperties().get("_attachments"));
Log.d(Database.TAG, "Doc _rev = %s", doc.getProperties().get("_rev"));
Log.d(Database.TAG, "Doc properties = %s", new String(jsonb));
rev = doc.createRevision();
rev.setAttachment("attachment1", "image/png", attachmentStream1);
rev.save();
attachmentStream1.close();
//Create a new revision updated properties
doc = database.getDocument(id);//not required
jsonb = Manager.getObjectMapper().writeValueAsBytes(doc.getProperties().get("_attachments"));
Log.d(Database.TAG, "Doc _rev = %s", doc.getProperties().get("_rev"));
Log.d(Database.TAG, "Doc properties = %s", new String(jsonb));
Map<String, Object> curProperties;
curProperties = doc.getProperties();
docProperties = new HashMap<String, Object>();
docProperties.putAll(curProperties);
docProperties.put("Iteration", 1);
doc.putProperties(docProperties);
//Create a new revision with attachment2
InputStream attachmentStream2 = getAsset("attachment.png");
doc = database.getDocument(id);//not required
jsonb = Manager.getObjectMapper().writeValueAsBytes(doc.getProperties().get("_attachments"));
Log.d(Database.TAG, "Doc _rev = %s", doc.getProperties().get("_rev"));
Log.d(Database.TAG, "Doc properties = %s", new String(jsonb));
rev = doc.createRevision();
rev.setAttachment("attachment2", "image/png", attachmentStream2);
rev.save();
attachmentStream2.close();
//Assert final document revision
doc = database.getDocument(id);
curProperties = doc.getProperties();
assertEquals(4, curProperties.size());
Map<String, Object> attachments = (Map<String, Object>) doc.getCurrentRevision().getProperty("_attachments");
assertNotNull(attachments);
assertEquals(2, attachments.size());
} catch (CouchbaseLiteException e) {
Log.e(Database.TAG, "Error adding attachment: " + e.getMessage(), e);
fail();
}
}
/**
* attempt to reproduce
* - https://github.com/couchbase/couchbase-lite-android/issues/328 &
* - https://github.com/couchbase/couchbase-lite-android/issues/325
*/
public void testSetAttachmentsSequentiallyInTransaction() throws CouchbaseLiteException, IOException {
boolean success = database.runInTransaction(new TransactionalTask() {
public boolean run() {
try {
// add a doc with an attachment
Document doc = database.createDocument();
String id = doc.getId();
InputStream jsonStream = getAsset("300k.json");
Map<String, Object> docProperties = null;
docProperties = Manager.getObjectMapper().readValue(jsonStream, Map.class);
docProperties.put("Iteration", 0);
doc.putProperties(docProperties);
jsonStream.close();
UnsavedRevision rev = null;
for (int i = 0; i < 5; i++) {
InputStream attachmentStream1 = getAsset("attachment.png");
Log.e(Database.TAG, "TEST ITERATION " + i);
doc = database.getDocument(id);//not required
rev = doc.createRevision();
rev.setAttachment("attachment " + i * 3, "image/png", attachmentStream1);
rev.save();
attachmentStream1.close();
InputStream attachmentStream2 = getAsset("attachment.png");
doc = database.getDocument(id);//not required
rev = doc.createRevision();
rev.setAttachment("attachment " + i * 3 + 1, "image/png", attachmentStream2);
rev.save();
attachmentStream2.close();
InputStream attachmentStream3 = getAsset("attachment.png");
doc = database.getDocument(id);//not required
rev = doc.createRevision();
rev.setAttachment("attachment " + i * 3 + 2, "image/png", attachmentStream3);
rev.save();
attachmentStream3.close();
Map<String, Object> curProperties;
doc = database.getDocument(id);//not required
curProperties = doc.getProperties();
docProperties = new HashMap<String, Object>();
docProperties.putAll(curProperties);
docProperties.put("Iteration", (i + 1) * 3);
doc.putProperties(docProperties);
}
Map<String, Object> curProperties;
doc = database.getDocument(id);//not required
curProperties = doc.getProperties();
assertEquals(22, curProperties.size());
Map<String, Object> attachments = (Map<String, Object>) doc.getCurrentRevision().getProperty("_attachments");
assertNotNull(attachments);
assertEquals(15, attachments.size());
} catch (Exception e) {
Log.e(Database.TAG, "Error deserializing properties from JSON", e);
return false;
}
return true;
}
});
assertTrue("transaction with set attachments sequentially failed", success);
}
/**
* Regression test for https://github.com/couchbase/couchbase-lite-android-core/issues/70
*/
public void testAttachmentInstallBodies() throws Exception {
Map<String, Object> attachmentsMap = new HashMap<String, Object>();
Map<String, Object> attachmentMap = new HashMap<String, Object>();
attachmentMap.put("length", 25);
String attachmentName = "index.html";
attachmentsMap.put(attachmentName, attachmentMap);
Map<String, Object> updatedAttachments = Attachment.installAttachmentBodies(attachmentsMap, database);
assertTrue(updatedAttachments.size() > 0);
assertTrue(updatedAttachments.containsKey(attachmentName));
}
public void testGetContentURL() throws Exception {
String attachmentName = "index.html";
String content = "This is a test attachment!";
Document doc = createDocWithAttachment(database, attachmentName, content);
Attachment attachment = doc.getCurrentRevision().getAttachment(attachmentName);
URL url = attachment.getContentURL();
assertNotNull(url);
FileInputStream fis = new FileInputStream(new File(url.toURI()));
byte[] buffer = new byte[1024];
int len = fis.read(buffer);
assertTrue(len != -1);
String content2 = new String(buffer, 0, len);
assertEquals(content, content2);
fis.close();
}
public void testAttachmentThrowIoException() {
InputStream in = new InputStream() {
@Override
public int read() throws IOException {
throw new IOException();
}
};
Document doc = database.createDocument();
UnsavedRevision rev = doc.createRevision();
rev.setAttachment("ioe_attach", "text/plain", in);
try {
rev.save();
fail("Saved revision with corrupt attachment");
} catch (CouchbaseLiteException expected) {
assertEquals(Status.ATTACHMENT_ERROR, expected.getCBLStatus().getCode());
}
}
public void testGetAttachmentFromUnsavedRevision() throws Exception {
String attachmentName = "index.html";
String content = "This is a test attachment!";
Document doc = createDocWithAttachment(database, attachmentName, content);
UnsavedRevision rev = doc.createRevision();
Attachment attachment = rev.getAttachment(attachmentName);
assertNotNull(attachment);
InputStream in = attachment.getContent();
assertNotNull(in);
assertEquals(IOUtils.toString(in, "UTF-8"), content);
in.close();
}
public void testGetAttachmentFromUnsavedRevisionWithMultipleRevisions() throws Exception {
String attachmentName = "index.html";
String content = "This is a test attachment!";
Document doc = createDocWithAttachment(database, attachmentName, content);
// added extra two revisions to make sure in case the revision that has an attachment is
// more than two generation older than current.
doc.createRevision().save();
doc.createRevision().save();
UnsavedRevision rev = doc.createRevision();
Attachment attachment = rev.getAttachment(attachmentName);
assertNotNull(attachment);
InputStream in = attachment.getContent();
assertNotNull(in);
assertEquals(IOUtils.toString(in, "UTF-8"), content);
in.close();
}
public void testGzippedAttachments() throws Exception {
String attachmentName = "index.html";
byte content[] = "This is a test attachment!".getBytes("UTF-8");
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(byteOut);
gzipOut.write(content);
gzipOut.close();
byte contentGzipped[] = byteOut.toByteArray();
Document doc = database.createDocument();
UnsavedRevision rev = doc.createRevision();
rev.setAttachment(attachmentName, "text/html", new ByteArrayInputStream(contentGzipped));
rev.save();
SavedRevision savedRev = doc.getCurrentRevision();
Attachment attachment = savedRev.getAttachment(attachmentName);
// As far as revision users are concerned their data is not gzipped
InputStream in = attachment.getContent();
assertNotNull(in);
assertTrue(Arrays.equals(content, IOUtils.toByteArray(in)));
in.close();
Document gotDoc = database.getDocument(doc.getId());
Revision gotRev = gotDoc.getCurrentRevision();
Attachment gotAtt = gotRev.getAttachment(attachmentName);
in = gotAtt.getContent();
assertNotNull(in);
assertTrue(Arrays.equals(content, IOUtils.toByteArray(in)));
in.close();
}
// Store Gzipped attachment by Base64 encoding
public void testGzippedAttachmentByBase64() throws Exception {
String attachmentName = "attachment.png";
// 1. store attachment with doc
// 1.a load attachment data from asset
InputStream attachmentStream = getAsset(attachmentName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(attachmentStream, baos);
baos.close();
attachmentStream.close();
byte[] bytes = baos.toByteArray();
// 1.b apply GZIP + Base64
String attachmentBase64 = Base64.encodeBytes(bytes, Base64.GZIP);
// 1.c attachment Map object
Map<String, Object> attachmentMap = new HashMap<String, Object>();
attachmentMap.put("content_type", "image/png");
attachmentMap.put("data", attachmentBase64);
attachmentMap.put("encoding", "gzip");
attachmentMap.put("length", bytes.length);
// 1.d attachments Map object
Map<String, Object> attachmentsMap = new HashMap<String, Object>();
attachmentsMap.put(attachmentName, attachmentMap);
// 1.e document property Map object
Map<String, Object> propsMap = new HashMap<String, Object>();
propsMap.put("_attachments", attachmentsMap);
// 1.f store document into database
Document putDoc = database.createDocument();
putDoc.putProperties(propsMap);
String docId = putDoc.getId();
// 2. Load attachment from database and compare it with original
// 2.a load doc and attachment from database
Document getDoc = database.getDocument(docId);
Attachment attachment = getDoc.getCurrentRevision().getAttachment(attachmentName);
assertEquals(bytes.length, attachment.getLength());
assertEquals("image/png", attachment.getContentType());
assertEquals("gzip", attachment.getMetadata().get("encoding"));
InputStream is = attachment.getContent();
byte[] receivedBytes = getBytesFromInputStream(is);
assertEquals(bytes.length, receivedBytes.length);
is.close();
assertTrue(Arrays.equals(bytes, receivedBytes));
}
private RevisionInternal putDocWithAttachment(String docID, String attachmentText, boolean compress) throws CouchbaseLiteException {
byte[] attachmentData = attachmentText.getBytes();
String encoding = null;
int length = 0;
if (compress) {
length = attachmentData.length;
encoding = "gzip";
// encode
}
String base64 = Base64.encodeBytes(attachmentData);
Map<String, Object> itemDict = new HashMap<String, Object>();
itemDict.put("content_type", "text/plain");
itemDict.put("data", base64);
itemDict.put("encoding", encoding);
itemDict.put("length", length == 0 ? null : length);
Map<String, Object> attachmentDict = new HashMap<String, Object>();
attachmentDict.put("attach", itemDict);
Map<String, Object> props = new HashMap<String, Object>();
props.put("_id", docID);
props.put("foo", 1);
props.put("bar", false);
props.put("_attachments", attachmentDict);
Status status = new Status(Status.OK);
RevisionInternal rev = database.putRevision(new RevisionInternal(props), null, false, status);
Assert.assertEquals(Status.CREATED, status.getCode());
return rev;
}
private static byte[] getBytesFromInputStream(InputStream is) {
org.apache.commons.io.output.ByteArrayOutputStream os = new org.apache.commons.io.output.ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
try {
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
os.flush();
} catch (IOException e) {
Log.e(Log.TAG, "is.read(buffer) or os.flush() error", e);
return null;
}
return os.toByteArray();
}
private static Map<String, Map<String, Object>> getAttachmentsDict(byte[] data, String name, String type, boolean gzipped) {
if (gzipped)
// TODO
;
Map<String, Object> att = new HashMap<String, Object>();
att.put("content_type", type);
att.put("data", data);
if (gzipped)
att.put("encoding", "gzip");
Map<String, Map<String, Object>> atts = new HashMap<String, Map<String, Object>>();
atts.put(name, att);
return atts;
}
private static Map<String, Map<String, Object>> getAttachmentsStub(String name) {
Map<String, Object> att = new HashMap<String, Object>();
att.put("stub", true);
Map<String, Map<String, Object>> atts = new HashMap<String, Map<String, Object>>();
atts.put(name, att);
return atts;
}
private static BlobStoreWriter blobForData(Database db, byte[] data)
throws SymmetricKeyException {
try {
BlobStoreWriter blob = db.getAttachmentWriter();
blob.appendData(data);
blob.finish();
return blob;
} catch (IOException e) {
return null;
}
}
}