Adding recursive delete with cumulated result status to FileUtils
diff --git a/archiva-modules/archiva-base/archiva-common/pom.xml b/archiva-modules/archiva-base/archiva-common/pom.xml
index 6e62f14..283cbff 100644
--- a/archiva-modules/archiva-base/archiva-common/pom.xml
+++ b/archiva-modules/archiva-base/archiva-common/pom.xml
@@ -52,6 +52,12 @@
       <scope>test</scope>
     </dependency>
 
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-slf4j-impl</artifactId>
+      <scope>test</scope>
+    </dependency>
+
   </dependencies>
   <build>
     <pluginManagement>
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/FileStatus.java b/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/FileStatus.java
new file mode 100644
index 0000000..78f3ed3
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/FileStatus.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.archiva.common.utils;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * File operation status for a given file.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class FileStatus
+{
+    final private Path path;
+    final private StatusResult result;
+    final private IOException exception;
+
+    /**
+     * Success status
+     *
+     * @param path the file path
+     * @param statusResult the status of the file operation
+     */
+    FileStatus( Path path, StatusResult statusResult) {
+        this.path = path;
+        this.result = statusResult;
+        this.exception = null;
+    }
+
+    /**
+     * Error status
+     * @param path  the file path
+     * @param e the exception, that occured during the file operation
+     */
+    FileStatus( Path path, IOException e) {
+        this.path = path;
+        this.result = StatusResult.ERROR;
+        this.exception = e;
+    }
+
+    public IOException getException( )
+    {
+        return exception;
+    }
+
+    public Path getPath( )
+    {
+        return path;
+    }
+
+    public StatusResult getResult( )
+    {
+        return result;
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/FileUtils.java b/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/FileUtils.java
index 67b27cb..3dc9543 100644
--- a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/FileUtils.java
+++ b/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/FileUtils.java
@@ -29,6 +29,7 @@
 import java.nio.file.StandardOpenOption;
 import java.util.Comparator;
 import java.util.Optional;
+import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
@@ -46,14 +47,17 @@
      * @param dir
      */
     public static void deleteQuietly(Path dir) {
-        try {
-            Files.walk(dir)
-                    .sorted(Comparator.reverseOrder())
-                    .forEach(file -> {
+        try(Stream<Path> stream = Files.walk(dir)) {
+            stream
+                .sorted( Comparator.reverseOrder() )
+                .forEach(file -> {
                         try {
                             Files.delete(file);
                         } catch (IOException e) {
                             // Ignore this
+                            if (log.isDebugEnabled()) {
+                                log.debug( "Exception during file delete: {}", e.getMessage( ), e );
+                            }
                         }
 
                     });
@@ -64,6 +68,38 @@
 
     }
 
+    public static IOStatus deleteDirectoryWithStatus(Path dir) throws IOException {
+        if (!Files.exists(dir)) {
+            IOStatus status = new IOStatus( );
+            status.addError( dir, new FileNotFoundException( "Directory not found " + dir ) );
+            return status;
+        }
+        if (!Files.isDirectory(dir)) {
+            IOStatus status = new IOStatus( );
+            status.addError(dir, new IOException("Given path is not a directory " + dir));
+        }
+        try( Stream<Path> stream = Files.walk(dir)) {
+                return stream
+                    .sorted( Comparator.reverseOrder() )
+                .map(file ->
+                {
+                    try {
+                        Files.delete(file);
+                        return new FileStatus( file, StatusResult.DELETED );
+                    } catch (UncheckedIOException e) {
+                        log.warn("File could not be deleted {}", file);
+                        return new FileStatus( file, e.getCause( ) );
+                    }
+                    catch ( IOException e )
+                    {
+                        return new FileStatus( file, e );
+                    }
+                }).collect( IOStatus::new, IOStatus::accumulate, IOStatus::combine );
+        } catch (UncheckedIOException e) {
+            throw new IOException("File deletion failed ", e);
+        }
+    }
+
     public static void deleteDirectory(Path dir) throws IOException {
         if (!Files.exists(dir)) {
             return;
@@ -72,16 +108,16 @@
             throw new IOException("Given path is not a directory " + dir);
         }
         boolean result = true;
-        try {
-            result = Files.walk(dir)
-                    .sorted(Comparator.reverseOrder())
-                    .map(file ->
+        try(Stream<Path> stream = Files.walk(dir)) {
+            result = stream
+                .sorted( Comparator.reverseOrder() )
+                .map(file ->
                     {
                         try {
                             Files.delete(file);
                             return Optional.of(Boolean.TRUE);
                         } catch (UncheckedIOException | IOException e) {
-                            log.warn("File could not be deleted {}", file);
+                            log.warn("File could not be deleted {}: {}", file, e.getMessage());
                             return Optional.empty();
                         }
 
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/IOStatus.java b/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/IOStatus.java
new file mode 100644
index 0000000..ea67978
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/IOStatus.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.archiva.common.utils;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ *
+ * Collects information about file system operational status, e.g. if a file could be deleted,
+ * or IOException was thrown.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class IOStatus
+{
+
+    Map<Path,IOException> errorList;
+    Map<Path, StatusResult> okList = new TreeMap<>(  );
+
+    /**
+     * Returns <code>true</code>, if no error was recorded.
+     * @return
+     */
+    boolean isOk() {
+        return !hasErrors( );
+    }
+
+    /**
+     * Returns <code>true</code>, if at least one error was recorded
+     * @return
+     */
+    boolean hasErrors() {
+        if (errorList==null || errorList.size()==0) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Accumulator method used for stream collecting
+     *
+     * @param ioStatus
+     * @param fileStatus
+     * @return
+     */
+    public static IOStatus accumulate(IOStatus ioStatus, FileStatus fileStatus) {
+        ioStatus.addStatus( fileStatus );
+        return ioStatus;
+    }
+
+    /**
+     * Combiner used for stream collecting
+     * @param ioStatus1
+     * @param ioStatus2
+     * @return
+     */
+    public static IOStatus combine(IOStatus ioStatus1, IOStatus ioStatus2) {
+        IOStatus status = new IOStatus( );
+        status.addAllSuccess( ioStatus1.getSuccessFiles() );
+        status.addAllSuccess( ioStatus2.getSuccessFiles( ) );
+        status.addAllErrors( ioStatus1.getErrorFiles( ) );
+        status.addAllErrors( ioStatus2.getErrorFiles( ) );
+        return status;
+    }
+
+    /**
+     * Add the status for a specific file to this status collection.
+     *
+     * @param status the status for a given file
+     * @return the status object itself
+     */
+    public IOStatus addStatus(FileStatus status) {
+        if (status.getResult()== StatusResult.ERROR) {
+            addError( status.getPath( ), status.getException( ) );
+        } else {
+            addSuccess( status.getPath( ), status.getResult( ) );
+        }
+        return this;
+    }
+
+    /**
+     * Adds an error to the status collection.
+     *
+     * @param path the file path
+     * @param e the exception thrown during the file operation
+     */
+    public void addError( Path path, IOException e) {
+        if (errorList==null) {
+            errorList = new TreeMap<>( );
+        }
+        errorList.put( path, e );
+    }
+
+    /**
+     * Adds multiple errors to the collection.
+     *
+     * @param errors the map of file, error pairs
+     */
+    public void addAllErrors(Map<Path, IOException> errors) {
+        if (errorList == null) {
+            errorList = new TreeMap<>( );
+        }
+        errorList.putAll( errors );
+    }
+
+    /**
+     * Adds all successful states to the collection.
+     *
+     * @param success a map of file, StatusResult pairs
+     */
+    public void addAllSuccess( Map<Path, StatusResult> success) {
+        okList.putAll( success );
+    }
+
+    /**
+     * Add success status for a given file to the collection.
+     *
+     * @param path the file path
+     * @param status the status of the file operation, e.g. DELETED
+     */
+    public void addSuccess( Path path, StatusResult status) {
+        okList.put( path, status );
+    }
+
+    /**
+     * Returns all the recorded errors as map of path, exception pairs.
+     * @return the map of path, exception pairs.
+     */
+    public Map<Path, IOException> getErrorFiles() {
+        if (errorList==null) {
+            return Collections.emptyMap( );
+        }
+        return errorList;
+    }
+
+    /**
+     * Returns all the recorded successful operations.
+     *
+     * @return the map of path, StatusResult pairs
+     */
+    public Map<Path, StatusResult> getSuccessFiles() {
+        if (okList==null) {
+            return Collections.emptyMap( );
+        }
+        return okList;
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/StatusResult.java b/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/StatusResult.java
new file mode 100644
index 0000000..3544372
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/StatusResult.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.archiva.common.utils;
+
+/**
+ * Result status for file operations.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public enum StatusResult
+{
+    DELETED, EXIST, ERROR
+}
diff --git a/archiva-modules/archiva-base/archiva-common/src/test/java/org/apache/archiva/common/utils/FileUtilsTest.java b/archiva-modules/archiva-base/archiva-common/src/test/java/org/apache/archiva/common/utils/FileUtilsTest.java
index 998efb1..93d40aa 100644
--- a/archiva-modules/archiva-base/archiva-common/src/test/java/org/apache/archiva/common/utils/FileUtilsTest.java
+++ b/archiva-modules/archiva-base/archiva-common/src/test/java/org/apache/archiva/common/utils/FileUtilsTest.java
@@ -34,8 +34,7 @@
 import java.util.HashSet;
 import java.util.Set;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 /**
  * @author Martin Stockhammer <martin_s@apache.org>
@@ -110,6 +109,43 @@
 
     }
 
+
+    @Test
+    public void testDeleteWithStatus() throws IOException
+    {
+        Path td = Files.createTempDirectory( "FileUtilsTest" );
+        Path f1 = td.resolve("file1.txt");
+        Path f2 = td.resolve("file2.txt");
+        Path d1 = td.resolve("dir1");
+        Files.createDirectory( d1 );
+        Path d11 = d1.resolve("dir11");
+        Files.createDirectory( d11 );
+        Path f111 = d11.resolve("file111.txt");
+        Path f112 = d11.resolve("file112.txt");
+        Files.write(f1,"file1".getBytes());
+        Files.write(f2, "file2".getBytes());
+        Files.write(f111, "file111".getBytes());
+        Files.write(f112, "file112".getBytes());
+        assertTrue(Files.exists(d1));
+        assertTrue(Files.exists(f1));
+        assertTrue(Files.exists(f2));
+        assertTrue(Files.exists(f111));
+        assertTrue(Files.exists(f112));
+
+        IOStatus status = FileUtils.deleteDirectoryWithStatus( td );
+        assertFalse(Files.exists(f1));
+        assertFalse(Files.exists(f2));
+        assertFalse(Files.exists(f111));
+        assertFalse(Files.exists(f112));
+        assertFalse(Files.exists(d1));
+
+        assertTrue( status.isOk( ) );
+        assertFalse( status.hasErrors( ) );
+        assertEquals( 7, status.getSuccessFiles( ).size( ) );
+        assertEquals( 0, status.getErrorFiles( ).size() );
+    }
+
+
     @Test
     public void testDeleteNonExist() throws IOException
     {
diff --git a/archiva-modules/archiva-base/archiva-common/src/test/resources/log4j2-test.xml b/archiva-modules/archiva-base/archiva-common/src/test/resources/log4j2-test.xml
index 03547df..c7275f8 100644
--- a/archiva-modules/archiva-base/archiva-common/src/test/resources/log4j2-test.xml
+++ b/archiva-modules/archiva-base/archiva-common/src/test/resources/log4j2-test.xml
@@ -33,7 +33,7 @@
   </appenders>
   <loggers>
     <logger name="org.apache.archiva" level="info"/>
-    <logger name="org.apache.archiva.repository.scanner" level="info"/>
+    <logger name="org.apache.archiva.common.utils" level="info"
     <root level="error" includeLocation="true">
       <appender-ref ref="console"/>
     </root>