Coverage Report - org.apache.commons.io.FileCleaningTracker
 
Classes in this File Line Coverage Branch Coverage Complexity
FileCleaningTracker
96%
32/33
90%
9/10
2
FileCleaningTracker$Reaper
100%
15/15
100%
6/6
2
FileCleaningTracker$Tracker
100%
6/6
100%
2/2
2
 
 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.ArrayList;
 23  
 import java.util.Collection;
 24  
 import java.util.Collections;
 25  
 import java.util.HashSet;
 26  
 import java.util.List;
 27  
 
 28  
 /**
 29  
  * Keeps track of files awaiting deletion, and deletes them when an associated
 30  
  * marker object is reclaimed by the garbage collector.
 31  
  * <p>
 32  
  * This utility creates a background thread to handle file deletion.
 33  
  * Each file to be deleted is registered with a handler object.
 34  
  * When the handler object is garbage collected, the file is deleted.
 35  
  * <p>
 36  
  * In an environment with multiple class loaders (a servlet container, for
 37  
  * example), you should consider stopping the background thread if it is no
 38  
  * longer needed. This is done by invoking the method
 39  
  * {@link #exitWhenFinished}, typically in
 40  
  * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
 41  
  *
 42  
  * @version $Id: FileCleaningTracker.java 1563233 2014-01-31 19:58:50Z ggregory $
 43  
  */
 44  20
 public class FileCleaningTracker {
 45  
     /**
 46  
      * Queue of <code>Tracker</code> instances being watched.
 47  
      */
 48  20
     ReferenceQueue<Object> q = new ReferenceQueue<Object>();
 49  
     /**
 50  
      * Collection of <code>Tracker</code> instances in existence.
 51  
      */
 52  20
     final Collection<Tracker> trackers = Collections.synchronizedSet(new HashSet<Tracker>()); // synchronized
 53  
     /**
 54  
      * Collection of File paths that failed to delete.
 55  
      */
 56  20
     final List<String> deleteFailures = Collections.synchronizedList(new ArrayList<String>());
 57  
     /**
 58  
      * Whether to terminate the thread when the tracking is complete.
 59  
      */
 60  20
     volatile boolean exitWhenFinished = false;
 61  
     /**
 62  
      * The thread that will clean up registered files.
 63  
      */
 64  
     Thread reaper;
 65  
 
 66  
     //-----------------------------------------------------------------------
 67  
     /**
 68  
      * Track the specified file, using the provided marker, deleting the file
 69  
      * when the marker instance is garbage collected.
 70  
      * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
 71  
      *
 72  
      * @param file  the file to be tracked, not null
 73  
      * @param marker  the marker object used to track the file, not null
 74  
      * @throws NullPointerException if the file is null
 75  
      */
 76  
     public void track(final File file, final Object marker) {
 77  8
         track(file, marker, (FileDeleteStrategy) null);
 78  4
     }
 79  
 
 80  
     /**
 81  
      * Track the specified file, using the provided marker, deleting the file
 82  
      * when the marker instance is garbage collected.
 83  
      * The speified deletion strategy is used.
 84  
      *
 85  
      * @param file  the file to be tracked, not null
 86  
      * @param marker  the marker object used to track the file, not null
 87  
      * @param deleteStrategy  the strategy to delete the file, null means normal
 88  
      * @throws NullPointerException if the file is null
 89  
      */
 90  
     public void track(final File file, final Object marker, final FileDeleteStrategy deleteStrategy) {
 91  20
         if (file == null) {
 92  8
             throw new NullPointerException("The file must not be null");
 93  
         }
 94  12
         addTracker(file.getPath(), marker, deleteStrategy);
 95  12
     }
 96  
 
 97  
     /**
 98  
      * Track the specified file, using the provided marker, deleting the file
 99  
      * when the marker instance is garbage collected.
 100  
      * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
 101  
      *
 102  
      * @param path  the full path to the file to be tracked, not null
 103  
      * @param marker  the marker object used to track the file, not null
 104  
      * @throws NullPointerException if the path is null
 105  
      */
 106  
     public void track(final String path, final Object marker) {
 107  20
         track(path, marker, (FileDeleteStrategy) null);
 108  12
     }
 109  
 
 110  
     /**
 111  
      * Track the specified file, using the provided marker, deleting the file
 112  
      * when the marker instance is garbage collected.
 113  
      * The speified deletion strategy is used.
 114  
      *
 115  
      * @param path  the full path to the file to be tracked, not null
 116  
      * @param marker  the marker object used to track the file, not null
 117  
      * @param deleteStrategy  the strategy to delete the file, null means normal
 118  
      * @throws NullPointerException if the path is null
 119  
      */
 120  
     public void track(final String path, final Object marker, final FileDeleteStrategy deleteStrategy) {
 121  24
         if (path == null) {
 122  8
             throw new NullPointerException("The path must not be null");
 123  
         }
 124  16
         addTracker(path, marker, deleteStrategy);
 125  12
     }
 126  
 
 127  
     /**
 128  
      * Adds a tracker to the list of trackers.
 129  
      * 
 130  
      * @param path  the full path to the file to be tracked, not null
 131  
      * @param marker  the marker object used to track the file, not null
 132  
      * @param deleteStrategy  the strategy to delete the file, null means normal
 133  
      */
 134  
     private synchronized void addTracker(final String path, final Object marker, final FileDeleteStrategy deleteStrategy) {
 135  
         // synchronized block protects reaper
 136  28
         if (exitWhenFinished) {
 137  4
             throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
 138  
         }
 139  24
         if (reaper == null) {
 140  24
             reaper = new Reaper();
 141  24
             reaper.start();
 142  
         }
 143  24
         trackers.add(new Tracker(path, deleteStrategy, marker, q));
 144  24
     }
 145  
 
 146  
     //-----------------------------------------------------------------------
 147  
     /**
 148  
      * Retrieve the number of files currently being tracked, and therefore
 149  
      * awaiting deletion.
 150  
      *
 151  
      * @return the number of files being tracked
 152  
      */
 153  
     public int getTrackCount() {
 154  132
         return trackers.size();
 155  
     }
 156  
 
 157  
     /**
 158  
      * Return the file paths that failed to delete.
 159  
      *
 160  
      * @return the file paths that failed to delete
 161  
      * @since 2.0
 162  
      */
 163  
     public List<String> getDeleteFailures() {
 164  0
         return deleteFailures;
 165  
     }
 166  
 
 167  
     /**
 168  
      * Call this method to cause the file cleaner thread to terminate when
 169  
      * there are no more objects being tracked for deletion.
 170  
      * <p>
 171  
      * In a simple environment, you don't need this method as the file cleaner
 172  
      * thread will simply exit when the JVM exits. In a more complex environment,
 173  
      * with multiple class loaders (such as an application server), you should be
 174  
      * aware that the file cleaner thread will continue running even if the class
 175  
      * loader it was started from terminates. This can consitute a memory leak.
 176  
      * <p>
 177  
      * For example, suppose that you have developed a web application, which
 178  
      * contains the commons-io jar file in your WEB-INF/lib directory. In other
 179  
      * words, the FileCleaner class is loaded through the class loader of your
 180  
      * web application. If the web application is terminated, but the servlet
 181  
      * container is still running, then the file cleaner thread will still exist,
 182  
      * posing a memory leak.
 183  
      * <p>
 184  
      * This method allows the thread to be terminated. Simply call this method
 185  
      * in the resource cleanup code, such as 
 186  
      * {@link javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)}.
 187  
      * Once called, no new objects can be tracked by the file cleaner.
 188  
      */
 189  
     public synchronized void exitWhenFinished() {
 190  
         // synchronized block protects reaper
 191  16
         exitWhenFinished = true;
 192  16
         if (reaper != null) {
 193  8
             synchronized (reaper) {
 194  8
                 reaper.interrupt();
 195  8
             }
 196  
         }
 197  16
     }
 198  
 
 199  
     //-----------------------------------------------------------------------
 200  
     /**
 201  
      * The reaper thread.
 202  
      */
 203  
     private final class Reaper extends Thread {
 204  
         /** Construct a new Reaper */
 205  24
         Reaper() {
 206  24
             super("File Reaper");
 207  24
             setPriority(Thread.MAX_PRIORITY);
 208  24
             setDaemon(true);
 209  24
         }
 210  
 
 211  
         /**
 212  
          * Run the reaper thread that will delete files as their associated
 213  
          * marker objects are reclaimed by the garbage collector.
 214  
          */
 215  
         @Override
 216  
         public void run() {
 217  
             // thread exits when exitWhenFinished is true and there are no more tracked objects
 218  56
             while (exitWhenFinished == false || trackers.size() > 0) {
 219  
                 try {
 220  
                     // Wait for a tracker to remove.
 221  48
                     final Tracker tracker = (Tracker) q.remove(); // cannot return null
 222  24
                     trackers.remove(tracker);
 223  24
                     if (!tracker.delete()) {
 224  8
                         deleteFailures.add(tracker.getPath());
 225  
                     }
 226  24
                     tracker.clear();
 227  8
                 } catch (final InterruptedException e) {
 228  8
                     continue;
 229  24
                 }
 230  
             }
 231  8
         }
 232  
     }
 233  
 
 234  
     //-----------------------------------------------------------------------
 235  
     /**
 236  
      * Inner class which acts as the reference for a file pending deletion.
 237  
      */
 238  20
     private static final class Tracker extends PhantomReference<Object> {
 239  
 
 240  
         /**
 241  
          * The full path to the file being tracked.
 242  
          */
 243  
         private final String path;
 244  
         /**
 245  
          * The strategy for deleting files.
 246  
          */
 247  
         private final FileDeleteStrategy deleteStrategy;
 248  
 
 249  
         /**
 250  
          * Constructs an instance of this class from the supplied parameters.
 251  
          *
 252  
          * @param path  the full path to the file to be tracked, not null
 253  
          * @param deleteStrategy  the strategy to delete the file, null means normal
 254  
          * @param marker  the marker object used to track the file, not null
 255  
          * @param queue  the queue on to which the tracker will be pushed, not null
 256  
          */
 257  
         Tracker(final String path, final FileDeleteStrategy deleteStrategy, final Object marker, final ReferenceQueue<? super Object> queue) {
 258  24
             super(marker, queue);
 259  24
             this.path = path;
 260  24
             this.deleteStrategy = deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy;
 261  24
         }
 262  
 
 263  
         /**
 264  
          * Return the path.
 265  
          *
 266  
          * @return the path
 267  
          */
 268  
         public String getPath() {
 269  8
             return path;
 270  
         }
 271  
 
 272  
         /**
 273  
          * Deletes the file associated with this tracker instance.
 274  
          *
 275  
          * @return {@code true} if the file was deleted successfully;
 276  
          *         {@code false} otherwise.
 277  
          */
 278  
         public boolean delete() {
 279  24
             return deleteStrategy.deleteQuietly(new File(path));
 280  
         }
 281  
     }
 282  
 
 283  
 }