/* * Copyright 2010 Outerthought bvba * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 org.lilyproject.repository.impl.test; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.HashSet; import java.util.Random; import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.HTableInterface; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.util.Bytes; import org.junit.Test; import org.lilyproject.repository.api.Blob; import org.lilyproject.repository.api.BlobException; import org.lilyproject.repository.api.BlobManager; import org.lilyproject.repository.api.BlobNotFoundException; import org.lilyproject.repository.api.BlobReference; import org.lilyproject.repository.api.FieldNotFoundException; import org.lilyproject.repository.api.FieldType; import org.lilyproject.repository.api.FieldTypeEntry; import org.lilyproject.repository.api.FieldTypeNotFoundException; import org.lilyproject.repository.api.HierarchyPath; import org.lilyproject.repository.api.InvalidRecordException; import org.lilyproject.repository.api.QName; import org.lilyproject.repository.api.Record; import org.lilyproject.repository.api.RecordId; import org.lilyproject.repository.api.RecordNotFoundException; import org.lilyproject.repository.api.RecordType; import org.lilyproject.repository.api.Repository; import org.lilyproject.repository.api.RepositoryException; import org.lilyproject.repository.api.Scope; import org.lilyproject.repository.api.TypeManager; import org.lilyproject.repository.api.ValueType; import org.lilyproject.repository.impl.BlobIncubatorMonitor; import org.lilyproject.repository.impl.BlobStoreAccessRegistry; import org.lilyproject.repository.impl.id.IdGeneratorImpl; import org.lilyproject.repotestfw.RepositorySetup; import org.lilyproject.util.hbase.LilyHBaseSchema; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public abstract class AbstractBlobStoreTest { private static String namespace = "test"; protected static final RepositorySetup repoSetup = new RepositorySetup(); static { repoSetup.setBlobLimits(50, 1024); } protected static Repository repository; protected static TypeManager typeManager; protected static Random random = new Random(); protected static BlobStoreAccessRegistry testBlobStoreAccessRegistry; protected static BlobManager blobManager; @Test public void testCreate() throws Exception { QName fieldName = new QName(namespace, "testCreate"); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.NON_VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testCreateRT")); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = Bytes.toBytes("someBytes"); Blob blob = writeBlob(bytes, "aMediaType", "testCreate"); Record record = repository.newRecord(); record.setRecordType(recordType.getName()); record.setField(fieldName, blob); record = repository.create(record); byte[] readBytes = readBlob(record.getId(), fieldName); assertTrue(Arrays.equals(bytes, readBytes)); // Test the getInputStream with giving the record instead of the recordId InputStream inputStream = repository.getInputStream(record, fieldName); try { byte[] readBytes2 = IOUtils.toByteArray(inputStream); assertTrue(Arrays.equals(bytes, readBytes2)); } finally { IOUtils.closeQuietly(inputStream); } } @Test public void testThreeSizes() throws Exception { QName fieldName1 = new QName(namespace, "testThreeSizes1"); QName fieldName2 = new QName(namespace, "testThreeSizes2"); QName fieldName3 = new QName(namespace, "testThreeSizes3"); FieldType fieldType1 = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName1, Scope.NON_VERSIONED); fieldType1 = typeManager.createFieldType(fieldType1); FieldType fieldType2 = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName2, Scope.NON_VERSIONED); fieldType2 = typeManager.createFieldType(fieldType2); FieldType fieldType3 = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName3, Scope.NON_VERSIONED); fieldType3 = typeManager.createFieldType(fieldType3); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testThreeSizes")); recordType.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType1.getId(), true)); recordType.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType2.getId(), true)); recordType.addFieldTypeEntry(typeManager.newFieldTypeEntry(fieldType3.getId(), true)); recordType = typeManager.createRecordType(recordType); byte[] small = new byte[10]; random.nextBytes(small); byte[] medium = new byte[100]; random.nextBytes(medium); byte[] large = new byte[2048]; random.nextBytes(large); Blob smallBlob = writeBlob(small, "mime/small", "small"); Blob mediumBlob = writeBlob(medium, "mime/medium", "medium"); Blob largeBlob = writeBlob(large, "mime/large", "large"); Record record = repository.newRecord(); record.setRecordType(recordType.getName()); record.setField(fieldName1, smallBlob); record.setField(fieldName2, mediumBlob); record.setField(fieldName3, largeBlob); record = repository.create(record); byte[] readBytes = readBlob(record.getId(), fieldName1); assertTrue(Arrays.equals(small, readBytes)); readBytes = readBlob(record.getId(), fieldName2); assertTrue(Arrays.equals(medium, readBytes)); readBytes = readBlob(record.getId(), fieldName3); assertTrue(Arrays.equals(large, readBytes)); } /** * Test case to reproduce the 'Row key is invalid' problem reported here: * https://groups.google.com/forum/#!topic/lily-discuss/XiRxOxJTv70/discussion * * @throws Exception */ @Test public void testForceInline() throws Exception { QName fieldName = new QName(namespace, "testForceInline"); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.NON_VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testForceInlineRT")); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); int size = 4096; Random rg = new Random(); byte[] bytes = new byte[size]; rg.nextBytes(bytes); // create BLOB object Blob blob = new Blob("application/pdf", 0L, "Document"); // create a stream to write the BLOB OutputStream bos = repository.getOutputStream(blob); // write the data bos.write(bytes); bos.close(); blob.setSize(5L); // create a new record ID RecordId rid = repository.getIdGenerator().newRecordId(); // create a new record Record record = repository.newRecord(rid); record.setRecordType(new QName(namespace, "testForceInlineRT")); // set the blob record.setField(fieldName, blob); // create the record record = repository.create(record); byte[] readBytes = readBlob(record.getId(), fieldName); assertTrue(Arrays.equals(bytes, readBytes)); // Test the getInputStream with giving the record instead of the recordId InputStream inputStream = repository.getInputStream(record, fieldName); try { byte[] readBytes2 = IOUtils.toByteArray(inputStream); assertTrue(Arrays.equals(bytes, readBytes2)); } finally { IOUtils.closeQuietly(inputStream); } } @Test public void testCreateTwoRecordsWithSameBlob() throws Exception { QName fieldName = new QName(namespace, "ablob2"); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testCreateTwoRecordsWithSameBlobRT")); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = Bytes.toBytes("someBytes"); Blob blob = writeBlob(bytes, "aMediaType", "testCreate"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, blob); record = repository.create(record); Record record2 = repository.newRecord(); record2.setRecordType(recordType.getName(), null); record2.setField(fieldName, blob); record2 = repository.create(record2); // For an inline record this succeeds byte[] bytesLarge = new byte[3000]; random.nextBytes(bytesLarge); Blob largeBlob = writeBlob(bytesLarge, "largeBlob", "testCreate"); Record record3 = repository.newRecord(); record3.setRecordType(recordType.getName(), null); record3.setField(fieldName, largeBlob); record3 = repository.create(record3); Record record4 = repository.newRecord(); record4.setRecordType(recordType.getName(), null); record4.setField(fieldName, largeBlob); try { record4 = repository.create(record4); fail("Using the same blob in two records should not succeed"); } catch (InvalidRecordException expected) { } } @Test public void testUpdateNonVersionedBlobHDFS() throws Exception { testUpdateNonVersionedBlob(3000, true); } @Test public void testUpdateNonVersionedBlobHBase() throws Exception { testUpdateNonVersionedBlob(150, true); } @Test public void testUpdateNonVersionedBlobInline() throws Exception { testUpdateNonVersionedBlob(50, false); } private void testUpdateNonVersionedBlob(int size, boolean expectDelete) throws Exception { QName fieldName = new QName(namespace, "testUpdateNonVersionedBlob" + size); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.NON_VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testUpdateNonVersionedBlobRT" + size)); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[size]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testUpdateNonVersionedBlob"); byte[] bytes2 = new byte[size]; random.nextBytes(bytes2); Blob blob2 = writeBlob(bytes2, "aMediaType", "testUpdateNonVersionedBlob2"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, blob); record = repository.create(record); Record record2 = repository.newRecord(record.getId()); record2.setRecordType(recordType.getName(), null); record2.setField(fieldName, blob2); record = repository.update(record2); // Reading should return blob2 byte[] readBytes = readBlob(record.getId(), record.getVersion(), fieldName); assertTrue(Arrays.equals(bytes2, readBytes)); assertBlobDelete(expectDelete, blob); } @Test public void testDeleteNonVersionedBlobHDFS() throws Exception { testDeleteNonVersionedBlob(3000, true); } @Test public void testDeleteNonVersionedBlobHBase() throws Exception { testDeleteNonVersionedBlob(150, true); } @Test public void testDeleteNonVersionedBlobInline() throws Exception { testDeleteNonVersionedBlob(50, false); } private void testDeleteNonVersionedBlob(int size, boolean expectDelete) throws Exception { QName fieldName = new QName(namespace, "testDeleteNonVersionedBlob" + size); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.NON_VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testDeleteNonVersionedBlobRT" + size)); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), false); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[size]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testDeleteNonVersionedBlob"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, blob); record = repository.create(record); Record record2 = repository.newRecord(record.getId()); record2.setRecordType(recordType.getName(), null); record2.addFieldsToDelete(Arrays.asList(fieldName)); record = repository.update(record2); assertBlobDelete(expectDelete, blob); } @Test public void testUpdateMutableBlobHDFS() throws Exception { testUpdateMutableBlob(3000, true); } @Test public void testUpdateMutableBlobHBase() throws Exception { testUpdateMutableBlob(150, true); } @Test public void testUpdateMutableBlobInline() throws Exception { testUpdateMutableBlob(50, false); } private void testUpdateMutableBlob(int size, boolean expectDelete) throws Exception { QName fieldName = new QName(namespace, "testUpdateMutableBlob" + size); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.VERSIONED_MUTABLE); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testUpdateMutableBlobRT" + size)); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[size]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testUpdateMutableBlob"); byte[] bytes2 = new byte[size]; random.nextBytes(bytes2); Blob blob2 = writeBlob(bytes2, "aMediaType", "testUpdateMutableBlob2"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, blob); record = repository.create(record); Record record2 = repository.newRecord(record.getId()); record2.setRecordType(recordType.getName(), null); record2.setField(fieldName, blob2); record2.setVersion(record.getVersion()); record = repository.update(record2, true, false); // Blob2 should still exist byte[] readBytes = readBlob(record.getId(), record.getVersion(), fieldName); assertTrue(Arrays.equals(bytes2, readBytes)); assertBlobDelete(expectDelete, blob); } @Test public void testDeleteMutableBlobHDFS() throws Exception { testDeleteMutableBlob(3000, true); } @Test public void testDeleteMutableBlobHBase() throws Exception { testDeleteMutableBlob(150, true); } @Test public void testDeleteMutableBlobInline() throws Exception { testDeleteMutableBlob(50, false); } private void testDeleteMutableBlob(int size, boolean expectDelete) throws Exception { QName fieldName = new QName(namespace, "testDeleteMutableBlob" + size); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.VERSIONED_MUTABLE); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testDeleteMutableBlobRT" + size)); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), false); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[size]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testDeleteMutableBlob"); byte[] bytes2 = new byte[size]; random.nextBytes(bytes2); Blob blob2 = writeBlob(bytes2, "aMediaType", "testDeleteMutableBlob2"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, blob); record = repository.create(record); Record record2 = repository.newRecord(record.getId()); record2.setRecordType(recordType.getName(), null); record2.setField(fieldName, blob2); repository.update(record2, false, false); // Blob1 should still exist byte[] readBytes = readBlob(record.getId(), record.getVersion(), fieldName); assertTrue(Arrays.equals(bytes, readBytes)); // Blob2 should still exist readBytes = readBlob(record2.getId(), record2.getVersion(), fieldName); assertTrue(Arrays.equals(bytes2, readBytes)); Record record3 = repository.newRecord(record.getId()); record3.setRecordType(recordType.getName(), null); record3.addFieldsToDelete(Arrays.asList(fieldName)); record3.setVersion(record.getVersion()); repository.update(record3, true, false); // Blob2 should still exist readBytes = readBlob(record2.getId(), record2.getVersion(), fieldName); assertTrue(Arrays.equals(bytes2, readBytes)); assertBlobDelete(expectDelete, blob); } @Test public void testUpdateMutableMultivalueBlobHDFS() throws Exception { testUpdateMutableMultivalueBlob(3000, true); } @Test public void testUpdateMutableMultivalueBlobHBase() throws Exception { testUpdateMutableMultivalueBlob(150, true); } @Test public void testUpdateMutableMultivalueBlobInline() throws Exception { testUpdateMutableMultivalueBlob(50, false); } private void testUpdateMutableMultivalueBlob(int size, boolean expectDelete) throws Exception { QName fieldName = new QName(namespace, "testUpdateMutableMultivalueBlob" + size); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("LIST<BLOB>"), fieldName, Scope.VERSIONED_MUTABLE); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testUpdateMutableMultivalueBlobRT" + size)); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[size]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testUpdateMutableMultivalueBlob"); byte[] bytes2 = new byte[size]; random.nextBytes(bytes2); Blob blob2 = writeBlob(bytes2, "aMediaType", "testUpdateMutableMultivalueBlob2"); byte[] bytes3 = new byte[size]; random.nextBytes(bytes3); Blob blob3 = writeBlob(bytes3, "aMediaType", "testUpdateMutableMultivalueBlob3"); byte[] bytes4 = new byte[size]; random.nextBytes(bytes4); Blob blob4 = writeBlob(bytes4, "aMediaType", "testUpdateMutableMultivalueBlob4"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, Arrays.asList(blob, blob2)); record = repository.create(record); Record record2 = repository.newRecord(record.getId()); record2.setRecordType(recordType.getName(), null); record2.setField(fieldName, Arrays.asList(blob2, blob3)); record2 = repository.update(record2, false, false); // Mutable update of first version Record record3 = repository.newRecord(record.getId()); record3.setVersion(record.getVersion()); record3.setRecordType(recordType.getName(), null); record3.setField(fieldName, Arrays.asList(blob4)); record3 = repository.update(record3, true, false); //Blob2 byte[] readBytes = readBlob(record2.getId(), record2.getVersion(), fieldName, 0); assertTrue(Arrays.equals(bytes2, readBytes)); //Blob3 readBytes = readBlob(record2.getId(), record2.getVersion(), fieldName, 1); assertTrue(Arrays.equals(bytes3, readBytes)); //Blob4 in version 1 readBytes = readBlob(record.getId(), record.getVersion(), fieldName, 0); assertTrue(Arrays.equals(bytes4, readBytes)); assertBlobDelete(expectDelete, blob); try { readBlob(record.getId(), record.getVersion(), fieldName); fail("BlobNotFoundException expected since index should not be null"); } catch (BlobNotFoundException expected) { } try { readBlob(record.getId(), record.getVersion(), fieldName, 1); fail("BlobNotFoundException expected since index is out of bounds"); } catch (BlobNotFoundException expected) { } } @Test public void testUpdateMutableHierarchyBlobHDFS() throws Exception { testUpdateMutableHierarchyBlob(3000, true); } @Test public void testUpdateMutableHierarchyBlobHBase() throws Exception { testUpdateMutableHierarchyBlob(150, true); } @Test public void testUpdateMutableHierarchyBlobInline() throws Exception { testUpdateMutableHierarchyBlob(50, false); } private void testUpdateMutableHierarchyBlob(int size, boolean expectDelete) throws Exception { QName fieldName = new QName(namespace, "testUpdateMutableHierarchyBlob" + size); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("PATH<BLOB>"), fieldName, Scope.VERSIONED_MUTABLE); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testUpdateMutableHierarchyBlobRT" + size)); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[size]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testUpdateMutableHierarchyBlob"); byte[] bytes2 = new byte[size]; random.nextBytes(bytes2); Blob blob2 = writeBlob(bytes2, "aMediaType", "testUpdateMutableHierarchyBlob2"); byte[] bytes3 = new byte[size]; random.nextBytes(bytes3); Blob blob3 = writeBlob(bytes3, "aMediaType", "testUpdateMutableHierarchyBlob3"); byte[] bytes4 = new byte[size]; random.nextBytes(bytes4); Blob blob4 = writeBlob(bytes4, "aMediaType", "testUpdateMutableHierarchyBlob4"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, new HierarchyPath(blob, blob2)); record = repository.create(record); Record record2 = repository.newRecord(record.getId()); record2.setRecordType(recordType.getName(), null); record2.setField(fieldName, new HierarchyPath(blob2, blob3, blob4)); record2 = repository.update(record2, false, false); // Mutable update of first version Record record3 = repository.newRecord(record.getId()); record3.setVersion(record.getVersion()); record3.setRecordType(recordType.getName(), null); record3.setField(fieldName, new HierarchyPath(blob4, blob4)); record3 = repository.update(record3, true, false); // Blob2 byte[] readBytes = readBlob(record2.getId(), record2.getVersion(), fieldName, 0); assertTrue(Arrays.equals(bytes2, readBytes)); // Blob3 readBytes = readBlob(record2.getId(), record2.getVersion(), fieldName, 1); assertTrue(Arrays.equals(bytes3, readBytes)); // Blob4 in version1 readBytes = readBlob(record.getId(), record.getVersion(), fieldName, 1); assertTrue(Arrays.equals(bytes4, readBytes)); assertBlobDelete(expectDelete, blob); try { readBlob(record.getId(), record.getVersion(), fieldName); fail("BlobNotFoundException expected since index should not be null"); } catch (BlobNotFoundException expected) { } try { readBlob(record.getId(), record.getVersion(), fieldName, 2); fail("BlobNotFoundException expected since index is out of bounds"); } catch (BlobNotFoundException expected) { } } @Test public void testUpdateMutableMultivalueHierarchyBlobHDFS() throws Exception { testUpdateMutableMultivalueHierarchyBlob(3000, true); } @Test public void testUpdateMutableMultivalueHierarchyBlobHBase() throws Exception { testUpdateMutableMultivalueHierarchyBlob(150, true); } @Test public void testUpdateMutableMultivalueHierarchyBlobInline() throws Exception { testUpdateMutableMultivalueHierarchyBlob(50, false); } private void testUpdateMutableMultivalueHierarchyBlob(int size, boolean expectDelete) throws Exception { QName fieldName = new QName(namespace, "testUpdateMutableMultivalueHierarchyBlob" + size); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("LIST<PATH<BLOB>>"), fieldName, Scope.VERSIONED_MUTABLE); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testUpdateMutableMultivalueHierarchyBlobRT" + size)); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[size]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testUpdateMutableMultivalueHierarchyBlob"); byte[] bytes2 = new byte[size]; random.nextBytes(bytes); Blob blob2 = writeBlob(bytes2, "aMediaType", "testUpdateMutableMultivalueHierarchyBlob2"); byte[] bytes3 = new byte[size]; random.nextBytes(bytes3); Blob blob3 = writeBlob(bytes3, "aMediaType", "testUpdateMutableMultivalueHierarchyBlob3"); byte[] bytes4 = new byte[size]; random.nextBytes(bytes4); Blob blob4 = writeBlob(bytes4, "aMediaType", "testUpdateMutableMultivalueHierarchyBlob4"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, Arrays.asList(new HierarchyPath(blob, blob2), new HierarchyPath(blob3))); record = repository.create(record); Record record2 = repository.newRecord(record.getId()); record2.setRecordType(recordType.getName(), null); record2.setField(fieldName, Arrays.asList(new HierarchyPath(blob2), new HierarchyPath(blob3, blob4))); record2 = repository.update(record2, false, false); // Mutable update of first version Record record3 = repository.newRecord(record.getId()); record3.setVersion(record.getVersion()); record3.setRecordType(recordType.getName(), null); record3.setField(fieldName, Arrays.asList(new HierarchyPath(blob3, blob4), new HierarchyPath(blob4))); record3 = repository.update(record3, true, false); // Blob2 byte[] readBytes = readBlob(record2.getId(), record2.getVersion(), fieldName, 0, 0); assertTrue(Arrays.equals(bytes2, readBytes)); // Blob3 readBytes = readBlob(record2.getId(), record2.getVersion(), fieldName, 1, 0); assertTrue(Arrays.equals(bytes3, readBytes)); // Blob4 in version1 readBytes = readBlob(record.getId(), record.getVersion(), fieldName, 0, 1); assertTrue(Arrays.equals(bytes4, readBytes)); assertBlobDelete(expectDelete, blob); try { readBlob(record.getId(), record.getVersion(), fieldName); fail("BlobNotFoundException expected since index should not be null"); } catch (BlobNotFoundException expected) { } try { readBlob(record.getId(), record.getVersion(), fieldName, 0); fail("BlobNotFoundException expected since index should not be null"); } catch (BlobNotFoundException expected) { } try { readBlob(record.getId(), record.getVersion(), fieldName, 2, 0); fail("BlobNotFoundException expected since index is out of bounds"); } catch (BlobNotFoundException expected) { } try { readBlob(record.getId(), record.getVersion(), fieldName, 1, 1); fail("BlobNotFoundException expected since index is out of bounds"); } catch (BlobNotFoundException expected) { } } @Test public void testDelete() throws Exception { QName fieldName = new QName(namespace, "testDelete"); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.NON_VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testDeleteRT")); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[3000]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testCreate"); Record record = repository.newRecord(); record.setRecordType(recordType.getName()); record.setField(fieldName, blob); record = repository.create(record); repository.delete(record.getId()); assertBlobDelete(true, blob); } @Test public void testDeleteMultivalueHierarchyBlobSmall() throws Exception { testDeleteMultivalueHierarchyBlob(50, false); // An inputstream for the inline blob is created on the blobKey directly } @Test public void testDeleteMultivalueHierarchyBlobMedium() throws Exception { testDeleteMultivalueHierarchyBlob(150, true); } @Test public void testDeleteMultivalueHierarchyBlobLarge() throws Exception { testDeleteMultivalueHierarchyBlob(3000, true); } private void testDeleteMultivalueHierarchyBlob(int size, boolean expectDelete) throws Exception { QName fieldName = new QName(namespace, "testDeleteMultivalueHierarchyBlob" + size); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("LIST<PATH<BLOB>>"), fieldName, Scope.VERSIONED_MUTABLE); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testDeleteMultivalueHierarchyBlobRT" + size)); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); byte[] bytes = new byte[size]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testUpdateMutableMultivalueHierarchyBlob"); byte[] bytes2 = new byte[size]; random.nextBytes(bytes); Blob blob2 = writeBlob(bytes2, "aMediaType", "testUpdateMutableMultivalueHierarchyBlob2"); byte[] bytes3 = new byte[size]; random.nextBytes(bytes3); Blob blob3 = writeBlob(bytes3, "aMediaType", "testUpdateMutableMultivalueHierarchyBlob3"); byte[] bytes4 = new byte[size]; random.nextBytes(bytes4); Blob blob4 = writeBlob(bytes4, "aMediaType", "testUpdateMutableMultivalueHierarchyBlob4"); Record record = repository.newRecord(); record.setRecordType(recordType.getName(), null); record.setField(fieldName, Arrays.asList(new HierarchyPath(blob, blob2), new HierarchyPath(blob3))); record = repository.create(record); Record record2 = repository.newRecord(record.getId()); record2.setRecordType(recordType.getName(), null); record2.setField(fieldName, Arrays.asList(new HierarchyPath(blob2), new HierarchyPath(blob3, blob4))); record2 = repository.update(record2, false, false); repository.delete(record.getId()); assertBlobDelete(expectDelete, blob); assertBlobDelete(expectDelete, blob2); assertBlobDelete(expectDelete, blob3); assertBlobDelete(expectDelete, blob4); } @Test public void testBlobIncubatorMonitorUnusedBlob() throws Exception { QName fieldName = new QName(namespace, "testBlobIncubatorMonitorUnusedBlob"); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.NON_VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testBlobIncubatorMonitorUnusedBlobRT")); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); // Incubate blob but never use it byte[] bytes = new byte[3000]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testCreate"); // Give time for the blob to expire Thread.sleep(60); BlobIncubatorMonitor monitor = new BlobIncubatorMonitor(repoSetup.getZk(), repoSetup.getHbaseTableFactory(), repoSetup.getTableManager(), blobManager, typeManager, 50, 0, 0); monitor.runMonitorOnce(); assertBlobDelete(true, blob); } @Test public void testBlobIncubatorMonitorFailureAfterReservation() throws Exception { QName fieldName = new QName(namespace, "testBlobIncubatorMonitorFailureAfterReservation"); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.NON_VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testBlobIncubatorMonitorFailureAfterReservationRT")); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); // This is the failure scenario where creating the record fails after reserving the blob byte[] bytes = new byte[3000]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testCreate"); IdGeneratorImpl idGeneratorImpl = new IdGeneratorImpl(); RecordId recordId = idGeneratorImpl.newRecordId(); BlobReference blobReference = new BlobReference(blob, recordId, fieldType); Set<BlobReference> blobs = new HashSet<BlobReference>(); blobs.add(blobReference); blobManager.reserveBlobs(blobs); // Give time for the blob to expire Thread.sleep(60); BlobIncubatorMonitor monitor = new BlobIncubatorMonitor(repoSetup.getZk(), repoSetup.getHbaseTableFactory(), repoSetup.getTableManager(), blobManager, typeManager, 50, 0, 0); monitor.runMonitorOnce(); assertBlobDelete(true, blob); } @Test public void testBlobIncubatorMonitorFailureBeforeRemovingReservation() throws Exception { QName fieldName = new QName(namespace, "testBlobIncubatorMonitorFailureBeforeRemovingReservation"); FieldType fieldType = typeManager.newFieldType(typeManager.getValueType("BLOB"), fieldName, Scope.NON_VERSIONED); fieldType = typeManager.createFieldType(fieldType); RecordType recordType = typeManager.newRecordType(new QName(namespace, "testBlobIncubatorMonitorFailureBeforeRemovingReservation")); FieldTypeEntry fieldTypeEntry = typeManager.newFieldTypeEntry(fieldType.getId(), true); recordType.addFieldTypeEntry(fieldTypeEntry); recordType = typeManager.createRecordType(recordType); // This is the failure scenario where creating the record fails after reserving the blob byte[] bytes = new byte[3000]; random.nextBytes(bytes); Blob blob = writeBlob(bytes, "aMediaType", "testCreate"); IdGeneratorImpl idGeneratorImpl = new IdGeneratorImpl(); RecordId recordId = idGeneratorImpl.newRecordId(); BlobReference blobReference = new BlobReference(blob, recordId, fieldType); Set<BlobReference> blobs = new HashSet<BlobReference>(); blobs.add(blobReference); repository.newRecord(); Record record = repository.newRecord(); record.setRecordType(recordType.getName()); record.setField(fieldName, blob); record = repository.create(record); // Faking failure HTableInterface blobIncubatorTable = LilyHBaseSchema.getBlobIncubatorTable(repoSetup.getHbaseTableFactory(), true); Put put = new Put(blob.getValue()); put.add(LilyHBaseSchema.BlobIncubatorCf.REF.bytes, LilyHBaseSchema.BlobIncubatorColumn.RECORD.bytes, record.getId().toBytes()); put.add(LilyHBaseSchema.BlobIncubatorCf.REF.bytes, LilyHBaseSchema.BlobIncubatorColumn.FIELD.bytes, fieldType.getId().getBytes()); blobIncubatorTable.put(put); // Give time for the blob to expire Thread.sleep(60); BlobIncubatorMonitor monitor = new BlobIncubatorMonitor(repoSetup.getZk(), repoSetup.getHbaseTableFactory(), repoSetup.getTableManager(), blobManager, typeManager, 50, 0, 0); monitor.runMonitorOnce(); assertBlobDelete(false, blob); Get get = new Get(blob.getValue()); Result result = blobIncubatorTable.get(get); assertTrue(result == null || result.isEmpty()); } private void assertBlobDelete(boolean expectDelete, Blob blob) throws BlobNotFoundException, BlobException { if (expectDelete) { try { testBlobStoreAccessRegistry.getBlobAccess(blob).getInputStream(); fail("The blob " + blob + " should have been deleted."); } catch (BlobException expected) { } } else { testBlobStoreAccessRegistry.getBlobAccess(blob).getInputStream(); } } @Test public void testBadEncoding() throws Exception { Blob blob = new Blob("aMediaType", (long) 10, "aName"); blob.setValue(new byte[0]); try { testBlobStoreAccessRegistry.getBlobAccess(blob).getInputStream(); fail(); } catch (BlobException expected) { } } @Test public void testInvalidReadRequests() throws Exception { ValueType stringType = typeManager.getValueType("STRING"); ValueType blobType = typeManager.getValueType("BLOB"); FieldType nonBlobField = typeManager.newFieldType(stringType, new QName(namespace, "NonBlobField"), Scope.VERSIONED); nonBlobField = typeManager.createFieldType(nonBlobField); FieldType absentField = typeManager .newFieldType(blobType, new QName(namespace, "AbsentField"), Scope.VERSIONED); absentField = typeManager.createFieldType(absentField); RecordType rt = typeManager.newRecordType(new QName(namespace, "NoBlobsRT")); rt.addFieldTypeEntry(nonBlobField.getId(), false); rt = typeManager.createRecordType(rt); Record record = repository.newRecord(); record.setRecordType(rt.getName()); record.setField(nonBlobField.getName(), "This is not a blob"); record = repository.create(record); try { repository.getInputStream(record.getId(), record.getVersion(), nonBlobField.getName(), null, null); fail("Expected exception"); } catch (BlobException e) { // ok } try { repository.getInputStream(record.getId(), record.getVersion(), absentField.getName(), null, null); fail("Expected exception"); } catch (FieldNotFoundException e) { // ok } try { repository.getInputStream(record.getId(), record.getVersion(), new QName(namespace, "nonExistingFieldType"), null, null); fail("Expected exception"); } catch (FieldTypeNotFoundException e) { // ok } try { repository.getInputStream(record.getId(), record.getVersion(), null, null, null); fail("Expected exception"); } catch (IllegalArgumentException e) { // ok } try { repository.getInputStream(repoSetup.getIdGenerator().fromString("USER.nonexistingrecord"), null, absentField.getName()); fail("Expected exception"); } catch (RecordNotFoundException e) { // ok } } private Blob writeBlob(byte[] bytes, String mediaType, String name) throws RepositoryException, InterruptedException, IOException { return writeBlob(bytes, mediaType, name, bytes.length); } /** * @param length The blob site to be used when constructing the blob * (this can be used to control how the blob will be stored) */ private Blob writeBlob(byte[] bytes, String mediaType, String name, long length) throws RepositoryException, InterruptedException, IOException { Blob blob = new Blob(mediaType, length, name); OutputStream outputStream = repository.getOutputStream(blob); outputStream.write(bytes); outputStream.close(); return blob; } private byte[] readBlob(RecordId recordId, QName fieldName) throws RepositoryException, InterruptedException, IOException { return readBlob(recordId, null, fieldName); } private byte[] readBlob(RecordId recordId, Long version, QName fieldName, int...indexes) throws RepositoryException, InterruptedException, IOException { InputStream inputStream = repository.getInputStream(recordId, version, fieldName, indexes); try { return IOUtils.toByteArray(inputStream); } finally { IOUtils.closeQuietly(inputStream); } } }