1 package org.apache.archiva.repository.storage.fs;
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
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import org.apache.archiva.repository.storage.RepositoryStorage;
22 import org.apache.archiva.repository.storage.StorageAsset;
23 import org.apache.commons.lang3.StringUtils;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.nio.channels.FileChannel;
31 import java.nio.channels.ReadableByteChannel;
32 import java.nio.channels.WritableByteChannel;
33 import java.nio.file.*;
34 import java.nio.file.attribute.*;
35 import java.time.Instant;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.List;
40 import java.util.stream.Collectors;
43 * Implementation of an asset that is stored on the filesystem.
45 * The implementation does not check the given paths. Caller should normalize the asset path
46 * and check, if the base path is a parent of the resulting path.
48 * The file must not exist for all operations.
50 * @author Martin Stockhammer <martin_s@apache.org>
52 public class FilesystemAsset implements StorageAsset, Comparable {
54 private final static Logger log = LoggerFactory.getLogger(FilesystemAsset.class);
56 private final Path basePath;
57 private final Path assetPath;
58 private final String relativePath;
60 public static final String DEFAULT_POSIX_FILE_PERMS = "rw-rw----";
61 public static final String DEFAULT_POSIX_DIR_PERMS = "rwxrwx---";
63 public static final Set<PosixFilePermission> DEFAULT_POSIX_FILE_PERMISSIONS;
64 public static final Set<PosixFilePermission> DEFAULT_POSIX_DIR_PERMISSIONS;
66 public static final AclEntryPermission[] DEFAULT_ACL_FILE_PERMISSIONS = new AclEntryPermission[]{
67 AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
68 AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA
71 public static final AclEntryPermission[] DEFAULT_ACL_DIR_PERMISSIONS = new AclEntryPermission[]{
72 AclEntryPermission.ADD_FILE, AclEntryPermission.ADD_SUBDIRECTORY, AclEntryPermission.DELETE_CHILD,
73 AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
74 AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA
79 DEFAULT_POSIX_FILE_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_FILE_PERMS);
80 DEFAULT_POSIX_DIR_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_DIR_PERMS);
83 Set<PosixFilePermission> defaultPosixFilePermissions = DEFAULT_POSIX_FILE_PERMISSIONS;
84 Set<PosixFilePermission> defaultPosixDirectoryPermissions = DEFAULT_POSIX_DIR_PERMISSIONS;
86 List<AclEntry> defaultFileAcls;
87 List<AclEntry> defaultDirectoryAcls;
89 boolean supportsAcl = false;
90 boolean supportsPosix = false;
91 final boolean setPermissionsForNew;
92 final RepositoryStorage storage;
94 boolean directoryHint = false;
96 private static final OpenOption[] REPLACE_OPTIONS = new OpenOption[]{StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE};
97 private static final OpenOption[] APPEND_OPTIONS = new OpenOption[]{StandardOpenOption.APPEND};
100 FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath) {
101 this.assetPath = assetPath;
102 this.relativePath = normalizePath(path);
103 this.setPermissionsForNew=false;
104 this.basePath = basePath;
105 this.storage = storage;
110 * Creates an asset for the given path. The given paths are not checked.
111 * The base path should be an absolute path.
113 * @param path The logical path for the asset relative to the repository.
114 * @param assetPath The asset path.
116 public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath) {
117 this.assetPath = assetPath;
118 this.relativePath = normalizePath(path);
119 this.setPermissionsForNew = false;
120 this.basePath = null;
121 this.storage = storage;
122 // The base directory is always a directory
123 if ("".equals(path) || "/".equals(path)) {
124 this.directoryHint = true;
130 * Creates an asset for the given path. The given paths are not checked.
131 * The base path should be an absolute path.
133 * @param path The logical path for the asset relative to the repository
134 * @param assetPath The asset path.
135 * @param directory This is only relevant, if the represented file or directory does not exist yet and
138 public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath, boolean directory) {
139 this.assetPath = assetPath;
140 this.relativePath = normalizePath(path);
141 this.directoryHint = directory;
142 this.setPermissionsForNew = false;
143 this.basePath = basePath;
144 this.storage = storage;
149 * Creates an asset for the given path. The given paths are not checked.
150 * The base path should be an absolute path.
152 * @param path The logical path for the asset relative to the repository
153 * @param assetPath The asset path.
154 * @param directory This is only relevant, if the represented file or directory does not exist yet and
157 public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath, boolean directory, boolean setPermissionsForNew) {
158 this.assetPath = assetPath;
159 this.relativePath = normalizePath(path);
160 this.directoryHint = directory;
161 this.setPermissionsForNew = setPermissionsForNew;
162 this.basePath = basePath;
163 this.storage = storage;
167 private String normalizePath(final String path) {
168 if (!path.startsWith("/")) {
171 String tmpPath = path;
172 while (tmpPath.startsWith("//")) {
173 tmpPath = tmpPath.substring(1);
179 private void init() {
181 if (setPermissionsForNew) {
183 supportsAcl = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(AclFileAttributeView.class);
184 } catch (IOException e) {
185 log.error("Could not check filesystem capabilities {}", e.getMessage());
188 supportsPosix = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(PosixFileAttributeView.class);
189 } catch (IOException e) {
190 log.error("Could not check filesystem capabilities {}", e.getMessage());
194 AclFileAttributeView aclView = Files.getFileAttributeView(assetPath.getParent(), AclFileAttributeView.class);
195 UserPrincipal owner = null;
197 owner = aclView.getOwner();
198 setDefaultFileAcls(processPermissions(owner, DEFAULT_ACL_FILE_PERMISSIONS));
199 setDefaultDirectoryAcls(processPermissions(owner, DEFAULT_ACL_DIR_PERMISSIONS));
201 } catch (IOException e) {
210 private List<AclEntry> processPermissions(UserPrincipal owner, AclEntryPermission[] defaultAclFilePermissions) {
211 AclEntry.Builder aclBuilder = AclEntry.newBuilder();
212 aclBuilder.setPermissions(defaultAclFilePermissions);
213 aclBuilder.setType(AclEntryType.ALLOW);
214 aclBuilder.setPrincipal(owner);
215 ArrayList<AclEntry> aclList = new ArrayList<>();
216 aclList.add(aclBuilder.build());
222 public RepositoryStorage getStorage( )
228 public String getPath() {
233 public String getName() {
234 return assetPath.getFileName().toString();
238 public Instant getModificationTime() {
240 return Files.getLastModifiedTime(assetPath).toInstant();
241 } catch (IOException e) {
242 log.error("Could not read modification time of {}", assetPath);
243 return Instant.now();
248 * Returns true, if the path of this asset points to a directory
253 public boolean isContainer() {
254 if (Files.exists(assetPath)) {
255 return Files.isDirectory(assetPath);
257 return directoryHint;
262 public boolean isLeaf( )
264 if (Files.exists( assetPath )) {
265 return Files.isRegularFile( assetPath );
267 return !directoryHint;
272 * Returns the list of directory entries, if this asset represents a directory.
273 * Otherwise a empty list will be returned.
275 * @return The list of entries in the directory, if it exists.
278 public List<StorageAsset> list() {
280 return Files.list(assetPath).map(p -> new FilesystemAsset(storage, relativePath + "/" + p.getFileName().toString(), assetPath.resolve(p)))
281 .collect(Collectors.toList());
282 } catch (IOException e) {
283 return Collections.EMPTY_LIST;
288 * Returns the size of the represented file. If it cannot be determined, -1 is returned.
293 public long getSize() {
295 return Files.size(assetPath);
296 } catch (IOException e) {
302 * Returns a input stream to the underlying file, if it exists. The caller has to make sure, that
303 * the stream is closed after it was used.
306 * @throws IOException
309 public InputStream getReadStream() throws IOException {
311 throw new IOException("Can not create input stream for container");
313 return Files.newInputStream(assetPath);
317 public ReadableByteChannel getReadChannel( ) throws IOException
319 return FileChannel.open( assetPath, StandardOpenOption.READ );
322 private OpenOption[] getOpenOptions(boolean replace) {
323 return replace ? REPLACE_OPTIONS : APPEND_OPTIONS;
327 public OutputStream getWriteStream( boolean replace) throws IOException {
328 OpenOption[] options = getOpenOptions( replace );
329 if (!Files.exists( assetPath )) {
332 return Files.newOutputStream(assetPath, options);
336 public WritableByteChannel getWriteChannel( boolean replace ) throws IOException
338 OpenOption[] options = getOpenOptions( replace );
339 return FileChannel.open( assetPath, options );
343 public boolean replaceDataFromFile( Path newData) throws IOException {
344 final boolean createNew = !Files.exists(assetPath);
347 backup = findBackupFile(assetPath);
351 Files.move(assetPath, backup);
353 Files.move(newData, assetPath, StandardCopyOption.REPLACE_EXISTING);
354 applyDefaultPermissions(assetPath);
356 } catch (IOException e) {
357 log.error("Could not overwrite file {}", assetPath);
358 // Revert if possible
359 if (backup != null && Files.exists(backup)) {
360 Files.move(backup, assetPath, StandardCopyOption.REPLACE_EXISTING);
364 if (backup != null) {
366 Files.deleteIfExists(backup);
367 } catch (IOException e) {
368 log.error("Could not delete backup file {}", backup);
375 private void applyDefaultPermissions(Path filePath) {
378 Set<PosixFilePermission> perms;
379 if (Files.isDirectory(filePath)) {
380 perms = defaultPosixFilePermissions;
382 perms = defaultPosixDirectoryPermissions;
384 Files.setPosixFilePermissions(filePath, perms);
385 } else if (supportsAcl) {
386 List<AclEntry> perms;
387 if (Files.isDirectory(filePath)) {
388 perms = getDefaultDirectoryAcls();
390 perms = getDefaultFileAcls();
392 AclFileAttributeView aclAttr = Files.getFileAttributeView(filePath, AclFileAttributeView.class);
393 aclAttr.setAcl(perms);
395 } catch (IOException e) {
396 log.error("Could not set permissions for {}: {}", filePath, e.getMessage());
400 private Path findBackupFile(Path file) {
402 Path backupPath = file.getParent().resolve(file.getFileName().toString() + ext);
404 while (Files.exists(backupPath)) {
405 backupPath = file.getParent().resolve(file.getFileName().toString() + ext + idx++);
411 public boolean exists() {
412 return Files.exists(assetPath);
416 public Path getFilePath() throws UnsupportedOperationException {
421 public boolean isFileBased( )
427 public boolean hasParent( )
429 if (basePath!=null && assetPath.equals(basePath)) {
432 return assetPath.getParent()!=null;
436 public StorageAsset getParent( )
439 if (basePath!=null && assetPath.equals( basePath )) {
443 parentPath = assetPath.getParent( );
445 String relativeParent = StringUtils.substringBeforeLast( relativePath,"/");
446 if (parentPath!=null) {
447 return new FilesystemAsset(storage, relativeParent, parentPath, basePath, true, setPermissionsForNew );
454 public StorageAsset resolve(String toPath) {
455 return storage.getAsset(this.getPath()+"/"+toPath);
459 public void setDefaultFileAcls(List<AclEntry> acl) {
460 defaultFileAcls = acl;
463 public List<AclEntry> getDefaultFileAcls() {
464 return defaultFileAcls;
467 public void setDefaultPosixFilePermissions(Set<PosixFilePermission> perms) {
468 defaultPosixFilePermissions = perms;
471 public Set<PosixFilePermission> getDefaultPosixFilePermissions() {
472 return defaultPosixFilePermissions;
475 public void setDefaultDirectoryAcls(List<AclEntry> acl) {
476 defaultDirectoryAcls = acl;
479 public List<AclEntry> getDefaultDirectoryAcls() {
480 return defaultDirectoryAcls;
483 public void setDefaultPosixDirectoryPermissions(Set<PosixFilePermission> perms) {
484 defaultPosixDirectoryPermissions = perms;
487 public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions() {
488 return defaultPosixDirectoryPermissions;
492 public void create() throws IOException {
493 if (!Files.exists(assetPath)) {
495 Files.createDirectories(assetPath);
497 if (!Files.exists( assetPath.getParent() )) {
498 Files.createDirectories( assetPath.getParent( ) );
500 Files.createFile(assetPath);
502 if (setPermissionsForNew) {
503 applyDefaultPermissions(assetPath);
509 public String toString() {
510 return relativePath+":"+assetPath;
514 public int compareTo(Object o) {
515 if (o instanceof FilesystemAsset) {
516 if (this.getPath()!=null) {
517 return this.getPath().compareTo(((FilesystemAsset) o).getPath());