]> source.dussan.org Git - archiva.git/blob
f978d554f3eb03d95b7bc8f8895220e5d4c3a7eb
[archiva.git] /
1 package org.apache.archiva.metadata.repository.storage.maven2;
2
3 /*
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
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  * under the License.
20  */
21
22 import org.apache.archiva.common.utils.VersionUtil;
23 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
24 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
25 import org.apache.archiva.model.ArchivaRepositoryMetadata;
26 import org.apache.archiva.model.SnapshotVersion;
27 import org.apache.archiva.proxy.maven.WagonFactory;
28 import org.apache.archiva.proxy.maven.WagonFactoryException;
29 import org.apache.archiva.proxy.maven.WagonFactoryRequest;
30 import org.apache.archiva.proxy.model.NetworkProxy;
31 import org.apache.archiva.repository.ManagedRepository;
32 import org.apache.archiva.repository.RemoteRepository;
33 import org.apache.archiva.repository.RepositoryCredentials;
34 import org.apache.archiva.repository.maven2.MavenSystemManager;
35 import org.apache.archiva.xml.XMLException;
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.http.auth.UsernamePasswordCredentials;
38 import org.apache.maven.model.Dependency;
39 import org.apache.maven.model.Parent;
40 import org.apache.maven.model.Repository;
41 import org.apache.maven.model.building.FileModelSource;
42 import org.apache.maven.model.building.ModelSource;
43 import org.apache.maven.model.resolution.InvalidRepositoryException;
44 import org.apache.maven.model.resolution.ModelResolver;
45 import org.apache.maven.model.resolution.UnresolvableModelException;
46 import org.apache.maven.wagon.ConnectionException;
47 import org.apache.maven.wagon.ResourceDoesNotExistException;
48 import org.apache.maven.wagon.TransferFailedException;
49 import org.apache.maven.wagon.Wagon;
50 import org.apache.maven.wagon.authentication.AuthenticationException;
51 import org.apache.maven.wagon.authentication.AuthenticationInfo;
52 import org.apache.maven.wagon.authorization.AuthorizationException;
53 import org.apache.maven.wagon.proxy.ProxyInfo;
54 import org.eclipse.aether.RepositorySystemSession;
55 import org.eclipse.aether.artifact.Artifact;
56 import org.eclipse.aether.artifact.DefaultArtifact;
57 import org.eclipse.aether.impl.VersionRangeResolver;
58 import org.eclipse.aether.resolution.VersionRangeRequest;
59 import org.eclipse.aether.resolution.VersionRangeResolutionException;
60 import org.eclipse.aether.resolution.VersionRangeResult;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 import java.io.IOException;
65 import java.nio.file.Files;
66 import java.nio.file.Path;
67 import java.nio.file.Paths;
68 import java.util.HashMap;
69 import java.util.List;
70 import java.util.Map;
71
72 public class RepositoryModelResolver
73     implements ModelResolver
74 {
75
76     private final Map<String, NetworkProxy> networkProxyMap = new HashMap<>();
77
78     private RepositorySystemSession session;
79     private VersionRangeResolver versionRangeResolver;
80
81     private Path basedir;
82
83     private RepositoryPathTranslator pathTranslator;
84
85     private WagonFactory wagonFactory;
86
87     private List<RemoteRepository> remoteRepositories;
88
89     private ManagedRepository targetRepository;
90
91     private static final Logger log = LoggerFactory.getLogger( RepositoryModelResolver.class );
92
93     private static final String METADATA_FILENAME = "maven-metadata.xml";
94
95     private MavenSystemManager mavenSystemManager;
96
97
98
99     private ManagedRepository managedRepository;
100
101     public RepositoryModelResolver( Path basedir, RepositoryPathTranslator pathTranslator )
102     {
103         this.basedir = basedir;
104
105         this.pathTranslator = pathTranslator;
106     }
107
108     public RepositoryModelResolver(ManagedRepository managedRepository, RepositoryPathTranslator pathTranslator,
109                                    WagonFactory wagonFactory, List<RemoteRepository> remoteRepositories,
110                                    Map<String, NetworkProxy> networkProxiesMap, ManagedRepository targetRepository,
111                                    MavenSystemManager mavenSystemManager)
112     {
113         this( Paths.get( managedRepository.getLocation() ), pathTranslator );
114
115         this.managedRepository = managedRepository;
116
117         this.wagonFactory = wagonFactory;
118
119         this.remoteRepositories = remoteRepositories;
120
121         this.networkProxyMap.clear();
122         this.networkProxyMap.putAll(networkProxiesMap);
123
124         this.targetRepository = targetRepository;
125
126         this.session = MavenSystemManager.newRepositorySystemSession( managedRepository.getAsset("").getFilePath().toString() );
127
128         this.versionRangeResolver = mavenSystemManager.getLocator().getService(VersionRangeResolver.class);
129
130         this.mavenSystemManager = mavenSystemManager;
131     }
132
133
134     @Override
135     public ModelSource resolveModel( String groupId, String artifactId, String version )
136         throws UnresolvableModelException
137     {
138         String filename = artifactId + "-" + version + ".pom";
139         // TODO: we need to convert 1.0-20091120.112233-1 type paths to baseVersion for the below call - add a test
140
141         Path model = pathTranslator.toFile( basedir, groupId, artifactId, version, filename );
142
143         if ( !Files.exists(model) )
144         {
145             /**
146              *
147              */
148             // is a SNAPSHOT ? so we can try to find locally before asking remote repositories.
149             if ( StringUtils.contains( version, VersionUtil.SNAPSHOT ) )
150             {
151                 Path localSnapshotModel = findTimeStampedSnapshotPom( groupId, artifactId, version, model.getParent().toString() );
152                 if ( localSnapshotModel != null )
153                 {
154                     return new FileModelSource( localSnapshotModel.toFile() );
155                 }
156
157             }
158
159             for ( RemoteRepository remoteRepository : remoteRepositories )
160             {
161                 try
162                 {
163                     boolean success = getModelFromProxy( remoteRepository, groupId, artifactId, version, filename );
164                     if ( success && Files.exists(model) )
165                     {
166                         log.info( "Model '{}' successfully retrieved from remote repository '{}'",
167                                   model.toAbsolutePath(), remoteRepository.getId() );
168                         break;
169                     }
170                 }
171                 catch ( ResourceDoesNotExistException e )
172                 {
173                     log.info(
174                         "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
175                         model.toAbsolutePath(), remoteRepository.getId(), e.getMessage() );
176                 }
177                 catch ( Exception e )
178                 {
179                     log.warn(
180                         "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
181                         model.toAbsolutePath(), remoteRepository.getId(), e.getMessage() );
182
183                     continue;
184                 }
185             }
186         }
187
188         return new FileModelSource( model.toFile() );
189     }
190
191     public ModelSource resolveModel(Parent parent) throws UnresolvableModelException {
192         try {
193             Artifact artifact = new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion());
194             VersionRangeRequest versionRangeRequest;
195             versionRangeRequest = new VersionRangeRequest(artifact, null, null);
196             VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest);
197             if (versionRangeResult.getHighestVersion() == null) {
198                 throw new UnresolvableModelException(String.format("No versions matched the requested parent version range '%s'", parent.getVersion()), parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
199             } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
200                 throw new UnresolvableModelException(String.format("The requested parent version range '%s' does not specify an upper bound", parent.getVersion()), parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
201             } else {
202                 parent.setVersion(versionRangeResult.getHighestVersion().toString());
203                 return this.resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
204             }
205         } catch ( VersionRangeResolutionException var5) {
206             throw new UnresolvableModelException(var5.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), var5);
207         }
208     }
209
210     public ModelSource resolveModel(Dependency dependency) throws UnresolvableModelException {
211         try {
212             Artifact artifact = new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), "", "pom", dependency.getVersion());
213             VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, null, null);
214             VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest);
215             if (versionRangeResult.getHighestVersion() == null) {
216                 throw new UnresolvableModelException(String.format("No versions matched the requested dependency version range '%s'", dependency.getVersion()), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
217             } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
218                 throw new UnresolvableModelException(String.format("The requested dependency version range '%s' does not specify an upper bound", dependency.getVersion()), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
219             } else {
220                 dependency.setVersion(versionRangeResult.getHighestVersion().toString());
221                 return this.resolveModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
222             }
223         } catch (VersionRangeResolutionException var5) {
224             throw new UnresolvableModelException(var5.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), var5);
225         }
226     }
227
228     protected Path findTimeStampedSnapshotPom( String groupId, String artifactId, String version,
229                                                String parentDirectory )
230     {
231
232         // reading metadata if there
233         Path mavenMetadata = Paths.get( parentDirectory, METADATA_FILENAME );
234         if ( Files.exists(mavenMetadata) )
235         {
236             try
237             {
238                 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( mavenMetadata);
239                 SnapshotVersion snapshotVersion = archivaRepositoryMetadata.getSnapshotVersion();
240                 if ( snapshotVersion != null )
241                 {
242                     String lastVersion = snapshotVersion.getTimestamp();
243                     int buildNumber = snapshotVersion.getBuildNumber();
244                     String snapshotPath =
245                         StringUtils.replaceChars( groupId, '.', '/' ) + '/' + artifactId + '/' + version + '/'
246                             + artifactId + '-' + StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) + '-'
247                             + lastVersion + '-' + buildNumber + ".pom";
248
249                     log.debug( "use snapshot path {} for maven coordinate {}:{}:{}", snapshotPath, groupId, artifactId,
250                                version );
251
252                     Path model = basedir.resolve( snapshotPath );
253                     //model = pathTranslator.toFile( basedir, groupId, artifactId, lastVersion, filename );
254                     if ( Files.exists(model) )
255                     {
256                         return model;
257                     }
258                 }
259             }
260             catch ( XMLException e )
261             {
262                 log.warn( "fail to read {}, {}", mavenMetadata.toAbsolutePath(), e.getCause() );
263             }
264         }
265
266         return null;
267     }
268
269     @Override
270     public void addRepository( Repository repository )
271         throws InvalidRepositoryException
272     {
273         // we just ignore repositories outside of the current one for now
274         // 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
275         //       ID since they will rarely match
276     }
277
278     @Override
279     public void addRepository( Repository repository, boolean b ) throws InvalidRepositoryException
280     {
281
282     }
283
284     @Override
285     public ModelResolver newCopy()
286     {
287         return new RepositoryModelResolver( managedRepository,  pathTranslator, wagonFactory, remoteRepositories, 
288                                             networkProxyMap, targetRepository, mavenSystemManager);
289     }
290
291     // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository
292     // because it's causing a cyclic dependency
293     private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId,
294                                        String version, String filename )
295         throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException,
296         XMLException, IOException
297     {
298         boolean success = false;
299         Path tmpMd5 = null;
300         Path tmpSha1 = null;
301         Path tmpResource = null;
302         String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
303         Path resource = targetRepository.getAsset("").getFilePath().resolve( artifactPath );
304
305         Path workingDirectory = createWorkingDirectory( targetRepository.getLocation().toString() );
306         try
307         {
308             Wagon wagon = null;
309             try
310             {
311                 String protocol = getProtocol( remoteRepository.getLocation().toString() );
312                 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() );
313
314                 wagon = wagonFactory.getWagon(
315                     new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy(
316                         networkProxy )
317                 );
318
319                 if ( wagon == null )
320                 {
321                     throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
322                 }
323
324                 boolean connected = connectToRepository( wagon, remoteRepository );
325                 if ( connected )
326                 {
327                     tmpResource = workingDirectory.resolve( filename );
328
329                     if ( VersionUtil.isSnapshot( version ) )
330                     {
331                         // get the metadata first!
332                         Path tmpMetadataResource = workingDirectory.resolve( METADATA_FILENAME );
333
334                         String metadataPath =
335                             StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
336
337                         wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource.toFile() );
338
339                         log.debug( "Successfully downloaded metadata." );
340
341                         ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource );
342
343                         // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
344                         SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
345                         String timestampVersion = version;
346                         if ( snapshotVersion != null )
347                         {
348                             timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
349                                 - 8 ); // remove SNAPSHOT from end
350                             timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
351                                 + snapshotVersion.getBuildNumber();
352
353                             filename = artifactId + "-" + timestampVersion + ".pom";
354
355                             artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
356
357                             log.debug( "New artifactPath :{}", artifactPath );
358                         }
359                     }
360
361                     log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
362
363                     wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource.toFile() );
364
365                     log.debug( "Downloaded successfully." );
366
367                     tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
368                                                 ".sha1" );
369                     tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
370                                                ".md5" );
371                 }
372             }
373             finally
374             {
375                 if ( wagon != null )
376                 {
377                     try
378                     {
379                         wagon.disconnect();
380                     }
381                     catch ( ConnectionException e )
382                     {
383                         log.warn( "Unable to disconnect wagon.", e );
384                     }
385                 }
386             }
387
388             if ( resource != null )
389             {
390                 synchronized ( resource.toAbsolutePath().toString().intern() )
391                 {
392                     Path directory = resource.getParent();
393                     moveFileIfExists( tmpMd5, directory );
394                     moveFileIfExists( tmpSha1, directory );
395                     moveFileIfExists( tmpResource, directory );
396                     success = true;
397                 }
398             }
399         }
400         finally
401         {
402             org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
403         }
404
405         // do we still need to execute the consumers?
406
407         return success;
408     }
409
410     /**
411      * Using wagon, connect to the remote repository.
412      *
413      * @param wagon the wagon instance to establish the connection on.
414      * @return true if the connection was successful. false if not connected.
415      */
416     private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
417     {
418         boolean connected;
419
420         final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
421         ProxyInfo networkProxy = null;
422         if ( proxyConnector != null )
423         {
424             networkProxy = new ProxyInfo();
425             networkProxy.setType( proxyConnector.getProtocol() );
426             networkProxy.setHost( proxyConnector.getHost() );
427             networkProxy.setPort( proxyConnector.getPort() );
428             networkProxy.setUserName( proxyConnector.getUsername() );
429             networkProxy.setPassword( proxyConnector.getPassword() );
430
431             String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
432                 + " to connect to remote repository " + remoteRepository.getLocation();
433             if ( networkProxy.getNonProxyHosts() != null )
434             {
435                 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
436             }
437
438             if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
439             {
440                 msg += "; as user: " + networkProxy.getUserName();
441             }
442
443             log.debug( msg );
444         }
445
446         AuthenticationInfo authInfo = null;
447         RepositoryCredentials creds = remoteRepository.getLoginCredentials();
448         String username = "";
449         String password = "";
450         if (creds instanceof UsernamePasswordCredentials) {
451             UsernamePasswordCredentials c = (UsernamePasswordCredentials) creds;
452             username = c.getUserName();
453             password = c.getPassword();
454         }
455
456         if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
457         {
458             log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getLocation() );
459             authInfo = new AuthenticationInfo();
460             authInfo.setUserName( username );
461             authInfo.setPassword( password );
462         }
463
464         int timeoutInMilliseconds = ((int)remoteRepository.getTimeout().getSeconds())*1000;
465         // FIXME olamy having 2 config values
466         // Set timeout
467         wagon.setReadTimeout( timeoutInMilliseconds );
468         wagon.setTimeout( timeoutInMilliseconds );
469
470         try
471         {
472             org.apache.maven.wagon.repository.Repository wagonRepository =
473                 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getLocation().toString() );
474             if ( networkProxy != null )
475             {
476                 wagon.connect( wagonRepository, authInfo, networkProxy );
477             }
478             else
479             {
480                 wagon.connect( wagonRepository, authInfo );
481             }
482             connected = true;
483         }
484         catch ( ConnectionException | AuthenticationException e )
485         {
486             log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
487             connected = false;
488         }
489
490         return connected;
491     }
492
493     /**
494      *
495      * @param wagon The wagon instance that should be connected.
496      * @param remoteRepository The repository from where the checksum file should be retrieved
497      * @param remotePath The remote path of the artifact (without extension)
498      * @param resource The local artifact (without extension)
499      * @param workingDir The working directory where the downloaded file should be placed to
500      * @param ext The extension of th checksum file
501      * @return The file where the data has been downloaded to.
502      * @throws AuthorizationException
503      * @throws TransferFailedException
504      * @throws ResourceDoesNotExistException
505      */
506     private Path transferChecksum( final Wagon wagon, final RemoteRepository remoteRepository,
507                                    final String remotePath, final Path resource,
508                                    final Path workingDir, final String ext )
509         throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
510     {
511         Path destFile = workingDir.resolve( resource.getFileName() + ext );
512         String remoteChecksumPath = remotePath + ext;
513
514         log.info( "Retrieving {} from {}", remoteChecksumPath, remoteRepository.getName() );
515
516         wagon.get( addParameters( remoteChecksumPath, remoteRepository ), destFile.toFile() );
517
518         log.debug( "Downloaded successfully." );
519
520         return destFile;
521     }
522
523     private String getProtocol( String url )
524     {
525         String protocol = StringUtils.substringBefore( url, ":" );
526
527         return protocol;
528     }
529
530     private Path createWorkingDirectory( String targetRepository )
531         throws IOException
532     {
533         return Files.createTempDirectory( "temp" );
534     }
535
536     private void moveFileIfExists( Path fileToMove, Path directory )
537     {
538         if ( fileToMove != null && Files.exists(fileToMove) )
539         {
540             Path newLocation = directory.resolve( fileToMove.getFileName() );
541             try {
542                 Files.deleteIfExists(newLocation);
543             } catch (IOException e) {
544                 throw new RuntimeException(
545                         "Unable to overwrite existing target file: " + newLocation.toAbsolutePath(), e );
546             }
547
548             try {
549                 Files.createDirectories(newLocation.getParent());
550             } catch (IOException e) {
551                 e.printStackTrace();
552             }
553             try {
554                 Files.move(fileToMove, newLocation );
555             } catch (IOException e) {
556                 try {
557                     Files.copy(fileToMove, newLocation);
558                 } catch (IOException e1) {
559                     if (Files.exists(newLocation)) {
560                         log.error( "Tried to copy file {} to {} but file with this name already exists.",
561                                 fileToMove.getFileName(), newLocation.toAbsolutePath() );
562                     } else {
563                         throw new RuntimeException(
564                                 "Cannot copy tmp file " + fileToMove.toAbsolutePath() + " to its final location", e );
565                     }
566                 }
567             } finally {
568                 org.apache.archiva.common.utils.FileUtils.deleteQuietly(fileToMove);
569             }
570         }
571     }
572
573     protected String addParameters( String path, RemoteRepository remoteRepository )
574     {
575         if ( remoteRepository.getExtraParameters().isEmpty() )
576         {
577             return path;
578         }
579
580         boolean question = false;
581
582         StringBuilder res = new StringBuilder( path == null ? "" : path );
583
584         for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
585         {
586             if ( !question )
587             {
588                 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
589             }
590         }
591
592         return res.toString();
593     }
594 }