1 package org.apache.archiva.metadata.repository.storage.maven2;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
22 import org.apache.archiva.admin.model.beans.ManagedRepository;
23 import org.apache.archiva.admin.model.beans.NetworkProxy;
24 import org.apache.archiva.admin.model.beans.RemoteRepository;
25 import org.apache.archiva.common.utils.VersionUtil;
26 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
27 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
28 import org.apache.archiva.model.ArchivaRepositoryMetadata;
29 import org.apache.archiva.model.SnapshotVersion;
30 import org.apache.archiva.proxy.common.WagonFactory;
31 import org.apache.archiva.proxy.common.WagonFactoryException;
32 import org.apache.archiva.proxy.common.WagonFactoryRequest;
33 import org.apache.archiva.xml.XMLException;
34 import org.apache.commons.io.FileUtils;
35 import org.apache.commons.lang.StringUtils;
36 import org.apache.maven.model.Repository;
37 import org.apache.maven.model.building.FileModelSource;
38 import org.apache.maven.model.building.ModelSource;
39 import org.apache.maven.model.resolution.InvalidRepositoryException;
40 import org.apache.maven.model.resolution.ModelResolver;
41 import org.apache.maven.model.resolution.UnresolvableModelException;
42 import org.apache.maven.wagon.ConnectionException;
43 import org.apache.maven.wagon.ResourceDoesNotExistException;
44 import org.apache.maven.wagon.TransferFailedException;
45 import org.apache.maven.wagon.Wagon;
46 import org.apache.maven.wagon.authentication.AuthenticationException;
47 import org.apache.maven.wagon.authentication.AuthenticationInfo;
48 import org.apache.maven.wagon.authorization.AuthorizationException;
49 import org.apache.maven.wagon.proxy.ProxyInfo;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 import java.io.IOException;
55 import java.nio.file.Files;
56 import java.util.List;
59 public class RepositoryModelResolver
60 implements ModelResolver
64 private RepositoryPathTranslator pathTranslator;
66 private WagonFactory wagonFactory;
68 private List<RemoteRepository> remoteRepositories;
70 private ManagedRepository targetRepository;
72 private static final Logger log = LoggerFactory.getLogger( RepositoryModelResolver.class );
74 private static final String METADATA_FILENAME = "maven-metadata.xml";
76 // key/value: remote repo ID/network proxy
77 Map<String, NetworkProxy> networkProxyMap;
79 private ManagedRepository managedRepository;
81 public RepositoryModelResolver( File basedir, RepositoryPathTranslator pathTranslator )
83 this.basedir = basedir;
85 this.pathTranslator = pathTranslator;
88 public RepositoryModelResolver( ManagedRepository managedRepository, RepositoryPathTranslator pathTranslator,
89 WagonFactory wagonFactory, List<RemoteRepository> remoteRepositories,
90 Map<String, NetworkProxy> networkProxiesMap, ManagedRepository targetRepository )
92 this( new File( managedRepository.getLocation() ), pathTranslator );
94 this.managedRepository = managedRepository;
96 this.wagonFactory = wagonFactory;
98 this.remoteRepositories = remoteRepositories;
100 this.networkProxyMap = networkProxiesMap;
102 this.targetRepository = targetRepository;
106 public ModelSource resolveModel( String groupId, String artifactId, String version )
107 throws UnresolvableModelException
109 String filename = artifactId + "-" + version + ".pom";
110 // TODO: we need to convert 1.0-20091120.112233-1 type paths to baseVersion for the below call - add a test
112 File model = pathTranslator.toFile( basedir, groupId, artifactId, version, filename );
114 if ( !model.exists() )
119 // is a SNAPSHOT ? so we can try to find locally before asking remote repositories.
120 if ( StringUtils.contains( version, VersionUtil.SNAPSHOT ) )
122 File localSnapshotModel = findTimeStampedSnapshotPom( groupId, artifactId, version, model.getParent() );
123 if ( localSnapshotModel != null )
125 return new FileModelSource( localSnapshotModel );
130 for ( RemoteRepository remoteRepository : remoteRepositories )
134 boolean success = getModelFromProxy( remoteRepository, groupId, artifactId, version, filename );
135 if ( success && model.exists() )
137 log.info( "Model '{}' successfully retrieved from remote repository '{}'",
138 model.getAbsolutePath(), remoteRepository.getId() );
142 catch ( ResourceDoesNotExistException e )
145 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
146 model.getAbsolutePath(), remoteRepository.getId(), e.getMessage() );
148 catch ( Exception e )
151 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
152 model.getAbsolutePath(), remoteRepository.getId(), e.getMessage() );
159 return new FileModelSource( model );
162 protected File findTimeStampedSnapshotPom( String groupId, String artifactId, String version,
163 String parentDirectory )
166 // reading metadata if there
167 File mavenMetadata = new File( parentDirectory, METADATA_FILENAME );
168 if ( mavenMetadata.exists() )
172 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( mavenMetadata );
173 SnapshotVersion snapshotVersion = archivaRepositoryMetadata.getSnapshotVersion();
174 if ( snapshotVersion != null )
176 String lastVersion = snapshotVersion.getTimestamp();
177 int buildNumber = snapshotVersion.getBuildNumber();
178 String snapshotPath =
179 StringUtils.replaceChars( groupId, '.', '/' ) + '/' + artifactId + '/' + version + '/'
180 + artifactId + '-' + StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) + '-'
181 + lastVersion + '-' + buildNumber + ".pom";
183 log.debug( "use snapshot path {} for maven coordinate {}:{}:{}", snapshotPath, groupId, artifactId,
186 File model = new File( basedir, snapshotPath );
187 //model = pathTranslator.toFile( basedir, groupId, artifactId, lastVersion, filename );
188 if ( model.exists() )
194 catch ( XMLException e )
196 log.warn( "fail to read {}, {}", mavenMetadata.getAbsolutePath(), e.getCause() );
204 public void addRepository( Repository repository )
205 throws InvalidRepositoryException
207 // we just ignore repositories outside of the current one for now
208 // TODO: it'd be nice to look them up from Archiva's set, but we want to do that by URL / mapping, not just the
209 // ID since they will rarely match
213 public ModelResolver newCopy()
215 return new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories,
216 networkProxyMap, targetRepository );
219 // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository
220 // because it's causing a cyclic dependency
221 private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId,
222 String version, String filename )
223 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException,
224 XMLException, IOException
226 boolean success = false;
229 File tmpResource = null;
230 String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
231 File resource = new File( targetRepository.getLocation(), artifactPath );
233 File workingDirectory = createWorkingDirectory( targetRepository.getLocation() );
239 String protocol = getProtocol( remoteRepository.getUrl() );
240 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() );
242 wagon = wagonFactory.getWagon(
243 new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy(
249 throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
252 boolean connected = connectToRepository( wagon, remoteRepository );
255 tmpResource = new File( workingDirectory, filename );
257 if ( VersionUtil.isSnapshot( version ) )
259 // get the metadata first!
260 File tmpMetadataResource = new File( workingDirectory, METADATA_FILENAME );
262 String metadataPath =
263 StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
265 wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource );
267 log.debug( "Successfully downloaded metadata." );
269 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource );
271 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
272 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
273 String timestampVersion = version;
274 if ( snapshotVersion != null )
276 timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
277 - 8 ); // remove SNAPSHOT from end
278 timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
279 + snapshotVersion.getBuildNumber();
281 filename = artifactId + "-" + timestampVersion + ".pom";
283 artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
285 log.debug( "New artifactPath :{}", artifactPath );
289 log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
291 wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource );
293 log.debug( "Downloaded successfully." );
295 tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
297 tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
309 catch ( ConnectionException e )
311 log.warn( "Unable to disconnect wagon.", e );
316 if ( resource != null )
318 synchronized ( resource.getAbsolutePath().intern() )
320 File directory = resource.getParentFile();
321 moveFileIfExists( tmpMd5, directory );
322 moveFileIfExists( tmpSha1, directory );
323 moveFileIfExists( tmpResource, directory );
330 FileUtils.deleteQuietly( workingDirectory );
333 // do we still need to execute the consumers?
339 * Using wagon, connect to the remote repository.
341 * @param wagon the wagon instance to establish the connection on.
342 * @return true if the connection was successful. false if not connected.
344 private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
348 final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
349 ProxyInfo networkProxy = null;
350 if ( proxyConnector != null )
352 networkProxy = new ProxyInfo();
353 networkProxy.setType( proxyConnector.getProtocol() );
354 networkProxy.setHost( proxyConnector.getHost() );
355 networkProxy.setPort( proxyConnector.getPort() );
356 networkProxy.setUserName( proxyConnector.getUsername() );
357 networkProxy.setPassword( proxyConnector.getPassword() );
359 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
360 + " to connect to remote repository " + remoteRepository.getUrl();
361 if ( networkProxy.getNonProxyHosts() != null )
363 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
366 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
368 msg += "; as user: " + networkProxy.getUserName();
374 AuthenticationInfo authInfo = null;
375 String username = remoteRepository.getUserName();
376 String password = remoteRepository.getPassword();
378 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
380 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getUrl() );
381 authInfo = new AuthenticationInfo();
382 authInfo.setUserName( username );
383 authInfo.setPassword( password );
386 // Convert seconds to milliseconds
387 int timeoutInMilliseconds = remoteRepository.getTimeout() * 1000;
388 // FIXME olamy having 2 config values
390 wagon.setReadTimeout( timeoutInMilliseconds );
391 wagon.setTimeout( timeoutInMilliseconds );
395 org.apache.maven.wagon.repository.Repository wagonRepository =
396 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getUrl() );
397 if ( networkProxy != null )
399 wagon.connect( wagonRepository, authInfo, networkProxy );
403 wagon.connect( wagonRepository, authInfo );
407 catch ( ConnectionException | AuthenticationException e )
409 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
418 * @param wagon The wagon instance that should be connected.
419 * @param remoteRepository The repository from where the checksum file should be retrieved
420 * @param remotePath The remote path of the artifact (without extension)
421 * @param resource The local artifact (without extension)
422 * @param workingDir The working directory where the downloaded file should be placed to
423 * @param ext The extension of th checksum file
424 * @return The file where the data has been downloaded to.
425 * @throws AuthorizationException
426 * @throws TransferFailedException
427 * @throws ResourceDoesNotExistException
429 private File transferChecksum( final Wagon wagon, final RemoteRepository remoteRepository,
430 final String remotePath, final File resource,
431 final File workingDir, final String ext )
432 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
434 File destFile = new File( workingDir, resource.getName() + ext );
435 String remoteChecksumPath = remotePath + ext;
437 log.info( "Retrieving {} from {}", remoteChecksumPath, remoteRepository.getName() );
439 wagon.get( addParameters( remoteChecksumPath, remoteRepository ), destFile );
441 log.debug( "Downloaded successfully." );
446 private String getProtocol( String url )
448 String protocol = StringUtils.substringBefore( url, ":" );
453 private File createWorkingDirectory( String targetRepository )
456 return Files.createTempDirectory( "temp" ).toFile();
459 private void moveFileIfExists( File fileToMove, File directory )
461 if ( fileToMove != null && fileToMove.exists() )
463 File newLocation = new File( directory, fileToMove.getName() );
464 if ( newLocation.exists() && !newLocation.delete() )
466 throw new RuntimeException(
467 "Unable to overwrite existing target file: " + newLocation.getAbsolutePath() );
470 newLocation.getParentFile().mkdirs();
471 if ( !fileToMove.renameTo( newLocation ) )
473 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
477 FileUtils.copyFile( fileToMove, newLocation );
479 catch ( IOException e )
481 if ( newLocation.exists() )
483 log.error( "Tried to copy file {} to {} but file with this name already exists.",
484 fileToMove.getName(), newLocation.getAbsolutePath() );
488 throw new RuntimeException(
489 "Cannot copy tmp file " + fileToMove.getAbsolutePath() + " to its final location", e );
494 FileUtils.deleteQuietly( fileToMove );
500 protected String addParameters( String path, RemoteRepository remoteRepository )
502 if ( remoteRepository.getExtraParameters().isEmpty() )
507 boolean question = false;
509 StringBuilder res = new StringBuilder( path == null ? "" : path );
511 for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
515 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
519 return res.toString();