blob: 164da38f8354bb76437d72f03acfbd83f85e616a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.jackrabbit.oak.segment;
import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import com.google.common.base.Strings;
import org.apache.jackrabbit.oak.segment.SegmentBlob;
import org.apache.jackrabbit.oak.segment.SegmentTestConstants;
import org.apache.jackrabbit.oak.segment.file.FileStore;
import org.apache.jackrabbit.oak.spi.blob.BlobStore;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class ExternalBlobReferenceTest {
@Rule
public final TemporaryFolder segmentFolder = new TemporaryFolder(new File("target"));
private FileStore fileStore;
private BlobStore blobStore;
@Before
public void createFileStore() throws Exception {
blobStore = mock(BlobStore.class);
fileStore = fileStoreBuilder(segmentFolder.getRoot()).withBlobStore(blobStore).build();
}
@After
public void destroyFileStore() {
fileStore.close();
}
/**
* The {@code SegmentWriter} should be able to write blob IDs whose length
* is between 0 and {@code Segment.BLOB_ID_SMALL_LIMIT - 1} bytes. It should
* be possible to correctly read the blob ID back and pass it to the {@code
* BlobStore} to obtain information about the blob.
* <p/>
* This code path executes only if the written stream is {@code
* Segment.MEDIUM_LIMIT} bytes long (or more). If the length of the stream
* is smaller, the binary value is inlined in the segment and the {@code
* BlobStore} is never called.
* <p/>
* See OAK-3105.
*/
@Test
public void testShortBlobId() throws Exception {
testBlobIdWithLength(SegmentTestConstants.BLOB_ID_SMALL_LIMIT - 1);
}
/**
* If the {@code BlobStore} returns a blob ID whose length is {@code
* Segment.BLOB_ID_SMALL_LIMIT} bytes long (or more), writing the stream
* should succeed. In this case, the blob ID is considered a long blob ID
* and an alternate encoding is used. It should be possible to correctly
* read the blob ID back and pass it to the {@code BlobStore} to obtain
* information about the blob.
* <p/>
* This code path executes only if the written stream is {@code
* Segment.MEDIUM_LIMIT} bytes long (or more). If the length of the stream
* is smaller, the binary value is inlined in the segment and the {@code
* BlobStore} is never called.
* <p/>
* See OAK-3105 and OAK-3107.
*/
@Test
public void testLongBlobId() throws Exception {
testBlobIdWithLength(SegmentTestConstants.BLOB_ID_SMALL_LIMIT);
}
private void testBlobIdWithLength(int blobIdLength) throws Exception {
String blobId = Strings.repeat("x", blobIdLength);
long blobLength = SegmentTestConstants.MEDIUM_LIMIT;
doReturn(blobId).when(blobStore).writeBlob(any(InputStream.class));
doReturn(blobLength).when(blobStore).getBlobLength(blobId);
SegmentBlob blob = new SegmentBlob(blobStore, fileStore.getWriter().writeStream(newRandomInputStream(blobLength)));
assertEquals(blobLength, blob.length());
}
private static InputStream newRandomInputStream(long size) {
return new LimitInputStream(new ConstantInputStream(0), size);
}
private static class ConstantInputStream extends InputStream {
private final int value;
public ConstantInputStream(int value) {
this.value = value;
}
@Override
public int read() {
return value;
}
}
private static class LimitInputStream extends InputStream {
private final InputStream stream;
private final long limit;
private long read = 0;
public LimitInputStream(InputStream stream, long limit) {
this.stream = stream;
this.limit = limit;
}
@Override
public int read() throws IOException {
if (read >= limit) {
return -1;
}
read = read + 1;
return stream.read();
}
}
}