This project has retired. For details please refer to its Attic page.
RepositoryModelResolver xref
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 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 }