This project has retired. For details please refer to its Attic page.
FilesystemAsset xref
View Javadoc
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     /**
261      * Returns the list of directory entries, if this asset represents a directory.
262      * Otherwise a empty list will be returned.
263      *
264      * @return The list of entries in the directory, if it exists.
265      */
266     @Override
267     public List<StorageAsset> list() {
268         try {
269             return Files.list(assetPath).map(p -> new FilesystemAsset(storage, relativePath + "/" + p.getFileName().toString(), assetPath.resolve(p)))
270                     .collect(Collectors.toList());
271         } catch (IOException e) {
272             return Collections.EMPTY_LIST;
273         }
274     }
275 
276     /**
277      * Returns the size of the represented file. If it cannot be determined, -1 is returned.
278      *
279      * @return
280      */
281     @Override
282     public long getSize() {
283         try {
284             return Files.size(assetPath);
285         } catch (IOException e) {
286             return -1;
287         }
288     }
289 
290     /**
291      * Returns a input stream to the underlying file, if it exists. The caller has to make sure, that
292      * the stream is closed after it was used.
293      *
294      * @return
295      * @throws IOException
296      */
297     @Override
298     public InputStream getReadStream() throws IOException {
299         if (isContainer()) {
300             throw new IOException("Can not create input stream for container");
301         }
302         return Files.newInputStream(assetPath);
303     }
304 
305     @Override
306     public ReadableByteChannel getReadChannel( ) throws IOException
307     {
308         return FileChannel.open( assetPath, StandardOpenOption.READ );
309     }
310 
311     private OpenOption[] getOpenOptions(boolean replace) {
312         return replace ? REPLACE_OPTIONS : APPEND_OPTIONS;
313     }
314 
315     @Override
316     public OutputStream getWriteStream( boolean replace) throws IOException {
317         OpenOption[] options = getOpenOptions( replace );
318         if (!Files.exists( assetPath )) {
319             create();
320         }
321         return Files.newOutputStream(assetPath, options);
322     }
323 
324     @Override
325     public WritableByteChannel getWriteChannel( boolean replace ) throws IOException
326     {
327         OpenOption[] options = getOpenOptions( replace );
328         return FileChannel.open( assetPath, options );
329     }
330 
331     @Override
332     public boolean replaceDataFromFile( Path newData) throws IOException {
333         final boolean createNew = !Files.exists(assetPath);
334         Path backup = null;
335         if (!createNew) {
336             backup = findBackupFile(assetPath);
337         }
338         try {
339             if (!createNew) {
340                 Files.move(assetPath, backup);
341             }
342             Files.move(newData, assetPath, StandardCopyOption.REPLACE_EXISTING);
343             applyDefaultPermissions(assetPath);
344             return true;
345         } catch (IOException e) {
346             log.error("Could not overwrite file {}", assetPath);
347             // Revert if possible
348             if (backup != null && Files.exists(backup)) {
349                 Files.move(backup, assetPath, StandardCopyOption.REPLACE_EXISTING);
350             }
351             throw e;
352         } finally {
353             if (backup != null) {
354                 try {
355                     Files.deleteIfExists(backup);
356                 } catch (IOException e) {
357                     log.error("Could not delete backup file {}", backup);
358                 }
359             }
360         }
361 
362     }
363 
364     private void applyDefaultPermissions(Path filePath) {
365         try {
366             if (supportsPosix) {
367                 Set<PosixFilePermission> perms;
368                 if (Files.isDirectory(filePath)) {
369                     perms = defaultPosixFilePermissions;
370                 } else {
371                     perms = defaultPosixDirectoryPermissions;
372                 }
373                 Files.setPosixFilePermissions(filePath, perms);
374             } else if (supportsAcl) {
375                 List<AclEntry> perms;
376                 if (Files.isDirectory(filePath)) {
377                     perms = getDefaultDirectoryAcls();
378                 } else {
379                     perms = getDefaultFileAcls();
380                 }
381                 AclFileAttributeView aclAttr = Files.getFileAttributeView(filePath, AclFileAttributeView.class);
382                 aclAttr.setAcl(perms);
383             }
384         } catch (IOException e) {
385             log.error("Could not set permissions for {}: {}", filePath, e.getMessage());
386         }
387     }
388 
389     private Path findBackupFile(Path file) {
390         String ext = ".bak";
391         Path backupPath = file.getParent().resolve(file.getFileName().toString() + ext);
392         int idx = 0;
393         while (Files.exists(backupPath)) {
394             backupPath = file.getParent().resolve(file.getFileName().toString() + ext + idx++);
395         }
396         return backupPath;
397     }
398 
399     @Override
400     public boolean exists() {
401         return Files.exists(assetPath);
402     }
403 
404     @Override
405     public Path getFilePath() throws UnsupportedOperationException {
406         return assetPath;
407     }
408 
409     @Override
410     public boolean isFileBased( )
411     {
412         return true;
413     }
414 
415     @Override
416     public boolean hasParent( )
417     {
418         if (basePath!=null && assetPath.equals(basePath)) {
419                 return false;
420         }
421         return assetPath.getParent()!=null;
422     }
423 
424     @Override
425     public StorageAsset getParent( )
426     {
427         Path parentPath;
428         if (basePath!=null && assetPath.equals( basePath )) {
429             parentPath=null;
430         } else
431         {
432             parentPath = assetPath.getParent( );
433         }
434         String relativeParent = StringUtils.substringBeforeLast( relativePath,"/");
435         if (parentPath!=null) {
436             return new FilesystemAsset(storage, relativeParent, parentPath, basePath, true, setPermissionsForNew );
437         } else {
438             return null;
439         }
440     }
441 
442     @Override
443     public StorageAsset resolve(String toPath) {
444         return storage.getAsset(this.getPath()+"/"+toPath);
445     }
446 
447 
448     public void setDefaultFileAcls(List<AclEntry> acl) {
449         defaultFileAcls = acl;
450     }
451 
452     public List<AclEntry> getDefaultFileAcls() {
453         return defaultFileAcls;
454     }
455 
456     public void setDefaultPosixFilePermissions(Set<PosixFilePermission> perms) {
457         defaultPosixFilePermissions = perms;
458     }
459 
460     public Set<PosixFilePermission> getDefaultPosixFilePermissions() {
461         return defaultPosixFilePermissions;
462     }
463 
464     public void setDefaultDirectoryAcls(List<AclEntry> acl) {
465         defaultDirectoryAcls = acl;
466     }
467 
468     public List<AclEntry> getDefaultDirectoryAcls() {
469         return defaultDirectoryAcls;
470     }
471 
472     public void setDefaultPosixDirectoryPermissions(Set<PosixFilePermission> perms) {
473         defaultPosixDirectoryPermissions = perms;
474     }
475 
476     public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions() {
477         return defaultPosixDirectoryPermissions;
478     }
479 
480     @Override
481     public void create() throws IOException {
482         if (!Files.exists(assetPath)) {
483             if (directoryHint) {
484                 Files.createDirectories(assetPath);
485             } else {
486                 if (!Files.exists( assetPath.getParent() )) {
487                     Files.createDirectories( assetPath.getParent( ) );
488                 }
489                 Files.createFile(assetPath);
490             }
491             if (setPermissionsForNew) {
492                 applyDefaultPermissions(assetPath);
493             }
494         }
495     }
496 
497     @Override
498     public String toString() {
499         return relativePath+":"+assetPath;
500     }
501 
502     @Override
503     public int compareTo(Object o) {
504         if (o instanceof FilesystemAsset) {
505             if (this.getPath()!=null) {
506                 return this.getPath().compareTo(((FilesystemAsset) o).getPath());
507             } else {
508                 return 0;
509             }
510         }
511         return 0;
512     }
513 }