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 com.google.common.io.Files;
23 import org.apache.archiva.admin.model.beans.ManagedRepository;
24 import org.apache.archiva.admin.model.beans.NetworkProxy;
25 import org.apache.archiva.admin.model.beans.RemoteRepository;
26 import org.apache.archiva.common.utils.VersionUtil;
27 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
28 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
29 import org.apache.archiva.model.ArchivaRepositoryMetadata;
30 import org.apache.archiva.model.SnapshotVersion;
31 import org.apache.archiva.proxy.common.WagonFactory;
32 import org.apache.archiva.proxy.common.WagonFactoryException;
33 import org.apache.archiva.proxy.common.WagonFactoryRequest;
34 import org.apache.archiva.xml.XMLException;
35 import org.apache.commons.io.FileUtils;
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.maven.model.Repository;
38 import org.apache.maven.model.building.FileModelSource;
39 import org.apache.maven.model.building.ModelSource;
40 import org.apache.maven.model.resolution.InvalidRepositoryException;
41 import org.apache.maven.model.resolution.ModelResolver;
42 import org.apache.maven.model.resolution.UnresolvableModelException;
43 import org.apache.maven.wagon.ConnectionException;
44 import org.apache.maven.wagon.ResourceDoesNotExistException;
45 import org.apache.maven.wagon.TransferFailedException;
46 import org.apache.maven.wagon.Wagon;
47 import org.apache.maven.wagon.authentication.AuthenticationException;
48 import org.apache.maven.wagon.authentication.AuthenticationInfo;
49 import org.apache.maven.wagon.authorization.AuthorizationException;
50 import org.apache.maven.wagon.proxy.ProxyInfo;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
55 import java.io.IOException;
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;
105 public ModelSource resolveModel( String groupId, String artifactId, String version )
106 throws UnresolvableModelException
108 String filename = artifactId + "-" + version + ".pom";
109 // TODO: we need to convert 1.0-20091120.112233-1 type paths to baseVersion for the below call - add a test
111 File model = pathTranslator.toFile( basedir, groupId, artifactId, version, filename );
113 if ( !model.exists() )
118 // is a SNAPSHOT ? so we can try to find locally before asking remote repositories.
119 if ( StringUtils.contains( version, VersionUtil.SNAPSHOT ) )
121 File localSnapshotModel = findTimeStampedSnapshotPom( groupId, artifactId, version, model.getParent() );
122 if ( localSnapshotModel != null )
124 return new FileModelSource( localSnapshotModel );
129 for ( RemoteRepository remoteRepository : remoteRepositories )
133 boolean success = getModelFromProxy( remoteRepository, groupId, artifactId, version, filename );
134 if ( success && model.exists() )
136 log.info( "Model '{}' successfully retrieved from remote repository '{}'",
137 model.getAbsolutePath(), remoteRepository.getId() );
141 catch ( ResourceDoesNotExistException e )
144 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
145 new Object[]{ model.getAbsolutePath(), remoteRepository.getId(), e.getMessage() } );
147 catch ( Exception e )
150 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
151 new Object[]{ model.getAbsolutePath(), remoteRepository.getId(), e.getMessage() } );
158 return new FileModelSource( model );
161 protected File findTimeStampedSnapshotPom( String groupId, String artifactId, String version,
162 String parentDirectory )
165 // reading metadata if there
166 File mavenMetadata = new File( parentDirectory, METADATA_FILENAME );
167 if ( mavenMetadata.exists() )
171 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( mavenMetadata );
172 SnapshotVersion snapshotVersion = archivaRepositoryMetadata.getSnapshotVersion();
173 if ( snapshotVersion != null )
175 String lastVersion = snapshotVersion.getTimestamp();
176 int buildNumber = snapshotVersion.getBuildNumber();
177 String snapshotPath =
178 StringUtils.replaceChars( groupId, '.', '/' ) + '/' + artifactId + '/' + version + '/'
179 + artifactId + '-' + StringUtils.remove( version, "-SNAPSHOT" ) + '-' + lastVersion + '-'
180 + buildNumber + ".pom";
182 log.debug( "use snapshot path {} for maven coordinate {}:{}:{}", snapshotPath, groupId, artifactId,
185 File model = new File( basedir, snapshotPath );
186 //model = pathTranslator.toFile( basedir, groupId, artifactId, lastVersion, filename );
187 if ( model.exists() )
193 catch ( XMLException e )
195 log.warn( "fail to read {}, {}", mavenMetadata.getAbsolutePath(), e.getCause() );
202 public void addRepository( Repository repository )
203 throws InvalidRepositoryException
205 // we just ignore repositories outside of the current one for now
206 // 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
207 // ID since they will rarely match
210 public ModelResolver newCopy()
212 return new RepositoryModelResolver( basedir, pathTranslator );
215 // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository
216 // because it's causing a cyclic dependency
217 private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId,
218 String version, String filename )
219 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException,
222 boolean success = false;
225 File tmpResource = null;
226 String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
227 File resource = new File( targetRepository.getLocation(), artifactPath );
229 File workingDirectory = createWorkingDirectory( targetRepository.getLocation() );
235 String protocol = getProtocol( remoteRepository.getUrl() );
236 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() );
238 // if it's a ntlm proxy we have to lookup the wagon light which support thats
239 // wagon http client doesn't support that
240 wagon = ( networkProxy != null && networkProxy.isUseNtlm() )
241 ? wagonFactory.getWagon(
242 new WagonFactoryRequest( "wagon#" + protocol + "-ntlm", remoteRepository.getExtraHeaders() ) )
243 : wagonFactory.getWagon(
244 new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ) );
247 wagon = wagonFactory.getWagon(
248 new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ) );
252 throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
255 boolean connected = connectToRepository( wagon, remoteRepository );
258 tmpResource = new File( workingDirectory, filename );
260 if ( VersionUtil.isSnapshot( version ) )
262 // get the metadata first!
263 File tmpMetadataResource = new File( workingDirectory, METADATA_FILENAME );
265 String metadataPath =
266 StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
268 wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource );
270 log.debug( "Successfully downloaded metadata." );
272 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource );
274 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
275 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
276 String timestampVersion = version;
277 if ( snapshotVersion != null )
279 timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
280 - 8 ); // remove SNAPSHOT from end
281 timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
282 + snapshotVersion.getBuildNumber();
284 filename = artifactId + "-" + timestampVersion + ".pom";
286 artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
288 log.debug( "New artifactPath :{}", artifactPath );
292 log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
294 wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource );
296 log.debug( "Downloaded successfully." );
298 tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
300 tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
312 catch ( ConnectionException e )
314 log.warn( "Unable to disconnect wagon.", e );
319 if ( resource != null )
321 synchronized ( resource.getAbsolutePath().intern() )
323 File directory = resource.getParentFile();
324 moveFileIfExists( tmpMd5, directory );
325 moveFileIfExists( tmpSha1, directory );
326 moveFileIfExists( tmpResource, directory );
333 FileUtils.deleteQuietly( workingDirectory );
336 // do we still need to execute the consumers?
342 * Using wagon, connect to the remote repository.
344 * @param wagon the wagon instance to establish the connection on.
345 * @return true if the connection was successful. false if not connected.
347 private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
351 final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
352 ProxyInfo networkProxy = null;
353 if ( proxyConnector != null )
355 networkProxy = new ProxyInfo();
356 networkProxy.setType( proxyConnector.getProtocol() );
357 networkProxy.setHost( proxyConnector.getHost() );
358 networkProxy.setPort( proxyConnector.getPort() );
359 networkProxy.setUserName( proxyConnector.getUsername() );
360 networkProxy.setPassword( proxyConnector.getPassword() );
362 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
363 + " to connect to remote repository " + remoteRepository.getUrl();
364 if ( networkProxy.getNonProxyHosts() != null )
366 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
369 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
371 msg += "; as user: " + networkProxy.getUserName();
377 AuthenticationInfo authInfo = null;
378 String username = remoteRepository.getUserName();
379 String password = remoteRepository.getPassword();
381 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
383 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getUrl() );
384 authInfo = new AuthenticationInfo();
385 authInfo.setUserName( username );
386 authInfo.setPassword( password );
389 // Convert seconds to milliseconds
390 int timeoutInMilliseconds = remoteRepository.getTimeout() * 1000;
391 // FIXME olamy having 2 config values
393 wagon.setReadTimeout( timeoutInMilliseconds );
394 wagon.setTimeout( timeoutInMilliseconds );
398 org.apache.maven.wagon.repository.Repository wagonRepository =
399 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getUrl() );
400 if ( networkProxy != null )
402 wagon.connect( wagonRepository, authInfo, networkProxy );
406 wagon.connect( wagonRepository, authInfo );
410 catch ( ConnectionException e )
412 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
415 catch ( AuthenticationException e )
417 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
424 private File transferChecksum( Wagon wagon, RemoteRepository remoteRepository, String remotePath, File resource,
425 File tmpDirectory, String ext )
426 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
428 File destFile = new File( tmpDirectory, resource.getName() + ext );
430 log.info( "Retrieving {} from {}", remotePath, remoteRepository.getName() );
432 wagon.get( addParameters( remotePath, remoteRepository ), destFile );
434 log.debug( "Downloaded successfully." );
439 private String getProtocol( String url )
441 String protocol = StringUtils.substringBefore( url, ":" );
446 private File createWorkingDirectory( String targetRepository )
448 return Files.createTempDir();
451 private void moveFileIfExists( File fileToMove, File directory )
453 if ( fileToMove != null && fileToMove.exists() )
455 File newLocation = new File( directory, fileToMove.getName() );
456 if ( newLocation.exists() && !newLocation.delete() )
458 throw new RuntimeException(
459 "Unable to overwrite existing target file: " + newLocation.getAbsolutePath() );
462 newLocation.getParentFile().mkdirs();
463 if ( !fileToMove.renameTo( newLocation ) )
465 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
469 FileUtils.copyFile( fileToMove, newLocation );
471 catch ( IOException e )
473 if ( newLocation.exists() )
475 log.error( "Tried to copy file {} to {} but file with this name already exists.",
476 fileToMove.getName(), newLocation.getAbsolutePath() );
480 throw new RuntimeException(
481 "Cannot copy tmp file " + fileToMove.getAbsolutePath() + " to its final location", e );
486 FileUtils.deleteQuietly( fileToMove );
492 protected String addParameters( String path, RemoteRepository remoteRepository )
494 if ( remoteRepository.getExtraParameters().isEmpty() )
499 boolean question = false;
501 StringBuilder res = new StringBuilder( path == null ? "" : path );
503 for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
507 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
511 return res.toString();