Author: scheu
Date: Tue Oct 13 21:17:42 2009
New Revision: 824930

URL: http://svn.apache.org/viewvc?rev=824930&view=rev
Log:
WSCOMMONS-506
Contributor: Wendy Raschke
Added a property to ensure that attachment files are deleted.
Added a validation test.

Added:
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/AttachmentCacheMonitor.java
Modified:
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/CachedFileDataSource.java
    
webservices/commons/trunk/modules/axiom/modules/axiom-tests/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java

Added: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/AttachmentCacheMonitor.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/AttachmentCacheMonitor.java?rev=824930&view=auto
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/AttachmentCacheMonitor.java
 (added)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/AttachmentCacheMonitor.java
 Tue Oct 13 21:17:42 2009
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2004, 2009 The Apache Software Foundation.
+ *
+ * Licensed 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.axiom.attachments;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import java.io.File;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * The CacheMonitor is responsible for deleting temporary attachment files
+ * after a timeout period has expired.
+ * 
+ * The register method is invoked when the attachment file is created.
+ * The access method is invoked whenever the attachment file is accessed.
+ * The checkForAgedFiles method is invoked whenever the monitor should look 
for 
+ * files to cleanup (delete).
+ * 
+ */
+public final class AttachmentCacheMonitor {
+
+    static Log log =
+         LogFactory.getLog(AttachmentCacheMonitor.class.getName());
+
+    // Setting this property puts a limit on the lifetime of a cache file
+    // The default is "0", which is interpreted as forever
+    // The suggested value is 300 seconds
+    private int attachmentTimeoutSeconds = 0;  // Default is 0 (forever)
+    private int refreshSeconds = 0;
+    public static final String ATTACHMENT_TIMEOUT_PROPERTY = 
"org.apache.axiom.attachments.tempfile.expiration";
+
+    // HashMap
+    // Key String = Absolute file name
+    // Value Long = Last Access Time
+    private HashMap files = new HashMap();
+
+    // Delete detection is batched
+    private Long priorDeleteMillis = getTime();
+
+    private Timer timer = null;
+
+    private static AttachmentCacheMonitor _singleton = null;
+
+
+    /**
+     * Get or Create an AttachmentCacheMonitor singleton
+     * @return
+     */
+    public static synchronized AttachmentCacheMonitor 
getAttachmentCacheMonitor() {
+        if (_singleton == null) {
+            _singleton = new AttachmentCacheMonitor();
+        }
+        return _singleton;
+    }
+
+    /**
+     * Constructor
+     * Intentionally private.  Callers should use getAttachmentCacheMonitor
+     * @see getAttachmentCacheMonitor
+     */
+    private AttachmentCacheMonitor() {
+        String value = "";
+        try {
+            value = System.getProperty(ATTACHMENT_TIMEOUT_PROPERTY, "0");
+            attachmentTimeoutSeconds = Integer.valueOf(value).intValue();
+        } catch (Throwable t) {
+            // Swallow exception and use default, but log a warning message
+               if (log.isDebugEnabled()) {
+                       log.debug("The value of " + value + " was not valid. 
The default " + 
+                               attachmentTimeoutSeconds + " will be used 
instead.");
+               }
+        }
+        refreshSeconds = attachmentTimeoutSeconds / 2;
+
+        if (log.isDebugEnabled()) {
+            log.debug("Custom Property Key =  " + ATTACHMENT_TIMEOUT_PROPERTY);
+            log.debug("              Value = " + attachmentTimeoutSeconds);
+        }
+
+        if (refreshSeconds > 0) {
+            timer = new Timer( true );
+            timer.schedule( new CleanupFilesTask(), 
+                    refreshSeconds * 1000, 
+                    refreshSeconds * 1000 );
+        }
+    }
+    
+    /**
+     * @return timeout value in seconds
+     */
+    public synchronized int getTimeout() {
+       return attachmentTimeoutSeconds;
+    }
+    
+    /**
+     * This method should
+     * Set a new timeout value 
+     * @param timeout new timeout value in seconds
+     */
+    public synchronized void setTimeout(int timeout) {
+        // If the setting to the same value, simply return
+        if (timeout == attachmentTimeoutSeconds) {
+            return;
+        }
+        
+       attachmentTimeoutSeconds = timeout;
+       
+       // Reset the refresh
+       refreshSeconds = attachmentTimeoutSeconds / 2;
+       
+       // Make sure to cancel the prior timer
+       if (timer != null) {
+            timer.cancel(); // Remove scheduled tasks from the prior timer
+            timer = null;
+        }
+       
+       // Make a new timer if necessary
+        if (refreshSeconds > 0) {
+               timer = new Timer( true );
+            timer.schedule( new CleanupFilesTask(), 
+                    refreshSeconds * 1000, 
+                    refreshSeconds * 1000 );
+        }
+        
+        if (log.isDebugEnabled()) { 
+               log.debug("New timeout = " + attachmentTimeoutSeconds);
+               log.debug("New refresh = " + refreshSeconds);
+        }
+    }
+
+    /**
+     * Register a file name with the monitor.  
+     * This will allow the Monitor to remove the file after
+     * the timeout period.
+     * @param fileName
+     */
+    public void  register(String fileName) {
+        if (attachmentTimeoutSeconds    > 0) {
+            _register(fileName);
+            _checkForAgedFiles();
+        }
+    }
+    
+    /**
+     * Indicates that the file was accessed.
+     * @param fileName
+     */
+    public void access(String fileName) {
+        if (attachmentTimeoutSeconds    > 0) {
+            _access(fileName);
+            _checkForAgedFiles();
+        }
+    }
+    
+    /**
+     * Check for aged files and remove the aged ones.
+     */
+    public void checkForAgedFiles() {
+        if (attachmentTimeoutSeconds > 0) {
+            _checkForAgedFiles();
+        }
+    }
+
+    private synchronized void _register(String fileName) {
+        Long currentTime = getTime();
+        if (log.isDebugEnabled()) {
+            log.debug("Register file " + fileName);
+            log.debug("Time = " + currentTime); 
+        }
+        files.put(fileName, currentTime);
+    }
+
+    private synchronized void _access(String fileName) {
+        Long currentTime = getTime();
+        Long priorTime = (Long) files.get(fileName);
+        if (priorTime != null) {
+            files.put(fileName, currentTime);
+            if (log.isDebugEnabled()) {
+                log.debug("Access file " + fileName);
+                log.debug("Old Time = " + priorTime); 
+                log.debug("New Time = " + currentTime); 
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("The following file was already deleted and is no 
longer available: " + 
+                          fileName);
+                log.debug("The value of " + ATTACHMENT_TIMEOUT_PROPERTY + 
+                          " is " + attachmentTimeoutSeconds);
+            }
+        }
+    }
+
+    private synchronized void _checkForAgedFiles() {
+        Long currentTime = getTime();
+        // Don't keep checking the map, only trigger
+        // the checking if it is plausible that 
+        // files will need to be deleted.
+        // I chose a value of ATTACHMENTT_TIMEOUT_SECONDS/4
+        if (isExpired(priorDeleteMillis,
+                      currentTime,
+                      refreshSeconds)) {
+            Iterator it = files.keySet().iterator();
+            while (it.hasNext()) {
+                String fileName = (String) it.next();
+                Long lastAccess = (Long) files.get(fileName);
+                if (isExpired(lastAccess,
+                              currentTime,
+                              attachmentTimeoutSeconds)) {
+
+                    if (log.isDebugEnabled()) {
+                        log.debug("Expired file " + fileName);
+                        log.debug("Old Time = " + lastAccess); 
+                        log.debug("New Time = " + currentTime); 
+                        log.debug("Elapsed Time (ms) = " + 
+                                  (currentTime.longValue() - 
lastAccess.longValue())); 
+                    }
+
+                    deleteFile(fileName);
+                    // Use the iterator to remove this
+                    // file from the map (this avoids
+                    // the dreaded ConcurrentModificationException
+                    it.remove(); 
+                }     
+            }
+               
+            // Reset the prior delete time
+            priorDeleteMillis = currentTime;
+        }
+    }
+
+    private boolean deleteFile(final String fileName ) {
+        Boolean privRet = (Boolean) AccessController.doPrivileged(new 
PrivilegedAction() {
+                public Object run() {
+                    return _deleteFile(fileName);
+                }
+            });
+        return privRet.booleanValue();
+    }
+
+    private Boolean _deleteFile(String fileName) {
+        boolean ret = false;
+        File file = new File(fileName);
+        if (file.exists()) {
+            ret = file.delete();
+            if (log.isDebugEnabled()) {
+                log.debug("Deletion Successful ? " + ret);
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("This file no longer exists = " + fileName);
+            }
+        }
+        return new Boolean(ret);
+    }
+
+
+    private Long getTime() {
+        return new Long(System.currentTimeMillis());
+    }
+
+    private boolean isExpired (Long oldTimeMillis, 
+                                      Long newTimeMillis, 
+                                      int thresholdSecs) {
+        long elapse = newTimeMillis.longValue() -
+            oldTimeMillis.longValue();
+        return (elapse > (thresholdSecs*1000));
+    }
+
+
+    private class CleanupFilesTask extends TimerTask {
+
+        /**
+         * Trigger a checkForAgedFiles event
+         */
+        public void run() {
+            checkForAgedFiles();
+        }
+    }
+}

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/CachedFileDataSource.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/CachedFileDataSource.java?rev=824930&r1=824929&r2=824930&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/CachedFileDataSource.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/CachedFileDataSource.java
 Tue Oct 13 21:17:42 2009
@@ -22,12 +22,45 @@
 import javax.activation.FileDataSource;
 import java.io.File;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
 public class CachedFileDataSource extends FileDataSource {
 
     String contentType = null;
+    
+    protected static Log log = LogFactory.getLog(CachedFileDataSource.class);
+
+    // The AttachmentCacheMonitor is used to delete expired copies of 
attachment files.
+    private static AttachmentCacheMonitor acm = 
+        AttachmentCacheMonitor.getAttachmentCacheMonitor();
+    
+    // Represents the absolute pathname of cached attachment file
+    private String cachedFileName = null;
 
     public CachedFileDataSource(File arg0) {
         super(arg0);
+        if (log.isDebugEnabled()) {
+               log.debug("Enter CachedFileDataSource ctor");
+        }
+        if (arg0 != null) {
+               try {
+                       cachedFileName = arg0.getCanonicalPath();
+               } catch (java.io.IOException e) {
+                       log.error("IOException caught: " + e);
+               }
+        }
+        if (cachedFileName != null) {
+               if (log.isDebugEnabled()) {
+                       log.debug("Cached file: " + cachedFileName);
+                       log.debug("Registering the file with 
AttachmentCacheMonitor and also marked it as being accessed");
+               }
+            // Tell the monitor that the file is being accessed.
+               acm.access(cachedFileName);
+            // Register the file with the AttachmentCacheMonitor
+            acm.register(cachedFileName);
+        }
     }
 
     public String getContentType() {

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-tests/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-tests/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java?rev=824930&r1=824929&r2=824930&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-tests/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-tests/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java
 Tue Oct 13 21:17:42 2009
@@ -19,6 +19,7 @@
 
 package org.apache.axiom.attachments;
 
+import org.apache.axiom.attachments.AttachmentCacheMonitor;
 import org.apache.axiom.attachments.utils.IOUtils;
 import org.apache.axiom.om.AbstractTestCase;
 import org.apache.axiom.om.OMElement;
@@ -36,6 +37,7 @@
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -380,6 +382,78 @@
         assertTrue("Expected MessageContent Length of " + fileSize + " but 
received " + length,
                    length == fileSize);
     }
+    
+    public void testCachedFilesExpired() throws Exception {
+       
+       // Set file expiration to 10 seconds
+       long INTERVAL = 5 * 1000; // 5 seconds for Thread to sleep
+        Thread t = Thread.currentThread();
+
+       
+        // Get the AttachmentCacheMonitor and force it to remove files after
+        // 10 seconds.
+        AttachmentCacheMonitor acm = 
AttachmentCacheMonitor.getAttachmentCacheMonitor();
+        int previousTime = acm.getTimeout();
+        
+        try {
+            acm.setTimeout(10); 
+
+
+            File aFile = new File("A");
+            aFile.createNewFile();
+            String aFileName = aFile.getCanonicalPath();
+            acm.register(aFileName);
+
+            t.sleep(INTERVAL);
+
+            File bFile = new File("B");
+            bFile.createNewFile();
+            String bFileName = bFile.getCanonicalPath();
+            acm.register(bFileName);
+
+            t.sleep(INTERVAL);
+
+            acm.access(aFileName);
+
+            // time since file A registration <= cached file expiration
+            assertTrue("File A should still exist", aFile.exists());
+
+            t.sleep(INTERVAL);
+
+            acm.access(bFileName);
+
+            // time since file B registration <= cached file expiration
+            assertTrue("File B should still exist", bFile.exists());
+
+            t.sleep(INTERVAL);
+
+            File cFile = new File("C");
+            cFile.createNewFile();
+            String cFileName = cFile.getCanonicalPath();
+            acm.register(cFileName);
+            acm.access(bFileName);
+
+            t.sleep(INTERVAL);
+
+            acm.checkForAgedFiles();
+
+            // time since file C registration <= cached file expiration
+            assertTrue("File C should still exist", cFile.exists());
+
+            t.sleep(10* INTERVAL);  // Give task loop time to delete aged files
+
+
+            // All files should be gone by now
+            assertFalse("File A should no longer exist", aFile.exists());
+            assertFalse("File B should no longer exist", bFile.exists());
+            assertFalse("File C should no longer exist", cFile.exists());
+        } finally {
+       
+            // Reset the timeout to the previous value so that no 
+            // other tests are affected
+            acm.setTimeout(previousTime);
+        }
+    }
 
     /**
      * Returns the contents of the input stream as byte array.


Reply via email to