This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch feature/storage_refactoring
in repository https://gitbox.apache.org/repos/asf/archiva.git

commit 631ccdf51711978daf5943169dd695a9db1a2c40
Author: Martin Stockhammer <[email protected]>
AuthorDate: Sun May 19 17:30:10 2019 +0200

    Adding storage implementations
---
 .../repository/content/FilesystemAsset.java        | 463 +++++++++++----------
 .../repository/content/FilesystemStorage.java      | 180 ++++++++
 .../repository/content/FilesystemAssetTest.java    | 203 +++++++++
 .../repository/content/FilesystemStorageTest.java  | 208 +++++++++
 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 ef3aad3..6baed78 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 <[email protected]>
  */
-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 0000000..65b5610
--- /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 0000000..b06546a
--- /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 0000000..309c755
--- /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

Reply via email to