@@ -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> |
@@ -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; | |||
} | |||
} |
@@ -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<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 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<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 @@ 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<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(); | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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 | |||
} |
@@ -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 <martin_s@apache.org> | |||
@@ -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 | |||
{ |
@@ -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> |