aboutsummaryrefslogtreecommitdiffstats
path: root/archiva-modules
diff options
context:
space:
mode:
authorMartin Stockhammer <martin_s@apache.org>2019-05-19 17:30:10 +0200
committerMartin Stockhammer <martin_s@apache.org>2019-05-19 17:30:10 +0200
commit631ccdf51711978daf5943169dd695a9db1a2c40 (patch)
tree7c670a3be3dc3bec650a8c903f558a64dd6fe2d4 /archiva-modules
parent02cf1bd7e78603ec8c159822f20fe1c20417488f (diff)
downloadarchiva-631ccdf51711978daf5943169dd695a9db1a2c40.tar.gz
archiva-631ccdf51711978daf5943169dd695a9db1a2c40.zip
Adding storage implementations
Diffstat (limited to 'archiva-modules')
-rw-r--r--archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java463
-rw-r--r--archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemStorage.java180
-rw-r--r--archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemAssetTest.java203
-rw-r--r--archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemStorageTest.java208
4 files changed, 844 insertions, 210 deletions
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java
index ef3aad306..6baed7869 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemAsset.java
@@ -1,24 +1,32 @@
package org.apache.archiva.repository.content;
+/*
+ * 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.
+ */
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclEntryPermission;
-import java.nio.file.attribute.AclEntryType;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.*;
+import java.nio.file.attribute.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
@@ -27,330 +35,365 @@ import java.util.Set;
import java.util.stream.Collectors;
/**
+ * Implementation of an asset that is stored on the filesystem.
+ * <p>
+ * The implementation does not check the given paths. Caller should normalize the asset path
+ * and check, if the base path is a parent of the resulting path.
+ * <p>
+ * The file must not exist for all operations.
+ *
* @author Martin Stockhammer <martin_s@apache.org>
*/
-public class FilesystemAsset implements StorageAsset
-{
+public class FilesystemAsset implements StorageAsset {
- private final static Logger log = LoggerFactory.getLogger( FilesystemAsset.class );
+ private final static Logger log = LoggerFactory.getLogger(FilesystemAsset.class);
- private final Path basePath;
private final Path assetPath;
- private final Path completeAssetPath;
+ private final String relativePath;
+
+ public static final String DEFAULT_POSIX_FILE_PERMS = "rw-rw----";
+ public static final String DEFAULT_POSIX_DIR_PERMS = "rwxrwx---";
+
+ public static final Set<PosixFilePermission> DEFAULT_POSIX_FILE_PERMISSIONS;
+ public static final Set<PosixFilePermission> DEFAULT_POSIX_DIR_PERMISSIONS;
+
+ public static final AclEntryPermission[] DEFAULT_ACL_FILE_PERMISSIONS = new AclEntryPermission[]{
+ AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
+ AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA
+ };
+
+ public static final AclEntryPermission[] DEFAULT_ACL_DIR_PERMISSIONS = new AclEntryPermission[]{
+ AclEntryPermission.ADD_FILE, AclEntryPermission.ADD_SUBDIRECTORY, AclEntryPermission.DELETE_CHILD,
+ AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
+ AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA
+ };
+
+ static {
+
+ DEFAULT_POSIX_FILE_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_FILE_PERMS);
+ DEFAULT_POSIX_DIR_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_DIR_PERMS);
+ }
- public String DEFAULT_POSIX_FILE_PERMS = "rw-rw----";
- public String DEFAULT_POSIX_DIR_PERMS = "rwxrwx---";
+ Set<PosixFilePermission> defaultPosixFilePermissions = DEFAULT_POSIX_FILE_PERMISSIONS;
+ Set<PosixFilePermission> defaultPosixDirectoryPermissions = DEFAULT_POSIX_DIR_PERMISSIONS;
List<AclEntry> defaultFileAcls;
- Set<PosixFilePermission> defaultPosixFilePermissions;
List<AclEntry> defaultDirectoryAcls;
- Set<PosixFilePermission> defaultPosixDirectoryPermissions;
boolean supportsAcl = false;
boolean supportsPosix = false;
+ final boolean setPermissionsForNew;
+
+ boolean directoryHint = false;
+
+ /**
+ * Creates an asset for the given path. The given paths are not checked.
+ * The base path should be an absolute path.
+ *
+ * @param path The logical path for the asset relative to the repository.
+ * @param assetPath The asset path.
+ */
+ public FilesystemAsset(String path, Path assetPath) {
+ this.assetPath = assetPath;
+ this.relativePath = path;
+ this.setPermissionsForNew = false;
+ init();
+ }
- boolean directory = false;
-
- public FilesystemAsset( Path basePath, String assetPath )
- {
- this.basePath = basePath;
- this.assetPath = Paths.get( assetPath );
- this.completeAssetPath = basePath.resolve( assetPath ).toAbsolutePath( );
- init( );
+ /**
+ * Creates an asset for the given path. The given paths are not checked.
+ * The base path should be an absolute path.
+ *
+ * @param path The logical path for the asset relative to the repository
+ * @param assetPath The asset path.
+ * @param directory This is only relevant, if the represented file or directory does not exist yet and
+ * is a hint.
+ */
+ public FilesystemAsset(String path, Path assetPath, boolean directory) {
+ this.assetPath = assetPath;
+ this.relativePath = path;
+ this.directoryHint = directory;
+ this.setPermissionsForNew = false;
+ init();
}
- public FilesystemAsset( Path basePath, String assetPath, boolean directory )
- {
- this.basePath = basePath;
- this.assetPath = Paths.get( assetPath );
- this.completeAssetPath = basePath.resolve( assetPath ).toAbsolutePath( );
- this.directory = directory;
- init( );
+ /**
+ * Creates an asset for the given path. The given paths are not checked.
+ * The base path should be an absolute path.
+ *
+ * @param path The logical path for the asset relative to the repository
+ * @param assetPath The asset path.
+ * @param directory This is only relevant, if the represented file or directory does not exist yet and
+ * is a hint.
+ */
+ public FilesystemAsset(String path, Path assetPath, boolean directory, boolean setPermissionsForNew) {
+ this.assetPath = assetPath;
+ this.relativePath = path;
+ this.directoryHint = directory;
+ this.setPermissionsForNew = setPermissionsForNew;
+ init();
}
- private void init( )
- {
- defaultFileAcls = new ArrayList<>( );
- AclEntry.Builder aclBuilder = AclEntry.newBuilder( );
- aclBuilder.setPermissions( AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
- AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA );
- aclBuilder.setType( AclEntryType.ALLOW );
- defaultFileAcls.add( aclBuilder.build( ) );
- AclEntry.Builder aclDirBuilder = AclEntry.newBuilder( );
- aclDirBuilder.setPermissions( AclEntryPermission.ADD_FILE, AclEntryPermission.ADD_SUBDIRECTORY, AclEntryPermission.DELETE_CHILD,
- AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
- AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA );
- aclDirBuilder.setType( AclEntryType.ALLOW );
- defaultDirectoryAcls.add( aclDirBuilder.build( ) );
+ private void init() {
+
+ if (setPermissionsForNew) {
+ try {
+ supportsAcl = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(AclFileAttributeView.class);
+ } catch (IOException e) {
+ log.error("Could not check filesystem capabilities {}", e.getMessage());
+ }
+ try {
+ supportsPosix = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(PosixFileAttributeView.class);
+ } catch (IOException e) {
+ log.error("Could not check filesystem capabilities {}", e.getMessage());
+ }
- defaultPosixFilePermissions = PosixFilePermissions.fromString( DEFAULT_POSIX_FILE_PERMS );
- defaultPosixDirectoryPermissions = PosixFilePermissions.fromString( DEFAULT_POSIX_DIR_PERMS );
+ if (supportsAcl) {
+ AclFileAttributeView aclView = Files.getFileAttributeView(assetPath.getParent(), AclFileAttributeView.class);
+ UserPrincipal owner = null;
+ try {
+ owner = aclView.getOwner();
+ setDefaultFileAcls(processPermissions(owner, DEFAULT_ACL_FILE_PERMISSIONS));
+ setDefaultDirectoryAcls(processPermissions(owner, DEFAULT_ACL_DIR_PERMISSIONS));
- try
- {
- supportsAcl = Files.getFileStore( completeAssetPath ).supportsFileAttributeView( AclFileAttributeView.class );
- }
- catch ( IOException e )
- {
- log.error( "Could not check filesystem capabilities {}", e.getMessage( ) );
- }
- try
- {
- supportsPosix = Files.getFileStore( completeAssetPath ).supportsFileAttributeView( PosixFileAttributeView.class );
- }
- catch ( IOException e )
- {
- log.error( "Could not check filesystem capabilities {}", e.getMessage( ) );
+ } catch (IOException e) {
+ supportsAcl = false;
+ }
+
+
+ }
}
+ }
+ private List<AclEntry> processPermissions(UserPrincipal owner, AclEntryPermission[] defaultAclFilePermissions) {
+ AclEntry.Builder aclBuilder = AclEntry.newBuilder();
+ aclBuilder.setPermissions(defaultAclFilePermissions);
+ aclBuilder.setType(AclEntryType.ALLOW);
+ aclBuilder.setPrincipal(owner);
+ ArrayList<AclEntry> aclList = new ArrayList<>();
+ aclList.add(aclBuilder.build());
+ return aclList;
}
@Override
- public String getPath( )
- {
- return assetPath.toString( );
+ public String getPath() {
+ return relativePath;
}
@Override
- public String getName( )
- {
- return assetPath.getFileName( ).toString( );
+ public String getName() {
+ return assetPath.getFileName().toString();
}
@Override
- public Instant getModificationTime( )
- {
- try
- {
- return Files.getLastModifiedTime( completeAssetPath ).toInstant( );
- }
- catch ( IOException e )
- {
- log.error( "Could not read modification time of {}", completeAssetPath );
- return Instant.now( );
+ public Instant getModificationTime() {
+ try {
+ return Files.getLastModifiedTime(assetPath).toInstant();
+ } catch (IOException e) {
+ log.error("Could not read modification time of {}", assetPath);
+ return Instant.now();
}
}
+ /**
+ * Returns true, if the path of this asset points to a directory
+ *
+ * @return
+ */
@Override
- public boolean isContainer( )
- {
- return Files.isDirectory( completeAssetPath );
+ public boolean isContainer() {
+ if (Files.exists(assetPath)) {
+ return Files.isDirectory(assetPath);
+ } else {
+ return directoryHint;
+ }
}
+ /**
+ * Returns the list of directory entries, if this asset represents a directory.
+ * Otherwise a empty list will be returned.
+ *
+ * @return The list of entries in the directory, if it exists.
+ */
@Override
- public List<StorageAsset> list( )
- {
- try
- {
- return Files.list( completeAssetPath ).map( p -> new FilesystemAsset( basePath, basePath.relativize( p ).toString( ) ) )
- .collect( Collectors.toList( ) );
- }
- catch ( IOException e )
- {
+ public List<StorageAsset> list() {
+ try {
+ return Files.list(assetPath).map(p -> new FilesystemAsset(relativePath + "/" + p.getFileName().toString(), assetPath.resolve(p)))
+ .collect(Collectors.toList());
+ } catch (IOException e) {
return Collections.EMPTY_LIST;
}
}
+ /**
+ * Returns the size of the represented file. If it cannot be determined, -1 is returned.
+ *
+ * @return
+ */
@Override
- public long getSize( )
- {
- try
- {
- return Files.size( completeAssetPath );
- }
- catch ( IOException e )
- {
+ public long getSize() {
+ try {
+ return Files.size(assetPath);
+ } catch (IOException e) {
return -1;
}
}
+ /**
+ * Returns a input stream to the underlying file, if it exists. The caller has to make sure, that
+ * the stream is closed after it was used.
+ *
+ * @return
+ * @throws IOException
+ */
@Override
- public InputStream getData( ) throws IOException
- {
- return Files.newInputStream( completeAssetPath );
+ public InputStream getData() throws IOException {
+ if (isContainer()) {
+ throw new IOException("Can not create input stream for container");
+ }
+ return Files.newInputStream(assetPath);
}
@Override
- public OutputStream writeData( boolean replace ) throws IOException
- {
+ public OutputStream writeData(boolean replace) throws IOException {
OpenOption[] options;
- if ( replace )
- {
+ if (replace) {
options = new OpenOption[]{StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE};
- }
- else
- {
+ } else {
options = new OpenOption[]{StandardOpenOption.APPEND};
}
- return Files.newOutputStream( completeAssetPath, options );
+ return Files.newOutputStream(assetPath, options);
}
@Override
- public boolean storeDataFile( Path newData ) throws IOException
- {
- final boolean createNew = !Files.exists( completeAssetPath );
+ public boolean storeDataFile(Path newData) throws IOException {
+ final boolean createNew = !Files.exists(assetPath);
Path backup = null;
- if ( !createNew )
- {
- backup = findBackupFile( completeAssetPath );
+ if (!createNew) {
+ backup = findBackupFile(assetPath);
}
- try
- {
- if ( !createNew )
- {
- Files.move( completeAssetPath, backup );
+ try {
+ if (!createNew) {
+ Files.move(assetPath, backup);
}
- Files.move( newData, completeAssetPath, StandardCopyOption.REPLACE_EXISTING );
- setDefaultPermissions( completeAssetPath );
+ Files.move(newData, assetPath, StandardCopyOption.REPLACE_EXISTING);
+ applyDefaultPermissions(assetPath);
return true;
- }
- catch ( IOException e )
- {
- log.error( "Could not overwrite file {}", completeAssetPath );
+ } catch (IOException e) {
+ log.error("Could not overwrite file {}", assetPath);
// Revert if possible
- if ( backup != null && Files.exists( backup ) )
- {
- Files.move( backup, completeAssetPath, StandardCopyOption.REPLACE_EXISTING );
+ if (backup != null && Files.exists(backup)) {
+ Files.move(backup, assetPath, StandardCopyOption.REPLACE_EXISTING);
}
throw e;
- }
- finally
- {
- if ( backup != null )
- {
- try
- {
- Files.deleteIfExists( backup );
- }
- catch ( IOException e )
- {
- log.error( "Could not delete backup file {}", backup );
+ } finally {
+ if (backup != null) {
+ try {
+ Files.deleteIfExists(backup);
+ } catch (IOException e) {
+ log.error("Could not delete backup file {}", backup);
}
}
}
}
- private void setDefaultPermissions(Path filePath) {
- try
- {
- if ( supportsPosix )
- {
+ private void applyDefaultPermissions(Path filePath) {
+ try {
+ if (supportsPosix) {
Set<PosixFilePermission> perms;
- if ( Files.isDirectory( filePath ) )
- {
+ if (Files.isDirectory(filePath)) {
perms = defaultPosixFilePermissions;
- }
- else
- {
+ } else {
perms = defaultPosixDirectoryPermissions;
}
- Files.setPosixFilePermissions( filePath, perms );
- }
- else if ( supportsAcl )
- {
+ Files.setPosixFilePermissions(filePath, perms);
+ } else if (supportsAcl) {
List<AclEntry> perms;
- if ( Files.isDirectory( filePath ) )
- {
- perms = defaultDirectoryAcls;
+ if (Files.isDirectory(filePath)) {
+ perms = getDefaultDirectoryAcls();
+ } else {
+ perms = getDefaultFileAcls();
}
- else
- {
- perms = defaultFileAcls;
- }
- AclFileAttributeView aclAttr = Files.getFileAttributeView( filePath, AclFileAttributeView.class );
- aclAttr.setAcl( perms );
+ AclFileAttributeView aclAttr = Files.getFileAttributeView(filePath, AclFileAttributeView.class);
+ aclAttr.setAcl(perms);
}
} catch (IOException e) {
log.error("Could not set permissions for {}: {}", filePath, e.getMessage());
}
}
- private Path findBackupFile( Path file )
- {
+ private Path findBackupFile(Path file) {
String ext = ".bak";
- Path backupPath = file.getParent( ).resolve( file.getFileName( ).toString( ) + ext );
+ Path backupPath = file.getParent().resolve(file.getFileName().toString() + ext);
int idx = 0;
- while ( Files.exists( backupPath ) )
- {
- backupPath = file.getParent( ).resolve( file.getFileName( ).toString( ) + ext + idx++ );
+ while (Files.exists(backupPath)) {
+ backupPath = file.getParent().resolve(file.getFileName().toString() + ext + idx++);
}
return backupPath;
}
@Override
- public boolean exists( )
- {
- return Files.exists( completeAssetPath );
+ public boolean exists() {
+ return Files.exists(assetPath);
}
@Override
- public Path getFilePath( ) throws UnsupportedOperationException
- {
- return completeAssetPath;
+ public Path getFilePath() throws UnsupportedOperationException {
+ return assetPath;
}
- public void setDefaultFileAcls( List<AclEntry> acl )
- {
+ public void setDefaultFileAcls(List<AclEntry> acl) {
defaultFileAcls = acl;
}
- public List<AclEntry> getDefaultFileAcls( )
- {
+ public List<AclEntry> getDefaultFileAcls() {
return defaultFileAcls;
}
- public void setDefaultPosixFilePermissions( Set<PosixFilePermission> perms )
- {
+ public void setDefaultPosixFilePermissions(Set<PosixFilePermission> perms) {
defaultPosixFilePermissions = perms;
}
- public Set<PosixFilePermission> getDefaultPosixFilePermissions( )
- {
+ public Set<PosixFilePermission> getDefaultPosixFilePermissions() {
return defaultPosixFilePermissions;
}
- public void setDefaultDirectoryAcls( List<AclEntry> acl )
- {
+ public void setDefaultDirectoryAcls(List<AclEntry> acl) {
defaultDirectoryAcls = acl;
}
- public List<AclEntry> getDefaultDirectoryAcls( )
- {
+ public List<AclEntry> getDefaultDirectoryAcls() {
return defaultDirectoryAcls;
}
- public void setDefaultPosixDirectoryPermissions( Set<PosixFilePermission> perms )
- {
+ public void setDefaultPosixDirectoryPermissions(Set<PosixFilePermission> perms) {
defaultPosixDirectoryPermissions = perms;
}
- public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions( )
- {
+ public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions() {
return defaultPosixDirectoryPermissions;
}
@Override
- public void create( ) throws IOException
- {
- if ( !Files.exists( completeAssetPath ) )
- {
- if ( directory )
- {
- Files.createDirectories( completeAssetPath );
+ public void create() throws IOException {
+ if (!Files.exists(assetPath)) {
+ if (directoryHint) {
+ Files.createDirectories(assetPath);
} else {
- Files.createFile( completeAssetPath );
+ Files.createFile(assetPath);
+ }
+ if (setPermissionsForNew) {
+ applyDefaultPermissions(assetPath);
}
- setDefaultPermissions( completeAssetPath );
}
}
@Override
- public String toString( )
- {
- return assetPath.toString();
+ public String toString() {
+ return relativePath;
}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemStorage.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemStorage.java
new file mode 100644
index 000000000..65b5610be
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemStorage.java
@@ -0,0 +1,180 @@
+package org.apache.archiva.repository.content;
+
+/*
+ * 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.
+ */
+
+import org.apache.archiva.common.filelock.FileLockException;
+import org.apache.archiva.common.filelock.FileLockManager;
+import org.apache.archiva.common.filelock.FileLockTimeoutException;
+import org.apache.archiva.common.filelock.Lock;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Consumer;
+
+/**
+ * Implementation of <code>{@link RepositoryStorage}</code> where data is stored in the filesystem.
+ *
+ * All files are relative to a given base path. Path values are separated by '/', '..' is allowed to navigate
+ * to a parent directory, but navigation out of the base path will lead to a exception.
+ */
+public class FilesystemStorage implements RepositoryStorage {
+
+ private static final Logger log = LoggerFactory.getLogger(FilesystemStorage.class);
+
+ private final Path basePath;
+ private final FileLockManager fileLockManager;
+
+ public FilesystemStorage(Path basePath, FileLockManager fileLockManager) throws IOException {
+ this.basePath = basePath.normalize().toRealPath();
+ this.fileLockManager = fileLockManager;
+ }
+
+ private Path normalize(final String path) {
+ String nPath = path;
+ while (nPath.startsWith("/")) {
+ nPath = nPath.substring(1);
+ }
+ return Paths.get(nPath);
+ }
+
+ private Path getAssetPath(String path) throws IOException {
+ Path assetPath = basePath.resolve(normalize(path)).normalize();
+ if (!assetPath.startsWith(basePath))
+ {
+ throw new IOException("Path navigation out of allowed scope: "+path);
+ }
+ return assetPath;
+ }
+
+ @Override
+ public void consumeData( StorageAsset asset, Consumer<InputStream> consumerFunction, boolean readLock ) throws IOException
+ {
+ final Path path = asset.getFilePath();
+ try {
+ if (readLock) {
+ consumeDataLocked( path, consumerFunction );
+ } else
+ {
+ try ( InputStream is = Files.newInputStream( path ) )
+ {
+ consumerFunction.accept( is );
+ }
+ catch ( IOException e )
+ {
+ log.error("Could not read the input stream from file {}", path);
+ throw e;
+ }
+ }
+ } catch (RuntimeException e)
+ {
+ log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
+ throw new IOException( e );
+ }
+
+ }
+
+ private void consumeDataLocked( Path file, Consumer<InputStream> consumerFunction) throws IOException
+ {
+
+ final Lock lock;
+ try
+ {
+ lock = fileLockManager.readFileLock( file );
+ try ( InputStream is = Files.newInputStream( lock.getFile()))
+ {
+ consumerFunction.accept( is );
+ }
+ catch ( IOException e )
+ {
+ log.error("Could not read the input stream from file {}", file);
+ throw e;
+ } finally
+ {
+ fileLockManager.release( lock );
+ }
+ }
+ catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
+ {
+ log.error("Locking error on file {}", file);
+ throw new IOException(e);
+ }
+ }
+
+
+ @Override
+ public StorageAsset getAsset( String path )
+ {
+ try {
+ return new FilesystemAsset( path, getAssetPath(path));
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Path navigates outside of base directory "+path);
+ }
+ }
+
+ @Override
+ public StorageAsset addAsset( String path, boolean container )
+ {
+ try {
+ return new FilesystemAsset( path, getAssetPath(path), container);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Path navigates outside of base directory "+path);
+ }
+ }
+
+ @Override
+ public void removeAsset( StorageAsset asset ) throws IOException
+ {
+ Files.delete(asset.getFilePath());
+ }
+
+ @Override
+ public StorageAsset moveAsset( StorageAsset origin, String destination ) throws IOException
+ {
+ boolean container = origin.isContainer();
+ FilesystemAsset newAsset = new FilesystemAsset( destination, getAssetPath(destination), container );
+ Files.move(origin.getFilePath(), newAsset.getFilePath());
+ return newAsset;
+ }
+
+ @Override
+ public StorageAsset copyAsset( StorageAsset origin, String destination ) throws IOException
+ {
+ boolean container = origin.isContainer();
+ FilesystemAsset newAsset = new FilesystemAsset( destination, getAssetPath(destination), container );
+ if (Files.exists(newAsset.getFilePath())) {
+ throw new IOException("Destination file exists already "+ newAsset.getFilePath());
+ }
+ if (Files.isDirectory( origin.getFilePath() ))
+ {
+ FileUtils.copyDirectory(origin.getFilePath( ).toFile(), newAsset.getFilePath( ).toFile() );
+ } else if (Files.isRegularFile( origin.getFilePath() )) {
+ FileUtils.copyFile(origin.getFilePath( ).toFile(), newAsset.getFilePath( ).toFile() );
+ }
+ return newAsset;
+ }
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemAssetTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemAssetTest.java
new file mode 100644
index 000000000..b06546a1f
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemAssetTest.java
@@ -0,0 +1,203 @@
+package org.apache.archiva.repository.content;
+
+/*
+ * 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.
+ */
+
+import org.apache.commons.io.IOUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+
+import static org.junit.Assert.*;
+
+public class FilesystemAssetTest {
+
+ Path assetPathFile;
+ Path assetPathDir;
+
+ @Before
+ public void init() throws IOException {
+ assetPathFile = Files.createTempFile("assetFile", "dat");
+ assetPathDir = Files.createTempDirectory("assetDir");
+ }
+
+ @After
+ public void cleanup() {
+
+ try {
+ Files.deleteIfExists(assetPathFile);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ Files.deleteIfExists(assetPathDir);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @Test
+ public void getPath() {
+ FilesystemAsset asset = new FilesystemAsset("/"+assetPathFile.getFileName().toString(), assetPathFile);
+ assertEquals("/"+assetPathFile.getFileName().toString(), asset.getPath());
+ }
+
+ @Test
+ public void getName() {
+ FilesystemAsset asset = new FilesystemAsset("/"+assetPathFile.getFileName().toString(), assetPathFile);
+ assertEquals(assetPathFile.getFileName().toString(), asset.getName());
+
+ }
+
+ @Test
+ public void getModificationTime() throws IOException {
+ Instant modTime = Files.getLastModifiedTime(assetPathFile).toInstant();
+ FilesystemAsset asset = new FilesystemAsset("/test123", assetPathFile);
+ assertTrue(modTime.equals(asset.getModificationTime()));
+ }
+
+ @Test
+ public void isContainer() {
+ FilesystemAsset asset = new FilesystemAsset("/test1323", assetPathFile);
+ assertFalse(asset.isContainer());
+ FilesystemAsset asset2 = new FilesystemAsset("/test1234", assetPathDir);
+ assertTrue(asset2.isContainer());
+ }
+
+ @Test
+ public void list() throws IOException {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathFile);
+ assertEquals(0, asset.list().size());
+
+ FilesystemAsset asset2 = new FilesystemAsset("/test1235", assetPathDir);
+ assertEquals(0, asset2.list().size());
+ Path f1 = Files.createTempFile(assetPathDir, "testfile", "dat");
+ Path f2 = Files.createTempFile(assetPathDir, "testfile", "dat");
+ Path d1 = Files.createTempDirectory(assetPathDir, "testdir");
+ assertEquals(3, asset2.list().size());
+ assertTrue(asset2.list().stream().anyMatch(p -> p.getName().equals(f1.getFileName().toString())));
+ assertTrue(asset2.list().stream().anyMatch(p -> p.getName().equals(f2.getFileName().toString())));
+ assertTrue(asset2.list().stream().anyMatch(p -> p.getName().equals(d1.getFileName().toString())));
+ Files.deleteIfExists(f1);
+ Files.deleteIfExists(f2);
+ Files.deleteIfExists(d1);
+
+
+ }
+
+ @Test
+ public void getSize() throws IOException {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathFile);
+ assertEquals(0, asset.getSize());
+
+ Files.write(assetPathFile, new String("abcdef").getBytes("ASCII"));
+ assertTrue(asset.getSize()>=6);
+
+
+ }
+
+ @Test
+ public void getData() throws IOException {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathFile);
+ Files.write(assetPathFile, "abcdef".getBytes("ASCII"));
+ try(InputStream is = asset.getData()) {
+ assertEquals("abcdef", IOUtils.toString(is, "ASCII"));
+ }
+
+ }
+
+ @Test
+ public void getDataExceptionOnDir() throws IOException {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathDir);
+ Files.write(assetPathFile, "abcdef".getBytes("ASCII"));
+ try {
+ InputStream is = asset.getData();
+ assertFalse("Exception expected for data on dir", true);
+ } catch (IOException e) {
+ // fine
+ }
+
+ }
+
+ @Test
+ public void writeData() throws IOException {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathFile);
+ Files.write(assetPathFile, "abcdef".getBytes("ASCII"));
+ try(OutputStream os = asset.writeData(true)) {
+ IOUtils.write("test12345", os, "ASCII");
+ }
+ assertEquals("test12345", IOUtils.toString(assetPathFile.toUri().toURL(), "ASCII"));
+ }
+
+ @Test
+ public void writeDataAppend() throws IOException {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathFile);
+ Files.write(assetPathFile, "abcdef".getBytes("ASCII"));
+ try(OutputStream os = asset.writeData(false)) {
+ IOUtils.write("test12345", os, "ASCII");
+ }
+ assertEquals("abcdeftest12345", IOUtils.toString(assetPathFile.toUri().toURL(), "ASCII"));
+ }
+
+ @Test
+ public void writeDataExceptionOnDir() throws IOException {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathDir);
+ try {
+
+ OutputStream os = asset.writeData(true);
+ assertTrue("Writing to a directory should throw a IOException", false);
+ } catch (IOException e) {
+ // Fine
+ }
+ }
+
+ @Test
+ public void storeDataFile() throws IOException {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathFile);
+ Path dataFile = Files.createTempFile("testdata", "dat");
+ try(OutputStream os = Files.newOutputStream(dataFile)) {
+ IOUtils.write("testkdkdkd", os, "ASCII");
+ }
+ asset.storeDataFile(dataFile);
+ assertEquals("testkdkdkd", IOUtils.toString(assetPathFile.toUri().toURL(), "ASCII"));
+ }
+
+ @Test
+ public void exists() {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathFile);
+ assertTrue(asset.exists());
+ FilesystemAsset asset2 = new FilesystemAsset("/test1234", Paths.get("abcdefgkdkdk"));
+ assertFalse(asset2.exists());
+
+ }
+
+ @Test
+ public void getFilePath() {
+ FilesystemAsset asset = new FilesystemAsset("/test1234", assetPathFile);
+ assertEquals(assetPathFile, asset.getFilePath());
+ }
+} \ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemStorageTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemStorageTest.java
new file mode 100644
index 000000000..309c755e6
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemStorageTest.java
@@ -0,0 +1,208 @@
+package org.apache.archiva.repository.content;
+
+/*
+ * 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.
+ */
+
+import org.apache.archiva.common.filelock.DefaultFileLockManager;
+import org.apache.commons.io.IOUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.Assert.*;
+
+public class FilesystemStorageTest {
+
+ private FilesystemStorage fsStorage;
+ private FilesystemAsset file1Asset;
+ private FilesystemAsset dir1Asset;
+ private Path baseDir;
+ private Path file1;
+ private Path dir1;
+
+ @Before
+ public void init() throws IOException {
+ baseDir = Files.createTempDirectory("FsStorageTest");
+ DefaultFileLockManager fl = new DefaultFileLockManager();
+ fsStorage = new FilesystemStorage(baseDir,fl);
+ Files.createDirectories(baseDir.resolve("dir1"));
+ Files.createDirectories(baseDir.resolve("dir2"));
+ file1 = Files.createFile(baseDir.resolve("dir1/testfile1.dat"));
+ dir1 = Files.createDirectories(baseDir.resolve("dir1/testdir"));
+ file1Asset = new FilesystemAsset("/dir1/testfile1.dat", file1);
+ dir1Asset = new FilesystemAsset("/dir1/testdir", dir1);
+ }
+
+ private class StringResult {
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+
+ String data;
+ }
+
+
+ @After
+ public void cleanup() {
+ try {
+ Files.deleteIfExists(file1);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ Files.deleteIfExists(dir1);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ Files.deleteIfExists(baseDir.resolve("dir1"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ Files.deleteIfExists(baseDir.resolve("dir2"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ try {
+ Files.deleteIfExists(baseDir);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+
+
+ @Test
+ public void consumeData() throws IOException {
+ try(OutputStream os = Files.newOutputStream(file1)) {
+ IOUtils.write("abcdefghijkl", os, "ASCII");
+ }
+ StringResult result = new StringResult();
+ fsStorage.consumeData(file1Asset, is -> consume(is, result), false );
+ assertEquals("abcdefghijkl" ,result.getData());
+ }
+
+ private void consume(InputStream is, StringResult result) {
+ try {
+ result.setData(IOUtils.toString(is, "ASCII"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @Test
+ public void getAsset() {
+ StorageAsset asset = fsStorage.getAsset("/dir1/testfile1.dat");
+ assertEquals(file1, asset.getFilePath());
+ }
+
+ @Test
+ public void addAsset() {
+ StorageAsset newAsset = fsStorage.addAsset("dir2/test", false);
+ assertNotNull(newAsset);
+ assertFalse(newAsset.isContainer());
+ assertFalse(newAsset.exists());
+
+ StorageAsset newDirAsset = fsStorage.addAsset("/dir2/testdir2", true);
+ assertNotNull(newDirAsset);
+ assertTrue(newDirAsset.isContainer());
+ assertFalse(newDirAsset.exists());
+ }
+
+ @Test
+ public void removeAsset() throws IOException {
+ assertTrue(Files.exists(file1));
+ fsStorage.removeAsset(file1Asset);
+ assertFalse(Files.exists(file1));
+
+ assertTrue(Files.exists(dir1));
+ fsStorage.removeAsset(dir1Asset);
+ assertFalse(Files.exists(dir1));
+ }
+
+ @Test
+ public void moveAsset() throws IOException {
+ Path newFile=null;
+ Path newDir=null;
+ try {
+ assertTrue(Files.exists(file1));
+ try (OutputStream os = Files.newOutputStream(file1)) {
+ IOUtils.write("testakdkkdkdkdk", os, "ASCII");
+ }
+ long fileSize = Files.size(file1);
+ fsStorage.moveAsset(file1Asset, "/dir2/testfile2.dat");
+ assertFalse(Files.exists(file1));
+ newFile = baseDir.resolve("dir2/testfile2.dat");
+ assertTrue(Files.exists(newFile));
+ assertEquals(fileSize, Files.size(newFile));
+
+
+ assertTrue(Files.exists(dir1));
+ newDir = baseDir.resolve("dir2/testdir2");
+ fsStorage.moveAsset(dir1Asset, "dir2/testdir2");
+ assertFalse(Files.exists(dir1));
+ assertTrue(Files.exists(newDir));
+ } finally {
+ if (newFile!=null) Files.deleteIfExists(newFile);
+ if (newDir!=null) Files.deleteIfExists(newDir);
+ }
+ }
+
+ @Test
+ public void copyAsset() throws IOException {
+ Path newFile=null;
+ Path newDir=null;
+ try {
+ assertTrue(Files.exists(file1));
+ try (OutputStream os = Files.newOutputStream(file1)) {
+ IOUtils.write("testakdkkdkdkdk", os, "ASCII");
+ }
+ long fileSize = Files.size(file1);
+ fsStorage.copyAsset(file1Asset, "/dir2/testfile2.dat");
+ assertTrue(Files.exists(file1));
+ assertEquals(fileSize, Files.size(file1));
+ newFile = baseDir.resolve("dir2/testfile2.dat");
+ assertTrue(Files.exists(newFile));
+ assertEquals(fileSize, Files.size(newFile));
+
+
+ assertTrue(Files.exists(dir1));
+ newDir = baseDir.resolve("dir2/testdir2");
+ fsStorage.copyAsset(dir1Asset, "dir2/testdir2");
+ assertTrue(Files.exists(dir1));
+ assertTrue(Files.exists(newDir));
+ } finally {
+ if (newFile!=null) Files.deleteIfExists(newFile);
+ if (newDir!=null) Files.deleteIfExists(newDir);
+ }
+ }
+} \ No newline at end of file