[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