You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2022/11/08 09:16:35 UTC

[maven-resolver] branch master updated: [MRESOLVER-282] Drop PartialFile use (#212)

This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new c4aa48de [MRESOLVER-282] Drop PartialFile use (#212)
c4aa48de is described below

commit c4aa48de3961acb7f39e136b54e290e1bc5e236c
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Tue Nov 8 10:16:30 2022 +0100

    [MRESOLVER-282] Drop PartialFile use (#212)
    
    Drop PartialFile, clean up regarding resource handling and lessen temp file use. Downside: there is no "download resume" happening anymore.
    
    On the other hand, from now on, resolver will download "all or nothing", so no more "partial downloads" can happen either.
    
    HttpClientTransport is STILL ABLE to resume downloads (was the only one anyway), but due removal of PartialFile it will never resume. Later we may replace removed logic with something simpler.
    
    ---
    
    https://issues.apache.org/jira/browse/MRESOLVER-282
---
 .../connector/basic/BasicRepositoryConnector.java  |  47 +--
 .../aether/connector/basic/ChecksumValidator.java  |  66 +---
 .../aether/connector/basic/PartialFile.java        | 349 -------------------
 .../connector/basic/ChecksumValidatorTest.java     |  15 +-
 .../aether/connector/basic/PartialFileTest.java    | 369 ---------------------
 .../connector/transport/AbstractTransporter.java   |  78 +----
 .../aether/transport/http/HttpTransporter.java     |  88 +++--
 .../aether/transport/wagon/WagonTransporter.java   | 169 ++++------
 .../transport/wagon/WagonTransporterFactory.java   |   3 +
 .../eclipse/aether/transport/wagon/MemWagon.java   |   3 +
 .../java/org/eclipse/aether/util/FileUtils.java    |  65 +++-
 src/site/markdown/configuration.md                 |   2 -
 12 files changed, 213 insertions(+), 1041 deletions(-)

diff --git a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java
index 5f25e703..4c6ab9f4 100644
--- a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java
+++ b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java
@@ -68,6 +68,7 @@ import org.eclipse.aether.transfer.TransferEvent;
 import org.eclipse.aether.transfer.TransferResource;
 import org.eclipse.aether.transform.FileTransformer;
 import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.FileUtils;
 import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
 import org.eclipse.aether.util.concurrency.WorkerThreadFactory;
 import org.slf4j.Logger;
@@ -82,10 +83,6 @@ final class BasicRepositoryConnector
 
     private static final String CONFIG_PROP_THREADS = "aether.connector.basic.threads";
 
-    private static final String CONFIG_PROP_RESUME = "aether.connector.resumeDownloads";
-
-    private static final String CONFIG_PROP_RESUME_THRESHOLD = "aether.connector.resumeThreshold";
-
     private static final String CONFIG_PROP_SMART_CHECKSUMS = "aether.connector.smartChecksums";
 
     private static final Logger LOGGER = LoggerFactory.getLogger( BasicRepositoryConnector.class );
@@ -104,8 +101,6 @@ final class BasicRepositoryConnector
 
     private final ChecksumPolicyProvider checksumPolicyProvider;
 
-    private final PartialFile.Factory partialFileFactory;
-
     private final int maxThreads;
 
     private final boolean smartChecksums;
@@ -154,18 +149,6 @@ final class BasicRepositoryConnector
         persistedChecksums =
                 ConfigUtils.getBoolean( session, ConfigurationProperties.DEFAULT_PERSISTED_CHECKSUMS,
                         ConfigurationProperties.PERSISTED_CHECKSUMS );
-
-        boolean resumeDownloads =
-                ConfigUtils.getBoolean( session, true, CONFIG_PROP_RESUME + '.' + repository.getId(),
-                        CONFIG_PROP_RESUME );
-        long resumeThreshold =
-                ConfigUtils.getLong( session, 64 * 1024, CONFIG_PROP_RESUME_THRESHOLD + '.' + repository.getId(),
-                        CONFIG_PROP_RESUME_THRESHOLD );
-        int requestTimeout =
-                ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
-                        ConfigurationProperties.REQUEST_TIMEOUT + '.' + repository.getId(),
-                        ConfigurationProperties.REQUEST_TIMEOUT );
-        partialFileFactory = new PartialFile.Factory( resumeDownloads, resumeThreshold, requestTimeout );
     }
 
     private Executor getExecutor( Collection<?> artifacts, Collection<?> metadatas )
@@ -430,7 +413,7 @@ final class BasicRepositoryConnector
 
     class GetTaskRunner
             extends TaskRunner
-            implements PartialFile.RemoteAccessChecker, ChecksumValidator.ChecksumFetcher
+            implements ChecksumValidator.ChecksumFetcher
     {
 
         private final File file;
@@ -449,13 +432,6 @@ final class BasicRepositoryConnector
                     checksumPolicy, providedChecksums, safe( checksumLocations ) );
         }
 
-        @Override
-        public void checkRemoteAccess()
-                throws Exception
-        {
-            transporter.peek( new PeekTask( path ) );
-        }
-
         @Override
         public boolean fetchChecksum( URI remote, File local )
                 throws Exception
@@ -481,21 +457,13 @@ final class BasicRepositoryConnector
         {
             fileProcessor.mkdirs( file.getParentFile() );
 
-            PartialFile partFile = partialFileFactory.newInstance( file, this );
-            if ( partFile == null )
-            {
-                LOGGER.debug( "Concurrent download of {} just finished, skipping download", file );
-                return;
-            }
-
-            try
+            try ( FileUtils.TempFile tempFile = FileUtils.newTempFile( file.toPath() ) )
             {
-                File tmp = partFile.getFile();
+                final File tmp = tempFile.getPath().toFile();
                 listener.setChecksumCalculator( checksumValidator.newChecksumCalculator( tmp ) );
                 for ( int firstTrial = 0, lastTrial = 1, trial = firstTrial; ; trial++ )
                 {
-                    boolean resume = partFile.isResume() && trial <= firstTrial;
-                    GetTask task = new GetTask( path ).setDataFile( tmp, resume ).setListener( listener );
+                    GetTask task = new GetTask( path ).setDataFile( tmp, false ).setListener( listener );
                     transporter.get( task );
                     try
                     {
@@ -527,11 +495,6 @@ final class BasicRepositoryConnector
                     checksumValidator.commit();
                 }
             }
-            finally
-            {
-                partFile.close();
-                checksumValidator.close();
-            }
         }
 
     }
diff --git a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/ChecksumValidator.java b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/ChecksumValidator.java
index 14ae450a..988cf54c 100644
--- a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/ChecksumValidator.java
+++ b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/ChecksumValidator.java
@@ -24,9 +24,7 @@ import java.io.IOException;
 import java.net.URI;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.UUID;
 
 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
@@ -34,6 +32,7 @@ import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
 import org.eclipse.aether.spi.connector.layout.RepositoryLayout.ChecksumLocation;
 import org.eclipse.aether.spi.io.FileProcessor;
 import org.eclipse.aether.transfer.ChecksumFailureException;
+import org.eclipse.aether.util.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -61,8 +60,6 @@ final class ChecksumValidator
 
     private final Collection<ChecksumAlgorithmFactory> checksumAlgorithmFactories;
 
