diff --git a/tools/maven-bundle-plugin/pom.xml b/tools/maven-bundle-plugin/pom.xml index a9eaa54f4a..f4e5b2dbdf 100644 --- a/tools/maven-bundle-plugin/pom.xml +++ b/tools/maven-bundle-plugin/pom.xml @@ -220,6 +220,35 @@ 3.8.1 provided + + org.apache.maven + maven-settings + 3.8.1 + provided + + + org.apache.maven + maven-settings-builder + 3.8.1 + provided + + + org.apache.maven.wagon + wagon-provider-api + 3.5.3 + provided + + + + org.apache.maven.wagon + wagon-file + 3.5.3 + + + org.apache.maven.wagon + wagon-http + 3.5.3 + org.apache.maven.reporting maven-reporting-api diff --git a/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrDeploy.java b/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrDeploy.java index 90a79f892a..d1c4f2bc22 100644 --- a/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrDeploy.java +++ b/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrDeploy.java @@ -29,7 +29,6 @@ import java.util.regex.Pattern; import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.manager.WagonManager; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -40,6 +39,8 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.codehaus.plexus.PlexusContainer; /** @@ -123,10 +124,16 @@ public final class ObrDeploy extends AbstractMojo private Settings settings; /** - * The Wagon manager. + * The Plexus container, used to look up the Wagon for the repository protocol. */ @Component - private WagonManager m_wagonManager; + private PlexusContainer m_container; + + /** + * Decrypts the passwords of the server and proxy settings. + */ + @Component + private SettingsDecrypter m_settingsDecrypter; /** * Attached source artifact @@ -182,7 +189,7 @@ else if ( "javadoc".equals( artifact.getClassifier() ) ) Log log = getLog(); ObrUpdate update; - RemoteFileManager remoteFile = new RemoteFileManager( m_wagonManager, settings, log ); + RemoteFileManager remoteFile = new RemoteFileManager( m_container, m_settingsDecrypter, settings, log ); openRepositoryConnection( remoteFile ); // ======== LOCK REMOTE OBR ======== diff --git a/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrDeployFile.java b/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrDeployFile.java index 2230d0efb5..6a7fce6fa2 100644 --- a/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrDeployFile.java +++ b/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrDeployFile.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.List; -import org.apache.maven.artifact.manager.WagonManager; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; @@ -34,6 +33,8 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.codehaus.plexus.PlexusContainer; /** @@ -99,10 +100,16 @@ public final class ObrDeployFile extends AbstractFileMojo private Settings settings; /** - * The Wagon manager. + * The Plexus container, used to look up the Wagon for the repository protocol. */ @Component - private WagonManager m_wagonManager; + private PlexusContainer m_container; + + /** + * Decrypts the passwords of the server and proxy settings. + */ + @Component + private SettingsDecrypter m_settingsDecrypter; public void execute() throws MojoExecutionException @@ -135,7 +142,7 @@ else if ( "NONE".equalsIgnoreCase( remoteOBR ) || "false".equalsIgnoreCase( remo Log log = getLog(); ObrUpdate update; - RemoteFileManager remoteFile = new RemoteFileManager( m_wagonManager, settings, log ); + RemoteFileManager remoteFile = new RemoteFileManager( m_container, m_settingsDecrypter, settings, log ); remoteFile.connect( repositoryId, url ); // ======== LOCK REMOTE OBR ======== diff --git a/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrRemoteClean.java b/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrRemoteClean.java index 64061f9ee2..c6fbe218a9 100644 --- a/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrRemoteClean.java +++ b/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/ObrRemoteClean.java @@ -48,7 +48,6 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import org.apache.maven.artifact.manager.WagonManager; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -59,6 +58,8 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.codehaus.plexus.PlexusContainer; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -136,10 +137,16 @@ public final class ObrRemoteClean extends AbstractMojo private Settings settings; /** - * The Wagon manager. + * The Plexus container, used to look up the Wagon for the repository protocol. */ @Component - private WagonManager m_wagonManager; + private PlexusContainer m_container; + + /** + * Decrypts the passwords of the server and proxy settings. + */ + @Component + private SettingsDecrypter m_settingsDecrypter; public void execute() throws MojoExecutionException @@ -170,7 +177,7 @@ else if ( "NONE".equalsIgnoreCase( remoteOBR ) || "false".equalsIgnoreCase( remo Log log = getLog(); - RemoteFileManager remoteFile = new RemoteFileManager( m_wagonManager, settings, log ); + RemoteFileManager remoteFile = new RemoteFileManager( m_container, m_settingsDecrypter, settings, log ); openRepositoryConnection( remoteFile ); if ( null == prefixUrl ) { diff --git a/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/RemoteFileManager.java b/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/RemoteFileManager.java index c6958b297d..9b6e277519 100644 --- a/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/RemoteFileManager.java +++ b/tools/maven-bundle-plugin/src/main/java/org/apache/felix/obrplugin/RemoteFileManager.java @@ -24,22 +24,29 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Writer; +import java.util.List; +import java.util.Locale; -import org.apache.maven.artifact.manager.WagonManager; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.settings.Proxy; +import org.apache.maven.settings.Server; import org.apache.maven.settings.Settings; +import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest; +import org.apache.maven.settings.crypto.SettingsDecrypter; import org.apache.maven.wagon.ConnectionException; import org.apache.maven.wagon.ResourceDoesNotExistException; import org.apache.maven.wagon.TransferFailedException; import org.apache.maven.wagon.UnsupportedProtocolException; import org.apache.maven.wagon.Wagon; -import org.apache.maven.wagon.WagonException; import org.apache.maven.wagon.authentication.AuthenticationException; +import org.apache.maven.wagon.authentication.AuthenticationInfo; import org.apache.maven.wagon.authorization.AuthorizationException; import org.apache.maven.wagon.proxy.ProxyInfo; import org.apache.maven.wagon.repository.Repository; +import org.codehaus.plexus.PlexusContainer; +import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; /** @@ -54,9 +61,14 @@ public class RemoteFileManager private Wagon m_wagon; /** - * the wagon manager. + * the container used to look up the wagon for a protocol. */ - private WagonManager m_wagonManager; + private PlexusContainer m_container; + + /** + * decrypts the passwords found in the settings, may be null. + */ + private SettingsDecrypter m_settingsDecrypter; /** * the project settings. @@ -68,16 +80,18 @@ public class RemoteFileManager */ private Log m_log; - /** * initialize main information. - * @param wm WagonManager provides by maven + * @param container container provided by maven, used to look up the Wagon component of a protocol + * @param settingsDecrypter decrypts server and proxy passwords, may be null to use them as they are * @param settings settings of the current project provides by maven * @param log logger */ - public RemoteFileManager( WagonManager wm, Settings settings, Log log ) + public RemoteFileManager( PlexusContainer container, SettingsDecrypter settingsDecrypter, Settings settings, + Log log ) { - m_wagonManager = wm; + m_container = container; + m_settingsDecrypter = settingsDecrypter; m_settings = settings; m_log = log; m_wagon = null; @@ -100,6 +114,59 @@ public void disconnect() { m_log.error( "Error disconnecting Wagon", e ); } + finally + { + releaseWagon(); + } + } + + + /** + * Wagons are looked up per use, so give the instance back to the container. + */ + private void releaseWagon() + { + if ( m_wagon != null ) + { + try + { + m_container.release( m_wagon ); + } + catch ( ComponentLifecycleException e ) + { + m_log.debug( "Unable to release Wagon", e ); + } + finally + { + m_wagon = null; + } + } + } + + + /** + * Look up the Wagon implementing the given protocol. The wagon providers for file and http(s) are shipped with + * this plugin, other protocols come from the Wagon providers of the build extensions. + * @param protocol repository protocol, e.g. file or https + * @return a new wagon instance + * @throws UnsupportedProtocolException if no Wagon is available for the protocol + */ + Wagon lookupWagon( String protocol ) throws UnsupportedProtocolException + { + if ( protocol == null ) + { + throw new UnsupportedProtocolException( "Unspecified protocol" ); + } + + try + { + return m_container.lookup( Wagon.class, protocol.toLowerCase( Locale.ENGLISH ) ); + } + catch ( ComponentLookupException e ) + { + throw new UnsupportedProtocolException( "Cannot find wagon which supports the requested protocol: " + + protocol, e ); + } } @@ -115,35 +182,34 @@ public void connect( String id, String url ) throws MojoExecutionException try { - m_wagon = m_wagonManager.getWagon( repository ); + m_wagon = lookupWagon( repository.getProtocol() ); } catch ( UnsupportedProtocolException e ) { throw new MojoExecutionException( "Unsupported protocol: '" + repository.getProtocol() + "'", e ); } - catch ( WagonException e ) - { - throw new MojoExecutionException( "Unable to configure Wagon: '" + repository.getProtocol() + "'", e ); - } try { - ProxyInfo proxyInfo = getProxyInfo( m_settings ); + AuthenticationInfo authenticationInfo = getAuthenticationInfo( m_settings, m_settingsDecrypter, id ); + ProxyInfo proxyInfo = getProxyInfo( m_settings, m_settingsDecrypter ); if ( proxyInfo != null ) { - m_wagon.connect( repository, m_wagonManager.getAuthenticationInfo( id ), proxyInfo ); + m_wagon.connect( repository, authenticationInfo, proxyInfo ); } else { - m_wagon.connect( repository, m_wagonManager.getAuthenticationInfo( id ) ); + m_wagon.connect( repository, authenticationInfo ); } } catch ( ConnectionException e ) { + releaseWagon(); throw new MojoExecutionException( "Connection failed", e ); } catch ( AuthenticationException e ) { + releaseWagon(); throw new MojoExecutionException( "Authentication failed", e ); } } @@ -232,15 +298,32 @@ public void put( File file, String url ) throws MojoExecutionException /** * Convenience method to map a Proxy object from the user system settings to a ProxyInfo object. + * The proxy password is used as it is found in the settings. * @param settings project settings given by maven * @return a proxyInfo object instancied or null if no active proxy is define in the settings.xml */ public static ProxyInfo getProxyInfo( Settings settings ) + { + return getProxyInfo( settings, null ); + } + + + /** + * Map the active proxy of the user settings to a ProxyInfo object. + * @param settings project settings given by maven + * @param decrypter used to decrypt the proxy password, may be null + * @return a proxyInfo object instancied or null if no active proxy is define in the settings.xml + */ + static ProxyInfo getProxyInfo( Settings settings, SettingsDecrypter decrypter ) { ProxyInfo proxyInfo = null; if ( settings != null && settings.getActiveProxy() != null ) { Proxy settingsProxy = settings.getActiveProxy(); + if ( decrypter != null ) + { + settingsProxy = decrypter.decrypt( new DefaultSettingsDecryptionRequest( settingsProxy ) ).getProxy(); + } proxyInfo = new ProxyInfo(); proxyInfo.setHost( settingsProxy.getHost() ); @@ -255,6 +338,45 @@ public static ProxyInfo getProxyInfo( Settings settings ) } + /** + * Find the credentials for a repository id in the user settings, like Maven does for its own repositories. + * @param settings project settings given by maven + * @param decrypter used to decrypt the server passwords, may be null + * @param id repository id, matched case insensitively against the server ids + * @return the credentials of the matching server, or an empty object if there is none + */ + static AuthenticationInfo getAuthenticationInfo( Settings settings, SettingsDecrypter decrypter, String id ) + { + if ( settings != null && id != null ) + { + List servers = settings.getServers(); + if ( servers != null ) + { + for ( Server server : servers ) + { + if ( id.equalsIgnoreCase( server.getId() ) ) + { + if ( decrypter != null ) + { + server = decrypter.decrypt( new DefaultSettingsDecryptionRequest( server ) ).getServer(); + } + + AuthenticationInfo authInfo = new AuthenticationInfo(); + authInfo.setUserName( server.getUsername() ); + authInfo.setPassword( server.getPassword() ); + authInfo.setPrivateKey( server.getPrivateKey() ); + authInfo.setPassphrase( server.getPassphrase() ); + return authInfo; + } + } + } + } + + // an empty one, as Wagon expects authentication info to be present + return new AuthenticationInfo(); + } + + public void lockFile( String fileName, boolean ignoreLock ) throws MojoExecutionException { if ( !ignoreLock ) diff --git a/tools/maven-bundle-plugin/src/test/java/org/apache/felix/obrplugin/RemoteFileManagerTest.java b/tools/maven-bundle-plugin/src/test/java/org/apache/felix/obrplugin/RemoteFileManagerTest.java new file mode 100644 index 0000000000..dfabb7b20b --- /dev/null +++ b/tools/maven-bundle-plugin/src/test/java/org/apache/felix/obrplugin/RemoteFileManagerTest.java @@ -0,0 +1,353 @@ +/* + * 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. + */ +package org.apache.felix.obrplugin; + + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.apache.maven.settings.Proxy; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.apache.maven.settings.crypto.SettingsDecryptionRequest; +import org.apache.maven.settings.building.SettingsProblem; +import org.apache.maven.settings.crypto.SettingsDecryptionResult; +import org.apache.maven.wagon.UnsupportedProtocolException; +import org.apache.maven.wagon.Wagon; +import org.apache.maven.wagon.authentication.AuthenticationInfo; +import org.apache.maven.wagon.proxy.ProxyInfo; +import org.apache.maven.wagon.providers.file.FileWagon; +import org.codehaus.plexus.PlexusContainer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + + +public class RemoteFileManagerTest +{ + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private final List lookedUp = new ArrayList(); + + private final AtomicInteger released = new AtomicInteger(); + + private PlexusContainer container; + + /** Prefixes every password, so a test can tell that the decrypter was used. */ + private final SettingsDecrypter decrypter = new SettingsDecrypter() + { + public SettingsDecryptionResult decrypt( SettingsDecryptionRequest request ) + { + Server server = request.getServers().isEmpty() ? null : request.getServers().get( 0 ); + Proxy proxy = request.getProxies().isEmpty() ? null : request.getProxies().get( 0 ); + if ( server != null ) + { + server = server.clone(); + server.setPassword( "decrypted-" + server.getPassword() ); + } + if ( proxy != null ) + { + proxy = proxy.clone(); + proxy.setPassword( "decrypted-" + proxy.getPassword() ); + } + return new Result( server, proxy ); + } + }; + + + private static final class Result implements SettingsDecryptionResult + { + private final Server server; + + private final Proxy proxy; + + Result( Server server, Proxy proxy ) + { + this.server = server; + this.proxy = proxy; + } + + public Server getServer() + { + return server; + } + + public List getServers() + { + return server == null ? new ArrayList() : Collections.singletonList( server ); + } + + public Proxy getProxy() + { + return proxy; + } + + public List getProxies() + { + return proxy == null ? new ArrayList() : Collections.singletonList( proxy ); + } + + public List getProblems() + { + return new ArrayList(); + } + } + + + /** A container that only knows the file wagon, as the plugin realm would for the file protocol. */ + @Before + public void createContainer() + { + container = ( PlexusContainer ) java.lang.reflect.Proxy.newProxyInstance( getClass().getClassLoader(), + new Class[] { PlexusContainer.class }, ( proxy, method, args ) -> { + if ( "lookup".equals( method.getName() ) && args.length == 2 && args[1] instanceof String ) + { + lookedUp.add( ( String ) args[1] ); + if ( "file".equals( args[1] ) ) + { + return new FileWagon(); + } + throw new org.codehaus.plexus.component.repository.exception.ComponentLookupException( + "no such wagon", ( ( Class ) args[0] ).getName(), ( String ) args[1] ); + } + if ( "release".equals( method.getName() ) ) + { + released.incrementAndGet(); + return null; + } + throw new UnsupportedOperationException( method.getName() ); + } ); + } + + + private RemoteFileManager manager( Settings settings ) + { + return new RemoteFileManager( container, decrypter, settings, new SystemStreamLog() ); + } + + + private static Server server( String id, String user, String password ) + { + Server server = new Server(); + server.setId( id ); + server.setUsername( user ); + server.setPassword( password ); + return server; + } + + + @Test + public void authenticationComesFromTheServerWithTheSameId() + { + Settings settings = new Settings(); + settings.addServer( server( "other", "nobody", "secret" ) ); + Server server = server( "obr", "alice", "s3cr3t" ); + server.setPrivateKey( "/home/alice/key" ); + server.setPassphrase( "phrase" ); + settings.addServer( server ); + + AuthenticationInfo info = RemoteFileManager.getAuthenticationInfo( settings, decrypter, "OBR" ); + + assertEquals( "alice", info.getUserName() ); + assertEquals( "decrypted-s3cr3t", info.getPassword() ); + assertEquals( "/home/alice/key", info.getPrivateKey() ); + assertEquals( "phrase", info.getPassphrase() ); + } + + + @Test + public void authenticationWithoutDecrypterUsesPasswordAsIs() + { + Settings settings = new Settings(); + settings.addServer( server( "obr", "alice", "s3cr3t" ) ); + + assertEquals( "s3cr3t", RemoteFileManager.getAuthenticationInfo( settings, null, "obr" ).getPassword() ); + } + + + @Test + public void authenticationIsEmptyWhenNoServerMatches() + { + Settings settings = new Settings(); + settings.addServer( server( "other", "nobody", "secret" ) ); + + AuthenticationInfo info = RemoteFileManager.getAuthenticationInfo( settings, decrypter, "obr" ); + + assertNotNull( info ); + assertNull( info.getUserName() ); + assertNull( info.getPassword() ); + assertNotNull( RemoteFileManager.getAuthenticationInfo( null, decrypter, "obr" ) ); + assertNotNull( RemoteFileManager.getAuthenticationInfo( settings, decrypter, null ) ); + } + + + private static Proxy proxy( String id, boolean active, String host ) + { + Proxy proxy = new Proxy(); + proxy.setId( id ); + proxy.setActive( active ); + proxy.setProtocol( "http" ); + proxy.setHost( host ); + proxy.setPort( 3128 ); + proxy.setNonProxyHosts( "localhost|*.example.org" ); + proxy.setUsername( "pu" ); + proxy.setPassword( "pp" ); + return proxy; + } + + + @Test + public void proxyIsTheFirstActiveProxyWithDecryptedPassword() + { + Settings settings = new Settings(); + settings.addProxy( proxy( "off", false, "inactive.example.com" ) ); + settings.addProxy( proxy( "on", true, "proxy.example.com" ) ); + + ProxyInfo info = RemoteFileManager.getProxyInfo( settings, decrypter ); + + assertEquals( "proxy.example.com", info.getHost() ); + assertEquals( 3128, info.getPort() ); + assertEquals( "http", info.getType() ); + assertEquals( "localhost|*.example.org", info.getNonProxyHosts() ); + assertEquals( "pu", info.getUserName() ); + assertEquals( "decrypted-pp", info.getPassword() ); + + assertEquals( "pp", RemoteFileManager.getProxyInfo( settings ).getPassword() ); + } + + + @Test + public void noProxyWhenNoneIsActive() + { + Settings settings = new Settings(); + assertNull( RemoteFileManager.getProxyInfo( settings, decrypter ) ); + settings.addProxy( proxy( "off", false, "inactive.example.com" ) ); + assertNull( RemoteFileManager.getProxyInfo( settings, decrypter ) ); + assertNull( RemoteFileManager.getProxyInfo( null, decrypter ) ); + } + + + @Test + public void wagonIsLookedUpByLowerCaseProtocol() throws Exception + { + Wagon wagon = manager( new Settings() ).lookupWagon( "FILE" ); + + assertTrue( wagon instanceof FileWagon ); + assertEquals( Collections.singletonList( "file" ), lookedUp ); + } + + + @Test + public void unknownProtocolIsRejected() + { + try + { + manager( new Settings() ).lookupWagon( "gopher" ); + fail( "expected UnsupportedProtocolException" ); + } + catch ( UnsupportedProtocolException e ) + { + assertTrue( e.getMessage(), e.getMessage().contains( "gopher" ) ); + } + + try + { + manager( new Settings() ).lookupWagon( null ); + fail( "expected UnsupportedProtocolException" ); + } + catch ( UnsupportedProtocolException e ) + { + // expected + } + } + + + @Test + public void connectReportsUnsupportedProtocol() + { + try + { + manager( new Settings() ).connect( "obr", "gopher://example.com/obr" ); + fail( "expected MojoExecutionException" ); + } + catch ( MojoExecutionException e ) + { + assertEquals( "Unsupported protocol: 'gopher'", e.getMessage() ); + } + } + + + @Test + public void lockUploadDownloadAndUnlockOnFileRepository() throws Exception + { + File remote = tmp.newFolder( "obr" ); + RemoteFileManager manager = manager( new Settings() ); + manager.connect( "obr", remote.toURI().toString() ); + try + { + // a missing remote file is returned as a non-existent file + File missing = manager.get( "repository.xml", ".xml" ); + assertFalse( missing.exists() ); + + manager.lockFile( "repository.xml", false ); + assertEquals( "LOCKED", new String( Files.readAllBytes( new File( remote, "repository.xml.lock" ).toPath() ), + StandardCharsets.UTF_8 ) ); + assertTrue( manager.isLockedFile( "repository.xml" ) ); + + File local = tmp.newFile( "local.xml" ); + Files.write( local.toPath(), "".getBytes( StandardCharsets.UTF_8 ) ); + manager.put( local, "repository.xml" ); + File downloaded = manager.get( "repository.xml", ".xml" ); + assertEquals( "", new String( Files.readAllBytes( downloaded.toPath() ), StandardCharsets.UTF_8 ) ); + downloaded.delete(); + + manager.unlockFile( "repository.xml" ); + assertFalse( manager.isLockedFile( "repository.xml" ) ); + assertTrue( manager.toString(), manager.toString().endsWith( "/obr" ) || manager.toString().endsWith( "/obr/" ) ); + } + finally + { + manager.disconnect(); + } + + assertEquals( Collections.singletonList( "file" ), lookedUp ); + assertEquals( 1, released.get() ); + + // disconnecting twice does not release the wagon twice + manager.disconnect(); + assertEquals( 1, released.get() ); + } +}