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