1 package org.apache.archiva.repository.storage.util;
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
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
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;
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;
41 * Utility class for traversing the asset tree recursively and stream based access to the assets.
44 * @author Martin Stockhammer <martin_s@apache.org>
46 public class StorageUtil
49 private static final Logger LOG = LoggerFactory.getLogger( StorageUtil.class );
51 private static final int DEFAULT_BUFFER_SIZE = 4096;
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.
57 * @param start the starting asset
58 * @param consumer the consumer that is applied to each asset
60 public static void walk( StorageAsset start, Consumer<StorageAsset> consumer ) {
61 try(Stream<StorageAsset> assetStream = newAssetStream( start, false )) {
62 assetStream.forEach( consumer::accept );
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
70 * It runs a depth-first search where children are consumed before their parents.
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.
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 ) );
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.
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
91 public static Stream<StorageAsset> newAssetStream( StorageAsset start, boolean parallel )
93 return StreamSupport.stream( new AssetSpliterator( start ), parallel );
98 * Returns a non-parallel stream.
99 * Calls {@link #newAssetStream(StorageAsset, boolean)} with <code>parallel=false</code>.
101 * @param start the starting asset
102 * @return the returned stream object
104 public static Stream<StorageAsset> newAssetStream( StorageAsset start) {
105 return newAssetStream( start, false );
109 * Deletes the given asset and all child assets recursively.
110 * IOExceptions during deletion are ignored.
112 * @param baseDir The base asset to remove.
115 public static final void deleteRecursively(StorageAsset baseDir) {
116 RepositoryStorage storage = baseDir.getStorage( );
117 walk( baseDir, a -> {
119 storage.removeAsset(a);
120 } catch (IOException e) {
121 LOG.error( "Could not delete asset {}: {}", a.getPath( ), e.getMessage( ), e );
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>
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 ))
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 -> {
144 storage.removeAsset( a );
145 // Returning false, if OK
146 return Boolean.FALSE;
148 catch ( IOException e )
150 LOG.error( "Could not delete asset {}: {}", a.getPath( ), e.getMessage( ), e );
151 // Returning true, if exception
154 } ).anyMatch( r -> r );
156 // Return true, if all removals were OK
157 // We want to consume all, so we use allMatch
158 return stream.map( a -> {
161 storage.removeAsset( a );
162 // Returning true, if OK
165 catch ( IOException e )
167 LOG.error( "Could not delete asset {}: {}", a.getPath( ), e.getMessage( ), e );
168 // Returning false, if exception
169 return Boolean.FALSE;
171 } ).allMatch( r -> r );
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.
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
187 public static final void moveAsset(StorageAsset source, StorageAsset target, boolean locked, CopyOption... copyOptions) throws IOException
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());
195 Files.move( source.getFilePath(), target.getFilePath(), copyOptions );
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) {
204 } catch (Throwable e) {
205 Throwable cause = e.getCause();
206 if (cause instanceof IOException) {
207 throw (IOException)cause;
210 throw new IOException( e );
217 private static final void wrapWriteFunction( ReadableByteChannel is, RepositoryStorage targetStorage, StorageAsset target, boolean locked) {
219 targetStorage.writeDataToChannel( target, os -> copy(is, os), locked );
220 } catch (Exception e) {
221 throw new RuntimeException( e );
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);
234 ByteBuffer buffer = ByteBuffer.allocate( DEFAULT_BUFFER_SIZE );
235 while ( is.read( buffer ) != -1 )
238 while ( buffer.hasRemaining( ) )
245 catch ( IOException e )
247 throw new RuntimeException( e );
252 public static final void copy( final FileChannel is, final WritableByteChannel os ) {
255 is.transferTo( 0, is.size( ), os );
257 catch ( IOException e )
259 throw new RuntimeException( e );
263 public static final void copy( final ReadableByteChannel is, final FileChannel os ) {
266 os.transferFrom( is, 0, Long.MAX_VALUE );
268 catch ( IOException e )
270 throw new RuntimeException( e );
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.
279 * @param asset The asset from which to return the extension string.
280 * @return The extension.
282 public static final String getExtension(StorageAsset asset) {
283 return StringUtils.substringAfterLast(asset.getName(),".");