This project has retired. For details please refer to its Attic page.
Source code
001package org.apache.archiva.repository.storage;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.archiva.common.filelock.FileLockException;
023import org.apache.archiva.common.filelock.FileLockManager;
024import org.apache.archiva.common.filelock.FileLockTimeoutException;
025import org.apache.archiva.common.filelock.Lock;
026import org.apache.archiva.common.utils.PathUtil;
027import org.apache.commons.io.FileUtils;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import java.io.FileNotFoundException;
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.OutputStream;
035import java.net.URI;
036import java.nio.channels.FileChannel;
037import java.nio.channels.ReadableByteChannel;
038import java.nio.channels.WritableByteChannel;
039import java.nio.file.*;
040import java.util.function.Consumer;
041
042/**
043 * Implementation of <code>{@link RepositoryStorage}</code> where data is stored in the filesystem.
044 *
045 * All files are relative to a given base path. Path values are separated by '/', '..' is allowed to navigate
046 * to a parent directory, but navigation out of the base path will lead to a exception.
047 */
048public class FilesystemStorage implements RepositoryStorage {
049
050    private static final Logger log = LoggerFactory.getLogger(FilesystemStorage.class);
051
052    private Path basePath;
053    private final FileLockManager fileLockManager;
054
055    public FilesystemStorage(Path basePath, FileLockManager fileLockManager) throws IOException {
056        if (!Files.exists(basePath)) {
057            Files.createDirectories(basePath);
058        }
059        this.basePath = basePath.normalize().toRealPath();
060        this.fileLockManager = fileLockManager;
061    }
062
063    private Path normalize(final String path) {
064        String nPath = path;
065        while (nPath.startsWith("/")) {
066            nPath = nPath.substring(1);
067        }
068        return Paths.get(nPath);
069    }
070
071    private Path getAssetPath(String path) throws IOException {
072        Path assetPath = basePath.resolve(normalize(path)).normalize();
073        if (!assetPath.startsWith(basePath))
074        {
075            throw new IOException("Path navigation out of allowed scope: "+path);
076        }
077        return assetPath;
078    }
079
080    @Override
081    public void consumeData(StorageAsset asset, Consumer<InputStream> consumerFunction, boolean readLock ) throws IOException
082    {
083        final Path path = asset.getFilePath();
084        try {
085            if (readLock) {
086                consumeDataLocked( path, consumerFunction );
087            } else
088            {
089                try ( InputStream is = Files.newInputStream( path ) )
090                {
091                    consumerFunction.accept( is );
092                }
093                catch ( IOException e )
094                {
095                    log.error("Could not read the input stream from file {}", path);
096                    throw e;
097                }
098            }
099        } catch (RuntimeException e)
100        {
101            log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
102            throw new IOException( e );
103        }
104
105    }
106
107    @Override
108    public void consumeDataFromChannel( StorageAsset asset, Consumer<ReadableByteChannel> consumerFunction, boolean readLock ) throws IOException
109    {
110        final Path path = asset.getFilePath();
111        try {
112            if (readLock) {
113                consumeDataFromChannelLocked( path, consumerFunction );
114            } else
115            {
116                try ( FileChannel is = FileChannel.open( path, StandardOpenOption.READ ) )
117                {
118                    consumerFunction.accept( is );
119                }
120                catch ( IOException e )
121                {
122                    log.error("Could not read the input stream from file {}", path);
123                    throw e;
124                }
125            }
126        } catch (RuntimeException e)
127        {
128            log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
129            throw new IOException( e );
130        }
131    }
132
133    @Override
134    public void writeData( StorageAsset asset, Consumer<OutputStream> consumerFunction, boolean writeLock ) throws IOException
135    {
136        final Path path = asset.getFilePath();
137        try {
138            if (writeLock) {
139                writeDataLocked( path, consumerFunction );
140            } else
141            {
142                try ( OutputStream is = Files.newOutputStream( path ) )
143                {
144                    consumerFunction.accept( is );
145                }
146                catch ( IOException e )
147                {
148                    log.error("Could not write the output stream to file {}", path);
149                    throw e;
150                }
151            }
152        } catch (RuntimeException e)
153        {
154            log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
155            throw new IOException( e );
156        }
157
158    }
159
160    @Override
161    public void writeDataToChannel( StorageAsset asset, Consumer<WritableByteChannel> consumerFunction, boolean writeLock ) throws IOException
162    {
163        final Path path = asset.getFilePath();
164        try {
165            if (writeLock) {
166                writeDataToChannelLocked( path, consumerFunction );
167            } else
168            {
169                try ( FileChannel os = FileChannel.open( path, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE ))
170                {
171                    consumerFunction.accept( os );
172                }
173                catch ( IOException e )
174                {
175                    log.error("Could not write the data to file {}", path);
176                    throw e;
177                }
178            }
179        } catch (RuntimeException e)
180        {
181            log.error( "Runtime exception during data consume from artifact {}. Error: {}", path, e.getMessage() );
182            throw new IOException( e );
183        }
184    }
185
186    private void consumeDataLocked( Path file, Consumer<InputStream> consumerFunction) throws IOException
187    {
188
189        final Lock lock;
190        try
191        {
192            lock = fileLockManager.readFileLock( file );
193            try ( InputStream is = Files.newInputStream( lock.getFile()))
194            {
195                consumerFunction.accept( is );
196            }
197            catch ( IOException e )
198            {
199                log.error("Could not read the input stream from file {}", file);
200                throw e;
201            } finally
202            {
203                fileLockManager.release( lock );
204            }
205        }
206        catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
207        {
208            log.error("Locking error on file {}", file);
209            throw new IOException(e);
210        }
211    }
212
213    private void consumeDataFromChannelLocked( Path file, Consumer<ReadableByteChannel> consumerFunction) throws IOException
214    {
215
216        final Lock lock;
217        try
218        {
219            lock = fileLockManager.readFileLock( file );
220            try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.READ ))
221            {
222                consumerFunction.accept( is );
223            }
224            catch ( IOException e )
225            {
226                log.error("Could not read the input stream from file {}", file);
227                throw e;
228            } finally
229            {
230                fileLockManager.release( lock );
231            }
232        }
233        catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
234        {
235            log.error("Locking error on file {}", file);
236            throw new IOException(e);
237        }
238    }
239
240
241    private void writeDataLocked( Path file, Consumer<OutputStream> consumerFunction) throws IOException
242    {
243
244        final Lock lock;
245        try
246        {
247            lock = fileLockManager.writeFileLock( file );
248            try ( OutputStream is = Files.newOutputStream( lock.getFile()))
249            {
250                consumerFunction.accept( is );
251            }
252            catch ( IOException e )
253            {
254                log.error("Could not write the output stream to file {}", file);
255                throw e;
256            } finally
257            {
258                fileLockManager.release( lock );
259            }
260        }
261        catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
262        {
263            log.error("Locking error on file {}", file);
264            throw new IOException(e);
265        }
266    }
267
268    private void writeDataToChannelLocked( Path file, Consumer<WritableByteChannel> consumerFunction) throws IOException
269    {
270
271        final Lock lock;
272        try
273        {
274            lock = fileLockManager.writeFileLock( file );
275            try ( FileChannel is = FileChannel.open( lock.getFile( ), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE ))
276            {
277                consumerFunction.accept( is );
278            }
279            catch ( IOException e )
280            {
281                log.error("Could not write to file {}", file);
282                throw e;
283            } finally
284            {
285                fileLockManager.release( lock );
286            }
287        }
288        catch ( FileLockException | FileNotFoundException | FileLockTimeoutException e)
289        {
290            log.error("Locking error on file {}", file);
291            throw new IOException(e);
292        }
293    }
294
295    @Override
296    public URI getLocation() {
297        return basePath.toUri();
298    }
299
300    /**
301     * Updates the location and releases all locks.
302     *
303     * @param newLocation The URI to the new location
304     *
305     * @throws IOException If the directory cannot be created.
306     */
307    @Override
308    public void updateLocation(URI newLocation) throws IOException {
309        Path newPath = PathUtil.getPathFromUri(newLocation).toAbsolutePath();
310        if (!Files.exists(newPath)) {
311            Files.createDirectories(newPath);
312        }
313        basePath = newPath;
314        if (fileLockManager!=null) {
315            fileLockManager.clearLockFiles();
316        }
317    }
318
319    @Override
320    public StorageAsset getAsset( String path )
321    {
322        try {
323            return new FilesystemAsset(this, path, getAssetPath(path));
324        } catch (IOException e) {
325            throw new IllegalArgumentException("Path navigates outside of base directory "+path);
326        }
327    }
328
329    @Override
330    public StorageAsset addAsset( String path, boolean container )
331    {
332        try {
333            return new FilesystemAsset(this, path, getAssetPath(path), basePath, container);
334        } catch (IOException e) {
335            throw new IllegalArgumentException("Path navigates outside of base directory "+path);
336        }
337    }
338
339    @Override
340    public void removeAsset( StorageAsset asset ) throws IOException
341    {
342        Files.delete(asset.getFilePath());
343    }
344
345    @Override
346    public StorageAsset moveAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException
347    {
348        boolean container = origin.isContainer();
349        FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container );
350        moveAsset( origin, newAsset, copyOptions );
351        return newAsset;
352    }
353
354    @Override
355    public void moveAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException
356    {
357        if (origin.getStorage()!=this) {
358            throw new IOException("The origin asset does not belong to this storage instance. Cannot copy between different storage instances.");
359        }
360        if (destination.getStorage()!=this) {
361            throw new IOException("The destination asset does not belong to this storage instance. Cannot copy between different storage instances.");
362        }
363        Files.move(origin.getFilePath(), destination.getFilePath(), copyOptions);
364    }
365
366    @Override
367    public StorageAsset copyAsset( StorageAsset origin, String destination, CopyOption... copyOptions ) throws IOException
368    {
369        boolean container = origin.isContainer();
370        FilesystemAsset newAsset = new FilesystemAsset(this, destination, getAssetPath(destination), basePath, container );
371        copyAsset( origin, newAsset, copyOptions );
372        return newAsset;
373    }
374
375    @Override
376    public void copyAsset( StorageAsset origin, StorageAsset destination, CopyOption... copyOptions ) throws IOException
377    {
378        if (origin.getStorage()!=this) {
379            throw new IOException("The origin asset does not belong to this storage instance. Cannot copy between different storage instances.");
380        }
381        if (destination.getStorage()!=this) {
382            throw new IOException("The destination asset does not belong to this storage instance. Cannot copy between different storage instances.");
383        }
384        Path destinationPath = destination.getFilePath();
385        boolean overwrite = false;
386        for (int i=0; i<copyOptions.length; i++) {
387            if (copyOptions[i].equals( StandardCopyOption.REPLACE_EXISTING )) {
388                overwrite=true;
389            }
390        }
391        if (Files.exists(destinationPath) && !overwrite) {
392            throw new IOException("Destination file exists already "+ destinationPath);
393        }
394        if (Files.isDirectory( origin.getFilePath() ))
395        {
396            FileUtils.copyDirectory(origin.getFilePath( ).toFile(), destinationPath.toFile() );
397        } else if (Files.isRegularFile( origin.getFilePath() )) {
398            if (!Files.exists( destinationPath )) {
399                Files.createDirectories( destinationPath );
400            }
401            Files.copy( origin.getFilePath( ), destinationPath, copyOptions );
402        }
403    }
404
405    public FileLockManager getFileLockManager() {
406        return fileLockManager;
407    }
408
409}