/*
* 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.core.data;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.core.data.db.DbDataStore;
import org.apache.jackrabbit.test.JUnitTest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
/**
* Tests for the data store.
* Both file data store and database data store are tested,
* with single threaded and multi-threaded tests.
*/
public class DataStoreTest extends JUnitTest {
private static final boolean TEST_DATABASE = false;
private File testDir = new File(System.getProperty("java.io.tmpdir"), "dataStore");
public void setUp() {
testDir.mkdirs();
}
public void tearDown() throws IOException {
FileUtils.deleteDirectory(testDir);
}
public void test() throws Exception {
try {
if (TEST_DATABASE) {
DbDataStore dds = new DbDataStore();
String dbPath = (testDir + "/db").replace('\\', '/');
// 3 sec
String url = "jdbc:h2:mem:" + dbPath + "/db";
// 4 sec
// String url = "jdbc:h2:" + dbPath + "/db";
// 26 sec
// String url = "jdbc:derby:" + dbPath + "/db";
new File(dbPath).mkdirs();
dds.setUrl(url + ";create=true");
dds.setUser("sa");
dds.setPassword("sa");
dds.setCopyWhenReading(false);
dds.init(dbPath);
// doTest(dds, 0);
doTestMultiThreaded(dds, 4);
dds.close();
shutdownDatabase(url);
FileUtils.deleteDirectory(testDir);
new File(dbPath).mkdirs();
dds = new DbDataStore();
dds.setUrl(url + ";create=true");
dds.setUser("sa");
dds.setPassword("sa");
dds.setCopyWhenReading(true);
dds.init(dbPath);
// doTest(dds, 0);
doTestMultiThreaded(dds, 4);
dds.close();
shutdownDatabase(url);
}
FileDataStore fds = new FileDataStore();
fds.init(testDir + "/file");
doTest(fds, 0);
// doTestMultiThreaded(fds, 4);
fds.close();
} catch (Throwable t) {
t.printStackTrace();
throw new Error(t);
}
}
public static void main(String... args) throws NoSuchAlgorithmException {
// create and print a "directory-collision", that is, two byte arrays
// where the hash starts with the same bytes
// those values can be used for testDeleteRecordWithParentCollision
HashMap<Long, Long> map = new HashMap<Long, Long>();
MessageDigest digest = MessageDigest.getInstance("SHA-256");
ByteBuffer input = ByteBuffer.allocate(8);
byte[] array = input.array();
for(long x = 0;; x++) {
input.putLong(x).flip();
long h = ByteBuffer.wrap(digest.digest(array)).getLong();
Long old = map.put(h & 0xffffffffff000000L, x);
if (old != null) {
System.out.println(Long.toHexString(old) + " " + Long.toHexString(x));
break;
}
}
}
public void testDeleteRecordWithParentCollision() throws Exception {
FileDataStore fds = new FileDataStore();
fds.init(testDir + "/fileDeleteCollision");
ByteArrayInputStream c1 = new ByteArrayInputStream(ByteBuffer
.allocate(8).putLong(0x181c7).array());
ByteArrayInputStream c2 = new ByteArrayInputStream(ByteBuffer
.allocate(8).putLong(0x11fd78).array());
DataRecord d1 = fds.addRecord(c1);
DataRecord d2 = fds.addRecord(c2);
fds.deleteRecord(d1.getIdentifier());
DataRecord testRecord = fds.getRecordIfStored(d2.getIdentifier());
assertNotNull(testRecord);
assertEquals(d2.getIdentifier(), testRecord.getIdentifier());
// Check the presence of the parent directory (relies on internal details of the FileDataStore)
File parentDirD1 = new File(
fds.getPath() + System.getProperty("file.separator") + d1.getIdentifier().toString().substring(0, 2));
assertTrue(parentDirD1.exists());
}
public void testDeleteRecordWithoutParentCollision() throws Exception {
FileDataStore fds = new FileDataStore();
fds.init(testDir + "/fileDelete");
String c1 = "idhfigjhehgkdfgk";
String c2 = "02c60cb75083ceef";
DataRecord d1 = fds.addRecord(IOUtils.toInputStream(c1));
DataRecord d2 = fds.addRecord(IOUtils.toInputStream(c2));
fds.deleteRecord(d1.getIdentifier());
DataRecord testRecord = fds.getRecordIfStored(d2.getIdentifier());
assertNotNull(testRecord);
assertEquals(d2.getIdentifier(), testRecord.getIdentifier());
// Check the absence of the parent directory (relies on internal details of the FileDataStore)
File parentDirD1 = new File(
fds.getPath() + System.getProperty("file.separator") + d1.getIdentifier().toString().substring(0, 2));
assertFalse(parentDirD1.exists());
}
public void testReference() throws Exception {
byte[] data = new byte[12345];
new Random(12345).nextBytes(data);
String reference;
FileDataStore store = new FileDataStore();
store.init(testDir + "/reference");
try {
DataRecord record = store.addRecord(new ByteArrayInputStream(data));
reference = record.getReference();
assertReference(data, reference, store);
} finally {
store.close();
}
store = new FileDataStore();
store.init(testDir + "/reference");
try {
assertReference(data, reference, store);
} finally {
store.close();
}
}
private void assertReference(
byte[] expected, String reference, DataStore store)
throws Exception {
DataRecord record = store.getRecordFromReference(reference);
assertNotNull(record);
assertEquals(expected.length, record.getLength());
InputStream stream = record.getStream();
try {
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i] & 0xff, stream.read());
}
assertEquals(-1, stream.read());
} finally {
stream.close();
}
}
private void shutdownDatabase(String url) {
if (url.startsWith("jdbc:derby:") || url.startsWith("jdbc:hsqldb:")) {
try {
DriverManager.getConnection(url + ";shutdown=true");
} catch (SQLException e) {
// ignore
}
}
}
private void doTestMultiThreaded(final DataStore ds, int threadCount) throws Exception {
final Exception[] exception = new Exception[1];
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
final int x = i;
Thread t = new Thread() {
public void run() {
try {
doTest(ds, x);
} catch (Exception e) {
exception[0] = e;
}
}
};
threads[i] = t;
t.start();
}
for (int i = 0; i < threadCount; i++) {
threads[i].join();
}
if (exception[0] != null) {
throw exception[0];
}
}
void doTest(DataStore ds, int offset) throws Exception {
ArrayList<DataRecord> list = new ArrayList<DataRecord>();
HashMap<DataRecord, Integer> map = new HashMap<DataRecord, Integer>();
for (int i = 0; i < 100; i++) {
int size = 100 + i * 10;
RandomInputStream in = new RandomInputStream(size + offset, size);
DataRecord rec = ds.addRecord(in);
list.add(rec);
map.put(rec, new Integer(size));
}
Random random = new Random(1);
for (int i = 0; i < list.size(); i++) {
int pos = random.nextInt(list.size());
DataRecord rec = list.get(pos);
int size = map.get(rec);
rec = ds.getRecord(rec.getIdentifier());
assertEquals(size, rec.getLength());
InputStream in = rec.getStream();
RandomInputStream expected = new RandomInputStream(size + offset, size);
if (random.nextBoolean()) {
in = readInputStreamRandomly(in, random);
}
assertEquals(expected, in);
in.close();
}
}
InputStream readInputStreamRandomly(InputStream in, Random random) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[8000];
while (true) {
if (random.nextBoolean()) {
int x = in.read();
if (x < 0) {
break;
}
out.write(x);
} else {
if (random.nextBoolean()) {
int l = in.read(buffer);
if (l < 0) {
break;
}
out.write(buffer, 0, l);
} else {
int offset = random.nextInt(buffer.length / 2);
int len = random.nextInt(buffer.length / 2);
int l = in.read(buffer, offset, len);
if (l < 0) {
break;
}
out.write(buffer, offset, l);
}
}
}
in.close();
return new ByteArrayInputStream(out.toByteArray());
}
void assertEquals(InputStream a, InputStream b) throws IOException {
while (true) {
int ai = a.read();
int bi = b.read();
assertEquals(ai, bi);
if (ai < 0) {
break;
}
}
}
}