1 package org.apache.archiva.repository.storage;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
22 import org.apache.commons.lang3.StringUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.nio.channels.FileChannel;
30 import java.nio.channels.ReadableByteChannel;
31 import java.nio.channels.WritableByteChannel;
32 import java.nio.file.*;
33 import java.nio.file.attribute.*;
34 import java.time.Instant;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
39 import java.util.stream.Collectors;
42 * Implementation of an asset that is stored on the filesystem.
44 * The implementation does not check the given paths. Caller should normalize the asset path
45 * and check, if the base path is a parent of the resulting path.
47 * The file must not exist for all operations.
49 * @author Martin Stockhammer <martin_s@apache.org>
51 public class FilesystemAsset implements StorageAsset, Comparable {
53 private final static Logger log = LoggerFactory.getLogger(FilesystemAsset.class);
55 private final Path basePath;
56 private final Path assetPath;
57 private final String relativePath;
59 public static final String DEFAULT_POSIX_FILE_PERMS = "rw-rw----";
60 public static final String DEFAULT_POSIX_DIR_PERMS = "rwxrwx---";
62 public static final Set<PosixFilePermission> DEFAULT_POSIX_FILE_PERMISSIONS;
63 public static final Set<PosixFilePermission> DEFAULT_POSIX_DIR_PERMISSIONS;
65 public static final AclEntryPermission[] DEFAULT_ACL_FILE_PERMISSIONS = new AclEntryPermission[]{
66 AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
67 AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA
70 public static final AclEntryPermission[] DEFAULT_ACL_DIR_PERMISSIONS = new AclEntryPermission[]{
71 AclEntryPermission.ADD_FILE, AclEntryPermission.ADD_SUBDIRECTORY, AclEntryPermission.DELETE_CHILD,
72 AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
73 AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA
78 DEFAULT_POSIX_FILE_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_FILE_PERMS);
79 DEFAULT_POSIX_DIR_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_DIR_PERMS);
82 Set<PosixFilePermission> defaultPosixFilePermissions = DEFAULT_POSIX_FILE_PERMISSIONS;
83 Set<PosixFilePermission> defaultPosixDirectoryPermissions = DEFAULT_POSIX_DIR_PERMISSIONS;
85 List<AclEntry> defaultFileAcls;
86 List<AclEntry> defaultDirectoryAcls;
88 boolean supportsAcl = false;
89 boolean supportsPosix = false;
90 final boolean setPermissionsForNew;
91 final RepositoryStorage storage;
93 boolean directoryHint = false;
95 private static final OpenOption[] REPLACE_OPTIONS = new OpenOption[]{StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE};
96 private static final OpenOption[] APPEND_OPTIONS = new OpenOption[]{StandardOpenOption.APPEND};
99 FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath) {
100 this.assetPath = assetPath;
101 this.relativePath = normalizePath(path);
102 this.setPermissionsForNew=false;
103 this.basePath = basePath;
104 this.storage = storage;
109 * Creates an asset for the given path. The given paths are not checked.
110 * The base path should be an absolute path.
112 * @param path The logical path for the asset relative to the repository.
113 * @param assetPath The asset path.
115 public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath) {
116 this.assetPath = assetPath;
117 this.relativePath = normalizePath(path);
118 this.setPermissionsForNew = false;
119 this.basePath = null;
120 this.storage = storage;
121 // The base directory is always a directory
122 if ("".equals(path) || "/".equals(path)) {
123 this.directoryHint = true;
129 * Creates an asset for the given path. The given paths are not checked.
130 * The base path should be an absolute path.
132 * @param path The logical path for the asset relative to the repository
133 * @param assetPath The asset path.
134 * @param directory This is only relevant, if the represented file or directory does not exist yet and
137 public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath, boolean directory) {
138 this.assetPath = assetPath;
139 this.relativePath = normalizePath(path);
140 this.directoryHint = directory;
141 this.setPermissionsForNew = false;
142 this.basePath = basePath;
143 this.storage = storage;
148 * Creates an asset for the given path. The given paths are not checked.
149 * The base path should be an absolute path.
151 * @param path The logical path for the asset relative to the repository
152 * @param assetPath The asset path.
153 * @param directory This is only relevant, if the represented file or directory does not exist yet and
156 public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath, boolean directory, boolean setPermissionsForNew) {
157 this.assetPath = assetPath;
158 this.relativePath = normalizePath(path);
159 this.directoryHint = directory;
160 this.setPermissionsForNew = setPermissionsForNew;
161 this.basePath = basePath;
162 this.storage = storage;
166 private String normalizePath(final String path) {
167 if (!path.startsWith("/")) {
170 String tmpPath = path;
171 while (tmpPath.startsWith("//")) {
172 tmpPath = tmpPath.substring(1);
178 private void init() {
180 if (setPermissionsForNew) {
182 supportsAcl = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(AclFileAttributeView.class);
183 } catch (IOException e) {
184 log.error("Could not check filesystem capabilities {}", e.getMessage());
187 supportsPosix = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(PosixFileAttributeView.class);
188 } catch (IOException e) {
189 log.error("Could not check filesystem capabilities {}", e.getMessage());
193 AclFileAttributeView aclView = Files.getFileAttributeView(assetPath.getParent(), AclFileAttributeView.class);
194 UserPrincipal owner = null;
196 owner = aclView.getOwner();
197 setDefaultFileAcls(processPermissions(owner, DEFAULT_ACL_FILE_PERMISSIONS));
198 setDefaultDirectoryAcls(processPermissions(owner, DEFAULT_ACL_DIR_PERMISSIONS));
200 } catch (IOException e) {
209 private List<AclEntry> processPermissions(UserPrincipal owner, AclEntryPermission[] defaultAclFilePermissions) {
210 AclEntry.Builder aclBuilder = AclEntry.newBuilder();
211 aclBuilder.setPermissions(defaultAclFilePermissions);
212 aclBuilder.setType(AclEntryType.ALLOW);
213 aclBuilder.setPrincipal(owner);
214 ArrayList<AclEntry> aclList = new ArrayList<>();
215 aclList.add(aclBuilder.build());
221 public RepositoryStorage getStorage( )
227 public String getPath() {
232 public String getName() {
233 return assetPath.getFileName().toString();
237 public Instant getModificationTime() {
239 return Files.getLastModifiedTime(assetPath).toInstant();
240 } catch (IOException e) {
241 log.error("Could not read modification time of {}", assetPath);
242 return Instant.now();
247 * Returns true, if the path of this asset points to a directory
252 public boolean isContainer() {
253 if (Files.exists(assetPath)) {
254 return Files.isDirectory(assetPath);
256 return directoryHint;
261 public boolean isLeaf( )
263 if (Files.exists( assetPath )) {
264 return Files.isRegularFile( assetPath );
266 return !directoryHint;
271 * Returns the list of directory entries, if this asset represents a directory.
272 * Otherwise a empty list will be returned.
274 * @return The list of entries in the directory, if it exists.
277 public List<StorageAsset> list() {
279 return Files.list(assetPath).map(p -> new FilesystemAsset(storage, relativePath + "/" + p.getFileName().toString(), assetPath.resolve(p)))
280 .collect(Collectors.toList());
281 } catch (IOException e) {
282 return Collections.EMPTY_LIST;
287 * Returns the size of the represented file. If it cannot be determined, -1 is returned.
292 public long getSize() {
294 return Files.size(assetPath);
295 } catch (IOException e) {
301 * Returns a input stream to the underlying file, if it exists. The caller has to make sure, that
302 * the stream is closed after it was used.
305 * @throws IOException
308 public InputStream getReadStream() throws IOException {
310 throw new IOException("Can not create input stream for container");
312 return Files.newInputStream(assetPath);
316 public ReadableByteChannel getReadChannel( ) throws IOException
318 return FileChannel.open( assetPath, StandardOpenOption.READ );
321 private OpenOption[] getOpenOptions(boolean replace) {
322 return replace ? REPLACE_OPTIONS : APPEND_OPTIONS;
326 public OutputStream getWriteStream( boolean replace) throws IOException {
327 OpenOption[] options = getOpenOptions( replace );
328 if (!Files.exists( assetPath )) {
331 return Files.newOutputStream(assetPath, options);
335 public WritableByteChannel getWriteChannel( boolean replace ) throws IOException
337 OpenOption[] options = getOpenOptions( replace );
338 return FileChannel.open( assetPath, options );
342 public boolean replaceDataFromFile( Path newData) throws IOException {
343 final boolean createNew = !Files.exists(assetPath);
346 backup = findBackupFile(assetPath);
350 Files.move(assetPath, backup);
352 Files.move(newData, assetPath, StandardCopyOption.REPLACE_EXISTING);
353 applyDefaultPermissions(assetPath);
355 } catch (IOException e) {
356 log.error("Could not overwrite file {}", assetPath);
357 // Revert if possible
358 if (backup != null && Files.exists(backup)) {
359 Files.move(backup, assetPath, StandardCopyOption.REPLACE_EXISTING);
363 if (backup != null) {
365 Files.deleteIfExists(backup);
366 } catch (IOException e) {
367 log.error("Could not delete backup file {}", backup);
374 private void applyDefaultPermissions(Path filePath) {
377 Set<PosixFilePermission> perms;
378 if (Files.isDirectory(filePath)) {
379 perms = defaultPosixFilePermissions;
381 perms = defaultPosixDirectoryPermissions;
383 Files.setPosixFilePermissions(filePath, perms);
384 } else if (supportsAcl) {
385 List<AclEntry> perms;
386 if (Files.isDirectory(filePath)) {
387 perms = getDefaultDirectoryAcls();
389 perms = getDefaultFileAcls();
391 AclFileAttributeView aclAttr = Files.getFileAttributeView(filePath, AclFileAttributeView.class);
392 aclAttr.setAcl(perms);
394 } catch (IOException e) {
395 log.error("Could not set permissions for {}: {}", filePath, e.getMessage());
399 private Path findBackupFile(Path file) {
401 Path backupPath = file.getParent().resolve(file.getFileName().toString() + ext);
403 while (Files.exists(backupPath)) {
404 backupPath = file.getParent().resolve(file.getFileName().toString() + ext + idx++);
410 public boolean exists() {
411 return Files.exists(assetPath);
415 public Path getFilePath() throws UnsupportedOperationException {
420 public boolean isFileBased( )
426 public boolean hasParent( )
428 if (basePath!=null && assetPath.equals(basePath)) {
431 return assetPath.getParent()!=null;
435 public StorageAsset getParent( )
438 if (basePath!=null && assetPath.equals( basePath )) {
442 parentPath = assetPath.getParent( );
444 String relativeParent = StringUtils.substringBeforeLast( relativePath,"/");
445 if (parentPath!=null) {
446 return new FilesystemAsset(storage, relativeParent, parentPath, basePath, true, setPermissionsForNew );
453 public StorageAsset resolve(String toPath) {
454 return storage.getAsset(this.getPath()+"/"+toPath);
458 public void setDefaultFileAcls(List<AclEntry> acl) {
459 defaultFileAcls = acl;
462 public List<AclEntry> getDefaultFileAcls() {
463 return defaultFileAcls;
466 public void setDefaultPosixFilePermissions(Set<PosixFilePermission> perms) {
467 defaultPosixFilePermissions = perms;
470 public Set<PosixFilePermission> getDefaultPosixFilePermissions() {
471 return defaultPosixFilePermissions;
474 public void setDefaultDirectoryAcls(List<AclEntry> acl) {
475 defaultDirectoryAcls = acl;
478 public List<AclEntry> getDefaultDirectoryAcls() {
479 return defaultDirectoryAcls;
482 public void setDefaultPosixDirectoryPermissions(Set<PosixFilePermission> perms) {
483 defaultPosixDirectoryPermissions = perms;
486 public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions() {
487 return defaultPosixDirectoryPermissions;
491 public void create() throws IOException {
492 if (!Files.exists(assetPath)) {
494 Files.createDirectories(assetPath);
496 if (!Files.exists( assetPath.getParent() )) {
497 Files.createDirectories( assetPath.getParent( ) );
499 Files.createFile(assetPath);
501 if (setPermissionsForNew) {
502 applyDefaultPermissions(assetPath);
508 public String toString() {
509 return relativePath+":"+assetPath;
513 public int compareTo(Object o) {
514 if (o instanceof FilesystemAsset) {
515 if (this.getPath()!=null) {
516 return this.getPath().compareTo(((FilesystemAsset) o).getPath());