-    private final Collection<File> tempFiles;
-
     private final FileProcessor fileProcessor;
 
     private final ChecksumFetcher checksumFetcher;
@@ -73,7 +70,7 @@ final class ChecksumValidator
 
     private final Collection<ChecksumLocation> checksumLocations;
 
-    private final Map<File, Object> checksumFiles;
+    private final Map<File, String> checksumExpectedValues;
 
     ChecksumValidator( File dataFile,
                        Collection<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
@@ -85,13 +82,12 @@ final class ChecksumValidator
     {
         this.dataFile = dataFile;
         this.checksumAlgorithmFactories = checksumAlgorithmFactories;
-        this.tempFiles = new HashSet<>();
         this.fileProcessor = fileProcessor;
         this.checksumFetcher = checksumFetcher;
         this.checksumPolicy = checksumPolicy;
         this.providedChecksums = providedChecksums;
         this.checksumLocations = checksumLocations;
-        this.checksumFiles = new HashMap<>();
+        this.checksumExpectedValues = new HashMap<>();
     }
 
     public ChecksumCalculator newChecksumCalculator( File targetFile )
@@ -152,7 +148,7 @@ final class ChecksumValidator
 
             String actual = String.valueOf( calculated );
             String expected = entry.getValue().toString();
-            checksumFiles.put( getChecksumFile( checksumAlgorithmFactory ), expected );
+            checksumExpectedValues.put( getChecksumFile( checksumAlgorithmFactory ), expected );
 
             if ( !isEqualChecksum( expected, actual ) )
             {
@@ -183,10 +179,10 @@ final class ChecksumValidator
                 );
                 continue;
             }
