View Javadoc

1   package org.apache.archiva.metadata.repository.storage.maven2;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import 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                 wagon = wagonFactory.getWagon(
239                     new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy(
240                         networkProxy ) );
241 
242                 if ( wagon == null )
243                 {
244                     throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
245                 }
246 
247                 boolean connected = connectToRepository( wagon, remoteRepository );
248                 if ( connected )
249                 {
250                     tmpResource = new File( workingDirectory, filename );
251 
252                     if ( VersionUtil.isSnapshot( version ) )
253                     {
254                         // get the metadata first!
255                         File tmpMetadataResource = new File( workingDirectory, METADATA_FILENAME );
256 
257                         String metadataPath =
258                             StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
259 
260                         wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource );
261 
262                         log.debug( "Successfully downloaded metadata." );
263 
264                         ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource );
265 
266                         // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
267                         SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
268                         String timestampVersion = version;
269                         if ( snapshotVersion != null )
270                         {
271                             timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
272                                 - 8 ); // remove SNAPSHOT from end
273                             timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
274                                 + snapshotVersion.getBuildNumber();
275 
276                             filename = artifactId + "-" + timestampVersion + ".pom";
277 
278                             artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
279 
280                             log.debug( "New artifactPath :{}", artifactPath );
281                         }
282                     }
283 
284                     log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
285 
286                     wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource );
287 
288                     log.debug( "Downloaded successfully." );
289 
290                     tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
291                                                 ".sha1" );
292                     tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
293                                                ".md5" );
294                 }
295             }
296             finally
297             {
298                 if ( wagon != null )
299                 {
300                     try
301                     {
302                         wagon.disconnect();
303                     }
304                     catch ( ConnectionException e )
305                     {
306                         log.warn( "Unable to disconnect wagon.", e );
307                     }
308                 }
309             }
310 
311             if ( resource != null )
312             {
313                 synchronized ( resource.getAbsolutePath().intern() )
314                 {
315                     File directory = resource.getParentFile();
316                     moveFileIfExists( tmpMd5, directory );
317                     moveFileIfExists( tmpSha1, directory );
318                     moveFileIfExists( tmpResource, directory );
319                     success = true;
320                 }
321             }
322         }
323         finally
324         {
325             FileUtils.deleteQuietly( workingDirectory );
326         }
327 
328         // do we still need to execute the consumers?
329 
330         return success;
331     }
332 
333     /**
334      * Using wagon, connect to the remote repository.
335      *
336      * @param wagon the wagon instance to establish the connection on.
337      * @return true if the connection was successful. false if not connected.
338      */
339     private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
340     {
341         boolean connected;
342 
343         final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
344         ProxyInfo networkProxy = null;
345         if ( proxyConnector != null )
346         {
347             networkProxy = new ProxyInfo();
348             networkProxy.setType( proxyConnector.getProtocol() );
349             networkProxy.setHost( proxyConnector.getHost() );
350             networkProxy.setPort( proxyConnector.getPort() );
351             networkProxy.setUserName( proxyConnector.getUsername() );
352             networkProxy.setPassword( proxyConnector.getPassword() );
353 
354             String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
355                 + " to connect to remote repository " + remoteRepository.getUrl();
356             if ( networkProxy.getNonProxyHosts() != null )
357             {
358                 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
359             }
360 
361             if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
362             {
363                 msg += "; as user: " + networkProxy.getUserName();
364             }
365 
366             log.debug( msg );
367         }
368 
369         AuthenticationInfo authInfo = null;
370         String username = remoteRepository.getUserName();
371         String password = remoteRepository.getPassword();
372 
373         if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
374         {
375             log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getUrl() );
376             authInfo = new AuthenticationInfo();
377             authInfo.setUserName( username );
378             authInfo.setPassword( password );
379         }
380 
381         // Convert seconds to milliseconds
382         int timeoutInMilliseconds = remoteRepository.getTimeout() * 1000;
383         // FIXME olamy having 2 config values
384         // Set timeout
385         wagon.setReadTimeout( timeoutInMilliseconds );
386         wagon.setTimeout( timeoutInMilliseconds );
387 
388         try
389         {
390             org.apache.maven.wagon.repository.Repository wagonRepository =
391                 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getUrl() );
392             if ( networkProxy != null )
393             {
394                 wagon.connect( wagonRepository, authInfo, networkProxy );
395             }
396             else
397             {
398                 wagon.connect( wagonRepository, authInfo );
399             }
400             connected = true;
401         }
402         catch ( ConnectionException e )
403         {
404             log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
405             connected = false;
406         }
407         catch ( 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     private File transferChecksum( Wagon wagon, RemoteRepository remoteRepository, String remotePath, File resource,
417                                    File tmpDirectory, String ext )
418         throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
419     {
420         File destFile = new File( tmpDirectory, resource.getName() + ext );
421 
422         log.info( "Retrieving {} from {}", remotePath, remoteRepository.getName() );
423 
424         wagon.get( addParameters( remotePath, remoteRepository ), destFile );
425 
426         log.debug( "Downloaded successfully." );
427 
428         return destFile;
429     }
430 
431     private String getProtocol( String url )
432     {
433         String protocol = StringUtils.substringBefore( url, ":" );
434 
435         return protocol;
436     }
437 
438     private File createWorkingDirectory( String targetRepository )
439     {
440         return Files.createTempDir();
441     }
442 
443     private void moveFileIfExists( File fileToMove, File directory )
444     {
445         if ( fileToMove != null && fileToMove.exists() )
446         {
447             File newLocation = new File( directory, fileToMove.getName() );
448             if ( newLocation.exists() && !newLocation.delete() )
449             {
450                 throw new RuntimeException(
451                     "Unable to overwrite existing target file: " + newLocation.getAbsolutePath() );
452             }
453 
454             newLocation.getParentFile().mkdirs();
455             if ( !fileToMove.renameTo( newLocation ) )
456             {
457                 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
458 
459                 try
460                 {
461                     FileUtils.copyFile( fileToMove, newLocation );
462                 }
463                 catch ( IOException e )
464                 {
465                     if ( newLocation.exists() )
466                     {
467                         log.error( "Tried to copy file {} to {} but file with this name already exists.",
468                                    fileToMove.getName(), newLocation.getAbsolutePath() );
469                     }
470                     else
471                     {
472                         throw new RuntimeException(
473                             "Cannot copy tmp file " + fileToMove.getAbsolutePath() + " to its final location", e );
474                     }
475                 }
476                 finally
477                 {
478                     FileUtils.deleteQuietly( fileToMove );
479                 }
480             }
481         }
482     }
483 
484     protected String addParameters( String path, RemoteRepository remoteRepository )
485     {
486         if ( remoteRepository.getExtraParameters().isEmpty() )
487         {
488             return path;
489         }
490 
491         boolean question = false;
492 
493         StringBuilder res = new StringBuilder( path == null ? "" : path );
494 
495         for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
496         {
497             if ( !question )
498             {
499                 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
500             }
501         }
502 
503         return res.toString();
504     }
505 }