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;
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( basedir, pathTranslator );
218 // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository
219 // because it's causing a cyclic dependency
220 private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId,
221 String version, String filename )
222 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException,
225 boolean success = false;
228 File tmpResource = null;
229 String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
230 File resource = new File( targetRepository.getLocation(), artifactPath );
232 File workingDirectory = createWorkingDirectory( targetRepository.getLocation() );
238 String protocol = getProtocol( remoteRepository.getUrl() );
239 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() );
241 wagon = wagonFactory.getWagon(
242 new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy(
247 throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
250 boolean connected = connectToRepository( wagon, remoteRepository );
253 tmpResource = new File( workingDirectory, filename );
255 if ( VersionUtil.isSnapshot( version ) )
257 // get the metadata first!
258 File tmpMetadataResource = new File( workingDirectory, METADATA_FILENAME );
260 String metadataPath =
261 StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
263 wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource );
265 log.debug( "Successfully downloaded metadata." );
267 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource );
269 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
270 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
271 String timestampVersion = version;
272 if ( snapshotVersion != null )
274 timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
275 - 8 ); // remove SNAPSHOT from end
276 timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
277 + snapshotVersion.getBuildNumber();
279 filename = artifactId + "-" + timestampVersion + ".pom";
281 artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
283 log.debug( "New artifactPath :{}", artifactPath );
287 log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
289 wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource );
291 log.debug( "Downloaded successfully." );
293 tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
295 tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
307 catch ( ConnectionException e )
309 log.warn( "Unable to disconnect wagon.", e );
314 if ( resource != null )
316 synchronized ( resource.getAbsolutePath().intern() )
318 File directory = resource.getParentFile();
319 moveFileIfExists( tmpMd5, directory );
320 moveFileIfExists( tmpSha1, directory );
321 moveFileIfExists( tmpResource, directory );
328 FileUtils.deleteQuietly( workingDirectory );
331 // do we still need to execute the consumers?
337 * Using wagon, connect to the remote repository.
339 * @param wagon the wagon instance to establish the connection on.
340 * @return true if the connection was successful. false if not connected.
342 private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
346 final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
347 ProxyInfo networkProxy = null;
348 if ( proxyConnector != null )
350 networkProxy = new ProxyInfo();
351 networkProxy.setType( proxyConnector.getProtocol() );
352 networkProxy.setHost( proxyConnector.getHost() );
353 networkProxy.setPort( proxyConnector.getPort() );
354 networkProxy.setUserName( proxyConnector.getUsername() );
355 networkProxy.setPassword( proxyConnector.getPassword() );
357 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
358 + " to connect to remote repository " + remoteRepository.getUrl();
359 if ( networkProxy.getNonProxyHosts() != null )
361 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
364 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
366 msg += "; as user: " + networkProxy.getUserName();
372 AuthenticationInfo authInfo = null;
373 String username = remoteRepository.getUserName();
374 String password = remoteRepository.getPassword();
376 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
378 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getUrl() );
379 authInfo = new AuthenticationInfo();
380 authInfo.setUserName( username );
381 authInfo.setPassword( password );
384 // Convert seconds to milliseconds
385 int timeoutInMilliseconds = remoteRepository.getTimeout() * 1000;
386 // FIXME olamy having 2 config values
388 wagon.setReadTimeout( timeoutInMilliseconds );
389 wagon.setTimeout( timeoutInMilliseconds );
393 org.apache.maven.wagon.repository.Repository wagonRepository =
394 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getUrl() );
395 if ( networkProxy != null )
397 wagon.connect( wagonRepository, authInfo, networkProxy );
401 wagon.connect( wagonRepository, authInfo );
405 catch ( ConnectionException e )
407 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
410 catch ( AuthenticationException e )
412 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
419 private File transferChecksum( Wagon wagon, RemoteRepository remoteRepository, String remotePath, File resource,
420 File tmpDirectory, String ext )
421 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
423 File destFile = new File( tmpDirectory, resource.getName() + ext );
425 log.info( "Retrieving {} from {}", remotePath, remoteRepository.getName() );
427 wagon.get( addParameters( remotePath, remoteRepository ), destFile );
429 log.debug( "Downloaded successfully." );
434 private String getProtocol( String url )
436 String protocol = StringUtils.substringBefore( url, ":" );
441 private File createWorkingDirectory( String targetRepository )
443 return Files.createTempDir();
446 private void moveFileIfExists( File fileToMove, File directory )
448 if ( fileToMove != null && fileToMove.exists() )
450 File newLocation = new File( directory, fileToMove.getName() );
451 if ( newLocation.exists() && !newLocation.delete() )
453 throw new RuntimeException(
454 "Unable to overwrite existing target file: " + newLocation.getAbsolutePath() );
457 newLocation.getParentFile().mkdirs();
458 if ( !fileToMove.renameTo( newLocation ) )
460 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
464 FileUtils.copyFile( fileToMove, newLocation );
466 catch ( IOException e )
468 if ( newLocation.exists() )
470 log.error( "Tried to copy file {} to {} but file with this name already exists.",
471 fileToMove.getName(), newLocation.getAbsolutePath() );
475 throw new RuntimeException(
476 "Cannot copy tmp file " + fileToMove.getAbsolutePath() + " to its final location", e );
481 FileUtils.deleteQuietly( fileToMove );
487 protected String addParameters( String path, RemoteRepository remoteRepository )
489 if ( remoteRepository.getExtraParameters().isEmpty() )
494 boolean question = false;
496 StringBuilder res = new StringBuilder( path == null ? "" : path );
498 for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
502 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
506 return res.toString();