From dfe9604884b74199ea1880b826be6f578390f5c8 Mon Sep 17 00:00:00 2001 From: Martin Stockhammer Date: Sun, 16 Feb 2020 21:20:37 +0100 Subject: [PATCH] Adding recursive delete with cumulated result status to FileUtils --- .../archiva-base/archiva-common/pom.xml | 6 + .../archiva/common/utils/FileStatus.java | 72 ++++++++ .../archiva/common/utils/FileUtils.java | 54 +++++- .../apache/archiva/common/utils/IOStatus.java | 168 ++++++++++++++++++ .../archiva/common/utils/StatusResult.java | 29 +++ .../archiva/common/utils/FileUtilsTest.java | 40 ++++- .../src/test/resources/log4j2-test.xml | 2 +- 7 files changed, 359 insertions(+), 12 deletions(-) create mode 100644 archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/FileStatus.java create mode 100644 archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/IOStatus.java create mode 100644 archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/common/utils/StatusResult.java diff --git a/archiva-modules/archiva-base/archiva-common/pom.xml b/archiva-modules/archiva-base/archiva-common/pom.xml index 6e62f1417..283cbff41 100644 --- a/archiva-modules/archiva-base/archiva-common/pom.xml +++ b/archiva-modules/archiva-base/archiva-common/pom.xml @@ -52,6 +52,12 @@ test + + org.apache.logging.log4j + log4j-slf4j-impl + test + + 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 000000000..78f3ed361 --- /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 + */ +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 67b27cb19..3dc95432b 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.Paths; 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 @@ public class FileUtils { * @param dir */ public static void deleteQuietly(Path dir) { - try { - Files.walk(dir) - .sorted(Comparator.reverseOrder()) - .forEach(file -> { + try(Stream 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 class FileUtils { } + 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 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 @@ public class FileUtils { 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 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 000000000..ea67978a9 --- /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 + */ +public class IOStatus +{ + + Map errorList; + Map okList = new TreeMap<>( ); + + /** + * Returns true, if no error was recorded. + * @return + */ + boolean isOk() { + return !hasErrors( ); + } + + /** + * Returns true, 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 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 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 getErrorFiles() { + if (errorList==null) { + return Collections.emptyMap( ); + } + return errorList; + } + + /** + * Returns all the recorded successful operations. + * + * @return the map of path, StatusResult pairs + */ + public Map 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 000000000..354437229 --- /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 + */ +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 998efb147..93d40aac3 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.nio.file.attribute.PosixFilePermission; 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 @@ -110,6 +109,43 @@ public class FileUtilsTest } + + @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 03547df43..c7275f874 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 @@ - +