You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

StorageUtil.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package org.apache.archiva.repository.storage;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  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. import org.apache.archiva.common.filelock.FileLockException;
  21. import org.apache.archiva.common.filelock.FileLockManager;
  22. import org.apache.archiva.common.filelock.FileLockTimeoutException;
  23. import org.apache.archiva.common.filelock.Lock;
  24. import org.apache.commons.lang.StringUtils;
  25. import org.slf4j.Logger;
  26. import org.slf4j.LoggerFactory;
  27. import java.io.IOException;
  28. import java.nio.ByteBuffer;
  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.util.HashSet;
  34. import java.util.function.Consumer;
  35. /**
  36. *
  37. * Utility class for assets. Allows to copy, move between different storage instances and
  38. * recursively consume the tree.
  39. *
  40. * @author Martin Stockhammer <martin_s@apache.org>
  41. */
  42. public class StorageUtil
  43. {
  44. private static final int DEFAULT_BUFFER_SIZE = 4096;
  45. private static final Logger log = LoggerFactory.getLogger(StorageUtil.class);
  46. /**
  47. * Copies the source asset to the target. The assets may be from different RepositoryStorage instances.
  48. * If you know that source and asset are from the same storage instance, the copy method of the storage
  49. * instance may be faster.
  50. *
  51. * @param source The source asset
  52. * @param target The target asset
  53. * @param locked If true, a readlock is set on the source and a write lock is set on the target.
  54. * @param copyOptions Copy options
  55. * @throws IOException
  56. */
  57. public static final void copyAsset( final StorageAsset source,
  58. final StorageAsset target,
  59. boolean locked,
  60. final CopyOption... copyOptions ) throws IOException
  61. {
  62. if (source.isFileBased() && target.isFileBased()) {
  63. // Short cut for FS operations
  64. final Path sourcePath = source.getFilePath();
  65. final Path targetPath = target.getFilePath( );
  66. if (locked) {
  67. final FileLockManager lmSource = ((FilesystemStorage)source.getStorage()).getFileLockManager();
  68. final FileLockManager lmTarget = ((FilesystemStorage)target.getStorage()).getFileLockManager();
  69. Lock lockRead = null;
  70. Lock lockWrite = null;
  71. try {
  72. lockRead = lmSource.readFileLock(sourcePath);
  73. } catch (Exception e) {
  74. log.error("Could not create read lock on {}", sourcePath);
  75. throw new IOException(e);
  76. }
  77. try {
  78. lockWrite = lmTarget.writeFileLock(targetPath);
  79. } catch (Exception e) {
  80. log.error("Could not create write lock on {}", targetPath);
  81. throw new IOException(e);
  82. }
  83. try {
  84. Files.copy(sourcePath, targetPath, copyOptions);
  85. } finally {
  86. if (lockRead!=null) {
  87. try {
  88. lmSource.release(lockRead);
  89. } catch (FileLockException e) {
  90. log.error("Error during lock release of read lock {}", lockRead.getFile());
  91. }
  92. }
  93. if (lockWrite!=null) {
  94. try {
  95. lmTarget.release(lockWrite);
  96. } catch (FileLockException e) {
  97. log.error("Error during lock release of write lock {}", lockWrite.getFile());
  98. }
  99. }
  100. }
  101. } else
  102. {
  103. Files.copy( sourcePath, targetPath, copyOptions );
  104. }
  105. } else {
  106. try {
  107. final RepositoryStorage sourceStorage = source.getStorage();
  108. final RepositoryStorage targetStorage = target.getStorage();
  109. sourceStorage.consumeDataFromChannel( source, is -> wrapWriteFunction( is, targetStorage, target, locked ), locked);
  110. } catch (IOException e) {
  111. throw e;
  112. } catch (Throwable e) {
  113. Throwable cause = e.getCause();
  114. if (cause instanceof IOException) {
  115. throw (IOException)cause;
  116. } else
  117. {
  118. throw new IOException( e );
  119. }
  120. }
  121. }
  122. }
  123. /**
  124. * Moves a asset between different storage instances.
  125. * If you know that source and asset are from the same storage instance, the move method of the storage
  126. * instance may be faster.
  127. *
  128. * @param source The source asset
  129. * @param target The target asset
  130. * @param locked If true, a lock is used for the move operation.
  131. * @param copyOptions Options for copying
  132. * @throws IOException If the move fails
  133. */
  134. public static final void moveAsset(StorageAsset source, StorageAsset target, boolean locked, CopyOption... copyOptions) throws IOException
  135. {
  136. if (source.isFileBased() && target.isFileBased()) {
  137. // Short cut for FS operations
  138. // Move is atomic operation
  139. if (!Files.exists(target.getFilePath().getParent())) {
  140. Files.createDirectories(target.getFilePath().getParent());
  141. }
  142. Files.move( source.getFilePath(), target.getFilePath(), copyOptions );
  143. } else {
  144. try {
  145. final RepositoryStorage sourceStorage = source.getStorage();
  146. final RepositoryStorage targetStorage = target.getStorage();
  147. sourceStorage.consumeDataFromChannel( source, is -> wrapWriteFunction( is, targetStorage, target, locked ), locked);
  148. sourceStorage.removeAsset( source );
  149. } catch (IOException e) {
  150. throw e;
  151. } catch (Throwable e) {
  152. Throwable cause = e.getCause();
  153. if (cause instanceof IOException) {
  154. throw (IOException)cause;
  155. } else
  156. {
  157. throw new IOException( e );
  158. }
  159. }
  160. }
  161. }
  162. private static final void wrapWriteFunction(ReadableByteChannel is, RepositoryStorage targetStorage, StorageAsset target, boolean locked) {
  163. try {
  164. targetStorage.writeDataToChannel( target, os -> copy(is, os), locked );
  165. } catch (Exception e) {
  166. throw new RuntimeException( e );
  167. }
  168. }
  169. private static final void copy( final ReadableByteChannel is, final WritableByteChannel os ) {
  170. if (is instanceof FileChannel) {
  171. copy( (FileChannel) is, os );
  172. } else if (os instanceof FileChannel) {
  173. copy(is, (FileChannel)os);
  174. } else
  175. {
  176. try
  177. {
  178. ByteBuffer buffer = ByteBuffer.allocate( DEFAULT_BUFFER_SIZE );
  179. while ( is.read( buffer ) != -1 )
  180. {
  181. buffer.flip( );
  182. while ( buffer.hasRemaining( ) )
  183. {
  184. os.write( buffer );
  185. }
  186. buffer.clear( );
  187. }
  188. }
  189. catch ( IOException e )
  190. {
  191. throw new RuntimeException( e );
  192. }
  193. }
  194. }
  195. private static final void copy( final FileChannel is, final WritableByteChannel os ) {
  196. try
  197. {
  198. is.transferTo( 0, is.size( ), os );
  199. }
  200. catch ( IOException e )
  201. {
  202. throw new RuntimeException( e );
  203. }
  204. }
  205. private static final void copy( final ReadableByteChannel is, final FileChannel os ) {
  206. try
  207. {
  208. os.transferFrom( is, 0, Long.MAX_VALUE );
  209. }
  210. catch ( IOException e )
  211. {
  212. throw new RuntimeException( e );
  213. }
  214. }
  215. /**
  216. * Runs the consumer function recursively on each asset found starting at the base path
  217. * @param baseAsset The base path where to start search
  218. * @param consumer The consumer function applied to each found asset
  219. * @param depthFirst If true, the deepest elements are consumed first.
  220. * @param maxDepth The maximum depth to recurse into. 0 means, only the baseAsset is consumed, 1 the base asset and its children and so forth.
  221. */
  222. public static final void recurse(final StorageAsset baseAsset, final Consumer<StorageAsset> consumer, final boolean depthFirst, final int maxDepth) throws IOException {
  223. recurse(baseAsset, consumer, depthFirst, maxDepth, 0);
  224. }
  225. /**
  226. * Runs the consumer function recursively on each asset found starting at the base path. The function descends into
  227. * maximum depth.
  228. *
  229. * @param baseAsset The base path where to start search
  230. * @param consumer The consumer function applied to each found asset
  231. * @param depthFirst If true, the deepest elements are consumed first.
  232. */
  233. public static final void recurse(final StorageAsset baseAsset, final Consumer<StorageAsset> consumer, final boolean depthFirst) throws IOException {
  234. recurse(baseAsset, consumer, depthFirst, Integer.MAX_VALUE, 0);
  235. }
  236. /**
  237. * Runs the consumer function recursively on each asset found starting at the base path. It does not recurse with
  238. * depth first and stops only if there are no more children available.
  239. *
  240. * @param baseAsset The base path where to start search
  241. * @param consumer The consumer function applied to each found asset
  242. */
  243. public static final void recurse(final StorageAsset baseAsset, final Consumer<StorageAsset> consumer) throws IOException {
  244. recurse(baseAsset, consumer, false, Integer.MAX_VALUE, 0);
  245. }
  246. private static final void recurse(final StorageAsset baseAsset, final Consumer<StorageAsset> consumer, final boolean depthFirst, final int maxDepth, final int currentDepth)
  247. throws IOException {
  248. if (!depthFirst) {
  249. consumer.accept(baseAsset);
  250. }
  251. if (currentDepth<maxDepth && baseAsset.isContainer()) {
  252. for(StorageAsset asset : baseAsset.list() ) {
  253. recurse(asset, consumer, depthFirst, maxDepth, currentDepth+1);
  254. }
  255. }
  256. if (depthFirst) {
  257. consumer.accept(baseAsset);
  258. }
  259. }
  260. /**
  261. * Deletes the given asset and all child assets recursively.
  262. * @param baseDir The base asset to remove.
  263. * @throws IOException
  264. */
  265. public static final void deleteRecursively(StorageAsset baseDir) throws IOException {
  266. recurse(baseDir, a -> {
  267. try {
  268. a.getStorage().removeAsset(a);
  269. } catch (IOException e) {
  270. log.error("Could not delete asset {}", a.getPath());
  271. }
  272. },true);
  273. }
  274. /**
  275. * Returns the extension of the name of a given asset. Extension is the substring after the last occurence of '.' in the
  276. * string. If no '.' is found, the empty string is returned.
  277. *
  278. * @param asset The asset from which to return the extension string.
  279. * @return The extension.
  280. */
  281. public static final String getExtension(StorageAsset asset) {
  282. return StringUtils.substringAfterLast(asset.getName(),".");
  283. }
  284. public static final void copyToLocalFile(StorageAsset asset, Path destination, CopyOption... copyOptions) throws IOException {
  285. if (asset.isFileBased()) {
  286. Files.copy(asset.getFilePath(), destination, copyOptions);
  287. } else {
  288. try {
  289. HashSet<OpenOption> openOptions = new HashSet<>();
  290. for (CopyOption option : copyOptions) {
  291. if (option == StandardCopyOption.REPLACE_EXISTING) {
  292. openOptions.add(StandardOpenOption.CREATE);
  293. openOptions.add(StandardOpenOption.TRUNCATE_EXISTING);
  294. openOptions.add(StandardOpenOption.WRITE);
  295. } else {
  296. openOptions.add(StandardOpenOption.WRITE);
  297. openOptions.add(StandardOpenOption.CREATE_NEW);
  298. }
  299. }
  300. asset.getStorage().consumeDataFromChannel(asset, channel -> {
  301. try {
  302. FileChannel.open(destination, openOptions).transferFrom(channel, 0, Long.MAX_VALUE);
  303. } catch (IOException e) {
  304. throw new RuntimeException(e);
  305. }
  306. }, false);
  307. } catch (Throwable e) {
  308. if (e.getCause() instanceof IOException) {
  309. throw (IOException)e.getCause();
  310. } else {
  311. throw new IOException(e);
  312. }
  313. }
  314. }
  315. }
  316. public static class PathInformation {
  317. final Path path ;
  318. final boolean tmpFile;
  319. PathInformation(Path path, boolean tmpFile) {
  320. this.path = path;
  321. this.tmpFile = tmpFile;
  322. }
  323. public Path getPath() {
  324. return path;
  325. }
  326. public boolean isTmpFile() {
  327. return tmpFile;
  328. }
  329. }
  330. public static final PathInformation getAssetDataAsPath(StorageAsset asset) throws IOException {
  331. if (!asset.exists()) {
  332. throw new IOException("Asset does not exist");
  333. }
  334. if (asset.isFileBased()) {
  335. return new PathInformation(asset.getFilePath(), false);
  336. } else {
  337. Path tmpFile = Files.createTempFile(asset.getName(), getExtension(asset));
  338. copyToLocalFile(asset, tmpFile, StandardCopyOption.REPLACE_EXISTING);
  339. return new PathInformation(tmpFile, true);
  340. }
  341. }
  342. }