/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.jcr.value.binary;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.junit.SkipOnOS;
import org.modeshape.common.util.FileUtil;
import org.modeshape.common.util.IoUtil;
import org.modeshape.jcr.value.BinaryKey;
import org.modeshape.jcr.value.BinaryValue;
/**
* Unit test for {@link CompositeBinaryStore}
*/
@ThreadSafe
public class CompositeBinaryStoreTest extends AbstractBinaryStoreTest {
private static final int MIN_BINARY_SIZE = 20;
private static final Random RANDOM = new Random();
static CompositeBinaryStore store;
static BinaryStore defaultStore;
static BinaryStore alternativeStore;
static BinaryStore anotherAlternativeStore;
static String defaultHint;
static String alternativeHint;
static File directory;
static File altDirectory;
static File altDirectory2;
@BeforeClass
public static void beforeClass() {
Map<String, BinaryStore> stores = new LinkedHashMap<String, BinaryStore>();
directory = new File("target/cfsbs/");
FileUtil.delete(directory);
directory.mkdirs();
defaultStore = new FileSystemBinaryStore(directory);
stores.put("default", defaultStore);
defaultHint = "default";
altDirectory = new File("target/afsbs/");
FileUtil.delete(altDirectory);
altDirectory.mkdirs();
alternativeStore = new FileSystemBinaryStore(altDirectory);
altDirectory2 = new File("target/afsbs2/");
FileUtil.delete(altDirectory2);
altDirectory2.mkdirs();
anotherAlternativeStore = new FileSystemBinaryStore(altDirectory2);
stores.put("alternative", alternativeStore);
alternativeHint = "alternative";
store = new CompositeBinaryStore(stores);
store.setMinimumBinarySizeInBytes(MIN_BINARY_SIZE);
store.setMimeTypeDetector(DEFAULT_DETECTOR);
store.start();
}
@AfterClass
public static void afterClass() {
store.shutdown();
FileUtil.delete(directory);
FileUtil.delete(altDirectory);
FileUtil.delete(altDirectory2);
}
@Override
@Test( expected = BinaryStoreException.class )
public void shouldStoreZeroLengthBinary() throws BinaryStoreException, IOException {
// the file system binary store will not store a 0 byte size content
super.shouldStoreZeroLengthBinary();
}
@Test
public void shouldAggregateBinaryKeysFromAllStores() throws BinaryStoreException {
byte[] content = randomContent();
defaultStore.storeValue(new ByteArrayInputStream(content), false);
byte[] content1 = randomContent();
alternativeStore.storeValue(new ByteArrayInputStream(content1), false);
final Iterable<BinaryKey> allBinaryKeys = store.getAllBinaryKeys();
final List<BinaryKey> expected = new ArrayList<BinaryKey>();
for (BinaryKey binaryKey : defaultStore.getAllBinaryKeys()) {
expected.add(binaryKey);
}
for (BinaryKey binaryKey : alternativeStore.getAllBinaryKeys()) {
expected.add(binaryKey);
}
assertTrue(expected.size() > 1);
assertThat(allBinaryKeys, hasItems(expected.toArray(new BinaryKey[expected.size()])));
}
@Test
public void shouldPersistContentIntoTheDefaultStore() throws BinaryStoreException, IOException {
byte[] content = randomContent();
BinaryValue v = store.storeValue(new ByteArrayInputStream(content), false);
InputStream is = store.getInputStream(v.getKey());
byte[] storeContent = IoUtil.readBytes(is);
InputStream is1 = defaultStore.getInputStream(v.getKey());
byte[] defaultStoreContent = IoUtil.readBytes(is1);
assertArrayEquals(content, storeContent);
assertArrayEquals(content, defaultStoreContent);
}
@Test
public void shouldLookInAllBinaryStoresForAKey() throws BinaryStoreException, IOException {
byte[] content = randomContent();
BinaryValue v = alternativeStore.storeValue(new ByteArrayInputStream(content), false);
InputStream is = store.getInputStream(v.getKey());
byte[] storedContent = IoUtil.readBytes(is);
assertArrayEquals(content, storedContent);
assertThat(store.hasBinary(v.getKey()), is(true));
}
@Test
public void shouldStoreThingsInTheDefaultStoreWhenTheStrategyFails() throws BinaryStoreException {
BinaryValue v = store.storeValue(new ByteArrayInputStream(randomContent()), "this-hint-doesnt-reference-a-store", false);
assertTrue(defaultStore.hasBinary(v.getKey()));
}
@Test
public void shouldKnowWhatBinaryStoreAKeyIsIn() throws BinaryStoreException {
BinaryValue v = defaultStore.storeValue(new ByteArrayInputStream(randomContent()), false);
BinaryValue v1 = alternativeStore.storeValue(new ByteArrayInputStream(randomContent()), false);
assertEquals(defaultStore, store.findBinaryStoreContainingKey(v.getKey()));
assertEquals(alternativeStore, store.findBinaryStoreContainingKey(v1.getKey()));
assertNull(store.findBinaryStoreContainingKey(new BinaryKey("this-is-not-a-key")));
}
@Test
public void shouldMoveBinaryKeysBetweenStores() throws BinaryStoreException {
BinaryValue v = defaultStore.storeValue(new ByteArrayInputStream(randomContent()), false);
assertFalse(alternativeStore.hasBinary(v.getKey()));
store.moveValue(v.getKey(), defaultHint, alternativeHint);
assertTrue(alternativeStore.hasBinary(v.getKey()));
}
@Test( expected = BinaryStoreException.class )
public void shouldRaiseAnExceptionWhenMovingAKeyThatDoesntExist() throws BinaryStoreException {
store.moveValue(new BinaryKey("this-doesnt-exist"), alternativeHint);
}
@Test( expected = BinaryStoreException.class )
public void shouldRaiseAnExceptionWhenMovingAKeyThatDoesntExistInTheSourceStore() throws BinaryStoreException {
BinaryValue v = defaultStore.storeValue(new ByteArrayInputStream(randomContent()), false);
store.moveValue(v.getKey(), alternativeHint, defaultHint);
}
@Test
public void shouldStoreThingsInTheFirstStoreWhenTheStrategyFailsAndNoDefaultStoreProvided() throws BinaryStoreException {
BinaryStore localStoreWithoutADefaultStore;
Map<String, BinaryStore> stores = new LinkedHashMap<String, BinaryStore>();
stores.put("alt", alternativeStore);
stores.put("also-alt", anotherAlternativeStore);
localStoreWithoutADefaultStore = new CompositeBinaryStore(stores);
localStoreWithoutADefaultStore.setMinimumBinarySizeInBytes(MIN_BINARY_SIZE);
localStoreWithoutADefaultStore.start();
BinaryValue v = localStoreWithoutADefaultStore.storeValue(new ByteArrayInputStream(randomContent()),
"this-hint-doesnt-reference-a-store", false);
assertTrue(alternativeStore.hasBinary(v.getKey()));
}
@Override
@SkipOnOS(value = SkipOnOS.WINDOWS, description = "Sometimes file locks prevent the cleanup thread from removing values")
@Test
public void shouldCleanupUnunsedValues() throws Exception {
super.shouldCleanupUnunsedValues();
}
private byte[] randomContent() {
byte[] content = new byte[MIN_BINARY_SIZE + 1];
RANDOM.nextBytes(content);
return content;
}
@Override
protected BinaryStore getBinaryStore() {
return store;
}
}