Hi Rich! This change apparently broke the Hudson build:
http://hudson.zones.apache.org/hudson/job/ws-axiom-trunk/72/ Could you look into this? Thanks, --Glen sc...@apache.org wrote: > 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. > >