[java-opensaml] 02/03: OSJ-249: Enhance FilesystemLoadSaveManager to check file last modified
Brent Putman
putmanb at georgetown.edu
Fri Sep 14 22:54:22 EDT 2018
This is an automated email from the git hooks/post-receive script.
putmanb pushed a commit to branch master
in repository java-opensaml.
View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=f3f909189f5035031327ea36ec7d97f845ec70a9
commit f3f909189f5035031327ea36ec7d97f845ec70a9
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Wed Sep 5 21:04:13 2018 -0400
OSJ-249: Enhance FilesystemLoadSaveManager to check file last modified
---
.../xml/persist/FilesystemLoadSaveManager.java | 101 ++++++++++++++++++++-
.../xml/persist/FilesystemLoadSaveManagerTest.java | 51 +++++++++++
2 files changed, 151 insertions(+), 1 deletion(-)
diff --git a/opensaml-core/src/main/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManager.java b/opensaml-core/src/main/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManager.java
index fb32abb..62106b8 100644
--- a/opensaml-core/src/main/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManager.java
+++ b/opensaml-core/src/main/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManager.java
@@ -25,9 +25,11 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
@@ -80,9 +82,16 @@ public class FilesystemLoadSaveManager<T extends XMLObject> implements XMLObject
/** Parser pool instance for deserializing XML from the filesystem. */
private ParserPool parserPool;
- /** File file used in filtering files in {@link #listKeys()} and {@link #listAll()}. */
+ /** File filter used in filtering files in {@link #listKeys()} and {@link #listAll()}. */
private FileFilter fileFilter;
+ /** Configuration flag for whether {@link #load(String)} will check and return data only if modified
+ * since the last request for that data. */
+ private boolean checkModifyTime;
+
+ /** Storage for last modified time of previously requested files. */
+ private Map<String, Long> lastModified;
+
/**
* Constructor.
*
@@ -137,6 +146,28 @@ public class FilesystemLoadSaveManager<T extends XMLObject> implements XMLObject
}
fileFilter = new DefaultFileFilter();
+
+ lastModified = new HashMap<>();
+ }
+
+ /**
+ * Get the configuration flag for whether {@link #load(String)} will check and return data only if modified
+ * since the last request for that data.
+ *
+ * @return true if file modify time check is enabled, false if not
+ */
+ public boolean isCheckModifyTime() {
+ return checkModifyTime;
+ }
+
+ /**
+ * Set the configuration flag for whether {@link #load(String)} will check and return data only if modified
+ * since the last request for that data.
+ *
+ * @param flag true if file modify time check should be enabled, false if not
+ */
+ public void setCheckModifyTime(final boolean flag) {
+ checkModifyTime = flag;
}
/** {@inheritDoc} */
@@ -164,6 +195,12 @@ public class FilesystemLoadSaveManager<T extends XMLObject> implements XMLObject
final File file = buildFile(key);
if (!file.exists()) {
log.debug("Target file with key '{}' does not exist, path: {}", key, file.getAbsolutePath());
+ clearCachedModified(key);
+ return null;
+ }
+ if (isCheckModifyTime() && isUnmodifiedSinceLastRequest(key)) {
+ log.debug("Target file with key '{}' has not been modified since the last request, returning null: {}",
+ key, file.getAbsolutePath());
return null;
}
try (final FileInputStream fis = new FileInputStream(file)) {
@@ -171,6 +208,7 @@ public class FilesystemLoadSaveManager<T extends XMLObject> implements XMLObject
try (final ByteArrayInputStream bais = new ByteArrayInputStream(source)) {
final XMLObject xmlObject = XMLObjectSupport.unmarshallFromInputStream(parserPool, bais);
xmlObject.getObjectMetadata().put(new XMLObjectSource(source));
+ updateCachedModified(key, file.lastModified());
//TODO via ctor, etc, does caller need to supply a Class so we can can test and throw an IOException,
// rather than an unchecked ClassCastException?
return (T) xmlObject;
@@ -179,6 +217,64 @@ public class FilesystemLoadSaveManager<T extends XMLObject> implements XMLObject
}
}
}
+
+ /**
+ * Check whether the file corresponding to the specified key has been modified since the last time it
+ * was requested.
+ *
+ * @param key the file key
+ * @return true if the corresponding file has been modified since the last request for it, false otherwise
+ * @throws IOException if there is a fatal error constructing or evaluating the candidate target path
+ */
+ protected synchronized boolean isUnmodifiedSinceLastRequest(@Nonnull final String key) throws IOException {
+ final File file = buildFile(key);
+ log.trace("File '{}' last modified was: {}", file.getAbsolutePath(), file.lastModified());
+ return getCachedModified(key) != null && file.lastModified() <= getCachedModified(key);
+ }
+
+ /**
+ * Retrieve the current cached modified time for the specified key.
+ * @param key the target key
+ * @return the current cached modified time, may be null
+ */
+ protected synchronized Long getCachedModified(@Nonnull final String key) {
+ return lastModified.get(key);
+ }
+
+ /**
+ * Update the cached modified time for the specified key with the current time.
+ * @param key the target key
+ * @return the previously cached modified time, or null if did not exist
+ */
+ protected synchronized Long updateCachedModified(@Nonnull final String key) {
+ return updateCachedModified(key, System.currentTimeMillis());
+ }
+
+ /**
+ * Update the cached modified time for the specified key with the specified time.
+ * @param key the target key
+ * @param modified the new cached modified time
+ * @return the previously cached modified time, or null if did not exist
+ */
+ protected synchronized Long updateCachedModified(@Nonnull final String key, @Nullable final Long modified) {
+ if (modified == null) {
+ return null;
+ }
+ final Long prev = lastModified.get(key);
+ lastModified.put(key, modified);
+ return prev;
+ }
+
+ /**
+ * Clear the current cached modified time for the specified key.
+ * @param key the target key
+ * @return the previously cached modified time, or null if did not exist
+ */
+ protected synchronized Long clearCachedModified(@Nonnull final String key) {
+ final Long prev = lastModified.get(key);
+ lastModified.remove(key);
+ return prev;
+ }
/** {@inheritDoc} */
public void save(final String key, final T xmlObject) throws IOException {
@@ -219,6 +315,7 @@ public class FilesystemLoadSaveManager<T extends XMLObject> implements XMLObject
if (file.exists()) {
final boolean success = file.delete();
if (success) {
+ clearCachedModified(key);
return true;
} else {
throw new IOException(String.format("Error removing target file: %s", file.getAbsolutePath()));
@@ -240,6 +337,8 @@ public class FilesystemLoadSaveManager<T extends XMLObject> implements XMLObject
throw new IOException(String.format("Specified new key already exists: %s", newKey));
} else {
Files.move(currentFile, newFile);
+ updateCachedModified(newKey, getCachedModified(currentKey));
+ clearCachedModified(currentKey);
return true;
}
}
diff --git a/opensaml-core/src/test/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManagerTest.java b/opensaml-core/src/test/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManagerTest.java
index 1d20744..3deea5e 100644
--- a/opensaml-core/src/test/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManagerTest.java
+++ b/opensaml-core/src/test/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManagerTest.java
@@ -24,6 +24,7 @@ import java.nio.file.Files;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import javax.xml.namespace.QName;
@@ -43,6 +44,7 @@ import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Uninterruptibles;
import net.shibboleth.utilities.java.support.collection.Pair;
import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
@@ -110,6 +112,9 @@ public class FilesystemLoadSaveManagerTest extends XMLObjectBaseTestCase {
}
testState(Sets.newHashSet("foo", "bar", "baz"));
+ // Test again. Since checkModifyTime=false, we should get back data even though unmodified
+ testState(Sets.newHashSet("foo", "bar", "baz"));
+
Assert.assertTrue(manager.updateKey("foo", "foo2"));
testState(Sets.newHashSet("foo2", "bar", "baz"));
@@ -137,6 +142,52 @@ public class FilesystemLoadSaveManagerTest extends XMLObjectBaseTestCase {
Assert.assertTrue(manager.remove("baz"));
testState(Sets.<String>newHashSet());
}
+
+ @Test
+ public void checkCheckModifyTimeTracking() throws IOException {
+ manager.setCheckModifyTime(true);
+
+ Assert.assertNull(manager.load("foo"));
+ Assert.assertNull(manager.getCachedModified("foo"));
+
+ manager.save("foo", (SimpleXMLObject) buildXMLObject(SimpleXMLObject.ELEMENT_NAME, true));
+
+ Assert.assertNotNull(manager.load("foo"));
+ Long initialCachedModified = manager.getCachedModified("foo");
+ Assert.assertNotNull(initialCachedModified);
+
+ // Hasn't changed
+ Assert.assertNull(manager.load("foo"));
+ Assert.assertEquals(manager.getCachedModified("foo"), initialCachedModified);
+
+ // We have to sleep a little to get an updated timestamp when we save a new one,
+ // since filesystem mtime granularity is only seconds.
+ Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
+
+ // Change it
+ manager.save("foo", (SimpleXMLObject) buildXMLObject(SimpleXMLObject.ELEMENT_NAME, true), true);
+
+ Assert.assertNotNull(manager.load("foo"));
+ Long updatedCachedModified = manager.getCachedModified("foo");
+ Assert.assertNotNull(updatedCachedModified);
+ Assert.assertNotEquals(updatedCachedModified, initialCachedModified);
+
+ // Hasn't changed (again)
+ Assert.assertNull(manager.load("foo"));
+ Assert.assertEquals(manager.getCachedModified("foo"), updatedCachedModified);
+
+ // Test update of key
+ manager.updateKey("foo", "bar");
+ Assert.assertNull(manager.load("foo"));
+ Assert.assertNull(manager.load("bar"));
+ Assert.assertNull(manager.getCachedModified("foo"));
+ Assert.assertNotNull(manager.getCachedModified("bar"));
+ Assert.assertEquals(manager.getCachedModified("bar"), updatedCachedModified);
+
+ // Test removal of key
+ manager.remove("bar");
+ Assert.assertNull(manager.getCachedModified("bar"));
+ }
@Test
public void buildTargetFileFromKey() throws IOException {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list