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