[cache] Added 'ByteArrayFileCache' (#1723)
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>pull/1758/head
parent
8b52cab5ef
commit
8744bc10fe
|
@ -28,5 +28,10 @@
|
||||||
<attribute name="test" value="true"/>
|
<attribute name="test" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|
|
@ -106,9 +106,7 @@ public class OpenHAB {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the user data folder path name. The main user data folder <code><openhab-home>/userdata</code> can
|
* Returns the user data folder path name. The main user data folder <code><openhab-home>/userdata</code> can
|
||||||
* be
|
* be overwritten by setting the System property <code>openhab.userdata</code>.
|
||||||
* overwritten by setting
|
|
||||||
* the System property <code>openhab.userdata</code>.
|
|
||||||
*
|
*
|
||||||
* @return the user data folder path name
|
* @return the user data folder path name
|
||||||
*/
|
*/
|
||||||
|
|
290
bundles/org.openhab.core/src/main/java/org/openhab/core/cache/ByteArrayFileCache.java
vendored
Normal file
290
bundles/org.openhab.core/src/main/java/org/openhab/core/cache/ByteArrayFileCache.java
vendored
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.cache;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.OpenHAB;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a simple file based cache implementation. It is not thread-safe.
|
||||||
|
*
|
||||||
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ByteArrayFileCache {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
|
||||||
|
|
||||||
|
static final String CACHE_FOLDER_NAME = "cache";
|
||||||
|
private static final char EXTENSION_SEPARATOR = '.';
|
||||||
|
|
||||||
|
private final File cacheFolder;
|
||||||
|
|
||||||
|
private final Duration expiry;
|
||||||
|
private final Map<String, File> filesInCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||||
|
* <code>$OPENHAB_USERDATA/cache/$servicePID</code>.
|
||||||
|
*
|
||||||
|
* @param servicePID PID of the service
|
||||||
|
*/
|
||||||
|
public ByteArrayFileCache(String servicePID) {
|
||||||
|
this(servicePID, Duration.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||||
|
* <code>$OPENHAB_USERDATA/cache/$servicePID/</code>.
|
||||||
|
*
|
||||||
|
* @param servicePID PID of the service
|
||||||
|
* @param expiry the duration for how long the files stay valid in the cache. Must be positive. 0 to
|
||||||
|
* disable this functionality.
|
||||||
|
*/
|
||||||
|
public ByteArrayFileCache(String servicePID, Duration expiry) {
|
||||||
|
// TODO track and limit folder size
|
||||||
|
// TODO support user specific folder
|
||||||
|
cacheFolder = new File(new File(OpenHAB.getUserDataFolder(), CACHE_FOLDER_NAME), servicePID);
|
||||||
|
if (!cacheFolder.exists()) {
|
||||||
|
logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||||
|
cacheFolder.mkdirs();
|
||||||
|
}
|
||||||
|
logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||||
|
|
||||||
|
if (expiry.isNegative()) {
|
||||||
|
throw new IllegalArgumentException("Cache expiration time must be greater than or equal to 0");
|
||||||
|
}
|
||||||
|
this.expiry = expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
|
||||||
|
* new content.
|
||||||
|
*
|
||||||
|
* @param key the key with which the file is to be associated
|
||||||
|
* @param content the content for the file to be associated with the specified key
|
||||||
|
*/
|
||||||
|
public void put(String key, byte[] content) {
|
||||||
|
writeFile(getUniqueFile(key), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a file to the cache.
|
||||||
|
*
|
||||||
|
* @param key the key with which the file is to be associated
|
||||||
|
* @param content the content for the file to be associated with the specified key
|
||||||
|
*/
|
||||||
|
public void putIfAbsent(String key, byte[] content) {
|
||||||
|
File fileInCache = getUniqueFile(key);
|
||||||
|
if (fileInCache.exists()) {
|
||||||
|
logger.debug("File '{}' present in cache", fileInCache.getName());
|
||||||
|
// update time of last use
|
||||||
|
fileInCache.setLastModified(System.currentTimeMillis());
|
||||||
|
} else {
|
||||||
|
writeFile(fileInCache, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a file to the cache and returns the content of the file.
|
||||||
|
*
|
||||||
|
* @param key the key with which the file is to be associated
|
||||||
|
* @param content the content for the file to be associated with the specified key
|
||||||
|
* @return the content of the file associated with the given key
|
||||||
|
* @throws IOException if an I/O error occurs reading the given file
|
||||||
|
*/
|
||||||
|
public byte[] putIfAbsentAndGet(String key, byte[] content) throws IOException {
|
||||||
|
putIfAbsent(key, content);
|
||||||
|
|
||||||
|
return get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the given content to the given {@link File}.
|
||||||
|
*
|
||||||
|
* @param fileInCache the {@link File}
|
||||||
|
* @param content the content to be written
|
||||||
|
*/
|
||||||
|
private void writeFile(File fileInCache, byte[] content) {
|
||||||
|
logger.debug("Caching file '{}'", fileInCache.getName());
|
||||||
|
try {
|
||||||
|
Files.write(fileInCache.toPath(), content);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the key is present in the cache.
|
||||||
|
*
|
||||||
|
* @param key the key whose presence in the cache is to be tested
|
||||||
|
* @return true if the cache contains a file for the specified key
|
||||||
|
*/
|
||||||
|
public boolean containsKey(String key) {
|
||||||
|
return getUniqueFile(key).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the file associated with the given key from the cache.
|
||||||
|
*
|
||||||
|
* @param key the key whose associated file is to be removed
|
||||||
|
*/
|
||||||
|
public void remove(String key) {
|
||||||
|
deleteFile(getUniqueFile(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given {@link File}.
|
||||||
|
*
|
||||||
|
* @param fileInCache the {@link File}
|
||||||
|
*/
|
||||||
|
private void deleteFile(File fileInCache) {
|
||||||
|
if (fileInCache.exists()) {
|
||||||
|
logger.debug("Deleting file '{}' from cache", fileInCache.getName());
|
||||||
|
fileInCache.delete();
|
||||||
|
} else {
|
||||||
|
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all files from the cache.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
File[] filesInCache = cacheFolder.listFiles();
|
||||||
|
if (filesInCache != null && filesInCache.length > 0) {
|
||||||
|
logger.debug("Deleting all files from cache");
|
||||||
|
Arrays.stream(filesInCache).forEach(File::delete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes expired files from the cache.
|
||||||
|
*/
|
||||||
|
public void clearExpired() {
|
||||||
|
// exit if expiry is set to 0 (disabled)
|
||||||
|
if (expiry.isZero() || expiry.isNegative()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File[] filesInCache = cacheFolder.listFiles();
|
||||||
|
if (filesInCache != null && filesInCache.length > 0) {
|
||||||
|
logger.debug("Deleting expired files from cache");
|
||||||
|
Arrays.stream(filesInCache).filter(this::isExpired).forEach(File::delete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given {@link File} is expired.
|
||||||
|
*
|
||||||
|
* @param fileInCache the {@link File}
|
||||||
|
* @return <code>true</code> if the file is expired, <code>false</code> otherwise
|
||||||
|
*/
|
||||||
|
private boolean isExpired(File fileInCache) {
|
||||||
|
// exit if expiry is set to 0 (disabled)
|
||||||
|
long expiryInMillis = expiry.toMillis();
|
||||||
|
return 0 < expiryInMillis && expiryInMillis < System.currentTimeMillis() - fileInCache.lastModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the content of the file associated with the given key, if it is present.
|
||||||
|
*
|
||||||
|
* @param key the key whose associated file is to be returned
|
||||||
|
* @return the content of the file associated with the given key
|
||||||
|
* @throws FileNotFoundException if the given file could not be found in cache
|
||||||
|
* @throws IOException if an I/O error occurs reading the given file
|
||||||
|
*/
|
||||||
|
public byte[] get(String key) throws FileNotFoundException, IOException {
|
||||||
|
return readFile(getUniqueFile(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the content from the given {@link File}, if it is present.
|
||||||
|
*
|
||||||
|
* @param fileInCache the {@link File}
|
||||||
|
* @return the content of the file
|
||||||
|
* @throws FileNotFoundException if the given file could not be found in cache
|
||||||
|
* @throws IOException if an I/O error occurs reading the given file
|
||||||
|
*/
|
||||||
|
private byte[] readFile(File fileInCache) throws FileNotFoundException, IOException {
|
||||||
|
if (fileInCache.exists()) {
|
||||||
|
logger.debug("Reading file '{}' from cache", fileInCache.getName());
|
||||||
|
// update time of last use
|
||||||
|
fileInCache.setLastModified(System.currentTimeMillis());
|
||||||
|
try {
|
||||||
|
return Files.readAllBytes(fileInCache.toPath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
|
||||||
|
throw new IOException(String.format("Could not read file '%s' from cache", fileInCache.getName()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||||
|
throw new FileNotFoundException(String.format("File '%s' not found in cache", fileInCache.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a unique {@link File} from the key with which the file is to be associated.
|
||||||
|
*
|
||||||
|
* @param key the key with which the file is to be associated
|
||||||
|
* @return unique file for the file associated with the given key
|
||||||
|
*/
|
||||||
|
File getUniqueFile(String key) {
|
||||||
|
String uniqueFileName = getUniqueFileName(key);
|
||||||
|
if (filesInCache.containsKey(uniqueFileName)) {
|
||||||
|
return filesInCache.get(uniqueFileName);
|
||||||
|
} else {
|
||||||
|
String fileExtension = getFileExtension(key);
|
||||||
|
File fileInCache = new File(cacheFolder,
|
||||||
|
uniqueFileName + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
|
||||||
|
filesInCache.put(uniqueFileName, fileInCache);
|
||||||
|
return fileInCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension of a file name.
|
||||||
|
*
|
||||||
|
* @param fileName the file name to retrieve the extension of
|
||||||
|
* @return the extension of the file or null if none exists
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
String getFileExtension(String fileName) {
|
||||||
|
String strippedFileName = fileName.replaceFirst("\\?.*$", "");
|
||||||
|
int extensionPos = strippedFileName.lastIndexOf(EXTENSION_SEPARATOR);
|
||||||
|
int lastSeparatorPos = strippedFileName.lastIndexOf(File.separator);
|
||||||
|
return lastSeparatorPos > extensionPos ? null : strippedFileName.substring(extensionPos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a unique file name from the key with which the file is to be associated.
|
||||||
|
*
|
||||||
|
* @param key the key with which the file is to be associated
|
||||||
|
* @return unique file name for the file associated with the given key
|
||||||
|
*/
|
||||||
|
String getUniqueFileName(String key) {
|
||||||
|
return String.format("%032x", BigInteger.valueOf(key.hashCode()));
|
||||||
|
}
|
||||||
|
}
|
238
bundles/org.openhab.core/src/test/java/org/openhab/core/cache/ByteArrayFileCacheTest.java
vendored
Normal file
238
bundles/org.openhab.core/src/test/java/org/openhab/core/cache/ByteArrayFileCacheTest.java
vendored
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.cache;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.core.OpenHAB;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for the {@link ByteArrayFileCache} class.
|
||||||
|
*
|
||||||
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ByteArrayFileCacheTest {
|
||||||
|
|
||||||
|
private static final String SERVICE_PID = "org.openhab.core.test";
|
||||||
|
|
||||||
|
private static final File USERDATA_FOLDER = new File(OpenHAB.getUserDataFolder());
|
||||||
|
private static final File CACHE_FOLDER = new File(USERDATA_FOLDER, ByteArrayFileCache.CACHE_FOLDER_NAME);
|
||||||
|
private static final File SERVICE_CACHE_FOLDER = new File(CACHE_FOLDER, SERVICE_PID);
|
||||||
|
|
||||||
|
private static final String MP3_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.mp3";
|
||||||
|
private static final String TXT_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.txt";
|
||||||
|
|
||||||
|
private static @Nullable File txtFile;
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCacheTest.class);
|
||||||
|
|
||||||
|
private @NonNullByDefault({}) ByteArrayFileCache subject;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void init() throws IOException {
|
||||||
|
// create temporary file
|
||||||
|
txtFile = createTempTxtFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
subject = new ByteArrayFileCache(SERVICE_PID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
// delete all files
|
||||||
|
subject.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void cleanUp() {
|
||||||
|
// delete all folders
|
||||||
|
SERVICE_CACHE_FOLDER.delete();
|
||||||
|
CACHE_FOLDER.delete();
|
||||||
|
USERDATA_FOLDER.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetFileExtension() {
|
||||||
|
assertThat(subject.getFileExtension("/var/log/openhab/"), is(nullValue()));
|
||||||
|
assertThat(subject.getFileExtension("/var/log/foo.bar/"), is(nullValue()));
|
||||||
|
assertThat(subject.getFileExtension("doorbell.mp3"), is(equalTo("mp3")));
|
||||||
|
assertThat(subject.getFileExtension("/tmp/doorbell.mp3"), is(equalTo("mp3")));
|
||||||
|
assertThat(subject.getFileExtension(MP3_FILE_NAME), is(equalTo("mp3")));
|
||||||
|
assertThat(subject.getFileExtension(TXT_FILE_NAME), is(equalTo("txt")));
|
||||||
|
assertThat(subject.getFileExtension("/var/log/openhab/.."), is(""));
|
||||||
|
assertThat(subject.getFileExtension(".hidden"), is(equalTo("hidden")));
|
||||||
|
assertThat(subject.getFileExtension("C:\\Program Files (x86)\\java\\bin\\javaw.exe"), is(equalTo("exe")));
|
||||||
|
assertThat(subject.getFileExtension("https://www.youtube.com/watch?v=qYrpPrLY868"), is(nullValue()));
|
||||||
|
assertThat(subject.getFileExtension("https://www.youtube.com/watch?v=Test.With.Dots"), is(nullValue()));
|
||||||
|
assertThat(subject.getFileExtension("https://host/test.xlsx?cache=false"), is("xlsx"));
|
||||||
|
// assertThat(subject.getFileExtension(
|
||||||
|
// "http://127.0.0.1:8080/image/image%3A%2F%2Fhttp%253a%252f%252f127.0.0.1%253a32400%252fphoto%252f%253a%252ftranscode%253fwidth%253d1920%2526height%253d1920%2526minSize%253d1%2526upscale%253d0%2526url%253d%252flibrary%252fmetadata%252f1896%252fthumb%252f1569782004%2526X-Plex-Token%253dXScJLJbUdcybNXFyHLuv"),
|
||||||
|
// is(nullValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUniqueFileName() {
|
||||||
|
String mp3UniqueFileName = subject.getUniqueFileName(MP3_FILE_NAME);
|
||||||
|
assertThat(mp3UniqueFileName, is(equalTo(subject.getUniqueFileName(MP3_FILE_NAME))));
|
||||||
|
|
||||||
|
String txtUniqueFileName = subject.getUniqueFileName(TXT_FILE_NAME);
|
||||||
|
assertThat(txtUniqueFileName, is(equalTo(subject.getUniqueFileName(TXT_FILE_NAME))));
|
||||||
|
|
||||||
|
assertThat(mp3UniqueFileName, is(not(equalTo(txtUniqueFileName))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGet() {
|
||||||
|
assertThrows(FileNotFoundException.class, () -> subject.get(TXT_FILE_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPut() throws IOException {
|
||||||
|
byte[] buffer = readTempTxtFile();
|
||||||
|
subject.put(TXT_FILE_NAME, buffer);
|
||||||
|
|
||||||
|
assertThat(subject.get(TXT_FILE_NAME), is(equalTo(buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutIfAbsentAddsANewFile() throws IOException {
|
||||||
|
byte[] buffer = readTempTxtFile();
|
||||||
|
subject.putIfAbsent(TXT_FILE_NAME, buffer);
|
||||||
|
|
||||||
|
assertThat(subject.get(TXT_FILE_NAME), is(equalTo(buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutIfAbsentDoesNotOverwriteExistingFile() throws IOException {
|
||||||
|
byte[] buffer = readTempTxtFile();
|
||||||
|
subject.putIfAbsent(TXT_FILE_NAME, buffer);
|
||||||
|
subject.putIfAbsent(TXT_FILE_NAME, TXT_FILE_NAME.getBytes());
|
||||||
|
|
||||||
|
assertThat(subject.get(TXT_FILE_NAME), is(equalTo(buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutIfAbsentAndGetAddsANewFile() throws IOException {
|
||||||
|
byte[] buffer = readTempTxtFile();
|
||||||
|
|
||||||
|
assertThat(subject.putIfAbsentAndGet(TXT_FILE_NAME, buffer), is(equalTo(buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutIfAbsentAndGetDoesNotOverwriteExistingFile() throws IOException {
|
||||||
|
byte[] buffer = readTempTxtFile();
|
||||||
|
|
||||||
|
subject.putIfAbsentAndGet(TXT_FILE_NAME, buffer);
|
||||||
|
assertThat(subject.putIfAbsentAndGet(TXT_FILE_NAME, TXT_FILE_NAME.getBytes()), is(equalTo(buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainsKey() {
|
||||||
|
assertThat(subject.containsKey(TXT_FILE_NAME), is(false));
|
||||||
|
|
||||||
|
subject.put(TXT_FILE_NAME, readTempTxtFile());
|
||||||
|
|
||||||
|
assertThat(subject.containsKey(TXT_FILE_NAME), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemove() {
|
||||||
|
subject.put(TXT_FILE_NAME, readTempTxtFile());
|
||||||
|
subject.remove(TXT_FILE_NAME);
|
||||||
|
|
||||||
|
assertThrows(FileNotFoundException.class, () -> subject.get(TXT_FILE_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClear() {
|
||||||
|
subject.put(TXT_FILE_NAME, readTempTxtFile());
|
||||||
|
subject.clear();
|
||||||
|
|
||||||
|
assertThrows(FileNotFoundException.class, () -> subject.get(TXT_FILE_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearExpiredClearsNothingIfExpiryNotSet() throws IOException {
|
||||||
|
byte[] buffer = readTempTxtFile();
|
||||||
|
subject.put(TXT_FILE_NAME, buffer);
|
||||||
|
subject.clearExpired();
|
||||||
|
|
||||||
|
assertThat(subject.get(TXT_FILE_NAME), is(equalTo(buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearExpiredClearsNothingIfNotExpired() throws IOException {
|
||||||
|
subject = new ByteArrayFileCache(SERVICE_PID, Duration.ofSeconds(5));
|
||||||
|
|
||||||
|
byte[] buffer = readTempTxtFile();
|
||||||
|
subject.put(TXT_FILE_NAME, buffer);
|
||||||
|
subject.clearExpired();
|
||||||
|
|
||||||
|
assertThat(subject.get(TXT_FILE_NAME), is(equalTo(buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearExpiredIfExpired() {
|
||||||
|
Duration expiry = Duration.ofSeconds(5);
|
||||||
|
subject = new ByteArrayFileCache(SERVICE_PID, expiry);
|
||||||
|
|
||||||
|
subject.put(TXT_FILE_NAME, readTempTxtFile());
|
||||||
|
|
||||||
|
// manipulate time of last use
|
||||||
|
File fileInCache = subject.getUniqueFile(TXT_FILE_NAME);
|
||||||
|
fileInCache.setLastModified(System.currentTimeMillis() - 2 * expiry.toMillis());
|
||||||
|
|
||||||
|
subject.clearExpired();
|
||||||
|
|
||||||
|
assertThrows(FileNotFoundException.class, () -> subject.get(TXT_FILE_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File createTempTxtFile() throws IOException {
|
||||||
|
final File file = File.createTempFile("doorbell", "txt");
|
||||||
|
file.deleteOnExit();
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readTempTxtFile() {
|
||||||
|
if (txtFile != null) {
|
||||||
|
try {
|
||||||
|
return Files.readAllBytes(txtFile.toPath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error while reading temp file");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error("Temp file not found");
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -32,24 +34,25 @@ import org.junit.jupiter.api.Test;
|
||||||
* @author Christoph Weitkamp - Initial contribution
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
* @author Martin van Wingerden - Added tests for putIfAbsentAndGet
|
* @author Martin van Wingerden - Added tests for putIfAbsentAndGet
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class ExpiringCacheMapTest {
|
public class ExpiringCacheMapTest {
|
||||||
private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toMillis(2);
|
private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toMillis(2);
|
||||||
|
|
||||||
private static final String RESPONSE_1 = "ACTION 1";
|
private static final String RESPONSE_1 = "ACTION 1";
|
||||||
private static final String RESPONSE_2 = "ACTION 2";
|
private static final String RESPONSE_2 = "ACTION 2";
|
||||||
|
|
||||||
private static final Supplier<String> CACHE_ACTION = () -> {
|
private static final Supplier<@Nullable String> CACHE_ACTION = () -> {
|
||||||
byte[] array = new byte[8];
|
byte[] array = new byte[8];
|
||||||
new Random().nextBytes(array);
|
new Random().nextBytes(array);
|
||||||
return new String(array, StandardCharsets.UTF_8);
|
return new String(array, StandardCharsets.UTF_8);
|
||||||
};
|
};
|
||||||
private static final Supplier<String> PREDICTABLE_CACHE_ACTION_1 = () -> RESPONSE_1;
|
private static final Supplier<@Nullable String> PREDICTABLE_CACHE_ACTION_1 = () -> RESPONSE_1;
|
||||||
private static final Supplier<String> PREDICTABLE_CACHE_ACTION_2 = () -> RESPONSE_2;
|
private static final Supplier<@Nullable String> PREDICTABLE_CACHE_ACTION_2 = () -> RESPONSE_2;
|
||||||
|
|
||||||
private static final String FIRST_TEST_KEY = "FIRST_TEST_KEY";
|
private static final String FIRST_TEST_KEY = "FIRST_TEST_KEY";
|
||||||
private static final String SECOND_TEST_KEY = "SECOND_TEST_KEY";
|
private static final String SECOND_TEST_KEY = "SECOND_TEST_KEY";
|
||||||
|
|
||||||
private ExpiringCacheMap<String, String> subject;
|
private @NonNullByDefault({}) ExpiringCacheMap<String, String> subject;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
@ -139,13 +142,15 @@ public class ExpiringCacheMapTest {
|
||||||
assertNotNull(value3);
|
assertNotNull(value3);
|
||||||
assertNotEquals(value1, value3);
|
assertNotEquals(value1, value3);
|
||||||
|
|
||||||
|
if (value1 != null && value3 != null) {
|
||||||
// get all values
|
// get all values
|
||||||
final Collection<String> expectedValues = new LinkedList<>();
|
final Collection<String> expectedValues = new LinkedList<>();
|
||||||
expectedValues.add(value3);
|
expectedValues.add(value3);
|
||||||
expectedValues.add(value1);
|
expectedValues.add(value1);
|
||||||
|
|
||||||
final Collection<String> values = subject.values();
|
final Collection<@Nullable String> values = subject.values();
|
||||||
assertEquals(expectedValues, values);
|
assertEquals(expectedValues, values);
|
||||||
|
}
|
||||||
|
|
||||||
// use another different key
|
// use another different key
|
||||||
String value4 = subject.get("KEY_NOT_FOUND");
|
String value4 = subject.get("KEY_NOT_FOUND");
|
||||||
|
@ -192,15 +197,18 @@ public class ExpiringCacheMapTest {
|
||||||
|
|
||||||
// refresh item
|
// refresh item
|
||||||
String value2 = subject.refresh(FIRST_TEST_KEY);
|
String value2 = subject.refresh(FIRST_TEST_KEY);
|
||||||
|
assertNotNull(value2);
|
||||||
assertNotEquals(value1, value2);
|
assertNotEquals(value1, value2);
|
||||||
|
|
||||||
|
if (value2 != null) {
|
||||||
// refresh all
|
// refresh all
|
||||||
final Collection<String> expectedValues = new LinkedList<>();
|
final Collection<String> expectedValues = new LinkedList<>();
|
||||||
expectedValues.add(value2);
|
expectedValues.add(value2);
|
||||||
|
|
||||||
final Collection<String> values = subject.refreshAll();
|
final Collection<@Nullable String> values = subject.refreshAll();
|
||||||
assertNotEquals(expectedValues, values);
|
assertNotEquals(expectedValues, values);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemove() {
|
public void testRemove() {
|
||||||
|
|
|
@ -19,6 +19,8 @@ import java.util.Random;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -27,15 +29,17 @@ import org.junit.jupiter.api.Test;
|
||||||
*
|
*
|
||||||
* @author Christoph Weitkamp - Initial contribution
|
* @author Christoph Weitkamp - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class ExpiringCacheTest {
|
public class ExpiringCacheTest {
|
||||||
private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toMillis(2);
|
private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toMillis(2);
|
||||||
private static final Supplier<String> CACHE_ACTION = () -> {
|
|
||||||
|
private static final Supplier<@Nullable String> CACHE_ACTION = () -> {
|
||||||
byte[] array = new byte[8];
|
byte[] array = new byte[8];
|
||||||
new Random().nextBytes(array);
|
new Random().nextBytes(array);
|
||||||
return new String(array, StandardCharsets.UTF_8);
|
return new String(array, StandardCharsets.UTF_8);
|
||||||
};
|
};
|
||||||
|
|
||||||
private ExpiringCache<String> subject;
|
private @NonNullByDefault({}) ExpiringCache<String> subject;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
|
Loading…
Reference in New Issue