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