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