]> source.dussan.org Git - archiva.git/blob
339e65d384e11030c11fb55280c155643c4ddeb3
[archiva.git] /
1 package org.apache.archiva.repository.storage.fs;
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  * 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
18  * under the License.
19  */
20
21 import org.apache.archiva.common.filelock.FileLockException;
22 import org.apache.archiva.common.filelock.FileLockManager;
23 import org.apache.archiva.common.filelock.FileLockTimeoutException;
24 import org.apache.archiva.common.filelock.Lock;
25 import org.apache.archiva.common.utils.PathUtil;
26 import org.apache.archiva.repository.storage.RepositoryStorage;
27 import org.apache.archiva.repository.storage.StorageAsset;
28 import org.apache.commons.io.FileUtils;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import java.io.FileNotFoundException;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.OutputStream;
36 import java.net.URI;
37 import java.nio.channels.FileChannel;
38 import java.nio.channels.ReadableByteChannel;
39 import java.nio.channels.WritableByteChannel;
40 import java.nio.file.*;
41 import java.util.function.Consumer;
42
43 /**
44  * Implementation of <code>{@link RepositoryStorage}</code> where data is stored in the filesystem.
45  *
46  * All files are relative to a given base path. Path values are separated by '/', '..' is allowed to navigate
47  * to a parent directory, but navigation out of the base path will lead to a exception.
48  */
49 public class FilesystemStorage implements RepositoryStorage {
50
51     private static final Logger log = LoggerFactory.getLogger(FilesystemStorage.class);
52
53     private Path basePath;
54     private final FileLockManager fileLockManager;
55
56     public FilesystemStorage(Path basePath, FileLockManager fileLockManager) throws IOException {
57         if (!Files.exists(basePath)) {
58             Files.createDirectories(basePath);
59         }
60         this.basePath = basePath.normalize().toRealPath();
61         this.fileLockManager = fileLockManager;
62     }
63
64     private Path normalize(final String path) {
65         String nPath = path;
66         while (nPath.startsWith("/")) {
67             nPath = nPath.substring(1);
68         }
69         return Paths.get(nPath);
70     }
71
72     private Path getAssetPath(String path) throws IOException {
73         Path assetPath = basePath.resolve(normalize(path)).normalize();
74         if (!assetPath.startsWith(basePath))
75         {
76             throw new IOException("Path navigation out of allowed scope: "+path);
77         }
78         return assetPath;
79     }
80
81     @Override
82     public void consumeData( StorageAsset asset, Consumer<InputStream> consumerFunction, boolean readLock ) throws IOException
83     {
84         final Path path = asset.getFilePath();
85         try {
86             if (readLock) {
87                 consumeDataLocked( path, consumerFunction );
88             } else
89             {
90                 try ( InputStream is = Files.newInputStream( path ) )
91                 {
92                     consumerFunction.accept( is );
93                 }
94                 catch ( IOException e )
95                 {
96                     log.error("Could not read the input stream from file {}", path);
97                     throw e;
98                 }
99             }
100         } catch (RuntimeException e)
101         {
102             log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
103             throw new IOException( e );
104         }
105
106     }
107
108     @Override
109     public void consumeDataFromChannel( StorageAsset asset, Consumer<ReadableByteChannel> consumerFunction, boolean readLock ) throws IOException
110     {
111         final Path path = asset.getFilePath();
112         try {
113             if (readLock) {
114                 consumeDataFromChannelLocked( path, consumerFunction );
115             } else
116             {
117                 try ( FileChannel is = FileChannel.open( path, StandardOpenOption.READ ) )
118                 {
119                     consumerFunction.accept( is );
120                 }
121                 catch ( IOException e )
122                 {
123                     log.error("Could not read the input stream from file {}", path);
124                     throw e;
125                 }
126             }
127         } catch (RuntimeException e)
128         {
129             log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
130             throw new IOException( e );
131         }
132     }
133
134     @Override
135     public void writeData( StorageAsset asset, Consumer<OutputStream> consumerFunction, boolean writeLock ) throws IOException
136     {
137         final Path path = asset.getFilePath();
138         try {
139             if (writeLock) {
140                 writeDataLocked( path, consumerFunction );
141             } else
142             {
143                 try ( OutputStream is = Files.newOutputStream( path ) )
144                 {
145                     consumerFunction.accept( is );
146                 }
147                 catch ( IOException e )
148                 {
149                     log.error("Could not write the output stream to file {}", path);
150                     throw e;
151                 }
152             }
153         } catch (RuntimeException e)
154         {
155             log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
156             throw new IOException( e );
157         }
158
159     }
160
161     @Override
162     public void writeDataToChannel( StorageAsset asset, Consumer<WritableByteChannel> consumerFunction, boolean writeLock ) throws IOException
163     {
164         final Path path = asset.getFilePath();
165         try {
166             if (writeLock) {
167                 writeDataToChannelLocked( path, consumerFunction );
168             } else
169             {
170                 try ( FileChannel os = FileChannel.open( path, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE ))
171                 {
172                     consumerFunction.accept( os );
173                 }
174                 catch ( IOException e )
175                 {
176                     log.error("Could not write the data to file {}", path);
177                     throw e;
178                 }
179             }
180         } catch (RuntimeException e)
181         {
182             log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
183             throw new IOException( e );
184         }
185     }
186
187     private void consumeDataLocked( Path file, Consumer<InputStream> consumerFunction) throws IOException
188     {
189
190         final Lock lock;
191         try
192         {
193             lock = fileLockManager.readFileLock( file );
194             try ( InputStream is = Files.newInputStream( lock.getFile()))
195             {
196                 consumerFunction.accept( is );
197             }
198             catch ( IOException e )
199             {
200                 log.error("Could not read the input stream from file {}", file);
201                 throw e;
202             } finally
203             {
204                 fileLockManager.release( lock );
205             }
206         }
207         catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
208         {
209             log.error("Locking error on file {}", file);
210             throw new IOException(e);
211         }
212     }
213
214     private void consumeDataFromChannelLocked( Path file, Consumer<ReadableByteChannel> consumerFunction) throws IOException
215     {
216
217         final Lock lock;
218         try
219         {
220             lock = fileLockManager.readFileLock( file );
221             try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.READ ))
222             {
223                 consumerFunction.accept( is );
224             }
225             catch ( IOException e )
226             {
227                 log.error("Could not read the input stream from file {}", file);
228                 throw e;
229             } finally
230             {
231                 fileLockManager.release( lock );
232             }
233         }
234         catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
235         {
236             log.error("Locking error on file {}", file);
237             throw new IOException(e);
238         }
239     }
240
241
242     private void writeDataLocked( Path file, Consumer<OutputStream> consumerFunction) throws IOException
243     {
244
245         final Lock lock;
246         try
247         {
248             lock = fileLockManager.writeFileLock( file );
249             try ( OutputStream is = Files.newOutputStream( lock.getFile()))
250             {
251                 consumerFunction.accept( is );
252             }
253             catch ( IOException e )
254             {
255                 log.error("Could not write the output stream to file {}", file);
256                 throw e;
257             } finally
258             {
259                 fileLockManager.release( lock );
260             }
261         }
262         catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
263         {
264             log.error("Locking error on file {}", file);
265             throw new IOException(e);
266         }
267     }
268
269     private void writeDataToChannelLocked( Path file, Consumer<WritableByteChannel> consumerFunction) throws IOException
270     {
271
272         final Lock lock;
273         try
274         {
275             lock = fileLockManager.writeFileLock( file );
276             try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE ))
277             {
278                 consumerFunction.accept( is );
279             }
280             catch ( IOException e )
281             {
282                 log.error("Could not write to file {}", file);
283                 throw e;
284             } finally
285             {
286                 fileLockManager.release( lock );
287             }
288         }
289         catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
290         {
291             log.error("Locking error on file {}", file);
292             throw new IOException(e);
293         }
294     }
295
296     @Override
297     public URI getLocation() {
298         return basePath.toUri();
299     }
300
301     /**
302      * Updates the location and releases all locks.
303      *
304      * @param newLocation The URI to the new location
305      *
306      * @throws IOException If the directory cannot be created.
307      */
308     @Override
309     public void updateLocation(URI newLocation) throws IOException {
310         Path newPath = PathUtil.getPathFromUri(newLocation).toAbsolutePath();
311         if (!Files.exists(newPath)) {
312             Files.createDirectories(newPath);
313         }
314         basePath = newPath;
315         if (fileLockManager!=null) {
316             fileLockManager.clearLockFiles();
317         }
318     }
319
320     @Override
321     public StorageAsset getAsset( String path )
322     {
323         try {
324             return new FilesystemAsset(this, path, getAssetPath(path), basePath);
325         } catch (IOException e) {
326             throw new IllegalArgumentException("Path navigates outside of base directory "+path);
327         }
328     }
329
330     @Override
331     public StorageAsset addAsset( String path, boolean container )
332     {
333         try {
334             return new FilesystemAsset(this, path, getAssetPath(path), basePath, container);
335         } catch (IOException e) {
336             throw new IllegalArgumentException("Path navigates outside of base directory "+path);
337         }
338     }
339
340     @Override
341     public void removeAsset( StorageAsset asset ) throws IOException
342     {
343         Files.delete(asset.getFilePath());
344     }
345
346     @Override
347     public StorageAsset moveAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException
348     {
349         boolean container = origin.isContainer();
350         FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container );
351         moveAsset( origin, newAsset, copyOptions );
352         return newAsset;
353     }
354
355     @Override
356     public void moveAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException
357     {
358         if (origin.getStorage()!=this) {
359             throw new IOException("The origin asset does not belong to this storage instance. Cannot copy between different storage instances.");
360         }
361         if (destination.getStorage()!=this) {
362             throw new IOException("The destination asset does not belong to this storage instance. Cannot copy between different storage instances.");
363         }
364         Files.move(origin.getFilePath(), destination.getFilePath(), copyOptions);
365     }
366
367     @Override
368     public StorageAsset copyAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException
369     {
370         boolean container = origin.isContainer();
371         FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container );
372         copyAsset( origin, newAsset, copyOptions );
373         return newAsset;
374     }
375
376     @Override
377     public void copyAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException
378     {
379         if (origin.getStorage()!=this) {
380             throw new IOException("The origin asset does not belong to this storage instance. Cannot copy between different storage instances.");
381         }
382         if (destination.getStorage()!=this) {
383             throw new IOException("The destination asset does not belong to this storage instance. Cannot copy between different storage instances.");
384         }
385         Path destinationPath = destination.getFilePath();
386         boolean overwrite = false;
387         for (int i=0; i<copyOptions.length; i++) {
388             if (copyOptions[i].equals( StandardCopyOption.REPLACE_EXISTING )) {
389                 overwrite=true;
390             }
391         }
392         if (Files.exists(destinationPath) && !overwrite) {
393             throw new IOException("Destination file exists already "+ destinationPath);
394         }
395         if (Files.isDirectory( origin.getFilePath() ))
396         {
397             FileUtils.copyDirectory(origin.getFilePath( ).toFile(), destinationPath.toFile() );
398         } else if (Files.isRegularFile( origin.getFilePath() )) {
399             if (!Files.exists( destinationPath )) {
400                 Files.createDirectories( destinationPath );
401             }
402             Files.copy( origin.getFilePath( ), destinationPath, copyOptions );
403         }
404     }
405
406     public FileLockManager getFileLockManager() {
407         return fileLockManager;
408     }
409
410 }