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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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. try (Lock lockRead = lmSource.readFileLock( sourcePath ); Lock lockWrite = lmTarget.writeFileLock( targetPath ) )
  70. {
  71. Files.copy( sourcePath, targetPath, copyOptions );
  72. }
  73. catch ( FileLockException e )
  74. {
  75. throw new IOException( e );
  76. }
  77. catch ( FileLockTimeoutException e )
  78. {
  79. throw new IOException( e );
  80. }
  81. } else
  82. {
  83. Files.copy( sourcePath, targetPath, copyOptions );
  84. }
  85. } else {
  86. try {
  87. final RepositoryStorage sourceStorage = source.getStorage();
  88. final RepositoryStorage targetStorage = target.getStorage();
  89. sourceStorage.consumeDataFromChannel( source, is -> wrapWriteFunction( is, targetStorage, target, locked ), locked);
  90. } catch (IOException e) {
  91. throw e;
  92. } catch (Throwable e) {
  93. Throwable cause = e.getCause();
  94. if (cause instanceof IOException) {
  95. throw (IOException)cause;
  96. } else
  97. {
  98. throw new IOException( e );
  99. }
  100. }
  101. }
  102. }
  103. /**
  104. * Moves a asset between different storage instances.
  105. * If you know that source and asset are from the same storage instance, the move method of the storage
  106. * instance may be faster.
  107. *
  108. * @param source The source asset
  109. * @param target The target asset
  110. * @param locked If true, a lock is used for the move operation.
  111. * @param copyOptions Options for copying
  112. * @throws IOException If the move fails
  113. */
  114. public static final void moveAsset(StorageAsset source, StorageAsset target, boolean locked, CopyOption... copyOptions) throws IOException
  115. {
  116. if (source.isFileBased() && target.isFileBased()) {
  117. // Short cut for FS operations
  118. // Move is atomic operation
  119. Files.move( source.getFilePath(), target.getFilePath(), copyOptions );
  120. } else {
  121. try {
  122. final RepositoryStorage sourceStorage = source.getStorage();
  123. final RepositoryStorage targetStorage = target.getStorage();
  124. sourceStorage.consumeDataFromChannel( source, is -> wrapWriteFunction( is, targetStorage, target, locked ), locked);
  125. sourceStorage.removeAsset( source );
  126. } catch (IOException e) {
  127. throw e;
  128. } catch (Throwable e) {
  129. Throwable cause = e.getCause();
  130. if (cause instanceof IOException) {
  131. throw (IOException)cause;
  132. } else
  133. {
  134. throw new IOException( e );
  135. }
  136. }
  137. }
  138. }
  139. private static final void wrapWriteFunction(ReadableByteChannel is, RepositoryStorage targetStorage, StorageAsset target, boolean locked) {
  140. try {
  141. targetStorage.writeDataToChannel( target, os -> copy(is, os), locked );
  142. } catch (Exception e) {
  143. throw new RuntimeException( e );
  144. }
  145. }
  146. private static final void copy( final ReadableByteChannel is, final WritableByteChannel os ) {
  147. if (is instanceof FileChannel) {
  148. copy( (FileChannel) is, os );
  149. } else if (os instanceof FileChannel) {
  150. copy(is, (FileChannel)os);
  151. } else
  152. {
  153. try
  154. {
  155. ByteBuffer buffer = ByteBuffer.allocate( DEFAULT_BUFFER_SIZE );
  156. while ( is.read( buffer ) != -1 )
  157. {
  158. buffer.flip( );
  159. while ( buffer.hasRemaining( ) )
  160. {
  161. os.write( buffer );
  162. }
  163. buffer.clear( );
  164. }
  165. }
  166. catch ( IOException e )
  167. {
  168. throw new RuntimeException( e );
  169. }
  170. }
  171. }
  172. private static final void copy( final FileChannel is, final WritableByteChannel os ) {
  173. try
  174. {
  175. is.transferTo( 0, is.size( ), os );
  176. }
  177. catch ( IOException e )
  178. {
  179. throw new RuntimeException( e );
  180. }
  181. }
  182. private static final void copy( final ReadableByteChannel is, final FileChannel os ) {
  183. try
  184. {
  185. os.transferFrom( is, 0, Long.MAX_VALUE );
  186. }
  187. catch ( IOException e )
  188. {
  189. throw new RuntimeException( e );
  190. }
  191. }
  192. /**
  193. * Runs the consumer function recursively on each asset found starting at the base path
  194. * @param baseAsset The base path where to start search
  195. * @param consumer The consumer function applied to each found asset
  196. * @param depthFirst If true, the deepest elements are consumed first.
  197. * @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.
  198. */
  199. public static final void recurse(final StorageAsset baseAsset, final Consumer<StorageAsset> consumer, final boolean depthFirst, final int maxDepth) throws IOException {
  200. recurse(baseAsset, consumer, depthFirst, maxDepth, 0);
  201. }
  202. /**
  203. * Runs the consumer function recursively on each asset found starting at the base path. The function descends into
  204. * maximum depth.
  205. *
  206. * @param baseAsset The base path where to start search
  207. * @param consumer The consumer function applied to each found asset
  208. * @param depthFirst If true, the deepest elements are consumed first.
  209. */
  210. public static final void recurse(final StorageAsset baseAsset, final Consumer<StorageAsset> consumer, final boolean depthFirst) throws IOException {
  211. recurse(baseAsset, consumer, depthFirst, Integer.MAX_VALUE, 0);
  212. }
  213. /**
  214. * Runs the consumer function recursively on each asset found starting at the base path. It does not recurse with
  215. * depth first and stops only if there are no more children available.
  216. *
  217. * @param baseAsset The base path where to start search
  218. * @param consumer The consumer function applied to each found asset
  219. */
  220. public static final void recurse(final StorageAsset baseAsset, final Consumer<StorageAsset> consumer) throws IOException {
  221. recurse(baseAsset, consumer, false, Integer.MAX_VALUE, 0);
  222. }
  223. private static final void recurse(final StorageAsset baseAsset, final Consumer<StorageAsset> consumer, final boolean depthFirst, final int maxDepth, final int currentDepth)
  224. throws IOException {
  225. if (!depthFirst) {
  226. consumer.accept(baseAsset);
  227. }
  228. if (currentDepth<maxDepth && baseAsset.isContainer()) {
  229. for(StorageAsset asset : baseAsset.list() ) {
  230. recurse(asset, consumer, depthFirst, maxDepth, currentDepth+1);
  231. }
  232. }
  233. if (depthFirst) {
  234. consumer.accept(baseAsset);
  235. }
  236. }
  237. /**
  238. * Deletes the given asset and all child assets recursively.
  239. * @param baseDir The base asset to remove.
  240. * @throws IOException
  241. */
  242. public static final void deleteRecursively(StorageAsset baseDir) throws IOException {
  243. recurse(baseDir, a -> {
  244. try {
  245. a.getStorage().removeAsset(a);
  246. } catch (IOException e) {
  247. log.error("Could not delete asset {}", a.getPath());
  248. }
  249. },true);
  250. }
  251. /**
  252. * Returns the extension of the name of a given asset. Extension is the substring after the last occurence of '.' in the
  253. * string. If no '.' is found, the empty string is returned.
  254. *
  255. * @param asset The asset from which to return the extension string.
  256. * @return The extension.
  257. */
  258. public static final String getExtension(StorageAsset asset) {
  259. return StringUtils.substringAfterLast(asset.getName(),".");
  260. }
  261. public static final void copyToLocalFile(StorageAsset asset, Path destination, CopyOption... copyOptions) throws IOException {
  262. if (asset.isFileBased()) {
  263. Files.copy(asset.getFilePath(), destination, copyOptions);
  264. } else {
  265. try {
  266. HashSet<OpenOption> openOptions = new HashSet<>();
  267. for (CopyOption option : copyOptions) {
  268. if (option == StandardCopyOption.REPLACE_EXISTING) {
  269. openOptions.add(StandardOpenOption.CREATE);
  270. openOptions.add(StandardOpenOption.TRUNCATE_EXISTING);
  271. openOptions.add(StandardOpenOption.WRITE);
  272. } else {
  273. openOptions.add(StandardOpenOption.WRITE);
  274. openOptions.add(StandardOpenOption.CREATE_NEW);
  275. }
  276. }
  277. asset.getStorage().consumeDataFromChannel(asset, channel -> {
  278. try {
  279. FileChannel.open(destination, openOptions).transferFrom(channel, 0, Long.MAX_VALUE);
  280. } catch (IOException e) {
  281. throw new RuntimeException(e);
  282. }
  283. }, false);
  284. } catch (Throwable e) {
  285. if (e.getCause() instanceof IOException) {
  286. throw (IOException)e.getCause();
  287. } else {
  288. throw new IOException(e);
  289. }
  290. }
  291. }
  292. }
  293. public static class PathInformation {
  294. final Path path ;
  295. final boolean tmpFile;
  296. PathInformation(Path path, boolean tmpFile) {
  297. this.path = path;
  298. this.tmpFile = tmpFile;
  299. }
  300. public Path getPath() {
  301. return path;
  302. }
  303. public boolean isTmpFile() {
  304. return tmpFile;
  305. }
  306. }
  307. public static final PathInformation getAssetDataAsPath(StorageAsset asset) throws IOException {
  308. if (!asset.exists()) {
  309. throw new IOException("Asset does not exist");
  310. }
  311. if (asset.isFileBased()) {
  312. return new PathInformation(asset.getFilePath(), false);
  313. } else {
  314. Path tmpFile = Files.createTempFile(asset.getName(), getExtension(asset));
  315. copyToLocalFile(asset, tmpFile, StandardCopyOption.REPLACE_EXISTING);
  316. return new PathInformation(tmpFile, true);
  317. }
  318. }
  319. }