]> source.dussan.org Git - archiva.git/blob
19cef4eddc9092f6ce11908d240382cb309e8807
[archiva.git] /
1 package org.apache.maven.repository.proxy;
2
3 /*
4  * Copyright 2005-2006 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 import org.apache.maven.artifact.Artifact;
20 import org.apache.maven.artifact.repository.ArtifactRepository;
21 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
22 import org.apache.maven.artifact.repository.metadata.Metadata;
23 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
24 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
25 import org.apache.maven.repository.digest.DigestUtils;
26 import org.apache.maven.repository.digest.DigesterException;
27 import org.apache.maven.repository.discovery.ArtifactDiscoverer;
28 import org.apache.maven.repository.discovery.DiscovererException;
29 import org.apache.maven.wagon.ConnectionException;
30 import org.apache.maven.wagon.ResourceDoesNotExistException;
31 import org.apache.maven.wagon.TransferFailedException;
32 import org.apache.maven.wagon.Wagon;
33 import org.apache.maven.wagon.authentication.AuthenticationException;
34 import org.apache.maven.wagon.authorization.AuthorizationException;
35 import org.apache.maven.wagon.observers.ChecksumObserver;
36 import org.apache.maven.wagon.proxy.ProxyInfo;
37 import org.apache.maven.wagon.repository.Repository;
38 import org.codehaus.plexus.logging.AbstractLogEnabled;
39 import org.codehaus.plexus.util.FileUtils;
40 import org.codehaus.plexus.util.IOUtil;
41 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
42
43 import java.io.File;
44 import java.io.FileReader;
45 import java.io.FileWriter;
46 import java.io.IOException;
47 import java.security.NoSuchAlgorithmException;
48 import java.util.Date;
49 import java.util.Iterator;
50 import java.util.LinkedHashMap;
51 import java.util.List;
52 import java.util.Map;
53
54 /**
55  * An implementation of the proxy handler.
56  *
57  * @author <a href="mailto:brett@apache.org">Brett Porter</a>
58  * @plexus.component
59  * @todo use wagonManager for cache use file:// as URL
60  * @todo this currently duplicates a lot of the wagon manager, and doesn't do things like snapshot resolution, etc.
61  * The checksum handling is inconsistent with that of the wagon manager.
62  * Should we have a more artifact based one? This will merge metadata so should behave correctly, and it is able to
63  * correct some limitations of the wagon manager (eg, it can retrieve newer SNAPSHOT files without metadata)
64  */
65 public class DefaultProxyRequestHandler
66     extends AbstractLogEnabled
67     implements ProxyRequestHandler
68 {
69     /**
70      * @plexus.requirement role-hint="default"
71      * @todo use a map, and have priorities in them
72      */
73     private ArtifactDiscoverer defaultArtifactDiscoverer;
74
75     /**
76      * @plexus.requirement role-hint="legacy"
77      */
78     private ArtifactDiscoverer legacyArtifactDiscoverer;
79
80     /**
81      * @plexus.requirement role="org.apache.maven.wagon.Wagon"
82      */
83     private Map/*<String,Wagon>*/ wagons;
84
85     /**
86      * @plexus.requirement role="org.apache.maven.repository.digest.Digester"
87      */
88     private Map/*<String,Digester>*/ digesters;
89
90     public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository )
91         throws ProxyException, ResourceDoesNotExistException
92     {
93         return get( path, proxiedRepositories, managedRepository, null );
94     }
95
96     public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository, ProxyInfo wagonProxy )
97         throws ProxyException, ResourceDoesNotExistException
98     {
99         return get( managedRepository, path, proxiedRepositories, wagonProxy, false );
100     }
101
102     public File getAlways( String path, List proxiedRepositories, ArtifactRepository managedRepository )
103         throws ProxyException, ResourceDoesNotExistException
104     {
105         return getAlways( path, proxiedRepositories, managedRepository, null );
106     }
107
108     public File getAlways( String path, List proxiedRepositories, ArtifactRepository managedRepository,
109                            ProxyInfo wagonProxy )
110         throws ResourceDoesNotExistException, ProxyException
111     {
112         return get( managedRepository, path, proxiedRepositories, wagonProxy, true );
113     }
114
115     private File get( ArtifactRepository managedRepository, String path, List proxiedRepositories, ProxyInfo wagonProxy,
116                       boolean force )
117         throws ProxyException, ResourceDoesNotExistException
118     {
119         File target = new File( managedRepository.getBasedir(), path );
120
121         for ( Iterator i = proxiedRepositories.iterator(); i.hasNext(); )
122         {
123             ProxiedArtifactRepository repository = (ProxiedArtifactRepository) i.next();
124
125             if ( !force && repository.isCachedFailure( path ) )
126             {
127                 processCachedRepositoryFailure( repository, "Cached failure found for: " + path );
128             }
129             else
130             {
131                 get( path, target, repository, managedRepository, wagonProxy, force );
132             }
133         }
134
135         if ( !target.exists() )
136         {
137             throw new ResourceDoesNotExistException( "Could not find " + path + " in any of the repositories." );
138         }
139
140         return target;
141     }
142
143     private void get( String path, File target, ProxiedArtifactRepository repository,
144                       ArtifactRepository managedRepository, ProxyInfo wagonProxy, boolean force )
145         throws ProxyException
146     {
147         ArtifactRepositoryPolicy policy;
148
149         if ( path.endsWith( ".md5" ) || path.endsWith( ".sha1" ) )
150         {
151             // always read from the managed repository, no need to make remote request
152         }
153         else if ( path.endsWith( "maven-metadata.xml" ) )
154         {
155             File metadataFile = new File( target.getParentFile(), ".metadata-" + repository.getRepository().getId() );
156
157             policy = repository.getRepository().getReleases();
158
159             // if it is snapshot metadata, use a different policy
160             if ( path.endsWith( "-SNAPSHOT/maven-metadata.xml" ) )
161             {
162                 policy = repository.getRepository().getSnapshots();
163             }
164
165             if ( force || !metadataFile.exists() || isOutOfDate( policy, metadataFile ) )
166             {
167                 getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, metadataFile,
168                                        policy, force );
169
170                 mergeMetadataFiles( target, metadataFile );
171             }
172         }
173         else
174         {
175             Artifact artifact = null;
176             try
177             {
178                 artifact = defaultArtifactDiscoverer.buildArtifact( path );
179             }
180             catch ( DiscovererException e )
181             {
182                 getLogger().debug( "Failed to build artifact using default layout with message: " + e.getMessage() );
183             }
184
185             if ( artifact == null )
186             {
187                 try
188                 {
189                     artifact = legacyArtifactDiscoverer.buildArtifact( path );
190                 }
191                 catch ( DiscovererException e )
192                 {
193                     getLogger().debug( "Failed to build artifact using legacy layout with message: " + e.getMessage() );
194                 }
195             }
196
197             if ( artifact != null )
198             {
199                 ArtifactRepository artifactRepository = repository.getRepository();
200
201                 // we use the release policy for tracking failures, but only check for updates on snapshots
202                 // also, we don't look for updates on timestamp snapshot files, only non-unique-version ones
203                 policy = artifact.isSnapshot() ? artifactRepository.getSnapshots() : artifactRepository.getReleases();
204
205                 boolean needsUpdate = false;
206                 if ( artifact.getVersion().endsWith( "-SNAPSHOT" ) && isOutOfDate( policy, target ) )
207                 {
208                     needsUpdate = true;
209                 }
210
211                 if ( needsUpdate || force || !target.exists() )
212                 {
213                     getFileFromRepository( artifactRepository.pathOf( artifact ), repository,
214                                            managedRepository.getBasedir(), wagonProxy, target, policy, force );
215                 }
216             }
217             else
218             {
219                 // Some other unknown file in the repository, proxy as is
220                 if ( force || !target.exists() )
221                 {
222                     policy = repository.getRepository().getReleases();
223                     getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, target, policy,
224                                            force );
225                 }
226             }
227         }
228
229         if ( target.exists() )
230         {
231             // in case it previously failed and we've since found it
232             repository.clearFailure( path );
233         }
234     }
235
236     private void mergeMetadataFiles( File target, File metadataFile )
237         throws ProxyException
238     {
239         MetadataXpp3Reader reader = new MetadataXpp3Reader();
240         if ( metadataFile.exists() )
241         {
242             Metadata metadata = null;
243             if ( target.exists() )
244             {
245                 FileReader fileReader = null;
246                 try
247                 {
248                     fileReader = new FileReader( target );
249                     metadata = reader.read( fileReader );
250                 }
251                 catch ( XmlPullParserException e )
252                 {
253                     throw new ProxyException( "Unable to parse existing metadata: " + e.getMessage(), e );
254                 }
255                 catch ( IOException e )
256                 {
257                     throw new ProxyException( "Unable to read existing metadata: " + e.getMessage(), e );
258                 }
259                 finally
260                 {
261                     IOUtil.close( fileReader );
262                 }
263             }
264
265             FileReader fileReader = null;
266             boolean changed = false;
267             try
268             {
269                 fileReader = new FileReader( metadataFile );
270                 Metadata newMetadata = reader.read( fileReader );
271
272                 if ( metadata != null )
273                 {
274                     changed = metadata.merge( newMetadata );
275                 }
276                 else
277                 {
278                     metadata = newMetadata;
279                     changed = true;
280                 }
281             }
282             catch ( IOException e )
283             {
284                 // ignore the merged file
285                 getLogger().warn( "Unable to read new metadata: " + e.getMessage() );
286             }
287             catch ( XmlPullParserException e )
288             {
289                 // ignore the merged file
290                 getLogger().warn( "Unable to parse new metadata: " + e.getMessage() );
291             }
292             finally
293             {
294                 IOUtil.close( fileReader );
295             }
296
297             if ( changed )
298             {
299                 FileWriter fileWriter = null;
300                 try
301                 {
302                     fileWriter = new FileWriter( target );
303                     new MetadataXpp3Writer().write( fileWriter, metadata );
304                 }
305                 catch ( IOException e )
306                 {
307                     getLogger().warn( "Unable to store new metadata: " + e.getMessage() );
308                 }
309                 finally
310                 {
311                     IOUtil.close( fileWriter );
312                 }
313             }
314         }
315     }
316
317     private void getFileFromRepository( String path, ProxiedArtifactRepository repository, String repositoryCachePath,
318                                         ProxyInfo httpProxy, File target, ArtifactRepositoryPolicy policy,
319                                         boolean force )
320         throws ProxyException
321     {
322         if ( !policy.isEnabled() )
323         {
324             getLogger().debug( "Skipping disabled repository " + repository.getName() );
325             return;
326         }
327
328         Map checksums = null;
329         Wagon wagon = null;
330
331         File temp = new File( target.getAbsolutePath() + ".tmp" );
332         temp.deleteOnExit();
333
334         boolean connected = false;
335         try
336         {
337             String protocol = repository.getRepository().getProtocol();
338             wagon = (Wagon) wagons.get( protocol );
339             if ( wagon == null )
340             {
341                 throw new ProxyException( "Unsupported remote protocol: " + protocol );
342             }
343
344             //@todo configure wagon (ssh settings, etc)
345
346             checksums = prepareChecksumListeners( wagon );
347
348             connected = connectToRepository( wagon, repository, httpProxy );
349             if ( connected )
350             {
351                 int tries = 0;
352                 boolean success;
353
354                 do
355                 {
356                     tries++;
357
358                     getLogger().debug( "Trying " + path + " from " + repository.getName() + "..." );
359
360                     boolean downloaded = true;
361                     if ( force || !target.exists() )
362                     {
363                         wagon.get( path, temp );
364                     }
365                     else
366                     {
367                         downloaded = wagon.getIfNewer( path, temp, target.lastModified() );
368                     }
369
370                     if ( downloaded )
371                     {
372                         success = checkChecksum( checksums, path, wagon, repositoryCachePath );
373
374                         if ( tries > 1 && !success )
375                         {
376                             processRepositoryFailure( repository,
377                                                       "Checksum failures occurred while downloading " + path, path,
378                                                       policy );
379                             return;
380                         }
381                     }
382                     else
383                     {
384                         // getIfNewer determined we were up to date
385                         success = true;
386                     }
387                 }
388                 while ( !success );
389
390                 // temp won't exist if we called getIfNewer and it was older, but its still a successful return
391                 if ( temp.exists() )
392                 {
393                     moveTempToTarget( temp, target );
394                 }
395             }
396             //try next repository
397         }
398         catch ( TransferFailedException e )
399         {
400             processRepositoryFailure( repository, e, path, policy );
401         }
402         catch ( AuthorizationException e )
403         {
404             processRepositoryFailure( repository, e, path, policy );
405         }
406         catch ( ResourceDoesNotExistException e )
407         {
408             // hard failure setting doesn't affect "not found".
409             getLogger().debug( "Artifact not found in repository: " + repository.getName() + ": " + e.getMessage() );
410         }
411         finally
412         {
413             temp.delete();
414
415             if ( wagon != null && checksums != null )
416             {
417                 releaseChecksumListeners( wagon, checksums );
418             }
419
420             if ( connected )
421             {
422                 disconnectWagon( wagon );
423             }
424         }
425     }
426
427     private static boolean isOutOfDate( ArtifactRepositoryPolicy policy, File target )
428     {
429         return policy != null && policy.checkOutOfDate( new Date( target.lastModified() ) );
430     }
431
432     /**
433      * Used to add checksum observers as transfer listeners to the wagonManager object
434      *
435      * @param wagon the wagonManager object to use the checksum with
436      * @return map of ChecksumObservers added into the wagonManager transfer listeners
437      */
438     private Map prepareChecksumListeners( Wagon wagon )
439     {
440         Map checksums = new LinkedHashMap();
441         try
442         {
443             ChecksumObserver checksum = new ChecksumObserver( "SHA-1" );
444             wagon.addTransferListener( checksum );
445             checksums.put( "sha1", checksum );
446
447             checksum = new ChecksumObserver( "MD5" );
448             wagon.addTransferListener( checksum );
449             checksums.put( "md5", checksum );
450         }
451         catch ( NoSuchAlgorithmException e )
452         {
453             getLogger().info( "An error occurred while preparing checksum observers", e );
454         }
455         return checksums;
456     }
457
458     private void releaseChecksumListeners( Wagon wagon, Map checksumMap )
459     {
460         for ( Iterator checksums = checksumMap.values().iterator(); checksums.hasNext(); )
461         {
462             ChecksumObserver listener = (ChecksumObserver) checksums.next();
463             wagon.removeTransferListener( listener );
464         }
465     }
466
467     private boolean connectToRepository( Wagon wagon, ProxiedArtifactRepository repository, ProxyInfo httpProxy )
468     {
469         boolean connected = false;
470         try
471         {
472             ArtifactRepository artifactRepository = repository.getRepository();
473             Repository wagonRepository = new Repository( artifactRepository.getId(), artifactRepository.getUrl() );
474             if ( repository.isUseNetworkProxy() && httpProxy != null )
475             {
476                 wagon.connect( wagonRepository, httpProxy );
477             }
478             else
479             {
480                 wagon.connect( wagonRepository );
481             }
482             connected = true;
483         }
484         catch ( ConnectionException e )
485         {
486             getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
487         }
488         catch ( AuthenticationException e )
489         {
490             getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
491         }
492
493         return connected;
494     }
495
496     private boolean checkChecksum( Map checksumMap, String path, Wagon wagon, String repositoryCachePath )
497         throws ProxyException
498     {
499         releaseChecksumListeners( wagon, checksumMap );
500
501         boolean correctChecksum = false;
502
503         boolean allNotFound = true;
504
505         for ( Iterator i = checksumMap.keySet().iterator(); i.hasNext() && !correctChecksum; )
506         {
507             String checksumExt = (String) i.next();
508             ChecksumObserver checksum = (ChecksumObserver) checksumMap.get( checksumExt );
509             String checksumPath = path + "." + checksumExt;
510             File checksumFile = new File( repositoryCachePath, checksumPath );
511
512             File tempChecksumFile = new File( checksumFile.getAbsolutePath() + ".tmp" );
513             tempChecksumFile.deleteOnExit();
514
515             try
516             {
517                 wagon.get( checksumPath, tempChecksumFile );
518
519                 allNotFound = false;
520
521                 String remoteChecksum = DigestUtils.cleanChecksum( FileUtils.fileRead( tempChecksumFile ),
522                                                                    checksumExt.toUpperCase(),
523                                                                    path.substring( path.lastIndexOf( '/' ) ) );
524
525                 String actualChecksum = checksum.getActualChecksum().toUpperCase();
526                 remoteChecksum = remoteChecksum.toUpperCase();
527
528                 if ( remoteChecksum.equals( actualChecksum ) )
529                 {
530                     moveTempToTarget( tempChecksumFile, checksumFile );
531
532                     correctChecksum = true;
533                 }
534                 else
535                 {
536                     getLogger().warn(
537                         "The checksum '" + actualChecksum + "' did not match the remote value: " + remoteChecksum );
538                 }
539             }
540             catch ( TransferFailedException e )
541             {
542                 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
543                                   e );
544                 // do nothing try the next checksum
545
546                 allNotFound = false;
547             }
548             catch ( ResourceDoesNotExistException e )
549             {
550                 getLogger().debug( "The checksum did not exist: " + checksumPath, e );
551                 // do nothing try the next checksum
552                 // remove it if it is present locally in case there is an old incorrect one
553                 if ( checksumFile.exists() )
554                 {
555                     checksumFile.delete();
556                 }
557             }
558             catch ( AuthorizationException e )
559             {
560                 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
561                                   e );
562                 // do nothing try the next checksum
563
564                 allNotFound = false;
565             }
566             catch ( IOException e )
567             {
568                 getLogger().warn( "An error occurred while reading the temporary checksum file.", e );
569                 // do nothing try the next checksum
570
571                 allNotFound = false;
572             }
573             catch ( DigesterException e )
574             {
575                 getLogger().warn( "The checksum was invalid: " + checksumPath + ": " + e.getMessage(), e );
576                 // do nothing try the next checksum
577
578                 allNotFound = false;
579             }
580             finally
581             {
582                 tempChecksumFile.delete();
583             }
584         }
585         return correctChecksum || allNotFound;
586     }
587
588     /**
589      * Used to move the temporary file to its real destination.  This is patterned from the way WagonManager handles
590      * its downloaded files.
591      *
592      * @param temp   The completed download file
593      * @param target The final location of the downloaded file
594      * @throws ProxyException when the temp file cannot replace the target file
595      */
596     private void moveTempToTarget( File temp, File target )
597         throws ProxyException
598     {
599         if ( target.exists() && !target.delete() )
600         {
601             throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
602         }
603
604         if ( !temp.renameTo( target ) )
605         {
606             getLogger().warn( "Unable to rename tmp file to its final name... resorting to copy command." );
607
608             try
609             {
610                 FileUtils.copyFile( temp, target );
611             }
612             catch ( IOException e )
613             {
614                 throw new ProxyException( "Cannot copy tmp file to its final location", e );
615             }
616             finally
617             {
618                 temp.delete();
619             }
620         }
621     }
622
623     /**
624      * Used to disconnect the wagonManager from its repository
625      *
626      * @param wagon the connected wagonManager object
627      */
628     private void disconnectWagon( Wagon wagon )
629     {
630         try
631         {
632             wagon.disconnect();
633         }
634         catch ( ConnectionException e )
635         {
636             getLogger().error( "Problem disconnecting from wagonManager - ignoring: " + e.getMessage() );
637         }
638     }
639
640     private void processRepositoryFailure( ProxiedArtifactRepository repository, Throwable t, String path,
641                                            ArtifactRepositoryPolicy policy )
642         throws ProxyException
643     {
644         repository.addFailure( path, policy );
645
646         String message = t.getMessage();
647         if ( repository.isHardFail() )
648         {
649             repository.addFailure( path, policy );
650             throw new ProxyException(
651                 "An error occurred in hardfailing repository " + repository.getName() + "...\n    " + message, t );
652         }
653
654         getLogger().warn( "Skipping repository " + repository.getName() + ": " + message );
655         getLogger().debug( "Cause", t );
656     }
657
658     private void processRepositoryFailure( ProxiedArtifactRepository repository, String message, String path,
659                                            ArtifactRepositoryPolicy policy )
660         throws ProxyException
661     {
662         repository.addFailure( path, policy );
663
664         processCachedRepositoryFailure( repository, message );
665     }
666
667     private void processCachedRepositoryFailure( ProxiedArtifactRepository repository, String message )
668         throws ProxyException
669     {
670         if ( repository.isHardFail() )
671         {
672             throw new ProxyException(
673                 "An error occurred in hardfailing repository " + repository.getName() + "...\n    " + message );
674         }
675
676         getLogger().warn( "Skipping repository " + repository.getName() + ": " + message );
677     }
678 }