/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.core.util; import static java.lang.System.getProperty; import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage; import org.mule.runtime.api.exception.MuleRuntimeException; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.JarURLConnection; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.nio.channels.Channel; import java.nio.channels.FileChannel; import java.util.Collection; import java.util.Enumeration; import java.util.concurrent.atomic.AtomicLong; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.commons.io.filefilter.FalseFileFilter; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.filefilter.TrueFileFilter; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <code>FileUtils</code> contains useful methods for dealing with files & directories. */ // @ThreadSafe public class FileUtils extends org.apache.commons.io.FileUtils { private static final Logger logger = LoggerFactory.getLogger(FileUtils.class); private static final String TEMP_DIR_SYSTEM_PROPERTY = "java.io.tmpdir"; private static final File TEMP_DIR = new File(getProperty(TEMP_DIR_SYSTEM_PROPERTY)); private static final AtomicLong TEMP_FILE_INDEX = new AtomicLong(-1); public static String DEFAULT_ENCODING = "UTF-8"; public static synchronized void copyStreamToFile(InputStream input, File destination) throws IOException { if (destination.exists() && !destination.canWrite()) { throw new IOException("Destination file does not exist or is not writeable"); } try { FileOutputStream output = new FileOutputStream(destination); try { IOUtils.copy(input, output); } finally { IOUtils.closeQuietly(output); } } finally { IOUtils.closeQuietly(input); } } /** * Cleans a directory without deleting it. * * @param directory directory to clean * @throws IOException in case cleaning is unsuccessful */ public static void cleanDirectory(File directory) throws IOException { org.apache.commons.io.FileUtils.cleanDirectory(directory); } // TODO Document me! public static File createFile(String filename) throws IOException { File file = FileUtils.newFile(filename); if (!file.canWrite()) { String dirName = file.getPath(); int i = dirName.lastIndexOf(File.separator); if (i > -1) { dirName = dirName.substring(0, i); File dir = FileUtils.newFile(dirName); dir.mkdirs(); } file.createNewFile(); } return file; } // TODO Document me! public static String prepareWinFilename(String filename) { filename = filename.replaceAll("<", "("); filename = filename.replaceAll(">", ")"); filename = filename.replaceAll("[/\\*?|:;\\]\\[\"]", "-"); return filename; } // TODO Document me! public static File openDirectory(String directory) throws IOException { File dir = FileUtils.newFile(directory); if (!dir.exists()) { dir.mkdirs(); } if (!dir.isDirectory() || !dir.canRead()) { throw new IOException("Path: " + directory + " exists but isn't a directory"); } return dir; } /** * Reads the incoming String into a file at at the given destination. * * @param filename name and path of the file to create * @param data the contents of the file * @return the new file. * @throws IOException If the creating or writing to the file stream fails */ public static File stringToFile(String filename, String data) throws IOException { return stringToFile(filename, data, false); } // TODO Document me! public static synchronized File stringToFile(String filename, String data, boolean append) throws IOException { return stringToFile(filename, data, append, false); } // TODO Document me! public static synchronized File stringToFile(String filename, String data, boolean append, boolean newLine) throws IOException { File f = createFile(filename); BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(f, append)); writer.write(data); if (newLine) { writer.newLine(); } } finally { if (writer != null) { writer.close(); } } return f; } // TODO Document me! public static String getResourcePath(String resourceName, Class callingClass) throws IOException { return getResourcePath(resourceName, callingClass, DEFAULT_ENCODING); } // TODO Document me! public static String getResourcePath(String resourceName, Class callingClass, String encoding) throws IOException { if (resourceName == null) { // no name return null; } URL url = IOUtils.getResourceAsUrl(resourceName, callingClass); if (url == null) { // not found return null; } return normalizeFilePath(url, encoding); } /** * Remove from uri to file prefix file:/ Add if need file separator to begin * * @param url file uri to resource * @param encoding - Java encoding names * @return normalized file path * @throws UnsupportedEncodingException if encoding is unknown */ public static String normalizeFilePath(URL url, String encoding) throws UnsupportedEncodingException { String resource = URLDecoder.decode(url.toExternalForm(), encoding); if (resource != null) { if (resource.startsWith("file:/")) { resource = resource.substring(6); if (!resource.startsWith(File.separator)) { resource = File.separator + resource; } } } return resource; } /** * Delete a file tree recursively. * * @param dir dir to wipe out * @return false when the first unsuccessful attempt encountered */ public static boolean deleteTree(File dir) { return deleteTree(dir, null); } /** * Delete a file tree recursively. This method additionally tries to be gentle with specified top-level dirs. E.g. this is the * case when a transaction manager asynchronously handles the recovery log, and the test wipes out everything, leaving the * transaction manager puzzled. * * @param dir dir to wipe out * @param topLevelDirsToIgnore which top-level directories to ignore, if null or empty then ignored * @return false when the first unsuccessful attempt encountered */ public static boolean deleteTree(File dir, final String[] topLevelDirsToIgnore) { if (dir == null || !dir.exists()) { return true; } File[] files = dir.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { OUTER: if (files[i].isDirectory()) { if (topLevelDirsToIgnore != null) { for (int j = 0; j < topLevelDirsToIgnore.length; j++) { String ignored = topLevelDirsToIgnore[j]; if (ignored.equals(FilenameUtils.getBaseName(files[i].getName()))) { break OUTER; } } } if (!deleteTree(files[i])) { return false; } } else { if (!files[i].delete()) { return false; } } } } return dir.delete(); } /** * Unzip the specified archive to the given directory */ public static void unzip(File archive, File directory) throws IOException { ZipFile zip = null; if (directory.exists()) { if (!directory.isDirectory()) { throw new IOException("Directory is not a directory: " + directory); } } else { if (!directory.mkdirs()) { throw new IOException("Could not create directory: " + directory); } } try { zip = new ZipFile(archive); for (Enumeration entries = zip.entries(); entries.hasMoreElements();) { ZipEntry entry = (ZipEntry) entries.nextElement(); File f = FileUtils.newFile(directory, entry.getName()); if (entry.isDirectory()) { if (!f.exists() && !f.mkdirs()) { throw new IOException("Could not create directory: " + f); } } else { File file = new File(directory, entry.getName()); if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) { throw new IOException("Unable to create folders for zip entry: " + entry.getName()); } InputStream is = zip.getInputStream(entry); OutputStream os = new BufferedOutputStream(new FileOutputStream(f)); IOUtils.copy(is, os); IOUtils.closeQuietly(is); IOUtils.closeQuietly(os); } } } finally { if (zip != null) { zip.close(); } } } /** * Workaround for JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4117557"> 4117557</a>. More * in-context information at <a href="http://mule.mulesoft.org/jira/browse/MULE-1112">MULE-1112</a> * <p/> * Factory methods correspond to constructors of the <code>java.io.File class</code>. No physical file created in this method. * * @see File */ public static File newFile(String pathName) { try { return new File(pathName).getCanonicalFile(); } catch (IOException e) { throw new MuleRuntimeException(createStaticMessage("Unable to create a canonical file for " + pathName), e); } } /** * Workaround for JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4117557"> 4117557</a>. More * in-context information at <a href="http://mule.mulesoft.org/jira/browse/MULE-1112">MULE-1112</a> * <p/> * Factory methods correspond to constructors of the <code>java.io.File class</code>. No physical file created in this method. * * @see File */ public static File newFile(URI uri) { try { return new File(uri).getCanonicalFile(); } catch (IOException e) { throw new MuleRuntimeException(createStaticMessage("Unable to create a canonical file for " + uri), e); } } /** * Workaround for JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4117557"> 4117557</a>. More * in-context information at <a href="http://mule.mulesoft.org/jira/browse/MULE-1112">MULE-1112</a> * <p/> * Factory methods correspond to constructors of the <code>java.io.File class</code>. No physical file created in this method. * * @see File */ public static File newFile(File parent, String child) { try { return new File(parent, child).getCanonicalFile(); } catch (IOException e) { throw new MuleRuntimeException( createStaticMessage("Unable to create a canonical file for parent: " + parent + " and child: " + child), e); } } /** * Workaround for JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4117557"> 4117557</a>. More * in-context information at <a href="http://mule.mulesoft.org/jira/browse/MULE-1112">MULE-1112</a> * <p/> * Factory methods correspond to constructors of the <code>java.io.File class</code>. No physical file created in this method. * * @see File */ public static File newFile(String parent, String child) { try { return new File(parent, child).getCanonicalFile(); } catch (IOException e) { throw new MuleRuntimeException( createStaticMessage("Unable to create a canonical file for parent: " + parent + " and child: " + child), e); } } /** * Extract the specified resource to the given directory for remain all directory struct * * @param resourceName - full resource name * @param callingClass - classloader for this class is used * @param outputDir - extract to this directory * @param keepParentDirectory true - full structure of directories is kept; false - file - removed all directories, directory - * started from resource point * @throws IOException if any errors */ public static void extractResources(String resourceName, Class callingClass, File outputDir, boolean keepParentDirectory) throws IOException { URL url = callingClass.getClassLoader().getResource(resourceName); URLConnection connection = url.openConnection(); if (connection instanceof JarURLConnection) { extractJarResources((JarURLConnection) connection, outputDir, keepParentDirectory); } else { extractFileResources(normalizeFilePath(url, DEFAULT_ENCODING), outputDir, resourceName, keepParentDirectory); } } /** * Extract resources contain in file * * @param path - path to file * @param outputDir Directory for unpack recources * @param resourceName * @param keepParentDirectory true - full structure of directories is kept; false - file - removed all directories, directory - * started from resource point * @throws IOException if any error */ private static void extractFileResources(String path, File outputDir, String resourceName, boolean keepParentDirectory) throws IOException { File file = FileUtils.newFile(path); if (!file.exists()) { throw new IOException("The resource by path " + path + " "); } if (file.isDirectory()) { if (keepParentDirectory) { outputDir = FileUtils.newFile(outputDir.getPath() + File.separator + resourceName); if (!outputDir.exists()) { outputDir.mkdirs(); } } else { outputDir = FileUtils.newFile(outputDir.getPath()); } copyDirectory(file, outputDir); } else { if (keepParentDirectory) { outputDir = FileUtils.newFile(outputDir.getPath() + File.separator + resourceName); } else { outputDir = FileUtils.newFile(outputDir.getPath() + File.separator + file.getName()); } copyFile(file, outputDir); } } /** * Extract recources contain if jar (have to in classpath) * * @param connection JarURLConnection to jar library * @param outputDir Directory for unpack recources * @param keepParentDirectory true - full structure of directories is kept; false - file - removed all directories, directory - * started from resource point * @throws IOException if any error */ private static void extractJarResources(JarURLConnection connection, File outputDir, boolean keepParentDirectory) throws IOException { JarFile jarFile = connection.getJarFile(); JarEntry jarResource = connection.getJarEntry(); Enumeration entries = jarFile.entries(); InputStream inputStream = null; OutputStream outputStream = null; int jarResourceNameLenght = jarResource.getName().length(); for (; entries.hasMoreElements();) { JarEntry entry = (JarEntry) entries.nextElement(); if (entry.getName().startsWith(jarResource.getName())) { String path = outputDir.getPath() + File.separator + entry.getName(); // remove directory struct for file and first dir for directory if (!keepParentDirectory) { if (entry.isDirectory()) { if (entry.getName().equals(jarResource.getName())) { continue; } path = outputDir.getPath() + File.separator + entry.getName().substring(jarResourceNameLenght, entry.getName().length()); } else { if (entry.getName().length() > jarResourceNameLenght) { path = outputDir.getPath() + File.separator + entry.getName().substring(jarResourceNameLenght, entry.getName().length()); } else { path = outputDir.getPath() + File.separator + entry.getName().substring(entry.getName().lastIndexOf("/"), entry.getName().length()); } } } File file = FileUtils.newFile(path); if (!file.getParentFile().exists()) { if (!file.getParentFile().mkdirs()) { throw new IOException("Could not create directory: " + file.getParentFile()); } } if (entry.isDirectory()) { if (!file.exists() && !file.mkdirs()) { throw new IOException("Could not create directory: " + file); } } else { try { inputStream = jarFile.getInputStream(entry); outputStream = new BufferedOutputStream(new FileOutputStream(file)); IOUtils.copy(inputStream, outputStream); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } } } } public static boolean renameFileHard(String srcFilePath, String destFilePath) { if (StringUtils.isNotBlank(srcFilePath) && StringUtils.isNotBlank(destFilePath)) { return renameFileHard(new File(srcFilePath), new File(destFilePath)); } else { return false; } } public static boolean renameFileHard(File srcFile, File destFile) { boolean isRenamed = false; if (srcFile != null && destFile != null) { logger.debug("Moving file " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath()); if (!destFile.exists()) { try { if (srcFile.isFile()) { logger.debug("Trying to rename file"); FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream(srcFile); out = new FileOutputStream(destFile); out.getChannel().transferFrom(in.getChannel(), 0, srcFile.length()); isRenamed = true; } catch (Exception e) { logger.debug("Error renaming file", e); } finally { if (in != null) { try { in.close(); } catch (Exception inNotClosed) { logger.debug("Error closing input file", inNotClosed); } } if (out != null) { try { out.close(); } catch (Exception outNotClosed) { logger.debug("Error closing output file", outNotClosed); } } } logger.debug("File renamed: " + isRenamed); if (isRenamed) { srcFile.delete(); } else { destFile.delete(); } } else { logger.debug(srcFile.getAbsolutePath() + " is not a valid file."); } } catch (Exception e) { logger.debug("Error renaming file from " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath()); } } else { logger.debug("Error renaming file " + srcFile.getAbsolutePath() + ". Destination file " + destFile.getAbsolutePath() + " already exists."); } } return isRenamed; } public static boolean renameFile(String srcFilePath, String destFilePath) { if (StringUtils.isNotBlank(srcFilePath) && StringUtils.isNotBlank(destFilePath)) { return renameFile(new File(srcFilePath), new File(destFilePath)); } else { return false; } } public static boolean renameFile(File srcFile, File destFile) { boolean isRenamed = false; if (srcFile != null && destFile != null) { logger.debug("Moving file " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath()); if (!destFile.exists()) { try { if (srcFile.isFile()) { logger.debug("Trying to rename file"); isRenamed = srcFile.renameTo(destFile); if (!isRenamed && srcFile.exists()) { logger.debug("Trying hard copy, assuming partition crossing ..."); isRenamed = renameFileHard(srcFile, destFile); } logger.debug("File renamed: " + isRenamed); } else { logger.debug(srcFile.getAbsolutePath() + " is not a valid file"); } } catch (Exception e) { logger.debug("Error moving file from " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath(), e); } } else { logger.debug("Error renaming file " + srcFile.getAbsolutePath() + ". Destination file " + destFile.getAbsolutePath() + " already exists."); } } else { logger.debug("Error renaming file. Source or destination file is null."); } return isRenamed; } /** * Try to move a file by renaming with backup attempt by copying/deleting via NIO. Creates intermidiate directories as required. */ public static boolean moveFileWithCopyFallback(File sourceFile, File destinationFile) { // try fast file-system-level move/rename first boolean success = sourceFile.renameTo(destinationFile); if (!success) { // try again using NIO copy FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(sourceFile); if (!destinationFile.exists()) { FileUtils.createFile(destinationFile.getPath()); } fos = new FileOutputStream(destinationFile); FileChannel srcChannel = fis.getChannel(); FileChannel dstChannel = fos.getChannel(); dstChannel.transferFrom(srcChannel, 0, srcChannel.size()); srcChannel.close(); dstChannel.close(); success = sourceFile.delete(); } catch (IOException ioex) { // grr! success = false; } finally { IOUtils.closeQuietly(fis); IOUtils.closeQuietly(fos); } } return success; } /** * Copy in file to out file * * Don't use java.nio as READ_ONLY memory mapped files cannot be deleted * * @param in * @param out */ public static void safeCopyFile(File in, File out) throws IOException { try { FileInputStream fis = new FileInputStream(in); FileOutputStream fos = new FileOutputStream(out); try { byte[] buf = new byte[1024]; int i = 0; while ((i = fis.read(buf)) != -1) { fos.write(buf, 0, i); } } catch (IOException e) { throw e; } finally { try { if (fis != null) fis.close(); if (fos != null) fos.close(); } catch (IOException e) { throw e; } } } catch (FileNotFoundException e) { throw e; } } // Override the following methods to use a new version of doCopyFile(File // srcFile, File destFile, boolean preserveFileDate) that uses nio to copy file /** * Copies a file to a new location. * <p> * This method copies the contents of the specified source file to the specified destination file. The directory holding the * destination file is created if it does not exist. If the destination file exists, then this method will overwrite it. * * @param srcFile an existing file to copy, must not be <code>null</code> * @param destFile the new file, must not be <code>null</code> * @param preserveFileDate true if the file date of the copy should be the same as the original * @throws NullPointerException if source or destination is <code>null</code> * @throws IOException if source or destination is invalid * @throws IOException if an IO error occurs during copying * @see #copyFileToDirectory(File, File, boolean) */ public static void copyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { if (srcFile == null) { throw new NullPointerException("Source must not be null"); } if (destFile == null) { throw new NullPointerException("Destination must not be null"); } if (srcFile.exists() == false) { throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); } if (srcFile.isDirectory()) { throw new IOException("Source '" + srcFile + "' exists but is a directory"); } if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); } if (destFile.getParentFile() != null && destFile.getParentFile().exists() == false) { if (destFile.getParentFile().mkdirs() == false) { throw new IOException("Destination '" + destFile + "' directory cannot be created"); } } if (destFile.exists() && destFile.canWrite() == false) { throw new IOException("Destination '" + destFile + "' exists but is read-only"); } doCopyFile(srcFile, destFile, preserveFileDate); } /** * Internal copy file method. * * @param srcFile the validated source file, must not be <code>null</code> * @param destFile the validated destination file, must not be <code>null</code> * @param preserveFileDate whether to preserve the file date * @throws IOException if an error occurs */ private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { if (destFile.exists() && destFile.isDirectory()) { throw new IOException("Destination '" + destFile + "' exists but is a directory"); } FileChannel input = new FileInputStream(srcFile).getChannel(); try { FileChannel output = new FileOutputStream(destFile).getChannel(); try { output.transferFrom(input, 0, input.size()); } finally { closeQuietly(output); } } finally { closeQuietly(input); } if (srcFile.length() != destFile.length()) { throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "'"); } if (preserveFileDate) { destFile.setLastModified(srcFile.lastModified()); } } /** * Unconditionally close a <code>Channel</code>. * <p> * Equivalent to {@link Channel#close()}, except any exceptions will be ignored. This is typically used in finally blocks. * * @param channel the Channel to close, may be null or already closed */ public static void closeQuietly(Channel channel) { try { if (channel != null) { channel.close(); } } catch (IOException ioe) { // ignore } } public static boolean isFile(URL url) { return "file".equals(url.getProtocol()); } /** * Returns a file timestamp. * * @param url the file URL. * @return the file's timestamp if the URL has the file protocol, otherwise. returns -1. */ public static long getFileTimeStamp(URL url) { long timeStamp = -1; if (isFile(url)) { try { String file = URLDecoder.decode(url.getFile(), "UTF-8"); timeStamp = new File(file).lastModified(); } catch (UnsupportedEncodingException e) { // Ignore } } return timeStamp; } public static Collection<File> findFiles(File folder, IOFileFilter filter, boolean recursive) { return listFiles(folder, filter, recursive ? TrueFileFilter.INSTANCE : FalseFileFilter.INSTANCE); } public static File findFileByName(File folder, final String filename, boolean recursive) { Collection<File> files = FileUtils.findFiles(folder, new IOFileFilter() { @Override public boolean accept(File file) { return filename.equals(file.getName()); } @Override public boolean accept(File dir, String name) { return true; } }, true); return CollectionUtils.isEmpty(files) ? null : files.iterator().next(); } /** * Creates a temporal file for buffering. The file is stored in the system temporal * folder. * * @param prefix the file's prefix * @param suffix the file's suffix * @return a {@link File} * @throws RuntimeException */ public static File createTempFile(String prefix, String suffix) { long n = TEMP_FILE_INDEX.addAndGet(1); if (!TEMP_DIR.exists()) { throw new MuleRuntimeException(createStaticMessage("Temp directory '" + TEMP_DIR.getAbsolutePath() + "' does not exist. " + "Please check the value of the '" + TEMP_DIR_SYSTEM_PROPERTY + "' system property.")); } return new File(TEMP_DIR, prefix + n + suffix); } }