| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Timer; |
| import java.util.TimerTask; |
| |
| import java.io.File; |
| |
| 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 Map<String,Long> files = new HashMap<String,Long>(); |
| |
| // Delete detection is batched |
| private Long priorDeleteMillis = getTime(); |
| |
| private Timer timer = null; |
| |
| private static AttachmentCacheMonitor _singleton = null; |
| |
| |
| /** |
| * Get or Create an AttachmentCacheMonitor singleton |
| * @return the singleton instance |
| */ |
| 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 = 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<String> it = files.keySet().iterator(); |
| while (it.hasNext()) { |
| String fileName = it.next(); |
| Long lastAccess = 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 ) { |
| return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { |
| @Override |
| public Boolean run() { |
| return _deleteFile(fileName); |
| } |
| }); |
| } |
| |
| 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 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 |
| */ |
| @Override |
| public void run() { |
| checkForAgedFiles(); |
| } |
| } |
| } |