]> source.dussan.org Git - archiva.git/blob
2f501d6e9dd63ccf4883fcaf09cfa50a6d5ba15f
[archiva.git] /
1 package org.apache.archiva.repository.storage;
2
3 /*
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
11  *
12  *  http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  * under the License.
20  */
21
22 import org.apache.commons.lang3.StringUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
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;
38 import java.util.Set;
39 import java.util.stream.Collectors;
40
41 /**
42  * Implementation of an asset that is stored on the filesystem.
43  * <p>
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.
46  * <p>
47  * The file must not exist for all operations.
48  *
49  * @author Martin Stockhammer <martin_s@apache.org>
50  */
51 public class FilesystemAsset implements StorageAsset, Comparable {
52
53     private final static Logger log = LoggerFactory.getLogger(FilesystemAsset.class);
54
55     private final Path basePath;
56     private final Path assetPath;
57     private final String relativePath;
58
59     public static final String DEFAULT_POSIX_FILE_PERMS = "rw-rw----";
60     public static final String DEFAULT_POSIX_DIR_PERMS = "rwxrwx---";
61
62     public static final Set<PosixFilePermission> DEFAULT_POSIX_FILE_PERMISSIONS;
63     public static final Set<PosixFilePermission> DEFAULT_POSIX_DIR_PERMISSIONS;
64
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
68     };
69
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
74     };
75
76     static {
77
78         DEFAULT_POSIX_FILE_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_FILE_PERMS);
79         DEFAULT_POSIX_DIR_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_DIR_PERMS);
80     }
81
82     Set<PosixFilePermission> defaultPosixFilePermissions = DEFAULT_POSIX_FILE_PERMISSIONS;
83     Set<PosixFilePermission> defaultPosixDirectoryPermissions = DEFAULT_POSIX_DIR_PERMISSIONS;
84
85     List<AclEntry> defaultFileAcls;
86     List<AclEntry> defaultDirectoryAcls;
87
88     boolean supportsAcl = false;
89     boolean supportsPosix = false;
90     final boolean setPermissionsForNew;
91     final RepositoryStorage storage;
92
93     boolean directoryHint = false;
94
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};
97
98
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;
105         init();
106     }
107
108     /**
109      * Creates an asset for the given path. The given paths are not checked.
110      * The base path should be an absolute path.
111      *
112      * @param path The logical path for the asset relative to the repository.
113      * @param assetPath The asset path.
114      */
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;
124         }
125         init();
126     }
127
128     /**
129      * Creates an asset for the given path. The given paths are not checked.
130      * The base path should be an absolute path.
131      *
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
135      *                  is a hint.
136      */
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;
144         init();
145     }
146
147     /**
148      * Creates an asset for the given path. The given paths are not checked.
149      * The base path should be an absolute path.
150      *
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
154      *                  is a hint.
155      */
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;
163         init();
164     }
165
166     private String normalizePath(final String path) {
167         if (!path.startsWith("/")) {
168             return "/"+path;
169         } else {
170             String tmpPath = path;
171             while (tmpPath.startsWith("//")) {
172                 tmpPath = tmpPath.substring(1);
173             }
174             return tmpPath;
175         }
176     }
177
178     private void init() {
179
180         if (setPermissionsForNew) {
181             try {
182                 supportsAcl = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(AclFileAttributeView.class);
183             } catch (IOException e) {
184                 log.error("Could not check filesystem capabilities {}", e.getMessage());
185             }
186             try {
187                 supportsPosix = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(PosixFileAttributeView.class);
188             } catch (IOException e) {
189                 log.error("Could not check filesystem capabilities {}", e.getMessage());
190             }
191
192             if (supportsAcl) {
193                 AclFileAttributeView aclView = Files.getFileAttributeView(assetPath.getParent(), AclFileAttributeView.class);
194                 UserPrincipal owner = null;
195                 try {
196                     owner = aclView.getOwner();
197                     setDefaultFileAcls(processPermissions(owner, DEFAULT_ACL_FILE_PERMISSIONS));
198                     setDefaultDirectoryAcls(processPermissions(owner, DEFAULT_ACL_DIR_PERMISSIONS));
199
200                 } catch (IOException e) {
201                     supportsAcl = false;
202                 }
203
204
205             }
206         }
207     }
208
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());
216         return aclList;
217     }
218
219
220     @Override
221     public RepositoryStorage getStorage( )
222     {
223         return storage;
224     }
225
226     @Override
227     public String getPath() {
228         return relativePath;
229     }
230
231     @Override
232     public String getName() {
233         return assetPath.getFileName().toString();
234     }
235
236     @Override
237     public Instant getModificationTime() {
238         try {
239             return Files.getLastModifiedTime(assetPath).toInstant();
240         } catch (IOException e) {
241             log.error("Could not read modification time of {}", assetPath);
242             return Instant.now();
243         }
244     }
245
246     /**
247      * Returns true, if the path of this asset points to a directory
248      *
249      * @return
250      */
251     @Override
252     public boolean isContainer() {
253         if (Files.exists(assetPath)) {
254             return Files.isDirectory(assetPath);
255         } else {
256             return directoryHint;
257         }
258     }
259
260     @Override
261     public boolean isLeaf( )
262     {
263         if (Files.exists( assetPath )) {
264             return Files.isRegularFile( assetPath );
265         } else {
266             return !directoryHint;
267         }
268     }
269
270     /**
271      * Returns the list of directory entries, if this asset represents a directory.
272      * Otherwise a empty list will be returned.
273      *
274      * @return The list of entries in the directory, if it exists.
275      */
276     @Override
277     public List<StorageAsset> list() {
278         try {
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;
283         }
284     }
285
286     /**
287      * Returns the size of the represented file. If it cannot be determined, -1 is returned.
288      *
289      * @return
290      */
291     @Override
292     public long getSize() {
293         try {
294             return Files.size(assetPath);
295         } catch (IOException e) {
296             return -1;
297         }
298     }
299
300     /**
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.
303      *
304      * @return
305      * @throws IOException
306      */
307     @Override
308     public InputStream getReadStream() throws IOException {
309         if (isContainer()) {
310             throw new IOException("Can not create input stream for container");
311         }
312         return Files.newInputStream(assetPath);
313     }
314
315     @Override
316     public ReadableByteChannel getReadChannel( ) throws IOException
317     {
318         return FileChannel.open( assetPath, StandardOpenOption.READ );
319     }
320
321     private OpenOption[] getOpenOptions(boolean replace) {
322         return replace ? REPLACE_OPTIONS : APPEND_OPTIONS;
323     }
324
325     @Override
326     public OutputStream getWriteStream( boolean replace) throws IOException {
327         OpenOption[] options = getOpenOptions( replace );
328         if (!Files.exists( assetPath )) {
329             create();
330         }
331         return Files.newOutputStream(assetPath, options);
332     }
333
334     @Override
335     public WritableByteChannel getWriteChannel( boolean replace ) throws IOException
336     {
337         OpenOption[] options = getOpenOptions( replace );
338         return FileChannel.open( assetPath, options );
339     }
340
341     @Override
342     public boolean replaceDataFromFile( Path newData) throws IOException {
343         final boolean createNew = !Files.exists(assetPath);
344         Path backup = null;
345         if (!createNew) {
346             backup = findBackupFile(assetPath);
347         }
348         try {
349             if (!createNew) {
350                 Files.move(assetPath, backup);
351             }
352             Files.move(newData, assetPath, StandardCopyOption.REPLACE_EXISTING);
353             applyDefaultPermissions(assetPath);
354             return true;
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);
360             }
361             throw e;
362         } finally {
363             if (backup != null) {
364                 try {
365                     Files.deleteIfExists(backup);
366                 } catch (IOException e) {
367                     log.error("Could not delete backup file {}", backup);
368                 }
369             }
370         }
371
372     }
373
374     private void applyDefaultPermissions(Path filePath) {
375         try {
376             if (supportsPosix) {
377                 Set<PosixFilePermission> perms;
378                 if (Files.isDirectory(filePath)) {
379                     perms = defaultPosixFilePermissions;
380                 } else {
381                     perms = defaultPosixDirectoryPermissions;
382                 }
383                 Files.setPosixFilePermissions(filePath, perms);
384             } else if (supportsAcl) {
385                 List<AclEntry> perms;
386                 if (Files.isDirectory(filePath)) {
387                     perms = getDefaultDirectoryAcls();
388                 } else {
389                     perms = getDefaultFileAcls();
390                 }
391                 AclFileAttributeView aclAttr = Files.getFileAttributeView(filePath, AclFileAttributeView.class);
392                 aclAttr.setAcl(perms);
393             }
394         } catch (IOException e) {
395             log.error("Could not set permissions for {}: {}", filePath, e.getMessage());
396         }
397     }
398
399     private Path findBackupFile(Path file) {
400         String ext = ".bak";
401         Path backupPath = file.getParent().resolve(file.getFileName().toString() + ext);
402         int idx = 0;
403         while (Files.exists(backupPath)) {
404             backupPath = file.getParent().resolve(file.getFileName().toString() + ext + idx++);
405         }
406         return backupPath;
407     }
408
409     @Override
410     public boolean exists() {
411         return Files.exists(assetPath);
412     }
413
414     @Override
415     public Path getFilePath() throws UnsupportedOperationException {
416         return assetPath;
417     }
418
419     @Override
420     public boolean isFileBased( )
421     {
422         return true;
423     }
424
425     @Override
426     public boolean hasParent( )
427     {
428         if (basePath!=null && assetPath.equals(basePath)) {
429                 return false;
430         }
431         return assetPath.getParent()!=null;
432     }
433
434     @Override
435     public StorageAsset getParent( )
436     {
437         Path parentPath;
438         if (basePath!=null && assetPath.equals( basePath )) {
439             parentPath=null;
440         } else
441         {
442             parentPath = assetPath.getParent( );
443         }
444         String relativeParent = StringUtils.substringBeforeLast( relativePath,"/");
445         if (parentPath!=null) {
446             return new FilesystemAsset(storage, relativeParent, parentPath, basePath, true, setPermissionsForNew );
447         } else {
448             return null;
449         }
450     }
451
452     @Override
453     public StorageAsset resolve(String toPath) {
454         return storage.getAsset(this.getPath()+"/"+toPath);
455     }
456
457
458     public void setDefaultFileAcls(List<AclEntry> acl) {
459         defaultFileAcls = acl;
460     }
461
462     public List<AclEntry> getDefaultFileAcls() {
463         return defaultFileAcls;
464     }
465
466     public void setDefaultPosixFilePermissions(Set<PosixFilePermission> perms) {
467         defaultPosixFilePermissions = perms;
468     }
469
470     public Set<PosixFilePermission> getDefaultPosixFilePermissions() {
471         return defaultPosixFilePermissions;
472     }
473
474     public void setDefaultDirectoryAcls(List<AclEntry> acl) {
475         defaultDirectoryAcls = acl;
476     }
477
478     public List<AclEntry> getDefaultDirectoryAcls() {
479         return defaultDirectoryAcls;
480     }
481
482     public void setDefaultPosixDirectoryPermissions(Set<PosixFilePermission> perms) {
483         defaultPosixDirectoryPermissions = perms;
484     }
485
486     public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions() {
487         return defaultPosixDirectoryPermissions;
488     }
489
490     @Override
491     public void create() throws IOException {
492         if (!Files.exists(assetPath)) {
493             if (directoryHint) {
494                 Files.createDirectories(assetPath);
495             } else {
496                 if (!Files.exists( assetPath.getParent() )) {
497                     Files.createDirectories( assetPath.getParent( ) );
498                 }
499                 Files.createFile(assetPath);
500             }
501             if (setPermissionsForNew) {
502                 applyDefaultPermissions(assetPath);
503             }
504         }
505     }
506
507     @Override
508     public String toString() {
509         return relativePath+":"+assetPath;
510     }
511
512     @Override
513     public int compareTo(Object o) {
514         if (o instanceof FilesystemAsset) {
515             if (this.getPath()!=null) {
516                 return this.getPath().compareTo(((FilesystemAsset) o).getPath());
517             } else {
518                 return 0;
519             }
520         }
521         return 0;
522     }
523 }