]> source.dussan.org Git - archiva.git/blob
298a586a35849efd38da6bf97a8855c9e3f835bc
[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.repository.discovery.ArtifactDiscoverer;
23 import org.apache.maven.repository.discovery.DiscovererException;
24 import org.apache.maven.wagon.ConnectionException;
25 import org.apache.maven.wagon.ResourceDoesNotExistException;
26 import org.apache.maven.wagon.TransferFailedException;
27 import org.apache.maven.wagon.Wagon;
28 import org.apache.maven.wagon.authentication.AuthenticationException;
29 import org.apache.maven.wagon.authorization.AuthorizationException;
30 import org.apache.maven.wagon.observers.ChecksumObserver;
31 import org.apache.maven.wagon.proxy.ProxyInfo;
32 import org.apache.maven.wagon.repository.Repository;
33 import org.codehaus.plexus.logging.AbstractLogEnabled;
34 import org.codehaus.plexus.util.FileUtils;
35
36 import java.io.File;
37 import java.io.IOException;
38 import java.security.NoSuchAlgorithmException;
39 import java.util.Date;
40 import java.util.Iterator;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Map;
44
45 /**
46  * An implementation of the proxy handler.
47  *
48  * @author <a href="mailto:brett@apache.org">Brett Porter</a>
49  * @plexus.component
50  * @todo this currently duplicates a lot of the wagon manager, and doesn't do things like snapshot resolution, etc.
51  * The checksum handling is inconsistent with that of the wagon manager.
52  * Should we have a more artifact based one? This will merge metadata so should behave correctly, and it is able to
53  * correct some limitations of the wagon manager (eg, it can retrieve newer SNAPSHOT files without metadata)
54  */
55 public class DefaultProxyRequestHandler
56     extends AbstractLogEnabled
57     implements ProxyRequestHandler
58 {
59     /**
60      * @plexus.requirement role-hint="default"
61      * @todo use a map, and have priorities in them
62      */
63     private ArtifactDiscoverer defaultArtifactDiscoverer;
64
65     /**
66      * @plexus.requirement role-hint="legacy"
67      */
68     private ArtifactDiscoverer legacyArtifactDiscoverer;
69
70     /**
71      * @plexus.requirement role="org.apache.maven.wagon.Wagon"
72      */
73     private Map/*<String,Wagon>*/ wagons;
74
75     public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository )
76         throws ProxyException, ResourceDoesNotExistException
77     {
78         return get( path, proxiedRepositories, managedRepository, null );
79     }
80
81     public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository, ProxyInfo wagonProxy )
82         throws ProxyException, ResourceDoesNotExistException
83     {
84         // TODO! this will prove wrong for metadata and snapshots, let tests bring it out
85         //@todo use wagonManager for cache use file:// as URL
86         File cachedFile = new File( managedRepository.getBasedir(), path );
87         if ( !cachedFile.exists() )
88         {
89             cachedFile = getAlways( path, proxiedRepositories, managedRepository, wagonProxy );
90         }
91         return cachedFile;
92     }
93
94
95     public File getAlways( String path, List proxiedRepositories, ArtifactRepository managedRepository )
96         throws ProxyException, ResourceDoesNotExistException
97     {
98         return getAlways( path, proxiedRepositories, managedRepository, null );
99     }
100
101     public File getAlways( String path, List proxiedRepositories, ArtifactRepository managedRepository,
102                            ProxyInfo wagonProxy )
103         throws ResourceDoesNotExistException, ProxyException
104     {
105         File target = new File( managedRepository.getBasedir(), path );
106
107         for ( Iterator i = proxiedRepositories.iterator(); i.hasNext(); )
108         {
109             ProxiedArtifactRepository repository = (ProxiedArtifactRepository) i.next();
110
111             if ( repository.isCachedFailure( path ) )
112             {
113                 processRepositoryFailure( repository, "Cached failure found" );
114             }
115             else
116             {
117                 try
118                 {
119                     get( path, target, repository, managedRepository, wagonProxy );
120
121                     if ( !target.exists() )
122                     {
123                         repository.addFailure( path );
124                     }
125                     else
126                     {
127                         // in case it previously failed and we've since found it
128                         repository.clearFailure( path );
129                     }
130                 }
131                 catch ( ProxyException e )
132                 {
133                     repository.addFailure( path );
134                     throw e;
135                 }
136             }
137         }
138
139         if ( !target.exists() )
140         {
141             throw new ResourceDoesNotExistException( "Could not find " + path + " in any of the repositories." );
142         }
143
144         return target;
145     }
146
147     private void get( String path, File target, ProxiedArtifactRepository repository,
148                       ArtifactRepository managedRepository, ProxyInfo wagonProxy )
149         throws ProxyException
150     {
151         if ( path.endsWith( ".md5" ) || path.endsWith( ".sha1" ) )
152         {
153             // always read from the managed repository, no need to make remote request
154         }
155         else if ( path.endsWith( "maven-metadata.xml" ) )
156         {
157             // TODO: this is not always!
158             if ( !target.exists() || isOutOfDate( repository.getRepository().getReleases(), target ) )
159             {
160                 getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, target );
161             }
162         }
163         else
164         {
165             Artifact artifact = null;
166             try
167             {
168                 artifact = defaultArtifactDiscoverer.buildArtifact( path );
169             }
170             catch ( DiscovererException e )
171             {
172                 getLogger().debug( "Failed to build artifact using default layout with message: " + e.getMessage() );
173             }
174
175             if ( artifact == null )
176             {
177                 try
178                 {
179                     artifact = legacyArtifactDiscoverer.buildArtifact( path );
180                 }
181                 catch ( DiscovererException e )
182                 {
183                     getLogger().debug( "Failed to build artifact using legacy layout with message: " + e.getMessage() );
184                 }
185             }
186
187             if ( artifact != null )
188             {
189                 getArtifactFromRepository( artifact, repository, managedRepository, wagonProxy, target );
190             }
191             else
192             {
193                 // Some other unknown file in the repository, proxy as is
194                 // TODO: this is not always!
195                 if ( !target.exists() )
196                 {
197                     getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, target );
198                 }
199             }
200         }
201     }
202
203     private void getFileFromRepository( String path, ProxiedArtifactRepository repository, String repositoryCachePath,
204                                         ProxyInfo httpProxy, File target )
205         throws ProxyException
206     {
207         boolean connected = false;
208         Map checksums = null;
209         Wagon wagon = null;
210
211         try
212         {
213             String protocol = repository.getRepository().getProtocol();
214             wagon = (Wagon) wagons.get( protocol );
215             if ( wagon == null )
216             {
217                 throw new ProxyException( "Unsupported remote protocol: " + protocol );
218             }
219
220             //@todo configure wagon (ssh settings, etc)
221
222             checksums = prepareChecksumListeners( wagon );
223
224             connected = connectToRepository( wagon, repository, httpProxy );
225             if ( connected )
226             {
227                 File temp = new File( target.getAbsolutePath() + ".tmp" );
228                 temp.deleteOnExit();
229
230                 int tries = 0;
231                 boolean success;
232
233                 do
234                 {
235                     tries++;
236
237                     getLogger().debug( "Trying " + path + " from " + repository.getName() + "..." );
238
239                     if ( !target.exists() )
240                     {
241                         wagon.get( path, temp );
242                     }
243                     else
244                     {
245                         wagon.getIfNewer( path, temp, target.lastModified() );
246                     }
247
248                     success = checkChecksum( checksums, path, wagon, repositoryCachePath );
249
250                     if ( tries > 1 && !success )
251                     {
252                         processRepositoryFailure( repository, "Checksum failures occurred while downloading " + path );
253                         return;
254                     }
255
256                     // temp won't exist if we called getIfNewer and it was older, but its still a successful return
257                     if ( temp.exists() )
258                     {
259                         moveTempToTarget( temp, target );
260                     }
261                 }
262                 while ( !success );
263             }
264             //try next repository
265         }
266         catch ( TransferFailedException e )
267         {
268             processRepositoryFailure( repository, e );
269         }
270         catch ( AuthorizationException e )
271         {
272             processRepositoryFailure( repository, e );
273         }
274         catch ( ResourceDoesNotExistException e )
275         {
276             // hard failure setting doesn't affect "not found".
277             getLogger().debug( "Artifact not found in repository: " + repository.getName() + ": " + e.getMessage() );
278         }
279         finally
280         {
281             if ( wagon != null && checksums != null )
282             {
283                 releaseChecksumListeners( wagon, checksums );
284             }
285
286             if ( connected )
287             {
288                 disconnectWagon( wagon );
289             }
290         }
291     }
292
293     private static boolean isOutOfDate( ArtifactRepositoryPolicy policy, File target )
294     {
295         return policy != null && policy.checkOutOfDate( new Date( target.lastModified() ) );
296     }
297
298     /**
299      * Used to add checksum observers as transfer listeners to the wagonManager object
300      *
301      * @param wagon the wagonManager object to use the checksum with
302      * @return map of ChecksumObservers added into the wagonManager transfer listeners
303      */
304     private Map prepareChecksumListeners( Wagon wagon )
305     {
306         Map checksums = new LinkedHashMap();
307         try
308         {
309             ChecksumObserver checksum = new ChecksumObserver( "SHA-1" );
310             wagon.addTransferListener( checksum );
311             checksums.put( "sha1", checksum );
312
313             checksum = new ChecksumObserver( "MD5" );
314             wagon.addTransferListener( checksum );
315             checksums.put( "md5", checksum );
316         }
317         catch ( NoSuchAlgorithmException e )
318         {
319             getLogger().info( "An error occurred while preparing checksum observers", e );
320         }
321         return checksums;
322     }
323
324     private void releaseChecksumListeners( Wagon wagon, Map checksumMap )
325     {
326         for ( Iterator checksums = checksumMap.values().iterator(); checksums.hasNext(); )
327         {
328             ChecksumObserver listener = (ChecksumObserver) checksums.next();
329             wagon.removeTransferListener( listener );
330         }
331     }
332
333     private boolean connectToRepository( Wagon wagon, ProxiedArtifactRepository repository, ProxyInfo httpProxy )
334     {
335         boolean connected = false;
336         try
337         {
338             ArtifactRepository artifactRepository = repository.getRepository();
339             Repository wagonRepository = new Repository( artifactRepository.getId(), artifactRepository.getUrl() );
340             if ( repository.isUseNetworkProxy() && httpProxy != null )
341             {
342                 wagon.connect( wagonRepository, httpProxy );
343             }
344             else
345             {
346                 wagon.connect( wagonRepository );
347             }
348             connected = true;
349         }
350         catch ( ConnectionException e )
351         {
352             getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
353         }
354         catch ( AuthenticationException e )
355         {
356             getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
357         }
358
359         return connected;
360     }
361
362     private boolean checkChecksum( Map checksumMap, String path, Wagon wagon, String repositoryCachePath )
363         throws ProxyException
364     {
365         releaseChecksumListeners( wagon, checksumMap );
366         for ( Iterator checksums = checksumMap.keySet().iterator(); checksums.hasNext(); )
367         {
368             String checksumExt = (String) checksums.next();
369             ChecksumObserver checksum = (ChecksumObserver) checksumMap.get( checksumExt );
370             String checksumPath = path + "." + checksumExt;
371             File checksumFile = new File( repositoryCachePath, checksumPath );
372
373             try
374             {
375                 File tempChecksumFile = new File( checksumFile.getAbsolutePath() + ".tmp" );
376
377                 wagon.get( checksumPath, tempChecksumFile );
378
379                 String remoteChecksum = FileUtils.fileRead( tempChecksumFile ).trim();
380                 if ( remoteChecksum.indexOf( ' ' ) > 0 )
381                 {
382                     remoteChecksum = remoteChecksum.substring( 0, remoteChecksum.indexOf( ' ' ) );
383                 }
384
385                 String actualChecksum = checksum.getActualChecksum().toUpperCase();
386                 remoteChecksum = remoteChecksum.toUpperCase();
387
388                 boolean checksumCheck;
389                 if ( remoteChecksum.equals( actualChecksum ) )
390                 {
391                     moveTempToTarget( tempChecksumFile, checksumFile );
392
393                     checksumCheck = true;
394                 }
395                 else
396                 {
397                     getLogger().warn(
398                         "The checksum '" + actualChecksum + "' did not match the remote value: " + remoteChecksum );
399                     checksumCheck = false;
400                 }
401                 return checksumCheck;
402             }
403             catch ( TransferFailedException e )
404             {
405                 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
406                                   e );
407                 // do nothing try the next checksum
408             }
409             catch ( ResourceDoesNotExistException e )
410             {
411                 getLogger().debug( "The checksum did not exist: " + checksumPath, e );
412                 // do nothing try the next checksum
413             }
414             catch ( AuthorizationException e )
415             {
416                 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
417                                   e );
418                 // do nothing try the next checksum
419             }
420             catch ( IOException e )
421             {
422                 getLogger().warn( "An error occurred while reading the temporary checksum file.", e );
423                 return false;
424             }
425         }
426
427         getLogger().debug( "No remote checksums available." );
428
429         return true;
430     }
431
432     /**
433      * Used to move the temporary file to its real destination.  This is patterned from the way WagonManager handles
434      * its downloaded files.
435      *
436      * @param temp   The completed download file
437      * @param target The final location of the downloaded file
438      * @throws ProxyException when the temp file cannot replace the target file
439      */
440     private void moveTempToTarget( File temp, File target )
441         throws ProxyException
442     {
443         if ( target.exists() && !target.delete() )
444         {
445             throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
446         }
447
448         if ( !temp.renameTo( target ) )
449         {
450             getLogger().warn( "Unable to rename tmp file to its final name... resorting to copy command." );
451
452             try
453             {
454                 FileUtils.copyFile( temp, target );
455             }
456             catch ( IOException e )
457             {
458                 throw new ProxyException( "Cannot copy tmp file to its final location", e );
459             }
460             finally
461             {
462                 temp.delete();
463             }
464         }
465     }
466
467     /**
468      * Used to disconnect the wagonManager from its repository
469      *
470      * @param wagon the connected wagonManager object
471      */
472     private void disconnectWagon( Wagon wagon )
473     {
474         try
475         {
476             wagon.disconnect();
477         }
478         catch ( ConnectionException e )
479         {
480             getLogger().error( "Problem disconnecting from wagonManager - ignoring: " + e.getMessage() );
481         }
482     }
483
484     private void processRepositoryFailure( ProxiedArtifactRepository repository, Throwable t )
485         throws ProxyException
486     {
487         if ( repository.isHardFail() )
488         {
489             throw new ProxyException(
490                 "An error occurred in hardfailing repository " + repository.getName() + "...\n    " + t.getMessage(),
491                 t );
492         }
493         else
494         {
495             getLogger().warn( "Skipping repository " + repository.getName() + ": " + t.getMessage() );
496             getLogger().debug( "Cause", t );
497         }
498     }
499
500     private void processRepositoryFailure( ProxiedArtifactRepository repository, String message )
501         throws ProxyException
502     {
503         if ( repository.isHardFail() )
504         {
505             throw new ProxyException(
506                 "An error occurred in hardfailing repository " + repository.getName() + "...\n    " + message );
507         }
508         else
509         {
510             getLogger().warn( "Skipping repository " + repository.getName() + ": " + message );
511         }
512     }
513
514     private void getArtifactFromRepository( Artifact artifact, ProxiedArtifactRepository repository,
515                                             ArtifactRepository managedRepository, ProxyInfo httpProxy, File remoteFile )
516         throws ProxyException
517     {
518         ArtifactRepository artifactRepository = repository.getRepository();
519         ArtifactRepositoryPolicy policy =
520             artifact.isSnapshot() ? artifactRepository.getSnapshots() : artifactRepository.getReleases();
521
522         if ( !policy.isEnabled() )
523         {
524             getLogger().debug( "Skipping disabled repository " + repository.getName() );
525         }
526         else
527         {
528             getLogger().debug( "Trying repository " + repository.getName() );
529             // Don't use releases policy, we don't want to perform updates on them (only metadata, as used earlier)
530             // TODO: this is not always!
531             if ( !remoteFile.exists() || isOutOfDate( policy, remoteFile ) )
532             {
533                 getFileFromRepository( artifactRepository.pathOf( artifact ), repository,
534                                        managedRepository.getBasedir(), httpProxy, remoteFile );
535             }
536             getLogger().debug( "  Artifact resolved" );
537
538             artifact.setResolved( true );
539         }
540     }
541
542 }