/*
* 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 java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import junit.framework.TestCase;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.core.data.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Testcase to test local cache.
*/
public class TestLocalCache extends TestCase {
private static final String CACHE_DIR = "target/cache";
private static final String TEMP_DIR = "target/temp";
private static final String TARGET_DIR = "target";
protected String cacheDirPath;
protected String tempDirPath;
/**
* Random number generator to populate data
*/
protected Random randomGen = new Random();
private static final Logger LOG = LoggerFactory.getLogger(TestLocalCache.class);
@Override
protected void setUp() {
try {
cacheDirPath = CACHE_DIR + "-"
+ String.valueOf(randomGen.nextInt(9999)) + "-"
+ String.valueOf(randomGen.nextInt(9999));
File cachedir = new File(cacheDirPath);
for (int i = 0; i < 4 && cachedir.exists(); i++) {
FileUtils.deleteQuietly(cachedir);
Thread.sleep(1000);
}
cachedir.mkdirs();
tempDirPath = TEMP_DIR + "-"
+ String.valueOf(randomGen.nextInt(9999)) + "-"
+ String.valueOf(randomGen.nextInt(9999));
File tempdir = new File(tempDirPath);
for (int i = 0; i < 4 && tempdir.exists(); i++) {
FileUtils.deleteQuietly(tempdir);
Thread.sleep(1000);
}
tempdir.mkdirs();
} catch (Exception e) {
LOG.error("error:", e);
fail();
}
}
@Override
protected void tearDown() throws Exception {
File cachedir = new File(cacheDirPath);
for (int i = 0; i < 4 && cachedir.exists(); i++) {
FileUtils.deleteQuietly(cachedir);
Thread.sleep(1000);
}
File tempdir = new File(tempDirPath);
for (int i = 0; i < 4 && tempdir.exists(); i++) {
FileUtils.deleteQuietly(tempdir);
Thread.sleep(1000);
}
}
/**
* Test to validate store retrieve in cache.
*/
public void testStoreRetrieve() {
try {
AsyncUploadCache pendingFiles = new AsyncUploadCache();
pendingFiles.init(tempDirPath, cacheDirPath, 100);
pendingFiles.reset();
LocalCache cache = new LocalCache(cacheDirPath, tempDirPath, 400,
0.95, 0.70, pendingFiles);
Random random = new Random(12345);
byte[] data = new byte[100];
Map<String, byte[]> byteMap = new HashMap<String, byte[]>();
random.nextBytes(data);
byteMap.put("a1", data);
data = new byte[100];
random.nextBytes(data);
byteMap.put("a2", data);
data = new byte[100];
random.nextBytes(data);
byteMap.put("a3", data);
cache.store("a1", new ByteArrayInputStream(byteMap.get("a1")));
cache.store("a2", new ByteArrayInputStream(byteMap.get("a2")));
cache.store("a3", new ByteArrayInputStream(byteMap.get("a3")));
InputStream result = cache.getIfStored("a1");
assertEquals(new ByteArrayInputStream(byteMap.get("a1")), result);
IOUtils.closeQuietly(result);
result = cache.getIfStored("a2");
assertEquals(new ByteArrayInputStream(byteMap.get("a2")), result);
IOUtils.closeQuietly(result);
result = cache.getIfStored("a3");
assertEquals(new ByteArrayInputStream(byteMap.get("a3")), result);
IOUtils.closeQuietly(result);
} catch (Exception e) {
LOG.error("error:", e);
fail();
}
}
/**
* Test to verify cache's purging if cache current size exceeds
* cachePurgeTrigFactor * size.
*/
public void testAutoPurge() {
try {
AsyncUploadCache pendingFiles = new AsyncUploadCache();
pendingFiles.init(tempDirPath, cacheDirPath, 100);
pendingFiles.reset();
LocalCache cache = new LocalCache(cacheDirPath, tempDirPath, 400,
0.95, 0.70, pendingFiles);
Random random = new Random(12345);
byte[] data = new byte[100];
Map<String, byte[]> byteMap = new HashMap<String, byte[]>();
random.nextBytes(data);
byteMap.put("a1", data);
data = new byte[100];
random.nextBytes(data);
byteMap.put("a2", data);
data = new byte[100];
random.nextBytes(data);
byteMap.put("a3", data);
data = new byte[100];
random.nextBytes(data);
byteMap.put("a4", data);
cache.store("a1", new ByteArrayInputStream(byteMap.get("a1")));
cache.store("a2", new ByteArrayInputStream(byteMap.get("a2")));
cache.store("a3", new ByteArrayInputStream(byteMap.get("a3")));
InputStream result = cache.getIfStored("a1");
assertEquals(new ByteArrayInputStream(byteMap.get("a1")), result);
IOUtils.closeQuietly(result);
result = cache.getIfStored("a2");
assertEquals(new ByteArrayInputStream(byteMap.get("a2")), result);
IOUtils.closeQuietly(result);
result = cache.getIfStored("a3");
assertEquals(new ByteArrayInputStream(byteMap.get("a3")), result);
IOUtils.closeQuietly(result);
data = new byte[90];
random.nextBytes(data);
byteMap.put("a4", data);
// storing a4 should purge cache
cache.store("a4", new ByteArrayInputStream(byteMap.get("a4")));
do {
Thread.sleep(1000);
} while (cache.isInPurgeMode());
result = cache.getIfStored("a1");
assertNull("a1 should be null", result);
IOUtils.closeQuietly(result);
result = cache.getIfStored("a2");
assertNull("a2 should be null", result);
IOUtils.closeQuietly(result);
result = cache.getIfStored("a3");
assertEquals(new ByteArrayInputStream(byteMap.get("a3")), result);
IOUtils.closeQuietly(result);
result = cache.getIfStored("a4");
assertEquals(new ByteArrayInputStream(byteMap.get("a4")), result);
IOUtils.closeQuietly(result);
data = new byte[100];
random.nextBytes(data);
byteMap.put("a5", data);
cache.store("a5", new ByteArrayInputStream(byteMap.get("a5")));
result = cache.getIfStored("a3");
assertEquals(new ByteArrayInputStream(byteMap.get("a3")), result);
IOUtils.closeQuietly(result);
} catch (Exception e) {
LOG.error("error:", e);
fail();
}
}
/**
* Test to verify cache's purging if cache current size exceeds
* cachePurgeTrigFactor * size.
*/
public void testAutoPurgeWithPendingUpload() {
try {
AsyncUploadCache pendingFiles = new AsyncUploadCache();
pendingFiles.init(tempDirPath, cacheDirPath, 100);
pendingFiles.reset();
LocalCache cache = new LocalCache(cacheDirPath, tempDirPath, 400,
0.95, 0.70, pendingFiles);
Random random = new Random(12345);
byte[] data = new byte[125];
Map<String, byte[]> byteMap = new HashMap<String, byte[]>();
random.nextBytes(data);
byteMap.put("a1", data);
data = new byte[125];
random.nextBytes(data);
byteMap.put("a2", data);
data = new byte[125];
random.nextBytes(data);
byteMap.put("a3", data);
data = new byte[100];
random.nextBytes(data);
byteMap.put("a4", data);
File tempDir = new File(tempDirPath);
File f = File.createTempFile("test", "tmp", tempDir);
FileOutputStream fos = new FileOutputStream(f);
fos.write(byteMap.get("a1"));
fos.close();
AsyncUploadCacheResult result = cache.store("a1", f, true);
assertTrue("should be able to add to pending upload",
result.canAsyncUpload());
f = File.createTempFile("test", "tmp", tempDir);
fos = new FileOutputStream(f);
fos.write(byteMap.get("a2"));
fos.close();
result = cache.store("a2", f, true);
assertTrue("should be able to add to pending upload",
result.canAsyncUpload());
f = File.createTempFile("test", "tmp", tempDir);
fos = new FileOutputStream(f);
fos.write(byteMap.get("a3"));
fos.close();
result = cache.store("a3", f, true);
assertTrue("should be able to add to pending upload",
result.canAsyncUpload());
InputStream inp = cache.getIfStored("a1");
assertEquals(new ByteArrayInputStream(byteMap.get("a1")), inp);
IOUtils.closeQuietly(inp);
inp = cache.getIfStored("a2");
assertEquals(new ByteArrayInputStream(byteMap.get("a2")), inp);
IOUtils.closeQuietly(inp);
inp = cache.getIfStored("a3");
assertEquals(new ByteArrayInputStream(byteMap.get("a3")), inp);
IOUtils.closeQuietly(inp);
data = new byte[90];
random.nextBytes(data);
byteMap.put("a4", data);
f = File.createTempFile("test", "tmp", tempDir);
fos = new FileOutputStream(f);
fos.write(byteMap.get("a4"));
fos.close();
result = cache.store("a4", f, true);
assertFalse("should not be able to add to pending upload",
result.canAsyncUpload());
Thread.sleep(1000);
inp = cache.getIfStored("a1");
assertEquals(new ByteArrayInputStream(byteMap.get("a1")), inp);
IOUtils.closeQuietly(inp);
inp = cache.getIfStored("a2");
assertEquals(new ByteArrayInputStream(byteMap.get("a2")), inp);
IOUtils.closeQuietly(inp);
inp = cache.getIfStored("a3");
assertEquals(new ByteArrayInputStream(byteMap.get("a3")), inp);
IOUtils.closeQuietly(inp);
inp = cache.getIfStored("a4");
assertNull("a4 should be null", inp);
} catch (Exception e) {
LOG.error("error:", e);
fail();
}
}
/**
* Test concurrent {@link LocalCache} initialization with storing
* {@link LocalCache}
*/
public void testConcurrentInitWithStore() {
try {
AsyncUploadCache pendingFiles = new AsyncUploadCache();
pendingFiles.init(tempDirPath, cacheDirPath, 100);
pendingFiles.reset();
LocalCache cache = new LocalCache(cacheDirPath, tempDirPath,
10000000, 0.95, 0.70, pendingFiles);
Random random = new Random(12345);
int fileUploads = 1000;
Map<String, byte[]> byteMap = new HashMap<String, byte[]>(
fileUploads);
byte[] data;
for (int i = 0; i < fileUploads; i++) {
data = new byte[100];
random.nextBytes(data);
String key = "a" + i;
byteMap.put(key, data);
cache.store(key, new ByteArrayInputStream(byteMap.get(key)));
}
cache.close();
ExecutorService executor = Executors.newFixedThreadPool(10,
new NamedThreadFactory("localcache-store-worker"));
cache = new LocalCache(cacheDirPath, tempDirPath, 10000000, 0.95,
0.70, pendingFiles);
executor.execute(new StoreWorker(cache, byteMap));
executor.shutdown();
while (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
}
} catch (Exception e) {
LOG.error("error:", e);
fail();
}
}
private class StoreWorker implements Runnable {
Map<String, byte[]> byteMap;
LocalCache cache;
Random random;
private StoreWorker(LocalCache cache, Map<String, byte[]> byteMap) {
this.byteMap = byteMap;
this.cache = cache;
random = new Random(byteMap.size());
}
public void run() {
try {
for (int i = 0; i < 100; i++) {
String key = "a" + random.nextInt(byteMap.size());
LOG.debug("key=" + key);
cache.store(key, new ByteArrayInputStream(byteMap.get(key)));
}
} catch (Exception e) {
LOG.error("error:", e);
fail();
}
}
}
/**
* Assert two inputstream
*/
protected 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;
}
}
IOUtils.closeQuietly(a);
IOUtils.closeQuietly(b);
}
}