| /* |
| * 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.tomcat.util.http.fileupload; |
| |
| import java.io.File; |
| import java.lang.ref.PhantomReference; |
| import java.lang.ref.ReferenceQueue; |
| import java.util.Collection; |
| import java.util.Vector; |
| |
| /** |
| * Keeps track of files awaiting deletion, and deletes them when an associated |
| * marker object is reclaimed by the garbage collector. |
| * <p> |
| * This utility creates a background thread to handle file deletion. |
| * Each file to be deleted is registered with a handler object. |
| * When the handler object is garbage collected, the file is deleted. |
| * <p> |
| * In an environment with multiple class loaders (a servlet container, for |
| * example), you should consider stopping the background thread if it is no |
| * longer needed. This is done by invoking the method |
| * {@link #exitWhenFinished}, typically in |
| * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar. |
| * |
| * @author Noel Bergman |
| * @author Martin Cooper |
| * @version $Id: FileCleaningTracker.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $ |
| */ |
| public class FileCleaningTracker { |
| /** |
| * Queue of <code>Tracker</code> instances being watched. |
| */ |
| ReferenceQueue /* Tracker */ q = new ReferenceQueue(); |
| /** |
| * Collection of <code>Tracker</code> instances in existence. |
| */ |
| final Collection<Tracker> trackers = new Vector<Tracker>(); // synchronized |
| /** |
| * Whether to terminate the thread when the tracking is complete. |
| */ |
| volatile boolean exitWhenFinished = false; |
| /** |
| * The thread that will clean up registered files. |
| */ |
| Thread reaper; |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Track the specified file, using the provided marker, deleting the file |
| * when the marker instance is garbage collected. |
| * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used. |
| * |
| * @param file the file to be tracked, not null |
| * @param marker the marker object used to track the file, not null |
| * @throws NullPointerException if the file is null |
| */ |
| public void track(File file, Object marker) { |
| track(file, marker, (FileDeleteStrategy) null); |
| } |
| |
| /** |
| * Track the specified file, using the provided marker, deleting the file |
| * when the marker instance is garbage collected. |
| * The speified deletion strategy is used. |
| * |
| * @param file the file to be tracked, not null |
| * @param marker the marker object used to track the file, not null |
| * @param deleteStrategy the strategy to delete the file, null means normal |
| * @throws NullPointerException if the file is null |
| */ |
| public void track(File file, Object marker, FileDeleteStrategy deleteStrategy) { |
| if (file == null) { |
| throw new NullPointerException("The file must not be null"); |
| } |
| addTracker(file.getPath(), marker, deleteStrategy); |
| } |
| |
| /** |
| * Track the specified file, using the provided marker, deleting the file |
| * when the marker instance is garbage collected. |
| * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used. |
| * |
| * @param path the full path to the file to be tracked, not null |
| * @param marker the marker object used to track the file, not null |
| * @throws NullPointerException if the path is null |
| */ |
| public void track(String path, Object marker) { |
| track(path, marker, (FileDeleteStrategy) null); |
| } |
| |
| /** |
| * Track the specified file, using the provided marker, deleting the file |
| * when the marker instance is garbage collected. |
| * The speified deletion strategy is used. |
| * |
| * @param path the full path to the file to be tracked, not null |
| * @param marker the marker object used to track the file, not null |
| * @param deleteStrategy the strategy to delete the file, null means normal |
| * @throws NullPointerException if the path is null |
| */ |
| public void track(String path, Object marker, FileDeleteStrategy deleteStrategy) { |
| if (path == null) { |
| throw new NullPointerException("The path must not be null"); |
| } |
| addTracker(path, marker, deleteStrategy); |
| } |
| |
| /** |
| * Adds a tracker to the list of trackers. |
| * |
| * @param path the full path to the file to be tracked, not null |
| * @param marker the marker object used to track the file, not null |
| * @param deleteStrategy the strategy to delete the file, null means normal |
| */ |
| private synchronized void addTracker(String path, Object marker, FileDeleteStrategy deleteStrategy) { |
| // synchronized block protects reaper |
| if (exitWhenFinished) { |
| throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called"); |
| } |
| if (reaper == null) { |
| reaper = new Reaper(); |
| reaper.start(); |
| } |
| trackers.add(new Tracker(path, deleteStrategy, marker, q)); |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Retrieve the number of files currently being tracked, and therefore |
| * awaiting deletion. |
| * |
| * @return the number of files being tracked |
| */ |
| public int getTrackCount() { |
| return trackers.size(); |
| } |
| |
| /** |
| * Call this method to cause the file cleaner thread to terminate when |
| * there are no more objects being tracked for deletion. |
| * <p> |
| * In a simple environment, you don't need this method as the file cleaner |
| * thread will simply exit when the JVM exits. In a more complex environment, |
| * with multiple class loaders (such as an application server), you should be |
| * aware that the file cleaner thread will continue running even if the class |
| * loader it was started from terminates. This can consitute a memory leak. |
| * <p> |
| * For example, suppose that you have developed a web application, which |
| * contains the commons-io jar file in your WEB-INF/lib directory. In other |
| * words, the FileCleaner class is loaded through the class loader of your |
| * web application. If the web application is terminated, but the servlet |
| * container is still running, then the file cleaner thread will still exist, |
| * posing a memory leak. |
| * <p> |
| * This method allows the thread to be terminated. Simply call this method |
| * in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}. |
| * One called, no new objects can be tracked by the file cleaner. |
| */ |
| public synchronized void exitWhenFinished() { |
| // synchronized block protects reaper |
| exitWhenFinished = true; |
| if (reaper != null) { |
| synchronized (reaper) { |
| reaper.interrupt(); |
| } |
| } |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * The reaper thread. |
| */ |
| private final class Reaper extends Thread { |
| /** Construct a new Reaper */ |
| Reaper() { |
| super("File Reaper"); |
| setPriority(Thread.MAX_PRIORITY); |
| setDaemon(true); |
| } |
| |
| /** |
| * Run the reaper thread that will delete files as their associated |
| * marker objects are reclaimed by the garbage collector. |
| */ |
| @Override |
| public void run() { |
| // thread exits when exitWhenFinished is true and there are no more tracked objects |
| while (exitWhenFinished == false || trackers.size() > 0) { |
| Tracker tracker = null; |
| try { |
| // Wait for a tracker to remove. |
| tracker = (Tracker) q.remove(); |
| } catch (Exception e) { |
| continue; |
| } |
| if (tracker != null) { |
| tracker.delete(); |
| tracker.clear(); |
| trackers.remove(tracker); |
| } |
| } |
| } |
| } |
| |
| //----------------------------------------------------------------------- |
| /** |
| * Inner class which acts as the reference for a file pending deletion. |
| */ |
| private static final class Tracker extends PhantomReference { |
| |
| /** |
| * The full path to the file being tracked. |
| */ |
| private final String path; |
| /** |
| * The strategy for deleting files. |
| */ |
| private final FileDeleteStrategy deleteStrategy; |
| |
| /** |
| * Constructs an instance of this class from the supplied parameters. |
| * |
| * @param path the full path to the file to be tracked, not null |
| * @param deleteStrategy the strategy to delete the file, null means normal |
| * @param marker the marker object used to track the file, not null |
| * @param queue the queue on to which the tracker will be pushed, not null |
| */ |
| Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue queue) { |
| super(marker, queue); |
| this.path = path; |
| this.deleteStrategy = (deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy); |
| } |
| |
| /** |
| * Deletes the file associated with this tracker instance. |
| * |
| * @return <code>true</code> if the file was deleted successfully; |
| * <code>false</code> otherwise. |
| */ |
| public boolean delete() { |
| return deleteStrategy.deleteQuietly(new File(path)); |
| } |
| } |
| |
| } |