1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.io;
18
19 import java.io.File;
20 import java.lang.ref.PhantomReference;
21 import java.lang.ref.ReferenceQueue;
22 import java.util.Collection;
23 import java.util.Vector;
24
25 /**
26 * Keeps track of files awaiting deletion, and deletes them when an associated
27 * marker object is reclaimed by the garbage collector.
28 * <p>
29 * This utility creates a background thread to handle file deletion.
30 * Each file to be deleted is registered with a handler object.
31 * When the handler object is garbage collected, the file is deleted.
32 * <p>
33 * In an environment with multiple class loaders (a servlet container, for
34 * example), you should consider stopping the background thread if it is no
35 * longer needed. This is done by invoking the method
36 * {@link #exitWhenFinished}, typically in
37 * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
38 *
39 * @author Noel Bergman
40 * @author Martin Cooper
41 * @version $Id: FileCleaner.java 490987 2006-12-29 12:11:48Z scolebourne $
42 */
43 public class FileCleaningTracker {
44 /**
45 * Queue of <code>Tracker</code> instances being watched.
46 */
47 ReferenceQueue /* Tracker */ q = new ReferenceQueue();
48 /**
49 * Collection of <code>Tracker</code> instances in existence.
50 */
51 final Collection /* Tracker */ trackers = new Vector(); // synchronized
52 /**
53 * Whether to terminate the thread when the tracking is complete.
54 */
55 volatile boolean exitWhenFinished = false;
56 /**
57 * The thread that will clean up registered files.
58 */
59 Thread reaper;
60
61 //-----------------------------------------------------------------------
62 /**
63 * Track the specified file, using the provided marker, deleting the file
64 * when the marker instance is garbage collected.
65 * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
66 *
67 * @param file the file to be tracked, not null
68 * @param marker the marker object used to track the file, not null
69 * @throws NullPointerException if the file is null
70 */
71 public void track(File file, Object marker) {
72 track(file, marker, (FileDeleteStrategy) null);
73 }
74
75 /**
76 * Track the specified file, using the provided marker, deleting the file
77 * when the marker instance is garbage collected.
78 * The speified deletion strategy is used.
79 *
80 * @param file the file to be tracked, not null
81 * @param marker the marker object used to track the file, not null
82 * @param deleteStrategy the strategy to delete the file, null means normal
83 * @throws NullPointerException if the file is null
84 */
85 public void track(File file, Object marker, FileDeleteStrategy deleteStrategy) {
86 if (file == null) {
87 throw new NullPointerException("The file must not be null");
88 }
89 addTracker(file.getPath(), marker, deleteStrategy);
90 }
91
92 /**
93 * Track the specified file, using the provided marker, deleting the file
94 * when the marker instance is garbage collected.
95 * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
96 *
97 * @param path the full path to the file to be tracked, not null
98 * @param marker the marker object used to track the file, not null
99 * @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 }