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    }