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