-            try
+            File checksumFile = getChecksumFile( checksumLocation.getChecksumAlgorithmFactory() );
+            try ( FileUtils.TempFile tempFile = FileUtils.newTempFile( checksumFile.toPath() ) )
             {
-                File checksumFile = getChecksumFile( checksumLocation.getChecksumAlgorithmFactory() );
-                File tmp = createTempFile( checksumFile );
+                File tmp = tempFile.getPath().toFile();
                 try
                 {
                     if ( !checksumFetcher.fetchChecksum(
@@ -206,7 +202,7 @@ final class ChecksumValidator
 
                 String actual = String.valueOf( calculated );
                 String expected = fileProcessor.readChecksum( tmp );
-                checksumFiles.put( checksumFile, tmp );
+                checksumExpectedValues.put( checksumFile, expected );
 
                 if ( !isEqualChecksum( expected, actual ) )
                 {
@@ -240,33 +236,10 @@ final class ChecksumValidator
         return new File( dataFile.getPath() + '.' + factory.getFileExtension() );
     }
 
-    private File createTempFile( File path )
-        throws IOException
-    {
-        File file =
-            File.createTempFile( path.getName() + "-"
-                + UUID.randomUUID().toString().replace( "-", "" ).substring( 0, 8 ), ".tmp", path.getParentFile() );
-        tempFiles.add( file );
-        return file;
-    }
-
-    private void clearTempFiles()
-    {
-        for ( File file : tempFiles )
-        {
-            if ( !file.delete() && file.exists() )
-            {
-                LOGGER.debug( "Could not delete temporary file {}", file );
-            }
-        }
-        tempFiles.clear();
-    }
-
     public void retry()
     {
         checksumPolicy.onTransferRetry();
-        checksumFiles.clear();
-        clearTempFiles();
+        checksumExpectedValues.clear();
     }
 
     public boolean handle( ChecksumFailureException exception )
@@ -276,33 +249,18 @@ final class ChecksumValidator
 
     public void commit()
     {
-        for ( Map.Entry<File, Object> entry : checksumFiles.entrySet() )
+        for ( Map.Entry<File, String> entry : checksumExpectedValues.entrySet() )
         {
             File checksumFile = entry.getKey();
-            Object tmp = entry.getValue();
             try
             {
-                if ( tmp instanceof File )
-                {
-                    fileProcessor.move( (File) tmp, checksumFile );
-                    tempFiles.remove( tmp );
-                }
-                else
-                {
-                    fileProcessor.writeChecksum( checksumFile, String.valueOf( tmp ) );
-                }
+                fileProcessor.writeChecksum( checksumFile, entry.getValue() );
             }
             catch ( IOException e )
             {
                 LOGGER.debug( "Failed to write checksum file {}", checksumFile, e );
             }
         }
-        checksumFiles.clear();
+        checksumExpectedValues.clear();
     }
-
-    public void close()
-    {
-        clearTempFiles();
-    }
-
 }
diff --git a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/PartialFile.java b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/PartialFile.java
deleted file mode 100644
index c7a74f7c..00000000
--- a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/PartialFile.java
+++ /dev/null
@@ -1,349 +0,0 @@
-package org.eclipse.aether.connector.basic;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.Channel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A partially downloaded file with optional support for resume. If resume is enabled, a well-known location is used for
- * the partial file in combination with a lock file to prevent concurrent requests from corrupting it (and wasting
- * network bandwith). Otherwise, a (non-locked) unique temporary file is used.
- */
-final class PartialFile
-    implements Closeable
-{
-
-    static final String EXT_PART = ".part";
-
-    static final String EXT_LOCK = ".lock";
-
-    interface RemoteAccessChecker
-    {
-
-        void checkRemoteAccess()
-            throws Exception;
-
-    }
-
-    static class LockFile
-    {
-
-        private final File lockFile;
-
-        private final FileLock lock;
-
-        private final AtomicBoolean concurrent;
-
-        LockFile( File partFile, int requestTimeout, RemoteAccessChecker checker )
-            throws Exception
-        {
-            lockFile = new File( partFile.getPath() + EXT_LOCK );
-            concurrent = new AtomicBoolean( false );
-            lock = lock( lockFile, partFile, requestTimeout, checker, concurrent );
-        }
-
-        private static FileLock lock( File lockFile, File partFile, int requestTimeout, RemoteAccessChecker checker,
-                                      AtomicBoolean concurrent )
-            throws Exception
-        {
-            boolean interrupted = false;
-            try
-            {
-                for ( long lastLength = -1L, lastTime = 0L;; )
-                {
-                    FileLock lock = tryLock( lockFile );
-                    if ( lock != null )
-                    {
-                        return lock;
-                    }
-
-                    long currentLength = partFile.length();
-                    long currentTime = System.currentTimeMillis();
-                    if ( currentLength != lastLength )
-                    {
-                        if ( lastLength < 0L )
-                        {
-                            concurrent.set( true );
-                            /*
-                             * NOTE: We're going with the optimistic assumption that the other thread is downloading the
-                             * file from an equivalent repository. As a bare minimum, ensure the repository we are given
-                             * at least knows about the file and is accessible to us.
-                             */
-                            checker.checkRemoteAccess();
-                            LOGGER.debug( "Concurrent download of {} in progress, awaiting completion", partFile );
-                        }
-                        lastLength = currentLength;
-                        lastTime = currentTime;
-                    }
-                    else if ( requestTimeout > 0 && currentTime - lastTime > Math.max( requestTimeout, 3 * 1000 ) )
-                    {
-                        throw new IOException( "Timeout while waiting for concurrent download of " + partFile
-                                                   + " to progress" );
-                    }
-
-                    try
-                    {
-                        Thread.sleep( 100 );
-                    }
-                    catch ( InterruptedException e )
-                    {
-                        interrupted = true;
-                    }
-                }
-            }
-            finally
-            {
-                if ( interrupted )
-                {
-                    Thread.currentThread().interrupt();
-                }
-            }
-        }
-
-        private static FileLock tryLock( File lockFile )
-            throws IOException
-        {
-            RandomAccessFile raf = null;
-            FileLock lock = null;
-            try
-            {
-                raf = new RandomAccessFile( lockFile, "rw" );
-                lock = raf.getChannel().tryLock( 0, 1, false );
-
-                if ( lock == null )
-                {
-                    raf.close();
-                    raf = null;
-                }
-            }
-            catch ( OverlappingFileLockException e )
-            {
-                close( raf );
-                raf = null;
-                lock = null;
-            }
-            catch ( RuntimeException | IOException e )
-            {
-                close( raf );
-                raf = null;
-                if ( !lockFile.delete() )
-                {
-                    lockFile.deleteOnExit();
-                }
-                throw e;
-            }
-            finally
-            {
-                try
-                {
-                    if ( lock == null && raf != null )
-                    {
-                        raf.close();
-                    }
-                }
-                catch ( final IOException e )
-                {
-                    // Suppressed due to an exception already thrown in the try block.
-                }
-            }
-
-            return lock;
-        }
-
-        private static void close( Closeable file )
-        {
-            try
-            {
-                if ( file != null )
-                {
-                    file.close();
-                }
-            }
-            catch ( IOException e )
-            {
-                // Suppressed.
-            }
-        }
-
-        public boolean isConcurrent()
-        {
-            return concurrent.get();
-        }
-
-        public void close() throws IOException
-        {
-            Channel channel = null;
-            try
-            {
-                channel = lock.channel();
-                lock.release();
-                channel.close();
-                channel = null;
-            }
-            finally
-            {
-                try
-                {
-                    if ( channel != null )
-                    {
-                        channel.close();
-                    }
-                }
-                catch ( final IOException e )
-                {
-                    // Suppressed due to an exception already thrown in the try block.
-                }
-                finally
-                {
-                    if ( !lockFile.delete() )
-                    {
-                        lockFile.deleteOnExit();
-                    }
-                }
-            }
-        }
-
-        @Override
-        public String toString()
-        {
-            return lockFile + " - " + lock.isValid();
-        }
-
-    }
-
-    static class Factory
-    {
-
-        private final boolean resume;
-
-        private final long resumeThreshold;
-
-        private final int requestTimeout;
-
-        private static final Logger LOGGER = LoggerFactory.getLogger( Factory.class );
-
-        Factory( boolean resume, long resumeThreshold, int requestTimeout )
-        {
-            this.resume = resume;
-            this.resumeThreshold = resumeThreshold;
-            this.requestTimeout = requestTimeout;
-        }
-
-        public PartialFile newInstance( File dstFile, RemoteAccessChecker checker )
-            throws Exception
-        {
-            if ( resume )
-            {
-                File partFile = new File( dstFile.getPath() + EXT_PART );
-
-                long reqTimestamp = System.currentTimeMillis();
-                LockFile lockFile = new LockFile( partFile, requestTimeout, checker );
-                if ( lockFile.isConcurrent() && dstFile.lastModified() >= reqTimestamp - 100L )
-                {
-                    lockFile.close();
-                    return null;
-                }
-                try
-                {
-                    if ( !partFile.createNewFile() && !partFile.isFile() )
-                    {
-                        throw new IOException( partFile.exists() ? "Path exists but is not a file" : "Unknown error" );
-                    }
-                    return new PartialFile( partFile, lockFile, resumeThreshold );
-                }
-                catch ( IOException e )
-                {
-                    lockFile.close();
-                    LOGGER.debug( "Cannot create resumable file {}", partFile.getAbsolutePath(), e );
-                    // fall through and try non-resumable/temporary file location
-                }
-            }
-
-            File tempFile =
-                File.createTempFile( dstFile.getName() + '-' + UUID.randomUUID().toString().replace( "-", "" ), ".tmp",
-                                     dstFile.getParentFile() );
-            return new PartialFile( tempFile );
-        }
-
-    }
-
-    private final File partFile;
-
-    private final LockFile lockFile;
-
-    private final long threshold;
-
-    private static final Logger LOGGER = LoggerFactory.getLogger( PartialFile.class );
-
-    private PartialFile( File partFile )
-    {
-        this( partFile, null, 0L );
-    }
-
-    private PartialFile( File partFile, LockFile lockFile, long threshold )
-    {
-        this.partFile = partFile;
-        this.lockFile = lockFile;
-        this.threshold = threshold;
-    }
-
-    public File getFile()
-    {
-        return partFile;
-    }
-
-    public boolean isResume()
-    {
-        return lockFile != null && partFile.length() >= threshold;
-    }
-
-    public void close() throws IOException
-    {
-        if ( partFile.exists() && !isResume() )
-        {
-            if ( !partFile.delete() && partFile.exists() )
-            {
-                LOGGER.debug( "Could not delete temporary file {}", partFile );
-            }
-        }
-        if ( lockFile != null )
-        {
-            lockFile.close();
-        }
-    }
-
-    @Override
-    public String toString()
-    {
-        return String.valueOf( getFile() );
-    }
-
-}
diff --git a/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/ChecksumValidatorTest.java b/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/ChecksumValidatorTest.java
index 530e5e77..7534c255 100644
--- a/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/ChecksumValidatorTest.java
+++ b/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/ChecksumValidatorTest.java
@@ -426,10 +426,6 @@ public class ChecksumValidatorTest
         validator.validate( checksums( SHA1, "foo" ), null );
         fetcher.assertFetchedFiles( SHA1 );
         assertEquals( 1, fetcher.checksumFiles.size() );
-        for ( File file : fetcher.checksumFiles )
-        {
-            assertTrue( file.getAbsolutePath(), file.isFile() );
-        }
         validator.retry();
         for ( File file : fetcher.checksumFiles )
         {
@@ -446,10 +442,6 @@ public class ChecksumValidatorTest
         fetcher.mock( MD5, "bar" );
         validator.validate( checksums( SHA1, "foo", MD5, "bar" ), checksums( SHA1, "foo" ) );
         assertEquals( 1, fetcher.checksumFiles.size() );
-        for ( File file : fetcher.checksumFiles )
-        {
-            assertTrue( file.getAbsolutePath(), file.isFile() );
-        }
         validator.commit();
         File checksumFile = new File( dataFile.getPath() + ".sha1" );
         assertTrue( checksumFile.getAbsolutePath(), checksumFile.isFile() );
@@ -464,7 +456,7 @@ public class ChecksumValidatorTest
     }
 
     @Test
-    public void testClose_RemoveTempFiles()
+    public void testNoCommit_NoTempFiles()
         throws Exception
     {
         ChecksumValidator validator = newValidator( SHA1 );
@@ -473,11 +465,6 @@ public class ChecksumValidatorTest
         fetcher.assertFetchedFiles( SHA1 );
         assertEquals( 1, fetcher.checksumFiles.size() );
         for ( File file : fetcher.checksumFiles )
-        {
-            assertTrue( file.getAbsolutePath(), file.isFile() );
-        }
-        validator.close();
-        for ( File file : fetcher.checksumFiles )
         {
             assertFalse( file.getAbsolutePath(), file.exists() );
         }
diff --git a/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/PartialFileTest.java b/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/PartialFileTest.java
deleted file mode 100644
index bb6530e6..00000000
--- a/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/PartialFileTest.java
+++ /dev/null
@@ -1,369 +0,0 @@
-package org.eclipse.aether.connector.basic;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- * 
- *  http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import static org.junit.Assert.*;
-import static org.junit.Assume.*;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileLock;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.CountDownLatch;
-
-import org.eclipse.aether.internal.test.util.TestFileUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class PartialFileTest
-{
-
-    private static class StubRemoteAccessChecker
-        implements PartialFile.RemoteAccessChecker
-    {
-
-        Exception exception;
-
-        int invocations;
-
-        public void checkRemoteAccess()
-            throws Exception
-        {
-            invocations++;
-            if ( exception != null )
-            {
-                throw exception;
-            }
-        }
-
-    }
-
-    private static class ConcurrentWriter
-        extends Thread
-    {
-
-        private final File dstFile;
-
-        private final File partFile;
-
-        private final File lockFile;
-
-        private final CountDownLatch locked;
-
-        private final int sleep;
-
-        volatile int length;
-
-        Exception error;
-
-        public ConcurrentWriter( File dstFile, int sleep, int length )
-            throws InterruptedException
-        {
-            super( "ConcurrentWriter-" + dstFile.getAbsolutePath() );
-            this.dstFile = dstFile;
-            partFile = new File( dstFile.getPath() + PartialFile.EXT_PART );
-            lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK );
-            this.sleep = sleep;
-            this.length = length;
-            locked = new CountDownLatch( 1 );
-            start();
-            locked.await();
-        }
-
-        @Override
-        public void run()
-        {
-            RandomAccessFile raf = null;
-            FileLock lock = null;
-            OutputStream out = null;
-            try
-            {
-                raf = new RandomAccessFile( lockFile, "rw" );
-                lock = raf.getChannel().lock( 0, 1, false );
-                locked.countDown();
-                out = new FileOutputStream( partFile );
-                for ( int i = 0, n = Math.abs( length ); i < n; i++ )
-                {
-                    for ( long start = System.currentTimeMillis(); System.currentTimeMillis() - start < sleep; )
-                    {
-                        Thread.sleep( 10 );
-                    }
-                    out.write( 65 );
-                    out.flush();
-                    System.out.println( "  " + System.currentTimeMillis() + " Wrote byte " + ( i + 1 ) + "/"
-                                            + n );
-                }
-                if ( length >= 0 && !dstFile.setLastModified( System.currentTimeMillis() ) )
-                {
-                    throw new IOException( "Could not update destination file" );
-                }
-
-                out.close();
-                out = null;
-                lock.release();
-                lock = null;
-                raf.close();
-                raf = null;
-            }
-            catch ( Exception e )
-            {
-                error = e;
-            }
-            finally
-            {
-                try
-                {
-                    if ( out != null )
-                    {
-                        out.close();
-                    }
-                }
-                catch ( final IOException e )
-                {
-                    // Suppressed due to an exception already thrown in the try block.
-                }
-                finally
-                {
-                    try
-                    {
-                        if ( lock != null )
-                        {
-                            lock.release();
-                        }
-                    }
-                    catch ( final IOException e )
-                    {
-                        // Suppressed due to an exception already thrown in the try block.
-                    }
-                    finally
-                    {
-                        try
-                        {
-                            if ( raf != null )
-                            {
-                                raf.close();
-                            }
-                        }
-                        catch ( final IOException e )
-                        {
-                            // Suppressed due to an exception already thrown in the try block.
-                        }
-                        finally
-                        {
-                            if ( !lockFile.delete() )
-                            {
-                                lockFile.deleteOnExit();
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-    }
-
-    private static final boolean PROPER_LOCK_SUPPORT;
-
-    static
-    {
-        String javaVersion = System.getProperty( "java.version" ).trim();
-        boolean notJava5 = !javaVersion.startsWith( "1.5." );
-        String osName = System.getProperty( "os.name" ).toLowerCase( Locale.ENGLISH );
-        boolean windows = osName.contains( "windows" );
-        PROPER_LOCK_SUPPORT = notJava5 || windows;
-    }
-
-    private StubRemoteAccessChecker remoteAccessChecker;
-
-    private File dstFile;
-
-    private File partFile;
-
-    private File lockFile;
-
-    private List<Closeable> closeables;
-
-    private PartialFile newPartialFile( long resumeThreshold, int requestTimeout )
-        throws Exception
-    {
-        PartialFile.Factory factory =
-            new PartialFile.Factory( resumeThreshold >= 0L, resumeThreshold, requestTimeout );
-        PartialFile partFile = factory.newInstance( dstFile, remoteAccessChecker );
-        if ( partFile != null )
-        {
-            closeables.add( partFile );
-        }
-        return partFile;
-    }
-
-    @Before
-    public void init()
-        throws Exception
-    {
-        closeables = new ArrayList<>();
-        remoteAccessChecker = new StubRemoteAccessChecker();
-        dstFile = TestFileUtils.createTempFile( "Hello World!" );
-        partFile = new File( dstFile.getPath() + PartialFile.EXT_PART );
-        lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK );
-    }
-
-    @After
-    public void exit()
-    {
-        for ( Closeable closeable : closeables )
-        {
-            try
-            {
-                closeable.close();
-            }
-            catch ( Exception e )
-            {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    @Test
-    public void testCloseNonResumableFile()
-        throws Exception
-    {
-        PartialFile partialFile = newPartialFile( -1, 100 );
-        assertNotNull( partialFile );
-        assertNotNull( partialFile.getFile() );
-        assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
-        partialFile.close();
-        assertFalse( partialFile.getFile().getAbsolutePath(), partialFile.getFile().exists() );
-    }
-
-    @Test
-    public void testCloseResumableFile()
-        throws Exception
-    {
-        PartialFile partialFile = newPartialFile( 0, 100 );
-        assertNotNull( partialFile );
-        assertNotNull( partialFile.getFile() );
-        assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
-        assertEquals( partFile, partialFile.getFile() );
-        assertTrue( lockFile.getAbsolutePath(), lockFile.isFile() );
-        partialFile.close();
-        assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
-        assertFalse( lockFile.getAbsolutePath(), lockFile.exists() );
-    }
-
-    @Test
-    public void testResumableFileCreationError()
-        throws Exception
-    {
-        assertTrue( partFile.getAbsolutePath(), partFile.mkdirs() );
-        PartialFile partialFile = newPartialFile( 0, 100 );
-        assertNotNull( partialFile );
-        assertFalse( partialFile.isResume() );
-        assertFalse( lockFile.getAbsolutePath(), lockFile.exists() );
-    }
-
-    @Test
-    public void testResumeThreshold()
-        throws Exception
-    {
-        PartialFile partialFile = newPartialFile( 0, 100 );
-        assertNotNull( partialFile );
-        assertTrue( partialFile.isResume() );
-        partialFile.close();
-        partialFile = newPartialFile( 1, 100 );
-        assertNotNull( partialFile );
-        assertFalse( partialFile.isResume() );
-        partialFile.close();
-    }
-
-    @Test( timeout = 10000L )
-    public void testResumeConcurrently_RequestTimeout()
-        throws Exception
-    {
-        assumeTrue( PROPER_LOCK_SUPPORT );
-        ConcurrentWriter writer = new ConcurrentWriter( dstFile, 5 * 1000, 1 );
-        try
-        {
-            newPartialFile( 0, 1000 );
-            fail( "expected exception" );
-        }
-        catch ( Exception e )
-        {
-            assertTrue( e.getMessage().contains( "Timeout" ) );
-        }
-        writer.interrupt();
-        writer.join();
-    }
-
-    @Test( timeout = 10000L )
-    public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterSucceeds()
-        throws Exception
-    {
-        assumeTrue( PROPER_LOCK_SUPPORT );
-        assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60L * 1000L ) );
-        ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, 10 );
-        assertNull( newPartialFile( 0, 500 ) );
-        writer.join();
-        assertNull( writer.error );
-        assertEquals( 1, remoteAccessChecker.invocations );
-    }
-
-    @Test( timeout = 10000L )
-    public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterFails()
-        throws Exception
-    {
-        assumeTrue( PROPER_LOCK_SUPPORT );
-        assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60L * 1000L ) );
-        ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, -10 );
-        PartialFile partialFile = newPartialFile( 0, 500 );
-        assertNotNull( partialFile );
-        assertTrue( partialFile.isResume() );
-        writer.join();
-        assertNull( writer.error );
-        assertEquals( 1, remoteAccessChecker.invocations );
-    }
-
-    @Test( timeout = 10000L )
-    public void testResumeConcurrently_CheckRemoteAccess()
-        throws Exception
-    {
-        assumeTrue( PROPER_LOCK_SUPPORT );
-        remoteAccessChecker.exception = new IOException( "missing" );
-        ConcurrentWriter writer = new ConcurrentWriter( dstFile, 1000, 1 );
-        try
-        {
-            newPartialFile( 0, 1000 );
-            fail( "expected exception" );
-        }
-        catch ( Exception e )
-        {
-            assertSame( remoteAccessChecker.exception, e );
-        }
-        writer.interrupt();
-        writer.join();
-    }
-
-}
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java
index 4bb276c5..c8205f20 100644
--- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java
@@ -100,47 +100,16 @@ public abstract class AbstractTransporter
     protected void utilGet( GetTask task, InputStream is, boolean close, long length, boolean resume )
         throws IOException, TransferCancelledException
     {
-        OutputStream os = null;
-        try
+        try ( OutputStream os = task.newOutputStream( resume ) )
         {
-            os = task.newOutputStream( resume );
             task.getListener().transportStarted( resume ? task.getResumeOffset() : 0L, length );
             copy( os, is, task.getListener() );
-            os.close();
-            os = null;
-
-            if ( close )
-            {
-                is.close();
-                is = null;
-            }
         }
         finally
         {
-            try
-            {
-                if ( os != null )
-                {
-                    os.close();
-                }
-            }
-            catch ( final IOException e )
-            {
-                // Suppressed due to an exception already thrown in the try block.
-            }
-            finally
+            if ( close )
             {
-                try
-                {
-                    if ( close && is != null )
-                    {
-                        is.close();
-                    }
-                }
-                catch ( final IOException e )
-                {
-                    // Suppressed due to an exception already thrown in the try block.
-                }
+                is.close();
             }
         }
     }
@@ -178,13 +147,13 @@ public abstract class AbstractTransporter
     protected void utilPut( PutTask task, OutputStream os, boolean close )
         throws IOException, TransferCancelledException
     {
-        InputStream is = null;
-        try
+        try ( InputStream is = task.newInputStream() )
         {
             task.getListener().transportStarted( 0, task.getDataLength() );
-            is = task.newInputStream();
             copy( os, is, task.getListener() );
-
+        }
+        finally
+        {
             if ( close )
             {
                 os.close();
@@ -193,39 +162,6 @@ public abstract class AbstractTransporter
             {
                 os.flush();
             }
-
-            os = null;
-
-            is.close();
-            is = null;
-        }
-        finally
-        {
-            try
-            {
-                if ( close && os != null )
-                {
-                    os.close();
-                }
-            }
-            catch ( final IOException e )
-            {
-                // Suppressed due to an exception already thrown in the try block.
-            }
-            finally
-            {
-                try
-                {
-                    if ( is != null )
-                    {
-                        is.close();
-                    }
-                }
-                catch ( final IOException e )
-                {
-                    // Suppressed due to an exception already thrown in the try block.
-                }
-            }
         }
     }
 
diff --git a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java
index 91b15aaf..32cb7388 100644
--- a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java
+++ b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java
@@ -24,7 +24,6 @@ import org.apache.http.HttpEntity;
 import org.apache.http.HttpEntityEnclosingRequest;
 import org.apache.http.HttpHeaders;
 import org.apache.http.HttpHost;
-import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
 import org.apache.http.auth.AuthSchemeProvider;
 import org.apache.http.auth.AuthScope;
@@ -32,6 +31,7 @@ import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.HttpResponseException;
 import org.apache.http.client.config.AuthSchemes;
 import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpHead;
 import org.apache.http.client.methods.HttpOptions;
@@ -65,9 +65,11 @@ import org.eclipse.aether.spi.connector.transport.TransportTask;
 import org.eclipse.aether.transfer.NoTransporterException;
 import org.eclipse.aether.transfer.TransferCancelledException;
 import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InterruptedIOException;
@@ -76,6 +78,8 @@ import java.io.UncheckedIOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
@@ -356,19 +360,21 @@ final class HttpTransporter
         {
             SharingHttpContext context = new SharingHttpContext( state );
             prepare( request, context );
-            HttpResponse response = client.execute( server, request, context );
-            try
+            try ( CloseableHttpResponse response = client.execute( server, request, context ) )
             {
-                context.close();
-                handleStatus( response );
-                if ( getter != null )
+                try
                 {
-                    getter.handle( response );
+                    context.close();
+                    handleStatus( response );
+                    if ( getter != null )
+                    {
+                        getter.handle( response );
+                    }
+                }
+                finally
+                {
+                    EntityUtils.consumeQuietly( response.getEntity() );
                 }
-            }
-            finally
-            {
-                EntityUtils.consumeQuietly( response.getEntity() );
             }
         }
         catch ( IOException e )
@@ -386,10 +392,9 @@ final class HttpTransporter
         boolean put = HttpPut.METHOD_NAME.equalsIgnoreCase( request.getMethod() );
         if ( state.getWebDav() == null && ( put || isPayloadPresent( request ) ) )
         {
-            try
+            HttpOptions req = commonHeaders( new HttpOptions( request.getURI() ) );
+            try ( CloseableHttpResponse response = client.execute( server, req, context ) )
             {
-                HttpOptions req = commonHeaders( new HttpOptions( request.getURI() ) );
-                HttpResponse response = client.execute( server, req, context );
                 state.setWebDav( isWebDav( response ) );
                 EntityUtils.consumeQuietly( response.getEntity() );
             }
@@ -404,7 +409,7 @@ final class HttpTransporter
         }
     }
 
-    private boolean isWebDav( HttpResponse response )
+    private boolean isWebDav( CloseableHttpResponse response )
     {
         return response.containsHeader( HttpHeaders.DAV );
     }
@@ -416,10 +421,9 @@ final class HttpTransporter
         int index = 0;
         for ( ; index < dirs.size(); index++ )
         {
-            try
+            try ( CloseableHttpResponse response =
+                          client.execute( server, commonHeaders( new HttpMkCol( dirs.get( index ) ) ), context ) )
             {
-                HttpResponse response =
-                    client.execute( server, commonHeaders( new HttpMkCol( dirs.get( index ) ) ), context );
                 try
                 {
                     int status = response.getStatusLine().getStatusCode();
@@ -446,10 +450,9 @@ final class HttpTransporter
         }
         for ( index--; index >= 0; index-- )
         {
-            try
+            try ( CloseableHttpResponse response =
+                          client.execute( server, commonHeaders( new HttpMkCol( dirs.get( index ) ) ), context ) )
             {
-                HttpResponse response =
-                    client.execute( server, commonHeaders( new HttpMkCol( dirs.get( index ) ) ), context );
                 try
                 {
                     handleStatus( response );
@@ -532,7 +535,7 @@ final class HttpTransporter
     }
 
     @SuppressWarnings( "checkstyle:magicnumber" )
-    private void handleStatus( HttpResponse response )
+    private void handleStatus( CloseableHttpResponse response )
         throws HttpResponseException
     {
         int status = response.getStatusLine().getStatusCode();
@@ -568,7 +571,7 @@ final class HttpTransporter
             this.task = task;
         }
 
-        public void handle( HttpResponse response )
+        public void handle( CloseableHttpResponse response )
             throws IOException, TransferCancelledException
         {
             HttpEntity entity = response.getEntity();
@@ -592,16 +595,47 @@ final class HttpTransporter
                 if ( offset < 0L || offset >= length || ( offset > 0L && offset != task.getResumeOffset() ) )
                 {
                     throw new IOException( "Invalid Content-Range header for partial download from offset "
-                        + task.getResumeOffset() + ": " + range );
+                            + task.getResumeOffset() + ": " + range );
                 }
             }
 
-            InputStream is = entity.getContent();
-            utilGet( task, is, true, length, offset > 0L );
+            final boolean resume = offset > 0L;
+            final File dataFile = task.getDataFile();
+            if ( dataFile == null )
+            {
+                try ( InputStream is = entity.getContent() )
+                {
+                    utilGet( task, is, true, length, resume );
+                    extractChecksums( response );
+                }
+            }
+            else
+            {
+                try ( FileUtils.TempFile tempFile = FileUtils.newTempFile( dataFile.toPath() ) )
+                {
+                    task.setDataFile( tempFile.getPath().toFile(), resume );
+                    if ( resume && Files.isRegularFile( dataFile.toPath() ) )
+                    {
+                        try ( InputStream inputStream = Files.newInputStream( dataFile.toPath() ) )
+                        {
+                            Files.copy( inputStream, tempFile.getPath(), StandardCopyOption.REPLACE_EXISTING );
+                        }
+                    }
+                    try ( InputStream is = entity.getContent() )
+                    {
+                        utilGet( task, is, true, length, resume );
+                    }
+                    Files.move( tempFile.getPath(), dataFile.toPath(), StandardCopyOption.ATOMIC_MOVE );
+                }
+                finally
+                {
+                    task.setDataFile( dataFile );
+                }
+            }
             extractChecksums( response );
         }
 
-        private void extractChecksums( HttpResponse response )
+        private void extractChecksums( CloseableHttpResponse response )
         {
             for ( Map.Entry<String, ChecksumExtractor> extractorEntry : checksumExtractors.entrySet() )
             {
diff --git a/maven-resolver-transport-wagon/src/main/java/org/eclipse/aether/transport/wagon/WagonTransporter.java b/maven-resolver-transport-wagon/src/main/java/org/eclipse/aether/transport/wagon/WagonTransporter.java
index 4a985633..06126ab1 100644
--- a/maven-resolver-transport-wagon/src/main/java/org/eclipse/aether/transport/wagon/WagonTransporter.java
+++ b/maven-resolver-transport-wagon/src/main/java/org/eclipse/aether/transport/wagon/WagonTransporter.java
@@ -20,19 +20,18 @@ package org.eclipse.aether.transport.wagon;
  */
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Properties;
 import java.util.Queue;
-import java.util.UUID;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -58,6 +57,7 @@ import org.eclipse.aether.spi.connector.transport.TransportTask;
 import org.eclipse.aether.spi.connector.transport.Transporter;
 import org.eclipse.aether.transfer.NoTransporterException;
 import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -65,7 +65,7 @@ import org.slf4j.LoggerFactory;
  * A transporter using Maven Wagon.
  */
 final class WagonTransporter
-    implements Transporter
+        implements Transporter
 {
 
     private static final String CONFIG_PROP_CONFIG = "aether.connector.wagon.config";
@@ -105,8 +105,8 @@ final class WagonTransporter
     private final AtomicBoolean closed = new AtomicBoolean();
 
     WagonTransporter( WagonProvider wagonProvider, WagonConfigurator wagonConfigurator,
-                             RemoteRepository repository, RepositorySystemSession session )
-        throws NoTransporterException
+                      RemoteRepository repository, RepositorySystemSession session )
+            throws NoTransporterException
     {
         this.wagonProvider = wagonProvider;
         this.wagonConfigurator = wagonConfigurator;
@@ -128,7 +128,7 @@ final class WagonTransporter
         }
         catch ( Exception e )
         {
-            LOGGER.debug( "No transport {}", e );
+            LOGGER.debug( "No transport", e );
             throw new NoTransporterException( repository, e );
         }
 
@@ -140,10 +140,10 @@ final class WagonTransporter
 
         headers = new Properties();
         headers.put( "User-Agent", ConfigUtils.getString( session, ConfigurationProperties.DEFAULT_USER_AGENT,
-                                                          ConfigurationProperties.USER_AGENT ) );
+                ConfigurationProperties.USER_AGENT ) );
         Map<?, ?> headers =
-            ConfigUtils.getMap( session, null, ConfigurationProperties.HTTP_HEADERS + "." + repository.getId(),
-                                ConfigurationProperties.HTTP_HEADERS );
+                ConfigUtils.getMap( session, null, ConfigurationProperties.HTTP_HEADERS + "." + repository.getId(),
+                        ConfigurationProperties.HTTP_HEADERS );
         if ( headers != null )
         {
             this.headers.putAll( headers );
@@ -271,7 +271,7 @@ final class WagonTransporter
     }
 
     private Wagon lookupWagon()
-        throws Exception
+            throws Exception
     {
         return wagonProvider.lookup( wagonHint );
     }
@@ -282,7 +282,7 @@ final class WagonTransporter
     }
 
     private void connectWagon( Wagon wagon )
-        throws WagonException
+            throws WagonException
     {
         if ( !headers.isEmpty() )
         {
@@ -302,16 +302,16 @@ final class WagonTransporter
         }
 
         int connectTimeout =
-            ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
-                                    ConfigurationProperties.CONNECT_TIMEOUT );
+                ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
+                        ConfigurationProperties.CONNECT_TIMEOUT );
         int requestTimeout =
-            ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
-                                    ConfigurationProperties.REQUEST_TIMEOUT );
+                ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
+                        ConfigurationProperties.REQUEST_TIMEOUT );
 
         wagon.setTimeout( Math.max( Math.max( connectTimeout, requestTimeout ), 0 ) );
 
         wagon.setInteractive( ConfigUtils.getBoolean( session, ConfigurationProperties.DEFAULT_INTERACTIVE,
-                                                      ConfigurationProperties.INTERACTIVE ) );
+                ConfigurationProperties.INTERACTIVE ) );
 
         Object configuration = ConfigUtils.getObject( session, null, CONFIG_PROP_CONFIG + "." + repository.getId() );
         if ( configuration != null && wagonConfigurator != null )
@@ -346,7 +346,7 @@ final class WagonTransporter
     }
 
     private Wagon pollWagon()
