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.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;
32 import java.io.FileNotFoundException;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.OutputStream;
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;
44 * Implementation of <code>{@link RepositoryStorage}</code> where data is stored in the filesystem.
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.
49 public class FilesystemStorage implements RepositoryStorage {
51 private static final Logger log = LoggerFactory.getLogger(FilesystemStorage.class);
53 private Path basePath;
54 private final FileLockManager fileLockManager;
56 public FilesystemStorage(Path basePath, FileLockManager fileLockManager) throws IOException {
57 if (!Files.exists(basePath)) {
58 Files.createDirectories(basePath);
60 this.basePath = basePath.normalize().toRealPath();
61 this.fileLockManager = fileLockManager;
64 private Path normalize(final String path) {
66 while (nPath.startsWith("/")) {
67 nPath = nPath.substring(1);
69 return Paths.get(nPath);
72 private Path getAssetPath(String path) throws IOException {
73 Path assetPath = basePath.resolve(normalize(path)).normalize();
74 if (!assetPath.startsWith(basePath))
76 throw new IOException("Path navigation out of allowed scope: "+path);
82 public void consumeData( StorageAsset asset, Consumer<InputStream> consumerFunction, boolean readLock ) throws IOException
84 final Path path = asset.getFilePath();
87 consumeDataLocked( path, consumerFunction );
90 try ( InputStream is = Files.newInputStream( path ) )
92 consumerFunction.accept( is );
94 catch ( IOException e )
96 log.error("Could not read the input stream from file {}", path);
100 } catch (RuntimeException e)
102 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
103 throw new IOException( e );
109 public void consumeDataFromChannel( StorageAsset asset, Consumer<ReadableByteChannel> consumerFunction, boolean readLock ) throws IOException
111 final Path path = asset.getFilePath();
114 consumeDataFromChannelLocked( path, consumerFunction );
117 try ( FileChannel is = FileChannel.open( path, StandardOpenOption.READ ) )
119 consumerFunction.accept( is );
121 catch ( IOException e )
123 log.error("Could not read the input stream from file {}", path);
127 } catch (RuntimeException e)
129 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
130 throw new IOException( e );
135 public void writeData( StorageAsset asset, Consumer<OutputStream> consumerFunction, boolean writeLock ) throws IOException
137 final Path path = asset.getFilePath();
140 writeDataLocked( path, consumerFunction );
143 try ( OutputStream is = Files.newOutputStream( path ) )
145 consumerFunction.accept( is );
147 catch ( IOException e )
149 log.error("Could not write the output stream to file {}", path);
153 } catch (RuntimeException e)
155 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
156 throw new IOException( e );
162 public void writeDataToChannel( StorageAsset asset, Consumer<WritableByteChannel> consumerFunction, boolean writeLock ) throws IOException
164 final Path path = asset.getFilePath();
167 writeDataToChannelLocked( path, consumerFunction );
170 try ( FileChannel os = FileChannel.open( path, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE ))
172 consumerFunction.accept( os );
174 catch ( IOException e )
176 log.error("Could not write the data to file {}", path);
180 } catch (RuntimeException e)
182 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
183 throw new IOException( e );
187 private void consumeDataLocked( Path file, Consumer<InputStream> consumerFunction) throws IOException
193 lock = fileLockManager.readFileLock( file );
194 try ( InputStream is = Files.newInputStream( lock.getFile()))
196 consumerFunction.accept( is );
198 catch ( IOException e )
200 log.error("Could not read the input stream from file {}", file);
204 fileLockManager.release( lock );
207 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
209 log.error("Locking error on file {}", file);
210 throw new IOException(e);
214 private void consumeDataFromChannelLocked( Path file, Consumer<ReadableByteChannel> consumerFunction) throws IOException
220 lock = fileLockManager.readFileLock( file );
221 try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.READ ))
223 consumerFunction.accept( is );
225 catch ( IOException e )
227 log.error("Could not read the input stream from file {}", file);
231 fileLockManager.release( lock );
234 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
236 log.error("Locking error on file {}", file);
237 throw new IOException(e);
242 private void writeDataLocked( Path file, Consumer<OutputStream> consumerFunction) throws IOException
248 lock = fileLockManager.writeFileLock( file );
249 try ( OutputStream is = Files.newOutputStream( lock.getFile()))
251 consumerFunction.accept( is );
253 catch ( IOException e )
255 log.error("Could not write the output stream to file {}", file);
259 fileLockManager.release( lock );
262 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
264 log.error("Locking error on file {}", file);
265 throw new IOException(e);
269 private void writeDataToChannelLocked( Path file, Consumer<WritableByteChannel> consumerFunction) throws IOException
275 lock = fileLockManager.writeFileLock( file );
276 try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE ))
278 consumerFunction.accept( is );
280 catch ( IOException e )
282 log.error("Could not write to file {}", file);
286 fileLockManager.release( lock );
289 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
291 log.error("Locking error on file {}", file);
292 throw new IOException(e);
297 public URI getLocation() {
298 return basePath.toUri();
302 * Updates the location and releases all locks.
304 * @param newLocation The URI to the new location
306 * @throws IOException If the directory cannot be created.
309 public void updateLocation(URI newLocation) throws IOException {
310 Path newPath = PathUtil.getPathFromUri(newLocation).toAbsolutePath();
311 if (!Files.exists(newPath)) {
312 Files.createDirectories(newPath);
315 if (fileLockManager!=null) {
316 fileLockManager.clearLockFiles();
321 public StorageAsset getAsset( String path )
324 return new FilesystemAsset(this, path, getAssetPath(path), basePath);
325 } catch (IOException e) {
326 throw new IllegalArgumentException("Path navigates outside of base directory "+path);
331 public StorageAsset addAsset( String path, boolean container )
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);
341 public void removeAsset( StorageAsset asset ) throws IOException
343 Files.delete(asset.getFilePath());
347 public StorageAsset moveAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException
349 boolean container = origin.isContainer();
350 FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container );
351 moveAsset( origin, newAsset, copyOptions );
356 public void moveAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException
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.");
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.");
364 Files.move(origin.getFilePath(), destination.getFilePath(), copyOptions);
368 public StorageAsset copyAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException
370 boolean container = origin.isContainer();
371 FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container );
372 copyAsset( origin, newAsset, copyOptions );
377 public void copyAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException
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.");
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.");
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 )) {
392 if (Files.exists(destinationPath) && !overwrite) {
393 throw new IOException("Destination file exists already "+ destinationPath);
395 if (Files.isDirectory( origin.getFilePath() ))
397 FileUtils.copyDirectory(origin.getFilePath( ).toFile(), destinationPath.toFile() );
398 } else if (Files.isRegularFile( origin.getFilePath() )) {
399 if (!Files.exists( destinationPath )) {
400 Files.createDirectories( destinationPath );
402 Files.copy( origin.getFilePath( ), destinationPath, copyOptions );
406 public FileLockManager getFileLockManager() {
407 return fileLockManager;