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 5f25e7038..4c6ab9f49 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.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 @@ protected void runTask() class GetTaskRunner extends TaskRunner - implements PartialFile.RemoteAccessChecker, ChecksumValidator.ChecksumFetcher + implements ChecksumValidator.ChecksumFetcher { private final File file; @@ -449,13 +432,6 @@ class GetTaskRunner 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 @@ protected void runTask() { 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 @@ protected void runTask() 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 14ae450a0..988cf54c4 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.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.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 @@ boolean fetchChecksum( URI remote, File local ) private final Collection checksumAlgorithmFactories; - private final Collection tempFiles; - private final FileProcessor fileProcessor; private final ChecksumFetcher checksumFetcher; @@ -73,7 +70,7 @@ boolean fetchChecksum( URI remote, File local ) private final Collection checksumLocations; - private final Map checksumFiles; + private final Map checksumExpectedValues; ChecksumValidator( File dataFile, Collection checksumAlgorithmFactories, @@ -85,13 +82,12 @@ boolean fetchChecksum( URI remote, File local ) { 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 @@ private boolean validateChecksums( Map actualChecksums, ChecksumKind 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 @@ private boolean validateExternalChecksums( Map actualChecksums ) ); 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 @@ private boolean validateExternalChecksums( Map actualChecksums ) 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 @@ private File getChecksumFile( ChecksumAlgorithmFactory factory ) 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 @@ public boolean handle( ChecksumFailureException exception ) public void commit() { - for ( Map.Entry entry : checksumFiles.entrySet() ) + for ( Map.Entry 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 c7a74f7ca..000000000 --- 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 530e5e776..7534c2550 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 void testRetry_RemoveTempFiles() 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 void testCommit_SaveChecksumFiles() 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 void testCommit_SaveChecksumFiles() } @Test - public void testClose_RemoveTempFiles() + public void testNoCommit_NoTempFiles() throws Exception { ChecksumValidator validator = newValidator( SHA1 ); @@ -473,11 +465,6 @@ public void testClose_RemoveTempFiles() 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 bb6530e6d..000000000 --- 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 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 4bb276c56..c8205f200 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 @@ protected abstract void implGet( GetTask task ) 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 @@ protected abstract void implPut( PutTask task ) 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 @@ protected void utilPut( PutTask task, OutputStream os, boolean close ) { 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 91b15aaf6..32cb73888 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.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.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.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.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 @@ private void execute( HttpUriRequest request, EntityGetter getter ) { 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 @@ private void prepare( HttpUriRequest request, SharingHttpContext context ) 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 @@ private void prepare( HttpUriRequest request, SharingHttpContext context ) } } - private boolean isWebDav( HttpResponse response ) + private boolean isWebDav( CloseableHttpResponse response ) { return response.containsHeader( HttpHeaders.DAV ); } @@ -416,10 +421,9 @@ private void mkdirs( URI uri, SharingHttpContext context ) 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 @@ else if ( status == HttpStatus.SC_CONFLICT ) } 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 @@ private T resume( T request, GetTask task ) } @SuppressWarnings( "checkstyle:magicnumber" ) - private void handleStatus( HttpResponse response ) + private void handleStatus( CloseableHttpResponse response ) throws HttpResponseException { int status = response.getStatusLine().getStatusCode(); @@ -568,7 +571,7 @@ private class EntityGetter this.task = task; } - public void handle( HttpResponse response ) + public void handle( CloseableHttpResponse response ) throws IOException, TransferCancelledException { HttpEntity entity = response.getEntity(); @@ -592,16 +595,47 @@ public void handle( HttpResponse response ) 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 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 4a985633c..06126ab19 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 @@ */ 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.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 @@ * 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 @@ public String getNtlmHost() } private Wagon lookupWagon() - throws Exception + throws Exception { return wagonProvider.lookup( wagonHint ); } @@ -282,7 +282,7 @@ private void releaseWagon( Wagon wagon ) } private void connectWagon( Wagon wagon ) - throws WagonException + throws WagonException { if ( !headers.isEmpty() ) { @@ -302,16 +302,16 @@ private void connectWagon( Wagon wagon ) } 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 @@ private void disconnectWagon( Wagon wagon ) } private Wagon pollWagon() - throws Exception + throws Exception { Wagon wagon = wagons.poll(); @@ -379,6 +379,7 @@ else if ( wagon.getRepository() == null ) return wagon; } + @Override public int classify( Throwable error ) { if ( error instanceof ResourceDoesNotExistException ) @@ -388,26 +389,29 @@ public int classify( Throwable error ) 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 @@ private void execute( TransportTask task, TaskRunner runner ) } } - 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 @@ private interface TaskRunner { 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 @@ private static class PeekTaskRunner @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 @@ private class GetTaskRunner @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 @@ public void run( Wagon wagon ) } 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 @@ public void run( Wagon wagon ) { 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 @@ private class PutTaskRunner @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 @@ public void run( Wagon wagon ) ( (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 9ef3ecd83..c98fef3ff 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 WagonTransporterFactory() setWagonConfigurator( wagonConfigurator ); } + @Override public void initService( ServiceLocator locator ) { setWagonProvider( locator.getService( WagonProvider.class ) ); @@ -95,6 +96,7 @@ public WagonTransporterFactory setWagonConfigurator( WagonConfigurator wagonConf return this; } + @Override public float getPriority() { return priority; @@ -112,6 +114,7 @@ public WagonTransporterFactory setPriority( float priority ) 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 bc62d3766..e6e46ceac 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 boolean resourceExists( String resourceName ) 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 @@ protected void fillInputData( InputData inputData ) 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 b20fef928..f82e4e1f7 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 @@ * 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 @@ private 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 interface FileWriter /** * 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 static void writeFile( Path target, FileWriter writer ) throws IOExceptio /** * 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 76b24f511..fe03055a0 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