-        throws Exception
+            throws Exception
     {
         Wagon wagon = wagons.poll();
 
@@ -379,6 +379,7 @@ final class WagonTransporter
         return wagon;
     }
 
+    @Override
     public int classify( Throwable error )
     {
         if ( error instanceof ResourceDoesNotExistException )
@@ -388,26 +389,29 @@ final class WagonTransporter
         return ERROR_OTHER;
     }
 
+    @Override
     public void peek( PeekTask task )
-        throws Exception
+            throws Exception
     {
         execute( task, new PeekTaskRunner( task ) );
     }
 
+    @Override
     public void get( GetTask task )
-        throws Exception
+            throws Exception
     {
         execute( task, new GetTaskRunner( task ) );
     }
 
+    @Override
     public void put( PutTask task )
-        throws Exception
+            throws Exception
     {
         execute( task, new PutTaskRunner( task ) );
     }
 
     private void execute( TransportTask task, TaskRunner runner )
-        throws Exception
+            throws Exception
     {
         Objects.requireNonNull( task, "task cannot be null" );
 
@@ -436,31 +440,7 @@ final class WagonTransporter
         }
     }
 
-    private static File newTempFile()
-        throws IOException
-    {
-        return File.createTempFile( "wagon-" + UUID.randomUUID().toString().replace( "-", "" ), ".tmp" );
-    }
-
-    private void delTempFile( File path )
-    {
-        if ( path != null && !path.delete() && path.exists() )
-        {
-            LOGGER.debug( "Could not delete temporary file {}", path );
-            path.deleteOnExit();
-        }
-    }
-
-    private static void copy( OutputStream os, InputStream is )
-        throws IOException
-    {
-        byte[] buffer = new byte[1024 * 32];
-        for ( int read = is.read( buffer ); read >= 0; read = is.read( buffer ) )
-        {
-            os.write( buffer, 0, read );
-        }
-    }
-
+    @Override
     public void close()
     {
         if ( closed.compareAndSet( false, true ) )
@@ -480,12 +460,12 @@ final class WagonTransporter
     {
 
         void run( Wagon wagon )
-            throws IOException, WagonException;
+                throws IOException, WagonException;
 
     }
 
     private static class PeekTaskRunner
-        implements TaskRunner
+            implements TaskRunner
     {
 
         private final PeekTask task;
@@ -497,20 +477,20 @@ final class WagonTransporter
 
         @Override
         public void run( Wagon wagon )
-            throws WagonException
+                throws WagonException
         {
             String src = task.getLocation().toString();
             if ( !wagon.resourceExists( src ) )
             {
                 throw new ResourceDoesNotExistException( "Could not find " + src + " in "
-                    + wagon.getRepository().getUrl() );
+                        + wagon.getRepository().getUrl() );
             }
         }
 
     }
 
