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.