From 631ccdf51711978daf5943169dd695a9db1a2c40 Mon Sep 17 00:00:00 2001 From: Martin Stockhammer Date: Sun, 19 May 2019 17:30:10 +0200 Subject: [PATCH] Adding storage implementations --- .../repository/content/FilesystemAsset.java | 463 ++++++++++-------- .../repository/content/FilesystemStorage.java | 180 +++++++ .../content/FilesystemAssetTest.java | 203 ++++++++ .../content/FilesystemStorageTest.java | 208 ++++++++ 4 files changed, 844 insertions(+), 210 deletions(-) create mode 100644 archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/FilesystemStorage.java create mode 100644 archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemAssetTest.java create mode 100644 archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/FilesystemStorageTest.java 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. + *

+ * 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. + *

+ * The file must not exist for all operations. + * * @author Martin Stockhammer */ -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 DEFAULT_POSIX_FILE_PERMISSIONS; + public static final Set 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 defaultPosixFilePermissions = DEFAULT_POSIX_FILE_PERMISSIONS; + Set defaultPosixDirectoryPermissions = DEFAULT_POSIX_DIR_PERMISSIONS; List defaultFileAcls; - Set defaultPosixFilePermissions; List defaultDirectoryAcls; - Set 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 processPermissions(UserPrincipal owner, AclEntryPermission[] defaultAclFilePermissions) { + AclEntry.Builder aclBuilder = AclEntry.newBuilder(); + aclBuilder.setPermissions(defaultAclFilePermissions); + aclBuilder.setType(AclEntryType.ALLOW); + aclBuilder.setPrincipal(owner); + ArrayList 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 list( ) - { - try - { - return Files.list( completeAssetPath ).map( p -> new FilesystemAsset( basePath, basePath.relativize( p ).toString( ) ) ) - .collect( Collectors.toList( ) ); - } - catch ( IOException e ) - { + public List 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 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 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 acl ) - { + public void setDefaultFileAcls(List acl) { defaultFileAcls = acl; } - public List getDefaultFileAcls( ) - { + public List getDefaultFileAcls() { return defaultFileAcls; } - public void setDefaultPosixFilePermissions( Set perms ) - { + public void setDefaultPosixFilePermissions(Set perms) { defaultPosixFilePermissions = perms; } - public Set getDefaultPosixFilePermissions( ) - { + public Set getDefaultPosixFilePermissions() { return defaultPosixFilePermissions; } - public void setDefaultDirectoryAcls( List acl ) - { + public void setDefaultDirectoryAcls(List acl) { defaultDirectoryAcls = acl; } - public List getDefaultDirectoryAcls( ) - { + public List getDefaultDirectoryAcls() { return defaultDirectoryAcls; } - public void setDefaultPosixDirectoryPermissions( Set perms ) - { + public void setDefaultPosixDirectoryPermissions(Set perms) { defaultPosixDirectoryPermissions = perms; } - public Set getDefaultPosixDirectoryPermissions( ) - { + public Set 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 {@link RepositoryStorage} 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 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 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 -- 2.39.5