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