001package org.apache.archiva.repository.storage; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.archiva.common.filelock.FileLockException; 023import org.apache.archiva.common.filelock.FileLockManager; 024import org.apache.archiva.common.filelock.FileLockTimeoutException; 025import org.apache.archiva.common.filelock.Lock; 026import org.apache.archiva.common.utils.PathUtil; 027import org.apache.commons.io.FileUtils; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031import java.io.FileNotFoundException; 032import java.io.IOException; 033import java.io.InputStream; 034import java.io.OutputStream; 035import java.net.URI; 036import java.nio.channels.FileChannel; 037import java.nio.channels.ReadableByteChannel; 038import java.nio.channels.WritableByteChannel; 039import java.nio.file.*; 040import java.util.function.Consumer; 041 042/** 043 * Implementation of <code>{@link RepositoryStorage}</code> where data is stored in the filesystem. 044 * 045 * All files are relative to a given base path. Path values are separated by '/', '..' is allowed to navigate 046 * to a parent directory, but navigation out of the base path will lead to a exception. 047 */ 048public class FilesystemStorage implements RepositoryStorage { 049 050 private static final Logger log = LoggerFactory.getLogger(FilesystemStorage.class); 051 052 private Path basePath; 053 private final FileLockManager fileLockManager; 054 055 public FilesystemStorage(Path basePath, FileLockManager fileLockManager) throws IOException { 056 if (!Files.exists(basePath)) { 057 Files.createDirectories(basePath); 058 } 059 this.basePath = basePath.normalize().toRealPath(); 060 this.fileLockManager = fileLockManager; 061 } 062 063 private Path normalize(final String path) { 064 String nPath = path; 065 while (nPath.startsWith("/")) { 066 nPath = nPath.substring(1); 067 } 068 return Paths.get(nPath); 069 } 070 071 private Path getAssetPath(String path) throws IOException { 072 Path assetPath = basePath.resolve(normalize(path)).normalize(); 073 if (!assetPath.startsWith(basePath)) 074 { 075 throw new IOException("Path navigation out of allowed scope: "+path); 076 } 077 return assetPath; 078 } 079 080 @Override 081 public void consumeData(StorageAsset asset, Consumer<InputStream> consumerFunction, boolean readLock ) throws IOException 082 { 083 final Path path = asset.getFilePath(); 084 try { 085 if (readLock) { 086 consumeDataLocked( path, consumerFunction ); 087 } else 088 { 089 try ( InputStream is = Files.newInputStream( path ) ) 090 { 091 consumerFunction.accept( is ); 092 } 093 catch ( IOException e ) 094 { 095 log.error("Could not read the input stream from file {}", path); 096 throw e; 097 } 098 } 099 } catch (RuntimeException e) 100 { 101 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() ); 102 throw new IOException( e ); 103 } 104 105 } 106 107 @Override 108 public void consumeDataFromChannel( StorageAsset asset, Consumer<ReadableByteChannel> consumerFunction, boolean readLock ) throws IOException 109 { 110 final Path path = asset.getFilePath(); 111 try { 112 if (readLock) { 113 consumeDataFromChannelLocked( path, consumerFunction ); 114 } else 115 { 116 try ( FileChannel is = FileChannel.open( path, StandardOpenOption.READ ) ) 117 { 118 consumerFunction.accept( is ); 119 } 120 catch ( IOException e ) 121 { 122 log.error("Could not read the input stream from file {}", path); 123 throw e; 124 } 125 } 126 } catch (RuntimeException e) 127 { 128 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() ); 129 throw new IOException( e ); 130 } 131 } 132 133 @Override 134 public void writeData( StorageAsset asset, Consumer<OutputStream> consumerFunction, boolean writeLock ) throws IOException 135 { 136 final Path path = asset.getFilePath(); 137 try { 138 if (writeLock) { 139 writeDataLocked( path, consumerFunction ); 140 } else 141 { 142 try ( OutputStream is = Files.newOutputStream( path ) ) 143 { 144 consumerFunction.accept( is ); 145 } 146 catch ( IOException e ) 147 { 148 log.error("Could not write the output stream to file {}", path); 149 throw e; 150 } 151 } 152 } catch (RuntimeException e) 153 { 154 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() ); 155 throw new IOException( e ); 156 } 157 158 } 159 160 @Override 161 public void writeDataToChannel( StorageAsset asset, Consumer<WritableByteChannel> consumerFunction, boolean writeLock ) throws IOException 162 { 163 final Path path = asset.getFilePath(); 164 try { 165 if (writeLock) { 166 writeDataToChannelLocked( path, consumerFunction ); 167 } else 168 { 169 try ( FileChannel os = FileChannel.open( path, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE )) 170 { 171 consumerFunction.accept( os ); 172 } 173 catch ( IOException e ) 174 { 175 log.error("Could not write the data to file {}", path); 176 throw e; 177 } 178 } 179 } catch (RuntimeException e) 180 { 181 log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() ); 182 throw new IOException( e ); 183 } 184 } 185 186 private void consumeDataLocked( Path file, Consumer<InputStream> consumerFunction) throws IOException 187 { 188 189 final Lock lock; 190 try 191 { 192 lock = fileLockManager.readFileLock( file ); 193 try ( InputStream is = Files.newInputStream( lock.getFile())) 194 { 195 consumerFunction.accept( is ); 196 } 197 catch ( IOException e ) 198 { 199 log.error("Could not read the input stream from file {}", file); 200 throw e; 201 } finally 202 { 203 fileLockManager.release( lock ); 204 } 205 } 206 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e) 207 { 208 log.error("Locking error on file {}", file); 209 throw new IOException(e); 210 } 211 } 212 213 private void consumeDataFromChannelLocked( Path file, Consumer<ReadableByteChannel> consumerFunction) throws IOException 214 { 215 216 final Lock lock; 217 try 218 { 219 lock = fileLockManager.readFileLock( file ); 220 try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.READ )) 221 { 222 consumerFunction.accept( is ); 223 } 224 catch ( IOException e ) 225 { 226 log.error("Could not read the input stream from file {}", file); 227 throw e; 228 } finally 229 { 230 fileLockManager.release( lock ); 231 } 232 } 233 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e) 234 { 235 log.error("Locking error on file {}", file); 236 throw new IOException(e); 237 } 238 } 239 240 241 private void writeDataLocked( Path file, Consumer<OutputStream> consumerFunction) throws IOException 242 { 243 244 final Lock lock; 245 try 246 { 247 lock = fileLockManager.writeFileLock( file ); 248 try ( OutputStream is = Files.newOutputStream( lock.getFile())) 249 { 250 consumerFunction.accept( is ); 251 } 252 catch ( IOException e ) 253 { 254 log.error("Could not write the output stream to file {}", file); 255 throw e; 256 } finally 257 { 258 fileLockManager.release( lock ); 259 } 260 } 261 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e) 262 { 263 log.error("Locking error on file {}", file); 264 throw new IOException(e); 265 } 266 } 267 268 private void writeDataToChannelLocked( Path file, Consumer<WritableByteChannel> consumerFunction) throws IOException 269 { 270 271 final Lock lock; 272 try 273 { 274 lock = fileLockManager.writeFileLock( file ); 275 try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE )) 276 { 277 consumerFunction.accept( is ); 278 } 279 catch ( IOException e ) 280 { 281 log.error("Could not write to file {}", file); 282 throw e; 283 } finally 284 { 285 fileLockManager.release( lock ); 286 } 287 } 288 catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e) 289 { 290 log.error("Locking error on file {}", file); 291 throw new IOException(e); 292 } 293 } 294 295 @Override 296 public URI getLocation() { 297 return basePath.toUri(); 298 } 299 300 /** 301 * Updates the location and releases all locks. 302 * 303 * @param newLocation The URI to the new location 304 * 305 * @throws IOException If the directory cannot be created. 306 */ 307 @Override 308 public void updateLocation(URI newLocation) throws IOException { 309 Path newPath = PathUtil.getPathFromUri(newLocation).toAbsolutePath(); 310 if (!Files.exists(newPath)) { 311 Files.createDirectories(newPath); 312 } 313 basePath = newPath; 314 if (fileLockManager!=null) { 315 fileLockManager.clearLockFiles(); 316 } 317 } 318 319 @Override 320 public StorageAsset getAsset( String path ) 321 { 322 try { 323 return new FilesystemAsset(this, path, getAssetPath(path)); 324 } catch (IOException e) { 325 throw new IllegalArgumentException("Path navigates outside of base directory "+path); 326 } 327 } 328 329 @Override 330 public StorageAsset addAsset( String path, boolean container ) 331 { 332 try { 333 return new FilesystemAsset(this, path, getAssetPath(path), basePath, container); 334 } catch (IOException e) { 335 throw new IllegalArgumentException("Path navigates outside of base directory "+path); 336 } 337 } 338 339 @Override 340 public void removeAsset( StorageAsset asset ) throws IOException 341 { 342 Files.delete(asset.getFilePath()); 343 } 344 345 @Override 346 public StorageAsset moveAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException 347 { 348 boolean container = origin.isContainer(); 349 FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container ); 350 moveAsset( origin, newAsset, copyOptions ); 351 return newAsset; 352 } 353 354 @Override 355 public void moveAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException 356 { 357 if (origin.getStorage()!=this) { 358 throw new IOException("The origin asset does not belong to this storage instance. Cannot copy between different storage instances."); 359 } 360 if (destination.getStorage()!=this) { 361 throw new IOException("The destination asset does not belong to this storage instance. Cannot copy between different storage instances."); 362 } 363 Files.move(origin.getFilePath(), destination.getFilePath(), copyOptions); 364 } 365 366 @Override 367 public StorageAsset copyAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException 368 { 369 boolean container = origin.isContainer(); 370 FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container ); 371 copyAsset( origin, newAsset, copyOptions ); 372 return newAsset; 373 } 374 375 @Override 376 public void copyAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException 377 { 378 if (origin.getStorage()!=this) { 379 throw new IOException("The origin asset does not belong to this storage instance. Cannot copy between different storage instances."); 380 } 381 if (destination.getStorage()!=this) { 382 throw new IOException("The destination asset does not belong to this storage instance. Cannot copy between different storage instances."); 383 } 384 Path destinationPath = destination.getFilePath(); 385 boolean overwrite = false; 386 for (int i=0; i<copyOptions.length; i++) { 387 if (copyOptions[i].equals( StandardCopyOption.REPLACE_EXISTING )) { 388 overwrite=true; 389 } 390 } 391 if (Files.exists(destinationPath) && !overwrite) { 392 throw new IOException("Destination file exists already "+ destinationPath); 393 } 394 if (Files.isDirectory( origin.getFilePath() )) 395 { 396 FileUtils.copyDirectory(origin.getFilePath( ).toFile(), destinationPath.toFile() ); 397 } else if (Files.isRegularFile( origin.getFilePath() )) { 398 if (!Files.exists( destinationPath )) { 399 Files.createDirectories( destinationPath ); 400 } 401 Files.copy( origin.getFilePath( ), destinationPath, copyOptions ); 402 } 403 } 404 405 public FileLockManager getFileLockManager() { 406 return fileLockManager; 407 } 408 409}