]> source.dussan.org Git - archiva.git/blob
fa637c66930976cab3b63196a9de869de49e3535
[archiva.git] /
1 package org.apache.archiva.repository.storage.util;
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  * 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
21 import org.apache.archiva.repository.storage.RepositoryStorage;
22 import org.apache.archiva.repository.storage.StorageAsset;
23 import org.apache.commons.lang3.StringUtils;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
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.CopyOption;
33 import java.nio.file.Files;
34 import java.util.function.Consumer;
35 import java.util.function.Function;
36 import java.util.stream.Stream;
37 import java.util.stream.StreamSupport;
38
39 /**
40  *
41  * Utility class for traversing the asset tree recursively and stream based access to the assets.
42  *
43  * @since 3.0
44  * @author Martin Stockhammer <martin_s@apache.org>
45  */
46 public class StorageUtil
47 {
48
49     private static final Logger LOG = LoggerFactory.getLogger( StorageUtil.class );
50
51     private static final int DEFAULT_BUFFER_SIZE = 4096;
52
53     /**
54      * Walk the tree starting at the given asset. The consumer is called for each asset found.
55      * It runs a depth-first search where children are consumed before their parents.
56      *
57      * @param start the starting asset
58      * @param consumer the consumer that is applied to each asset
59      */
60     public static void walk( StorageAsset start, Consumer<StorageAsset> consumer ) {
61         try(Stream<StorageAsset> assetStream = newAssetStream( start, false )) {
62             assetStream.forEach( consumer::accept );
63         }
64     }
65
66     /**
67      * Walk the tree starting at the given asset. The consumer function is called for each asset found
68      * as long as it returns <code>true</code> as result. If the function returns <code>false</code> the
69      * processing stops.
70      * It runs a depth-first search where children are consumed before their parents.
71      *
72      * @param start the starting asset
73      * @param consumer the consumer function that is applied to each asset and that has to return <code>true</code>,
74      *                 if the walk should continue.
75      */
76     public static void walk( StorageAsset start, Function<StorageAsset, Boolean> consumer ) {
77         try(Stream<StorageAsset> assetStream = newAssetStream( start, false )) {
78             assetStream.anyMatch( a -> !consumer.apply( a ) );
79         }
80     }
81
82
83     /**
84      * Returns a stream of assets starting at the given start node. The returned stream returns a closable
85      * stream and should always be used in a try-with-resources statement.
86      *
87      * @param start the starting asset
88      * @param parallel <code>true</code>, if a parallel stream should be created, otherwise <code>false</code>
89      * @return the newly created stream
90      */
91     public static Stream<StorageAsset> newAssetStream( StorageAsset start, boolean parallel )
92     {
93         return StreamSupport.stream( new AssetSpliterator( start ), parallel );
94     }
95
96
97     /**
98      * Returns a non-parallel stream.
99      * Calls {@link #newAssetStream(StorageAsset, boolean)} with <code>parallel=false</code>.
100      *
101      * @param start the starting asset
102      * @return the returned stream object
103      */
104     public static Stream<StorageAsset> newAssetStream( StorageAsset start) {
105         return newAssetStream( start, false );
106     }
107
108     /**
109      * Deletes the given asset and all child assets recursively.
110      * IOExceptions during deletion are ignored.
111      *
112      * @param baseDir The base asset to remove.
113      *
114      */
115     public static final void deleteRecursively(StorageAsset baseDir) {
116         RepositoryStorage storage = baseDir.getStorage( );
117         walk( baseDir,  a -> {
118             try {
119                 storage.removeAsset(a);
120             } catch (IOException e) {
121                 LOG.error( "Could not delete asset {}: {}", a.getPath( ), e.getMessage( ), e );
122             }
123         });
124     }
125
126     /**
127      * Deletes the given asset and all child assets recursively.
128      * @param baseDir The base asset to remove.
129      * @param stopOnError if <code>true</code> the traversal stops, if an exception is encountered
130      * @return returns <code>true</code>, if every item was removed. If an IOException was encountered during
131      * traversal it returns <code>false</code>
132      */
133     public static final boolean deleteRecursively(final StorageAsset baseDir, final boolean stopOnError) {
134         final RepositoryStorage storage = baseDir.getStorage( );
135         try(Stream<StorageAsset> stream = newAssetStream( baseDir ))
136         {
137             if ( stopOnError )
138             {
139                 // Return true, if no exception occurred
140                 // anyMatch is short-circuiting, that means it stops if the condition matches
141                 return !stream.map( a -> {
142                     try
143                     {
144                         storage.removeAsset( a );
145                         // Returning false, if OK
146                         return Boolean.FALSE;
147                     }
148                     catch ( IOException e )
149                     {
150                         LOG.error( "Could not delete asset {}: {}", a.getPath( ), e.getMessage( ), e );
151                         // Returning true, if exception
152                         return Boolean.TRUE;
153                     }
154                 } ).anyMatch( r -> r );
155             } else {
156                 // Return true, if all removals were OK
157                 // We want to consume all, so we use allMatch
158                 return stream.map( a -> {
159                     try
160                     {
161                         storage.removeAsset( a );
162                         // Returning true, if OK
163                         return Boolean.TRUE;
164                     }
165                     catch ( IOException e )
166                     {
167                         LOG.error( "Could not delete asset {}: {}", a.getPath( ), e.getMessage( ), e );
168                         // Returning false, if exception
169                         return Boolean.FALSE;
170                     }
171                 } ).allMatch( r -> r );
172             }
173         }
174     }
175
176     /**
177      * Moves a asset between different storage instances.
178      * If you know that source and asset are from the same storage instance, the move method of the storage
179      * instance may be faster.
180      *
181      * @param source The source asset
182      * @param target The target asset
183      * @param locked If true, a lock is used for the move operation.
184      * @param copyOptions Options for copying
185      * @throws IOException If the move fails
186      */
187     public static final void moveAsset(StorageAsset source, StorageAsset target, boolean locked, CopyOption... copyOptions) throws IOException
188     {
189         if (source.isFileBased() && target.isFileBased()) {
190             // Short cut for FS operations
191             // Move is atomic operation
192             if (!Files.exists(target.getFilePath().getParent())) {
193                 Files.createDirectories(target.getFilePath().getParent());
194             }
195             Files.move( source.getFilePath(), target.getFilePath(), copyOptions );
196         } else {
197             try {
198                 final RepositoryStorage sourceStorage = source.getStorage();
199                 final RepositoryStorage targetStorage = target.getStorage();
200                 sourceStorage.consumeDataFromChannel( source, is -> wrapWriteFunction( is, targetStorage, target, locked ), locked);
201                 sourceStorage.removeAsset( source );
202             }  catch (IOException e) {
203                 throw e;
204             }  catch (Throwable e) {
205                 Throwable cause = e.getCause();
206                 if (cause instanceof IOException) {
207                     throw (IOException)cause;
208                 } else
209                 {
210                     throw new IOException( e );
211                 }
212             }
213         }
214
215     }
216
217     private static final void wrapWriteFunction( ReadableByteChannel is, RepositoryStorage targetStorage, StorageAsset target, boolean locked) {
218         try {
219             targetStorage.writeDataToChannel( target, os -> copy(is, os), locked );
220         } catch (Exception e) {
221             throw new RuntimeException( e );
222         }
223     }
224
225     public static final void copy( final ReadableByteChannel is, final WritableByteChannel os ) {
226         if (is instanceof FileChannel ) {
227             copy( (FileChannel) is, os );
228         } else if (os instanceof FileChannel) {
229             copy(is, (FileChannel)os);
230         } else
231         {
232             try
233             {
234                 ByteBuffer buffer = ByteBuffer.allocate( DEFAULT_BUFFER_SIZE );
235                 while ( is.read( buffer ) != -1 )
236                 {
237                     buffer.flip( );
238                     while ( buffer.hasRemaining( ) )
239                     {
240                         os.write( buffer );
241                     }
242                     buffer.clear( );
243                 }
244             }
245             catch ( IOException e )
246             {
247                 throw new RuntimeException( e );
248             }
249         }
250     }
251
252     public static final void copy( final FileChannel is, final WritableByteChannel os ) {
253         try
254         {
255             is.transferTo( 0, is.size( ), os );
256         }
257         catch ( IOException e )
258         {
259             throw new RuntimeException( e );
260         }
261     }
262
263     public static final void copy( final ReadableByteChannel is, final FileChannel os ) {
264         try
265         {
266             os.transferFrom( is, 0, Long.MAX_VALUE );
267         }
268         catch ( IOException e )
269         {
270             throw new RuntimeException( e );
271         }
272     }
273
274
275     /**
276      * Returns the extension of the name of a given asset. Extension is the substring after the last occurence of '.' in the
277      * string. If no '.' is found, the empty string is returned.
278      *
279      * @param asset The asset from which to return the extension string.
280      * @return The extension.
281      */
282     public static final String getExtension(StorageAsset asset) {
283         return StringUtils.substringAfterLast(asset.getName(),".");
284     }
285 }