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