001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.io;
018
019 import java.io.File;
020 import java.lang.ref.PhantomReference;
021 import java.lang.ref.ReferenceQueue;
022 import java.util.Collection;
023 import java.util.Vector;
024
025 /**
026 * Keeps track of files awaiting deletion, and deletes them when an associated
027 * marker object is reclaimed by the garbage collector.
028 * <p>
029 * This utility creates a background thread to handle file deletion.
030 * Each file to be deleted is registered with a handler object.
031 * When the handler object is garbage collected, the file is deleted.
032 * <p>
033 * In an environment with multiple class loaders (a servlet container, for
034 * example), you should consider stopping the background thread if it is no
035 * longer needed. This is done by invoking the method
036 * {@link #exitWhenFinished}, typically in
037 * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
038 *
039 * @author Noel Bergman
040 * @author Martin Cooper
041 * @version $Id: FileCleaner.java 490987 2006-12-29 12:11:48Z scolebourne $
042 */
043 public class FileCleaningTracker {
044 /**
045 * Queue of <code>Tracker</code> instances being watched.
046 */
047 ReferenceQueue /* Tracker */ q = new ReferenceQueue();
048 /**
049 * Collection of <code>Tracker</code> instances in existence.
050 */
051 final Collection /* Tracker */ trackers = new Vector(); // synchronized
052 /**
053 * Whether to terminate the thread when the tracking is complete.
054 */
055 volatile boolean exitWhenFinished = false;
056 /**
057 * The thread that will clean up registered files.
058 */
059 Thread reaper;
060
061 //-----------------------------------------------------------------------
062 /**
063 * Track the specified file, using the provided marker, deleting the file
064 * when the marker instance is garbage collected.
065 * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
066 *
067 * @param file the file to be tracked, not null
068 * @param marker the marker object used to track the file, not null
069 * @throws NullPointerException if the file is null
070 */
071 public void track(File file, Object marker) {
072 track(file, marker, (FileDeleteStrategy) null);
073 }
074
075 /**
076 * Track the specified file, using the provided marker, deleting the file
077 * when the marker instance is garbage collected.
078 * The speified deletion strategy is used.
079 *
080 * @param file the file to be tracked, not null
081 * @param marker the marker object used to track the file, not null
082 * @param deleteStrategy the strategy to delete the file, null means normal
083 * @throws NullPointerException if the file is null
084 */
085 public void track(File file, Object marker, FileDeleteStrategy deleteStrategy) {
086 if (file == null) {
087 throw new NullPointerException("The file must not be null");
088 }
089 addTracker(file.getPath(), marker, deleteStrategy);
090 }
091
092 /**
093 * Track the specified file, using the provided marker, deleting the file
094 * when the marker instance is garbage collected.
095 * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
096 *
097 * @param path the full path to the file to be tracked, not null
098 * @param marker the marker object used to track the file, not null
099 * @throws NullPointerException if the path is null
100 */
101 public void track(String path, Object marker) {
102 track(path, marker, (FileDeleteStrategy) null);
103 }
104
105 /**
106 * Track the specified file, using the provided marker, deleting the file
107 * when the marker instance is garbage collected.
108 * The speified deletion strategy is used.
109 *
110 * @param path the full path to the file to be tracked, not null
111 * @param marker the marker object used to track the file, not null
112 * @param deleteStrategy the strategy to delete the file, null means normal
113 * @throws NullPointerException if the path is null
114 */
115 public void track(String path, Object marker, FileDeleteStrategy deleteStrategy) {
116 if (path == null) {
117 throw new NullPointerException("The path must not be null");
118 }
119 addTracker(path, marker, deleteStrategy);
120 }
121
122 /**
123 * Adds a tracker to the list of trackers.
124 *
125 * @param path the full path to the file to be tracked, not null
126 * @param marker the marker object used to track the file, not null
127 * @param deleteStrategy the strategy to delete the file, null means normal
128 */
129 private synchronized void addTracker(String path, Object marker, FileDeleteStrategy deleteStrategy) {
130 // synchronized block protects reaper
131 if (exitWhenFinished) {
132 throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
133 }
134 if (reaper == null) {
135 reaper = new Reaper();
136 reaper.start();
137 }
138 trackers.add(new Tracker(path, deleteStrategy, marker, q));
139 }
140
141 //-----------------------------------------------------------------------
142 /**
143 * Retrieve the number of files currently being tracked, and therefore
144 * awaiting deletion.
145 *
146 * @return the number of files being tracked
147 */
148 public int getTrackCount() {
149 return trackers.size();
150 }
151
152 /**
153 * Call this method to cause the file cleaner thread to terminate when
154 * there are no more objects being tracked for deletion.
155 * <p>
156 * In a simple environment, you don't need this method as the file cleaner
157 * thread will simply exit when the JVM exits. In a more complex environment,
158 * with multiple class loaders (such as an application server), you should be
159 * aware that the file cleaner thread will continue running even if the class
160 * loader it was started from terminates. This can consitute a memory leak.
161 * <p>
162 * For example, suppose that you have developed a web application, which
163 * contains the commons-io jar file in your WEB-INF/lib directory. In other
164 * words, the FileCleaner class is loaded through the class loader of your
165 * web application. If the web application is terminated, but the servlet
166 * container is still running, then the file cleaner thread will still exist,
167 * posing a memory leak.
168 * <p>
169 * This method allows the thread to be terminated. Simply call this method
170 * in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.
171 * One called, no new objects can be tracked by the file cleaner.
172 */
173 public synchronized void exitWhenFinished() {
174 // synchronized block protects reaper
175 exitWhenFinished = true;
176 if (reaper != null) {
177 synchronized (reaper) {
178 reaper.interrupt();
179 }
180 }
181 }
182
183 //-----------------------------------------------------------------------
184 /**
185 * The reaper thread.
186 */
187 private final class Reaper extends Thread {
188 /** Construct a new Reaper */
189 Reaper() {
190 super("File Reaper");
191 setPriority(Thread.MAX_PRIORITY);
192 setDaemon(true);
193 }
194
195 /**
196 * Run the reaper thread that will delete files as their associated
197 * marker objects are reclaimed by the garbage collector.
198 */
199 public void run() {
200 // thread exits when exitWhenFinished is true and there are no more tracked objects
201 while (exitWhenFinished == false || trackers.size() > 0) {
202 Tracker tracker = null;
203 try {
204 // Wait for a tracker to remove.
205 tracker = (Tracker) q.remove();
206 } catch (Exception e) {
207 continue;
208 }
209 if (tracker != null) {
210 tracker.delete();
211 tracker.clear();
212 trackers.remove(tracker);
213 }
214 }
215 }
216 }
217
218 //-----------------------------------------------------------------------
219 /**
220 * Inner class which acts as the reference for a file pending deletion.
221 */
222 private static final class Tracker extends PhantomReference {
223
224 /**
225 * The full path to the file being tracked.
226 */
227 private final String path;
228 /**
229 * The strategy for deleting files.
230 */
231 private final FileDeleteStrategy deleteStrategy;
232
233 /**
234 * Constructs an instance of this class from the supplied parameters.
235 *
236 * @param path the full path to the file to be tracked, not null
237 * @param deleteStrategy the strategy to delete the file, null means normal
238 * @param marker the marker object used to track the file, not null
239 * @param queue the queue on to which the tracker will be pushed, not null
240 */
241 Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue queue) {
242 super(marker, queue);
243 this.path = path;
244 this.deleteStrategy = (deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy);
245 }
246
247 /**
248 * Deletes the file associated with this tracker instance.
249 *
250 * @return <code>true</code> if the file was deleted successfully;
251 * <code>false</code> otherwise.
252 */
253 public boolean delete() {
254 return deleteStrategy.deleteQuietly(new File(path));
255 }
256 }
257
258 }