You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commons-dev@ws.apache.org by Glen Daniels <gl...@thoughtcraft.com> on 2009/10/14 05:26:44 UTC

Re: svn commit: r824930 - in /webservices/commons/trunk/modules/axiom/modules: axiom-api/src/main/java/org/apache/axiom/attachments/ axiom-tests/src/test/java/org/apache/axiom/attachments/

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

scheu@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.
> 
>