Package org.geoserver.rest.util

Source Code of org.geoserver.rest.util.IOUtils$FileCleaner

/* Copyright (c) 2008 TOPP - www.openplans.org.  All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/

package org.geoserver.rest.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.net.URL;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import javax.xml.transform.stream.StreamSource;

import org.apache.commons.io.FilenameUtils;
import org.geotools.data.DataUtilities;

/**
* Assorted IO related utilities
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
public class IOUtils extends org.apache.commons.io.IOUtils {


  private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(FileCleaner.class)

  /** Default size of element for {@link FileChannel} based copy method.*/
  private static final int DEFAULT_SIZE = 10 * 1024 * 1024;
 
  /**Background to perform file deletions.*/
  private final static FileCleaner FILE_CLEANER = new FileCleaner();
  private final static Set<String> FILES_PATH = Collections.synchronizedSet(new HashSet<String>());
  private final static Map<String, Integer> FILE_ATTEMPTS_COUNTS = Collections.synchronizedMap(new HashMap<String, Integer>());
 
  /**
   * 30 seconds is the default period beteen two checks.
   */
  private static long DEFAULT_PERIOD = 5L;

  /**
   * The default number of attempts is 50
   */
  private final static int DEF_MAX_ATTEMPTS = 50;
 
    static {
        FILE_CLEANER.setMaxAttempts(100);
        FILE_CLEANER.setPeriod(30);
        FILE_CLEANER.setPriority(1);
        FILE_CLEANER.start();
}

  /**
   * Simple class implementing a periodic Thread that periodically tries to
   * delete the files that were provided to him.
   * <p>
   * It tries to delete each file at most {@link FileCleaner#maxAttempts}
   * number of times. If this number is exceeded it simply throws the file
   * away notifying the users with a warning message.
   *
   * @author Simone Giannecchini, GeoSolutions.
   */
  public final static class FileCleaner extends Thread {
   

 
    /**
     * Maximum number of attempts to delete a given {@link File}.
     *
     * <p>
     * If the provided number of attempts is exceeded we simply drop warn the
     * user and we remove the {@link File} from our list.
     */
    private int maxAttempts = DEF_MAX_ATTEMPTS;

 
    /**
     * Period in seconds between two checks.
     */
    private volatile long period = DEFAULT_PERIOD;
 
    /**
     * Asks this {@link FileCleaner} to clean up this file.
     *
     * @param fileToDelete {@link File} that we want to permanently delete.
     */
    public void addFile(final File fileToDelete) {
      // does it exists
      if (!fileToDelete.exists())
        return;
      synchronized (FILES_PATH) {
        synchronized (FILE_ATTEMPTS_COUNTS) {
          // /////////////////////////////////////////////////////////////////
          //
          // We add the file to our lists for later check.
          //
          // /////////////////////////////////////////////////////////////////
          if (!FILES_PATH.contains(fileToDelete.getAbsolutePath())) {
            FILES_PATH.add(fileToDelete.getAbsolutePath());
            FILE_ATTEMPTS_COUNTS.put(fileToDelete.getAbsolutePath(),
                new Integer(0));
 
          }
        }
      }
    }
 
    /**
     * Default constructor for a {@link FileCleaner}.
     */
    public FileCleaner() {
      this(DEFAULT_PERIOD, Thread.NORM_PRIORITY - 3, DEF_MAX_ATTEMPTS);
    }
 
    /**
     * Constructor for a {@link FileCleaner}.
     *
     * @param period default time period between two cycles.
     * @param priority is the priority for the cleaner thread.
     * @param maxattempts maximum number of time the cleaner thread tries to delete a file.
     */
    public FileCleaner(long period, int priority, int maxattempts) {
      this.period = period;
      this.setName("FileCleaner");
      this.setPriority(priority);
      this.setDaemon(true);
      this.maxAttempts = maxattempts;
    }
 
    /**
     * This method does the magic:
     *
     * <ol>
     * <li>iterate over all the files</li>
     * <li>try to delete it</li>
     * <li>if successful drop the file references</li>
     * <li>if not successful increase the attempts count for the file and call
     * the gc. If the maximum number was exceeded drop the file and warn the
     * user </li>
     *
     */
    public void run() {
      while (true) {
        try {
          synchronized (FILES_PATH) {
            synchronized (FILE_ATTEMPTS_COUNTS) {
 
              final Iterator<String> it = FILES_PATH.iterator();
              while (it.hasNext()) {
 
                // get next file path and its count
                final String sFile = it.next();
                if(LOGGER.isLoggable(Level.INFO))
                    LOGGER.info("Trying to remove file " + sFile);
                int attempts = FILE_ATTEMPTS_COUNTS.get(sFile).intValue();
                if (!new File(sFile).exists()) {
                  it.remove();
                  FILE_ATTEMPTS_COUNTS.remove(sFile);
                } else {
                  // try to delete it
                  if (new File(sFile).delete()) {
                    if(LOGGER.isLoggable(Level.INFO))
                        LOGGER.info("Successfully removed file "+ sFile);
                    it.remove();
                    FILE_ATTEMPTS_COUNTS.remove(sFile);
                  } else {
                    if(LOGGER.isLoggable(Level.INFO))
                      LOGGER.info("Unable to  remove file "
                        + sFile);
                    attempts++;
                    if (maxAttempts < attempts) {
                      if(LOGGER.isLoggable(Level.INFO))
                        LOGGER.info("Dropping file " + sFile);
                      it.remove();
                      FILE_ATTEMPTS_COUNTS.remove(sFile);
                      if (LOGGER.isLoggable(Level.WARNING))
                        LOGGER
                            .warning("Unable to delete file "
                                + sFile);
                    } else {
                      FILE_ATTEMPTS_COUNTS.remove(sFile);
                      FILE_ATTEMPTS_COUNTS.put(sFile,new Integer(attempts));
                      // might help, see
                      // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154
                      Runtime.getRuntime().gc();
                      Runtime.getRuntime().gc();
                      Runtime.getRuntime().gc();
                      Runtime.getRuntime().gc();
                      Runtime.getRuntime().gc();
                      Runtime.getRuntime().gc();
                      System.runFinalization();
                      System.runFinalization();
                      System.runFinalization();
                      System.runFinalization();
                      System.runFinalization();
                      System.runFinalization();
 
                    }
                  }
                }
              }
            }
          }
          Thread.sleep(period * 1000);
 
        } catch (Throwable t) {
          if(LOGGER.isLoggable(Level.INFO))
            LOGGER.log(Level.INFO, t.getLocalizedMessage(), t);
        }
      }
    }
 
    /**
     * Retrieves the maximum number of times we try to delete a file before giving up.
     * @return  the maximum number of times we try to delete a file before giving  up.
     *
     */
    public int getMaxAttempts() {
      synchronized (FILES_PATH) {
        synchronized (FILE_ATTEMPTS_COUNTS) {
          return maxAttempts;
        }
      }
 
    }
 
    /**
     * Sets the maximum number of times we try to delete a file before giving up.
     * @param maxAttempts  the maximum number of times we try to delete a file before  giving up.
     *
     */
    public void setMaxAttempts(int maxAttempts) {
      synchronized (FILES_PATH) {
        synchronized (FILE_ATTEMPTS_COUNTS) {
          this.maxAttempts = maxAttempts;
        }
      }
 
    }
 
    /**
     * Retrieves the period in seconds for this  {@link FileCleaner} .
     * @return  the period in seconds for this  {@link FileCleaner}  .
     *
     */
    public long getPeriod() {
      return period;
    }
 
    /**
     * Sets the period in seconds for this  {@link FileCleaner} .
     * @param period  the new period for this  {@link FileCleaner}  .
     *
     */
    public void setPeriod(long period) {
      this.period = period;
    }
 
  }
 
 
 
  /**
   * Copies the content of the source channel onto the destination channel.
   *
   * @param bufferSize size of the temp buffer to use for this copy.
   * @param source the source {@link ReadableByteChannel}.
   * @param destination the destination {@link WritableByteChannel};.
   * @throws IOException in case something bad happens.
   */
  public static void copyChannel(int bufferSize, ReadableByteChannel source,WritableByteChannel destination) throws IOException {

    inputNotNull(source,destination);
    if(!source.isOpen()||!destination.isOpen())
      throw new IllegalStateException("Source and destination channels must be open.");
   
    final java.nio.ByteBuffer buffer= java.nio.ByteBuffer.allocateDirect(bufferSize);
    while(source.read(buffer)!=-1)
    {
      //prepare the buffer for draining
      buffer.flip();
     
      //write to destination
      while(buffer.hasRemaining())
        destination.write(buffer);
     
      //clear
      buffer.clear();
     
     
    }

  }
 
  /**
   * Optimize version of copy method for file channels.
   *
   * @param bufferSize size of the temp buffer to use for this copy.
   * @param source the source {@link ReadableByteChannel}.
   * @param destination the destination {@link WritableByteChannel};.
   * @throws IOException in case something bad happens.
   */
  public static void copyFileChannel(int bufferSize, FileChannel source,
      FileChannel destination) throws IOException {
   
    inputNotNull(source,destination);
    if(!source.isOpen()||!destination.isOpen())
      throw new IllegalStateException("Source and destination channels must be open.");   
    FileLock lock = null;
    try {

      lock = destination.lock();
      final long sourceSize = source.size();
      long pos = 0;
      while (pos < sourceSize) {
        // read and flip
        final long remaining = (sourceSize - pos);
        final int mappedZoneSize = remaining >= bufferSize ? bufferSize
            : (int) remaining;
        destination.transferFrom(source, pos, mappedZoneSize);
        // update zone
        pos += mappedZoneSize;

      }
    } finally {
      if (lock != null) {
        try {
          lock.release();
        }catch (Throwable t) {
              if(LOGGER.isLoggable(Level.INFO))
                LOGGER.log(Level.INFO,t.getLocalizedMessage(),t);
        }
      }

    }
  }
 
  /**
   * Close the specified input <code>FileChannel</code>
   *
   * @throws IOException in case something bad happens.
   */
  public static void closeQuietly(Channel channel)
      throws IOException {
    inputNotNull(channel);
    if (channel.isOpen())
      channel.close();
  }

  /**
   * Checks if the input is not null.
   * @param oList list of elements to check for null.
   */
  private static void inputNotNull(Object...oList) {
    for(Object o: oList)
      if(o==null)
        throw new NullPointerException("Input objects cannot be null");
   
  }


  /**
   * Copy the input file onto the output file using a default buffer size.
   *
   * @param sourceFile the {@link File} to copy from.
   * @param destinationFile the {@link File} to copy to.
   * @throws IOException in case something bad happens.
   */
  public static void copyFile(File sourceFile, File destinationFile)
      throws IOException {
    copyFile(sourceFile, destinationFile, DEFAULT_SIZE);
  }
 
  /**
   * Copy the input file onto the output file using the specified buffer size.
   *
   * @param sourceFile the {@link File} to copy from.
   * @param destinationFile the {@link File} to copy to.
   * @param size buffer size.
   * @throws IOException in case something bad happens.
   */
  public static void copyFile(File sourceFile, File destinationFile, int size)
      throws IOException {
    inputNotNull(sourceFile,destinationFile);
    if(!sourceFile.exists()||!sourceFile.canRead()||!sourceFile.isFile())
      throw new IllegalStateException("Source is not in a legal state.");       
    if (!destinationFile.exists()) {
      destinationFile.createNewFile();
    }
    if (destinationFile.getAbsolutePath().equalsIgnoreCase(
        sourceFile.getAbsolutePath()))
      throw new IllegalArgumentException("Cannot copy a file on itself");

    FileChannel source = null;
    FileChannel destination = null;
    source = new RandomAccessFile(sourceFile, "r").getChannel();
    destination = new RandomAccessFile(destinationFile, "rw").getChannel();
    try {
      copyFileChannel(size, source, destination);
    } finally {
      try {
        if (source != null) {
          try {
            source.close();
          }
          catch (Throwable t) {
                if(LOGGER.isLoggable(Level.INFO))
                  LOGGER.log(Level.INFO,t.getLocalizedMessage(),t);
          }
        }
      } finally {
        if (destination != null) {
          try {
            destination.close();
          }
          catch (Throwable t) {
                if(LOGGER.isLoggable(Level.INFO))
                  LOGGER.log(Level.INFO,t.getLocalizedMessage(),t);
          }
        }
      }
    }
  }
 
  /**
   * Delete all the files with matching the specified {@link FilenameFilter} in the specified directory.
   * The method can work recursively.
   *
   * @param sourceDirectory the directory to delete files from.
   * @param filter the {@link FilenameFilter} to use for selecting files to delete.
   * @param recursive boolean that specifies if we want to delete files recursively or not.
   * @return
   */
  public static boolean deleteDirectory(File sourceDirectory, FilenameFilter filter, boolean recursive, boolean deleteItself) {
    inputNotNull(sourceDirectory,filter);
    if(!sourceDirectory.exists()||!sourceDirectory.canRead()||!sourceDirectory.isDirectory())
      throw new IllegalStateException("Source is not in a legal state.");     
   
   
    final File[] files = (filter != null ? sourceDirectory.listFiles(filter) : sourceDirectory.listFiles());
    for (File file:files) {
      if (file.isDirectory()) {
        if(recursive)
          deleteDirectory(file, filter,recursive,deleteItself);
      } else {
        if(!file.delete())
          return false;
      }
    }
    return deleteItself?sourceDirectory.delete():true;

   
  }
 
 
  /**
   * Delete the specified File.
   *
   * @param sourceDirectory the directory to delete files from.
   * @param filter the {@link FilenameFilter} to use for selecting files to delete.
   * @param recursive boolean that specifies if we want to delete files recursively or not.
   * @return
   */
  public static void deleteFile(File file) {
    inputNotNull(file);
    if(!file.exists()||!file.canRead()||!file.isFile())
      throw new IllegalStateException("Source is not in a legal state.");     
   
   
    if(file.delete())
      return;
   
    IOUtils.FILE_CLEANER.addFile(file);
   

   
  }


  /**
   * Get an input <code>FileChannel</code> for the provided
   * <code>File</code>
   *
   * @param file
   *            <code>File</code> for which we need to get an input
   *            <code>FileChannel</code>
   * @return a <code>FileChannel</code>
   * @throws IOException in case something bad happens.
   */
  public static FileChannel getInputChannel(File source)
      throws IOException {
    inputNotNull(source);
    if(!source.exists()||!source.canRead()||!source.isDirectory())
      throw new IllegalStateException("Source is not in a legal state.");     
    FileChannel channel = null;
    while (channel == null) {
      try {
        channel = new FileInputStream(source).getChannel();
      } catch (Exception e) {
        channel = null;
      }
    }
    return channel;
  }

  /**
   * Get an output <code>FileChannel</code> for the provided
   * <code>File</code>
   *
   * @param file
   *            <code>File</code> for which we need to get an output
   *            <code>FileChannel</code>
   * @return a <code>FileChannel</code>
   * @throws IOException in case something bad happens.
   */
  public static FileChannel getOuputChannel(File file)
      throws IOException {
    inputNotNull(file);
    return new RandomAccessFile(file, "rw").getChannel();
 
  }

  /**
   * Move the specified input file to the specified destination directory.
   *
   * @param source
   *            the input <code>File</code> which need to be moved.
   * @param destDir
   *            the destination directory where to move the file.
   * @throws IOException
   */
  public static void moveFileTo(File source, File destDir, boolean removeInputFile) throws IOException {
    inputNotNull(source,destDir);
    if(!source.exists()||!source.canRead()||source.isDirectory())
      throw new IllegalStateException("Source is not in a legal state."  )
    if(!destDir.exists()||!destDir.canWrite()||!destDir.isDirectory())
      throw new IllegalStateException("Source is not in a legal state."  );   
    if (destDir.getAbsolutePath().equalsIgnoreCase(
        source.getParentFile().getAbsolutePath()))
      return;
    // ///////////////////////////////////////////////////////////////
    //
    // Copy the inputFile in the specified destination directory
    //
    // ///////////////////////////////////////////////////////////////
    copyFile(source, new File(destDir, source.getName()));

 
    // ///////////////////////////////////////////////////////////////
    //
    // Delete the source file.
    //
    // ///////////////////////////////////////////////////////////////
    // we need to call the gc, see
    // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154
    if (removeInputFile)
      FILE_CLEANER.addFile(source);
 
  }

  /**
   * Tries to convert a {@link URL} into a {@link File}. Return null if something bad happens
   * @param fileURL {@link URL} to be converted into a {@link File}.
   * @return {@link File} for this {@link URL} or null.
   */
  public static File URLToFile(URL fileURL) {
    inputNotNull(fileURL);
    try {
     
      final File retFile= DataUtilities.urlToFile(fileURL);
      return retFile;
   
    }catch (Throwable t) {
      if(LOGGER.isLoggable(Level.FINE))
        LOGGER.log(Level.FINE,t.getLocalizedMessage(),t);
    }
    return null;
  }

  /**
   * Copy {@link InputStream} to {@link OutputStream}.
   *
   * @param sourceStream {@link InputStream} to copy from.
   * @param destinationStream {@link OutputStream} to copy to.
   * @param closeInput quietly close {@link InputStream}.
   * @param closeOutput quietly close {@link OutputStream}
   * @throws IOException in case something bad happens.
   */
  public static void copyStream(InputStream sourceStream,
      OutputStream destinationStream, boolean closeInput,
      boolean closeOutput) throws IOException {
    copyStream(sourceStream, destinationStream, DEFAULT_SIZE, closeInput,
        closeOutput);
  }

  /**
   * Copy {@link InputStream} to {@link OutputStream}.
   *
   * @param sourceStream {@link InputStream} to copy from.
   * @param destinationStream {@link OutputStream} to copy to.
   * @param size  size of the buffer to use internally.
   * @param closeInput quietly close {@link InputStream}.
   * @param closeOutput quietly close {@link OutputStream}
   * @throws IOException in case something bad happens.

   */
  public static void copyStream(InputStream sourceStream,
      OutputStream destinationStream, int size, boolean closeInput,
      boolean closeOutput) throws IOException {
   
    inputNotNull(sourceStream,destinationStream);
    byte[] buf = new byte[size];
    int n = -1;
    try {
      while (-1 != (n = sourceStream.read(buf))) {
        destinationStream.write(buf, 0, n);
        destinationStream.flush();
      }
    } finally {
      // closing streams and connections
      try {
        destinationStream.flush();
      } finally {
        try {
          if (closeOutput)
            destinationStream.close();
        } finally {
          try {
            if (closeInput)
              sourceStream.close();
          } finally {

          }
        }
      }
    }
  }

  /**
   * Convert the input from the provided {@link InputStream} into a {@link String}.
   *
   * @param inputStream the {@link InputStream} to copy from.
   * @return a {@link String} that contains the content of the provided {@link InputStream}.
   * @throws IOException in case something bad happens.
   */
  public static String getStringFromStream(InputStream inputStream) throws IOException {
    inputNotNull(inputStream);
        final Reader inReq = new InputStreamReader(inputStream);
        return getStringFromReader(inReq);
  }

  /**
   * Convert the input from the provided {@link Reader} into a {@link String}.
   *
   * @param inputStream the {@link Reader} to copy from.
   * @return a {@link String} that contains the content of the provided {@link Reader}.
   * @throws IOException in case something bad happens.
   */
  public static String getStringFromReader(final Reader inputReader)
      throws IOException {
    inputNotNull(inputReader);
    final StringBuilder sb = new StringBuilder();
        final char[] buffer = new char[1024];
        int len;
        while ((len = inputReader.read(buffer)) >= 0) {
            char[] read = new char[len];
            System.arraycopy(buffer, 0, read, 0, len);
            sb.append(read);
        }
        return sb.toString();
  }
 
 
  /**
   * Convert the input from the provided {@link Reader} into a {@link String}.
   *
   * @param inputStream the {@link Reader} to copy from.
   * @return a {@link String} that contains the content of the provided {@link Reader}.
   * @throws IOException in case something bad happens.
   */
  public static String getStringFromStreamSource(StreamSource src) throws IOException {
     
    inputNotNull(src);
      InputStream inputStream = src.getInputStream();
      if(inputStream != null) {                  
          return getStringFromStream(inputStream);
      }else {
         
          final Reader r = src.getReader();
          return getStringFromReader(r);
      }
  }

  /**
   * Inflate the provided {@link ZipFile} in the provided output directory.
   *
   * @param archive the {@link ZipFile} to inflate.
   * @param outputDirectory the directory where to inflate the archive.
   * @throws IOException in case something bad happens.
   * @throws FileNotFoundException in case something bad happens.
   */
  public static void inflate(ZipFile archive, File outputDirectory, String fileName) throws IOException,
      FileNotFoundException {

    final Enumeration<? extends ZipEntry> entries = archive.entries();
    try {
      while (entries.hasMoreElements()) {
            ZipEntry entry = (ZipEntry)entries.nextElement();
   
            if (!entry.isDirectory()){
              final String name = entry.getName();
              final String ext = FilenameUtils.getExtension(name);
              final InputStream in = new BufferedInputStream(archive.getInputStream(entry));
              final File outFile = new File(outputDirectory, fileName!=null?new StringBuilder(fileName).append(".").append(ext).toString():name);
              final OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile));
   
                IOUtils.copyStream(in, out, true, true);
 
            }
            else {
                //if the entry is a directory attempt to make it
                new File(outputDirectory, entry.getName()).mkdirs();
            }
        }
    }
    finally {
      try {
        archive.close();
      }catch (Throwable e) {
        if(LOGGER.isLoggable(Level.FINE))
          LOGGER.isLoggable(Level.FINE);
      }
    }
    
  }
 

  /**
   * Singleton
   */
  private IOUtils() {
   
  } 
}
TOP

Related Classes of org.geoserver.rest.util.IOUtils$FileCleaner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.