-    private class GetTaskRunner
-        implements TaskRunner
+    private static class GetTaskRunner
+            implements TaskRunner
     {
 
         private final GetTask task;
@@ -522,10 +502,10 @@ final class WagonTransporter
 
         @Override
         public void run( Wagon wagon )
-            throws IOException, WagonException
+                throws IOException, WagonException
         {
-            String src = task.getLocation().toString();
-            File file = task.getDataFile();
+            final String src = task.getLocation().toString();
+            final File file = task.getDataFile();
             if ( file == null && wagon instanceof StreamingWagon )
             {
                 try ( OutputStream dst = task.newOutputStream() )
@@ -535,9 +515,11 @@ final class WagonTransporter
             }
             else
             {
-                File dst = ( file != null ) ? file : newTempFile();
-                try
+                // if file == null -> $TMP used, otherwise we place tmp file next to file
+                try ( FileUtils.TempFile tempFile = file == null ? FileUtils.newTempFile()
+                        : FileUtils.newTempFile( file.toPath() ) )
                 {
+                    File dst = tempFile.getPath().toFile();
                     wagon.get( src, dst );
                     /*
                      * NOTE: Wagon (1.0-beta-6) doesn't create the destination file when transferring a 0-byte
@@ -548,35 +530,25 @@ final class WagonTransporter
                     {
                         throw new IOException( String.format( "Failure creating file '%s'.", dst.getAbsolutePath() ) );
                     }
-                    if ( file == null )
+
+                    if ( file != null )
                     {
-                        readTempFile( dst );
+                        Files.move( dst.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE );
                     }
-                }
-                finally
-                {
-                    if ( file == null )
+                    else
                     {
-                        delTempFile( dst );
+                        try ( OutputStream outputStream = task.newOutputStream() )
+                        {
+                            Files.copy( dst.toPath(), outputStream );
+                        }
                     }
                 }
             }
         }
-
-        private void readTempFile( File dst )
-            throws IOException
-        {
-            try ( FileInputStream in = new FileInputStream( dst );
-                    OutputStream out = task.newOutputStream() )
-            {
-                copy( out, in );
-            }
-        }
-
     }
 
-    private class PutTaskRunner
-        implements TaskRunner
+    private static class PutTaskRunner
+            implements TaskRunner
     {
 
         private final PutTask task;
@@ -588,10 +560,10 @@ final class WagonTransporter
 
         @Override
         public void run( Wagon wagon )
-            throws WagonException, IOException
+                throws WagonException, IOException
         {
-            String dst = task.getLocation().toString();
-            File file = task.getDataFile();
+            final String dst = task.getLocation().toString();
+            final File file = task.getDataFile();
             if ( file == null && wagon instanceof StreamingWagon )
             {
                 try ( InputStream src = task.newInputStream() )
@@ -600,42 +572,21 @@ final class WagonTransporter
                     ( (StreamingWagon) wagon ).putFromStream( src, dst, task.getDataLength(), -1 );
                 }
             }
-            else
+            else if ( file == null )
             {
-                File src = ( file != null ) ? file : createTempFile();
-                try
-                {
-                    wagon.put( src, dst );
-                }
-                finally
+                try ( FileUtils.TempFile tempFile = FileUtils.newTempFile() )
                 {
-                    if ( file == null )
+                    try ( InputStream inputStream = task.newInputStream() )
                     {
-                        delTempFile( src );
+                        Files.copy( inputStream, tempFile.getPath(), StandardCopyOption.REPLACE_EXISTING );
                     }
+                    wagon.put( tempFile.getPath().toFile(), dst );
                 }
             }
-        }
-
-        private File createTempFile()
-            throws IOException
-        {
-            File tmp = newTempFile();
-
-            try ( InputStream in = task.newInputStream();
-                    OutputStream out = new FileOutputStream( tmp ) )
+            else
             {
-                copy( out, in );
+                wagon.put( file, dst );
             }
-            catch ( IOException e )
-            {
-                delTempFile( tmp );
-                throw e;
-            }
-
-            return tmp;
         }
-
     }
-
 }
diff --git a/maven-resolver-transport-wagon/src/main/java/org/eclipse/aether/transport/wagon/WagonTransporterFactory.java b/maven-resolver-transport-wagon/src/main/java/org/eclipse/aether/transport/wagon/WagonTransporterFactory.java
index 9ef3ecd8..c98fef3f 100644
--- a/maven-resolver-transport-wagon/src/main/java/org/eclipse/aether/transport/wagon/WagonTransporterFactory.java
+++ b/maven-resolver-transport-wagon/src/main/java/org/eclipse/aether/transport/wagon/WagonTransporterFactory.java
@@ -65,6 +65,7 @@ public final class WagonTransporterFactory
         setWagonConfigurator( wagonConfigurator );
     }
 
+    @Override
     public void initService( ServiceLocator locator )
     {
         setWagonProvider( locator.getService( WagonProvider.class ) );
@@ -95,6 +96,7 @@ public final class WagonTransporterFactory
         return this;
     }
 
+    @Override
     public float getPriority()
     {
         return priority;
@@ -112,6 +114,7 @@ public final class WagonTransporterFactory
         return this;
     }
 
+    @Override
     public Transporter newInstance( RepositorySystemSession session, RemoteRepository repository )
         throws NoTransporterException
     {
diff --git a/maven-resolver-transport-wagon/src/test/java/org/eclipse/aether/transport/wagon/MemWagon.java b/maven-resolver-transport-wagon/src/test/java/org/eclipse/aether/transport/wagon/MemWagon.java
index bc62d376..e6e46cea 100644
--- a/maven-resolver-transport-wagon/src/test/java/org/eclipse/aether/transport/wagon/MemWagon.java
+++ b/maven-resolver-transport-wagon/src/test/java/org/eclipse/aether/transport/wagon/MemWagon.java
@@ -97,12 +97,14 @@ public class MemWagon
         return data != null;
     }
 
+    @Override
     public void get( String resourceName, File destination )
         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
     {
         getIfNewer( resourceName, destination, 0 );
     }
 
+    @Override
     public boolean getIfNewer( String resourceName, File destination, long timestamp )
         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
     {
@@ -151,6 +153,7 @@ public class MemWagon
         inputData.setInputStream( new ByteArrayInputStream( bytes ) );
     }
 
+    @Override
     public void put( File source, String resourceName )
         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
     {
diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/FileUtils.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/FileUtils.java
index b20fef92..f82e4e1f 100644
--- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/FileUtils.java
+++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/FileUtils.java
@@ -19,6 +19,7 @@ package org.eclipse.aether.util;
  * under the License.
  */
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -38,6 +39,62 @@ public final class FileUtils
         // hide constructor
     }
 
+    /**
+     * A temporary file, that is removed when closed.
+     */
+    public interface TempFile extends Closeable
+    {
+        Path getPath();
+    }
+
+    /**
+     * Creates a {@link TempFile}. It will be in the default temporary-file directory. Returned instance should be
+     * handled in try-with-resource construct and created temp file is removed on close, if exists.
+     */
+    public static TempFile newTempFile() throws IOException
+    {
+        Path tempFile = Files.createTempFile( "resolver", "tmp" );
+        return new TempFile()
+        {
+            @Override
+            public Path getPath()
+            {
+                return tempFile;
+            }
+
+            @Override
+            public void close() throws IOException
+            {
+                Files.deleteIfExists( tempFile );
+            }
+        };
+    }
+
+    /**
+     * Creates a {@link TempFile} for given file. It will be in same directory where given file is, and will reuse its
+     * name for generated name. Returned instance should be handled in try-with-resource construct and created temp
+     * file is removed on close, if exists.
+     */
+    public static TempFile newTempFile( Path file ) throws IOException
+    {
+        requireNonNull( file.getParent(), "file must have parent" );
+        Path tempFile = Files.createTempFile( file.getParent(), file.getFileName().toString(), "tmp" );
+        return new TempFile()
+        {
+            @Override
+            public Path getPath()
+            {
+                return tempFile;
+            }
+
+            @Override
+            public void close() throws IOException
+            {
+                Files.deleteIfExists( tempFile );
+            }
+        };
+    }
+
     /**
      * A file writer, that accepts a {@link Path} to write some content to.
      */
@@ -50,8 +107,8 @@ public final class FileUtils
     /**
      * Writes file without backup.
      *
-     * @param target   that is the target file (must be file, the path must have parent).
-     * @param writer   the writer that will accept a {@link Path} to write content to.
+     * @param target that is the target file (must be file, the path must have parent).
+     * @param writer the writer that will accept a {@link Path} to write content to.
      * @throws IOException if at any step IO problem occurs.
      */
     public static void writeFile( Path target, FileWriter writer ) throws IOException
@@ -62,8 +119,8 @@ public final class FileUtils
     /**
      * Writes file with backup copy (appends ".bak" extension).
      *
-     * @param target   that is the target file (must be file, the path must have parent).
-     * @param writer   the writer that will accept a {@link Path} to write content to.
+     * @param target that is the target file (must be file, the path must have parent).
+     * @param writer the writer that will accept a {@link Path} to write content to.
      * @throws IOException if at any step IO problem occurs.
      */
     public static void writeFileWithBackup( Path target, FileWriter writer ) throws IOException
diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md
index 76b24f51..fe03055a 100644
--- a/src/site/markdown/configuration.md
+++ b/src/site/markdown/configuration.md
@@ -42,8 +42,6 @@ Option | Type | Description | Default Value | Supports Repo ID Suffix
 `aether.connector.perms.dirMode` | String | [Octal numerical notation of permissions](https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation) to set for newly created directories. Only considered by certain Wagon providers. | - | no
 `aether.connector.perms.group` | String | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | no
 `aether.connector.persistedChecksums` | boolean | Flag indicating whether checksums which are retrieved during checksum validation should be persisted in the local filesystem next to the file they provide the checksum for. | `true` | no
-`aether.connector.resumeDownloads` | boolean | Whether to resume partially downloaded files if the download has been interrupted. | `true` | yes
-`aether.connector.resumeThreshold` | long | The size in bytes which a partial download needs to have at least to be resumed. Requires `aether.connector.resumeDownloads` to be `true` to be effective. | `64 * 1024` | yes
 `aether.connector.requestTimeout` | long | Request timeout in milliseconds. | `1800000` | yes
 `aether.connector.smartChecksums` | boolean | Flag indicating that instead of comparing the external checksum fetched from the remote repo with the calculated one, it should try to extract the reference checksum from the actual artifact requests's response headers (several (strategies supported)[included-checksum-strategies.html]). This only works for transport-http transport. | `true` | no
 `aether.connector.userAgent` | String | The user agent that repository connectors should report to servers. |  `"Aether"` | no