Home | History | Annotate | Download | only in io
      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<Object> /* Tracker */ q = new ReferenceQueue<Object>();
     48     /**
     49      * Collection of <code>Tracker</code> instances in existence.
     50      */
     51     final Collection<Tracker> /* Tracker */ trackers = new Vector<Tracker>();  // 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         @Override
    200         public void run() {
    201             // thread exits when exitWhenFinished is true and there are no more tracked objects
    202             while (exitWhenFinished == false || trackers.size() > 0) {
    203                 Tracker tracker = null;
    204                 try {
    205                     // Wait for a tracker to remove.
    206                     tracker = (Tracker) q.remove();
    207                 } catch (Exception e) {
    208                     continue;
    209                 }
    210                 if (tracker != null) {
    211                     tracker.delete();
    212                     tracker.clear();
    213                     trackers.remove(tracker);
    214                 }
    215             }
    216         }
    217     }
    218 
    219     //-----------------------------------------------------------------------
    220     /**
    221      * Inner class which acts as the reference for a file pending deletion.
    222      */
    223     private static final class Tracker extends PhantomReference<Object> {
    224 
    225         /**
    226          * The full path to the file being tracked.
    227          */
    228         private final String path;
    229         /**
    230          * The strategy for deleting files.
    231          */
    232         private final FileDeleteStrategy deleteStrategy;
    233 
    234         /**
    235          * Constructs an instance of this class from the supplied parameters.
    236          *
    237          * @param path  the full path to the file to be tracked, not null
    238          * @param deleteStrategy  the strategy to delete the file, null means normal
    239          * @param marker  the marker object used to track the file, not null
    240          * @param queue  the queue on to which the tracker will be pushed, not null
    241          */
    242         Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue<Object> queue) {
    243             super(marker, queue);
    244             this.path = path;
    245             this.deleteStrategy = (deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy);
    246         }
    247 
    248         /**
    249          * Deletes the file associated with this tracker instance.
    250          *
    251          * @return <code>true</code> if the file was deleted successfully;
    252          *         <code>false</code> otherwise.
    253          */
    254         public boolean delete() {
    255             return deleteStrategy.deleteQuietly(new File(path));
    256         }
    257     }
    258 
    259 }
    260