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