1 package org.apache.archiva.repository.content;
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
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
22 import org.apache.archiva.common.filelock.FileLockException;
23 import org.apache.archiva.common.filelock.FileLockManager;
24 import org.apache.archiva.common.filelock.FileLockTimeoutException;
25 import org.apache.archiva.common.filelock.Lock;
26 import org.apache.commons.io.FileUtils;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.nio.channels.FileChannel;
35 import java.nio.channels.ReadableByteChannel;
36 import java.nio.channels.WritableByteChannel;
37 import java.nio.file.CopyOption;
38 import java.nio.file.Files;
39 import java.nio.file.Path;
40 import java.nio.file.Paths;
41 import java.nio.file.StandardCopyOption;
42 import java.nio.file.StandardOpenOption;
43 import java.util.function.Consumer;
46 * Implementation of <code>{@link RepositoryStorage}</code> where data is stored in the filesystem.
48 * All files are relative to a given base path. Path values are separated by '/', '..' is allowed to navigate
49 * to a parent directory, but navigation out of the base path will lead to a exception.
51 public class FilesystemStorage implements RepositoryStorage {
53 private static final Logger log = LoggerFactory.getLogger(FilesystemStorage.class);
55 private final Path basePath;
56 private final FileLockManager fileLockManager;
58 public FilesystemStorage(Path basePath, FileLockManager fileLockManager) throws IOException {
59 if (!Files.exists(basePath)) {
60 Files.createDirectories(basePath);
62 this.basePath = basePath.normalize().toRealPath();
63 this.fileLockManager = fileLockManager;
66 private Path normalize(final String path) {
68 while (nPath.startsWith("/")) {
69 nPath = nPath.substring(1);
71 return Paths.get(nPath);
74 private Path getAssetPath(String path) throws IOException {
75 Path assetPath = basePath.resolve(normalize(path)).normalize();
76 if (!assetPath.startsWith(basePath))
78 throw new IOException("Path navigation out of allowed scope: "+path);
84 public void consumeData( StorageAsset asset, Consumer<InputStream> consumerFunction, boolean readLock ) throws IOException
86 final Path path = asset.getFilePath();
89 consumeDataLocked( path, consumerFunction );
92 try ( InputStream is = Files.newInputStream( path ) )
94 consumerFunction.accept( is );
96 catch ( IOException e )
98 log.error("Could not read the input stream from file {}", path);
102 } catch (RuntimeException e)
104 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
105 throw new IOException( e );
111 public void consumeDataFromChannel( StorageAsset asset, Consumer<ReadableByteChannel> consumerFunction, boolean readLock ) throws IOException
113 final Path path = asset.getFilePath();
116 consumeDataFromChannelLocked( path, consumerFunction );
119 try ( FileChannel is = FileChannel.open( path, StandardOpenOption.READ ) )
121 consumerFunction.accept( is );
123 catch ( IOException e )
125 log.error("Could not read the input stream from file {}", path);
129 } catch (RuntimeException e)
131 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
132 throw new IOException( e );
137 public void writeData( StorageAsset asset, Consumer<OutputStream> consumerFunction, boolean writeLock ) throws IOException
139 final Path path = asset.getFilePath();
142 writeDataLocked( path, consumerFunction );
145 try ( OutputStream is = Files.newOutputStream( path ) )
147 consumerFunction.accept( is );
149 catch ( IOException e )
151 log.error("Could not write the output stream to file {}", path);
155 } catch (RuntimeException e)
157 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
158 throw new IOException( e );
164 public void writeDataToChannel( StorageAsset asset, Consumer<WritableByteChannel> consumerFunction, boolean writeLock ) throws IOException
166 final Path path = asset.getFilePath();
169 writeDataToChannelLocked( path, consumerFunction );
172 try ( FileChannel os = FileChannel.open( path, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE ))
174 consumerFunction.accept( os );
176 catch ( IOException e )
178 log.error("Could not write the data to file {}", path);
182 } catch (RuntimeException e)
184 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
185 throw new IOException( e );
189 private void consumeDataLocked( Path file, Consumer<InputStream> consumerFunction) throws IOException
195 lock = fileLockManager.readFileLock( file );
196 try ( InputStream is = Files.newInputStream( lock.getFile()))
198 consumerFunction.accept( is );
200 catch ( IOException e )
202 log.error("Could not read the input stream from file {}", file);
206 fileLockManager.release( lock );
209 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
211 log.error("Locking error on file {}", file);
212 throw new IOException(e);
216 private void consumeDataFromChannelLocked( Path file, Consumer<ReadableByteChannel> consumerFunction) throws IOException
222 lock = fileLockManager.readFileLock( file );
223 try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.READ ))
225 consumerFunction.accept( is );
227 catch ( IOException e )
229 log.error("Could not read the input stream from file {}", file);
233 fileLockManager.release( lock );
236 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
238 log.error("Locking error on file {}", file);
239 throw new IOException(e);
244 private void writeDataLocked( Path file, Consumer<OutputStream> consumerFunction) throws IOException
250 lock = fileLockManager.writeFileLock( file );
251 try ( OutputStream is = Files.newOutputStream( lock.getFile()))
253 consumerFunction.accept( is );
255 catch ( IOException e )
257 log.error("Could not write the output stream to file {}", file);
261 fileLockManager.release( lock );
264 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
266 log.error("Locking error on file {}", file);
267 throw new IOException(e);
271 private void writeDataToChannelLocked( Path file, Consumer<WritableByteChannel> consumerFunction) throws IOException
277 lock = fileLockManager.writeFileLock( file );
278 try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE ))
280 consumerFunction.accept( is );
282 catch ( IOException e )
284 log.error("Could not write to file {}", file);
288 fileLockManager.release( lock );
291 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
293 log.error("Locking error on file {}", file);
294 throw new IOException(e);
299 public StorageAsset getAsset( String path )
302 return new FilesystemAsset(this, path, getAssetPath(path));
303 } catch (IOException e) {
304 throw new IllegalArgumentException("Path navigates outside of base directory "+path);
309 public StorageAsset addAsset( String path, boolean container )
312 return new FilesystemAsset(this, path, getAssetPath(path), basePath, container);
313 } catch (IOException e) {
314 throw new IllegalArgumentException("Path navigates outside of base directory "+path);
319 public void removeAsset( StorageAsset asset ) throws IOException
321 Files.delete(asset.getFilePath());
325 public StorageAsset moveAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException
327 boolean container = origin.isContainer();
328 FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container );
329 moveAsset( origin, newAsset, copyOptions );
334 public void moveAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException
336 Files.move(origin.getFilePath(), destination.getFilePath(), copyOptions);
340 public StorageAsset copyAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException
342 boolean container = origin.isContainer();
343 FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container );
344 copyAsset( origin, newAsset, copyOptions );
349 public void copyAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException
351 Path destinationPath = destination.getFilePath();
352 boolean overwrite = false;
353 for (int i=0; i<copyOptions.length; i++) {
354 if (copyOptions[i].equals( StandardCopyOption.REPLACE_EXISTING )) {
358 if (Files.exists(destinationPath) && !overwrite) {
359 throw new IOException("Destination file exists already "+ destinationPath);
361 if (Files.isDirectory( origin.getFilePath() ))
363 FileUtils.copyDirectory(origin.getFilePath( ).toFile(), destinationPath.toFile() );
364 } else if (Files.isRegularFile( origin.getFilePath() )) {
365 if (!Files.exists( destinationPath )) {
366 Files.createDirectories( destinationPath );
368 Files.copy( origin.getFilePath( ), destinationPath, copyOptions );
372 public FileLockManager getFileLockManager() {
373 return fileLockManager;