1

I'm struggling to solve an issue whereby a directory and its content is deleted and then later re-created by different process.

However, it seems as if a handle to the file is still kept open by the delete process, and results in an AccessDeniedException being thrown when attempting to recreate. Any help would be greatly appreciated. Sample code as follows:

private static void deleteFilesAndDirectories(Path targetPath, Predicate<? super Path> filter) {
    try {
        try (Stream<Path> eligibleFiles = Files.walk(targetPath)
                .filter(path -> !Files.isDirectory(path))
                .filter(filter)) {

            for (Path file : eligibleFiles.collect(Collectors.toList())) {
                if (Files.isDirectory(file)) {
                    deleteFilesAndDirectories(file, filter);
                }

                Path parentDir = file.getParent();
                Files.delete(file);

                //Delete holding directory
                if (Files.list(parentDir).count() == 0) {
                    Files.delete(parentDir);
                }
            }
        }

    } catch (IOException e) {
        logger.error("Failed to delete directory");  //AccessDeniedException is caught
    }
}

public static void delete(String subdirectory) {
  deleteFilesAndDirectories(Paths.get(subdirectory), path -> true);
}

public static void store(MultipartFile file, String subdirectory) {
  Path subdirectoryPath = Paths.get(subdirectory);

  if (!Files.isDirectory(subdirectoryPath)) {
  try {
    Files.createDirectories(subdirectoryPath);

  } catch (IOException e) {
    throw new StorageException("Could not create sub-directory " + subdirectoryPath.toAbsolutePath(), e);
  }
}

public static void main(String[] args) {
  MultipartFile file = ...;

  delete("mydir"); //Attempts to delete directory, but is still visible in Windows Explorer though access is denied when trying to go into it

  store(file, "mydir");  //Checks if directory already exists, and attempts to create if not there.
}

Stacktrace printed when Files.createDirectories(subdirectoryPath) is executed:

Caused by: java.nio.file.AccessDeniedException: D:\Workspaces\***\application\pending\0
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:83)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
at sun.nio.fs.WindowsFileSystemProvider.createDirectory(WindowsFileSystemProvider.java:504)
at java.nio.file.Files.createDirectory(Files.java:674)
at java.nio.file.Files.createAndCheckIsDirectory(Files.java:781)
at java.nio.file.Files.createDirectories(Files.java:767)
LuCio
  • 5,055
  • 2
  • 18
  • 34
Razeen
  • 252
  • 2
  • 4
  • 14
  • From your comment in the code, it seems that the `AccessDeniedException` occurs in the delete method. – Arnaud Oct 24 '18 at 14:48
  • @Razeen can you put the code `e.printStackTrace();` to capture the failure and report in his question ? Only the print stack error message is important to check the problem – e2a Oct 24 '18 at 15:07
  • Just out of pure curiosity: `eligibleFiles` is a `Stream` whcih `.filter(path -> !Files.isDirectory(path))`. Then you collect the `Stream` to a list and are iterating over the files using the a for-loop. Within this loop you're checking `if (Files.isDirectory(file))`. How can this be the case? – LuCio Oct 24 '18 at 15:44
  • @Arnaud No, the delete method executes, but holds onto the object (a directory) being deleted. The store method then attempts to re-create it as it cannot determine whether it currently exists. – Razeen Oct 24 '18 at 21:43
  • @LuCio Its recursive programming. If a directory has another directory, then clear that directory's files and subdirectories. – Razeen Oct 24 '18 at 21:45
  • @e2a I've added a stacktrace – Razeen Oct 24 '18 at 21:46
  • @Razeen Your code deletes all files of a directory and then the empty directory - but not by recursion but by iteration . All `file`s of `parentDir` are deleted (`Files.delete(file)`) within the for-loop. If the last `file` has been deleted then `parentDir` is empty (`Files.list(parentDir).count() == 0`) and is deleted (`Files.delete(parentDir)`) in the same iteration step too. A recursive call of `deleteFilesAndDirectories` does not happen since `Files.isDirectory(file)` is never the case. because `eligibleFiles` omitted all directories (`filter(path -> !Files.isDirectory(path))`). – LuCio Oct 25 '18 at 07:17
  • Regarding your `AccessDeniedException` [this answer](https://stackoverflow.com/a/31608180/2838289) could explain it. You're deleting the files first. But as the answer describes they aren't deleted by Windows but marked for deletion. In the same process you then going on to create a directory with the same name. The answer says: _Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED._ – LuCio Oct 25 '18 at 07:27
  • Possible duplicate of [Odd behaviour when deleting Files with Files.delete()](https://stackoverflow.com/questions/31606978/odd-behaviour-when-deleting-files-with-files-delete) – LuCio Oct 25 '18 at 07:32
  • @LuCio When you call the same method you're in, then that is recursion. But that's not the issue. The problem originally surfaced through integration tests on a Jenkins build that runs on Linux, so I've been trying to troubleshoot this on my Windows PC. So I only have explanations of the problem, but no clear solution on how to solve it. – Razeen Oct 25 '18 at 21:51

1 Answers1

0

The trouble is in this part of your code:

if (Files.list(parentDir).count() == 0) {
    Files.delete(parentDir);
}

Unfortunately, Files.list(Path) puts some sort of lock on the directory. Here's a workaround for you:

Stream<Path> list = Files.list(parentDir);
int count = list.count();
list.close();
if (0 == count) {
    Files.delete(parentDir);
}
Gary Sheppard
  • 4,764
  • 3
  • 25
  • 35