]> source.dussan.org Git - archiva.git/blob
a419bb91f64916f1b57d5d58ae82a3086e03cc34
[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     @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( basedir, pathTranslator );
216     }
217
218     // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository
219     // because it's causing a cyclic dependency
220     private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId,
221                                        String version, String filename )
222         throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException,
223         XMLException
224     {
225         boolean success = false;
226         File tmpMd5 = null;
227         File tmpSha1 = null;
228         File tmpResource = null;
229         String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
230         File resource = new File( targetRepository.getLocation(), artifactPath );
231
232         File workingDirectory = createWorkingDirectory( targetRepository.getLocation() );
233         try
234         {
235             Wagon wagon = null;
236             try
237             {
238                 String protocol = getProtocol( remoteRepository.getUrl() );
239                 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() );
240
241                 wagon = wagonFactory.getWagon(
242                     new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy(
243                         networkProxy ) );
244
245                 if ( wagon == null )
246                 {
247                     throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
248                 }
249
250                 boolean connected = connectToRepository( wagon, remoteRepository );
251                 if ( connected )
252                 {
253                     tmpResource = new File( workingDirectory, filename );
254
255                     if ( VersionUtil.isSnapshot( version ) )
256                     {
257                         // get the metadata first!
258                         File tmpMetadataResource = new File( workingDirectory, METADATA_FILENAME );
259
260                         String metadataPath =
261                             StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
262
263                         wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource );
264
265                         log.debug( "Successfully downloaded metadata." );
266
267                         ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource );
268
269                         // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
270                         SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
271                         String timestampVersion = version;
272                         if ( snapshotVersion != null )
273                         {
274                             timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
275                                 - 8 ); // remove SNAPSHOT from end
276                             timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
277                                 + snapshotVersion.getBuildNumber();
278
279                             filename = artifactId + "-" + timestampVersion + ".pom";
280
281                             artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
282
283                             log.debug( "New artifactPath :{}", artifactPath );
284                         }
285                     }
286
287                     log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
288
289                     wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource );
290
291                     log.debug( "Downloaded successfully." );
292
293                     tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
294                                                 ".sha1" );
295                     tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
296                                                ".md5" );
297                 }
298             }
299             finally
300             {
301                 if ( wagon != null )
302                 {
303                     try
304                     {
305                         wagon.disconnect();
306                     }
307                     catch ( ConnectionException e )
308                     {
309                         log.warn( "Unable to disconnect wagon.", e );
310                     }
311                 }
312             }
313
314             if ( resource != null )
315             {
316                 synchronized ( resource.getAbsolutePath().intern() )
317                 {
318                     File directory = resource.getParentFile();
319                     moveFileIfExists( tmpMd5, directory );
320                     moveFileIfExists( tmpSha1, directory );
321                     moveFileIfExists( tmpResource, directory );
322                     success = true;
323                 }
324             }
325         }
326         finally
327         {
328             FileUtils.deleteQuietly( workingDirectory );
329         }
330
331         // do we still need to execute the consumers?
332
333         return success;
334     }
335
336     /**
337      * Using wagon, connect to the remote repository.
338      *
339      * @param wagon the wagon instance to establish the connection on.
340      * @return true if the connection was successful. false if not connected.
341      */
342     private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
343     {
344         boolean connected;
345
346         final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
347         ProxyInfo networkProxy = null;
348         if ( proxyConnector != null )
349         {
350             networkProxy = new ProxyInfo();
351             networkProxy.setType( proxyConnector.getProtocol() );
352             networkProxy.setHost( proxyConnector.getHost() );
353             networkProxy.setPort( proxyConnector.getPort() );
354             networkProxy.setUserName( proxyConnector.getUsername() );
355             networkProxy.setPassword( proxyConnector.getPassword() );
356
357             String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
358                 + " to connect to remote repository " + remoteRepository.getUrl();
359             if ( networkProxy.getNonProxyHosts() != null )
360             {
361                 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
362             }
363
364             if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
365             {
366                 msg += "; as user: " + networkProxy.getUserName();
367             }
368
369             log.debug( msg );
370         }
371
372         AuthenticationInfo authInfo = null;
373         String username = remoteRepository.getUserName();
374         String password = remoteRepository.getPassword();
375
376         if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
377         {
378             log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getUrl() );
379             authInfo = new AuthenticationInfo();
380             authInfo.setUserName( username );
381             authInfo.setPassword( password );
382         }
383
384         // Convert seconds to milliseconds
385         int timeoutInMilliseconds = remoteRepository.getTimeout() * 1000;
386         // FIXME olamy having 2 config values
387         // Set timeout
388         wagon.setReadTimeout( timeoutInMilliseconds );
389         wagon.setTimeout( timeoutInMilliseconds );
390
391         try
392         {
393             org.apache.maven.wagon.repository.Repository wagonRepository =
394                 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getUrl() );
395             if ( networkProxy != null )
396             {
397                 wagon.connect( wagonRepository, authInfo, networkProxy );
398             }
399             else
400             {
401                 wagon.connect( wagonRepository, authInfo );
402             }
403             connected = true;
404         }
405         catch ( ConnectionException e )
406         {
407             log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
408             connected = false;
409         }
410         catch ( AuthenticationException e )
411         {
412             log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
413             connected = false;
414         }
415
416         return connected;
417     }
418
419     private File transferChecksum( Wagon wagon, RemoteRepository remoteRepository, String remotePath, File resource,
420                                    File tmpDirectory, String ext )
421         throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
422     {
423         File destFile = new File( tmpDirectory, resource.getName() + ext );
424
425         log.info( "Retrieving {} from {}", remotePath, remoteRepository.getName() );
426
427         wagon.get( addParameters( remotePath, remoteRepository ), destFile );
428
429         log.debug( "Downloaded successfully." );
430
431         return destFile;
432     }
433
434     private String getProtocol( String url )
435     {
436         String protocol = StringUtils.substringBefore( url, ":" );
437
438         return protocol;
439     }
440
441     private File createWorkingDirectory( String targetRepository )
442     {
443         return Files.createTempDir();
444     }
445
446     private void moveFileIfExists( File fileToMove, File directory )
447     {
448         if ( fileToMove != null && fileToMove.exists() )
449         {
450             File newLocation = new File( directory, fileToMove.getName() );
451             if ( newLocation.exists() && !newLocation.delete() )
452             {
453                 throw new RuntimeException(
454                     "Unable to overwrite existing target file: " + newLocation.getAbsolutePath() );
455             }
456
457             newLocation.getParentFile().mkdirs();
458             if ( !fileToMove.renameTo( newLocation ) )
459             {
460                 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
461
462                 try
463                 {
464                     FileUtils.copyFile( fileToMove, newLocation );
465                 }
466                 catch ( IOException e )
467                 {
468                     if ( newLocation.exists() )
469                     {
470                         log.error( "Tried to copy file {} to {} but file with this name already exists.",
471                                    fileToMove.getName(), newLocation.getAbsolutePath() );
472                     }
473                     else
474                     {
475                         throw new RuntimeException(
476                             "Cannot copy tmp file " + fileToMove.getAbsolutePath() + " to its final location", e );
477                     }
478                 }
479                 finally
480                 {
481                     FileUtils.deleteQuietly( fileToMove );
482                 }
483             }
484         }
485     }
486
487     protected String addParameters( String path, RemoteRepository remoteRepository )
488     {
489         if ( remoteRepository.getExtraParameters().isEmpty() )
490         {
491             return path;
492         }
493
494         boolean question = false;
495
496         StringBuilder res = new StringBuilder( path == null ? "" : path );
497
498         for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
499         {
500             if ( !question )
501             {
502                 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
503             }
504         }
505
506         return res.toString();
507     }
508 }