1 package org.apache.maven.repository.proxy;
4 * Copyright 2005-2006 The Apache Software Foundation.
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
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;
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;
48 * An implementation of the proxy handler.
50 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
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)
58 public class DefaultProxyRequestHandler
59 extends AbstractLogEnabled
60 implements ProxyRequestHandler
63 * @plexus.requirement role-hint="default"
64 * @todo use a map, and have priorities in them
66 private ArtifactDiscoverer defaultArtifactDiscoverer;
69 * @plexus.requirement role-hint="legacy"
71 private ArtifactDiscoverer legacyArtifactDiscoverer;
74 * @plexus.requirement role="org.apache.maven.wagon.Wagon"
76 private Map/*<String,Wagon>*/ wagons;
79 * @plexus.requirement role="org.apache.maven.repository.digest.Digester"
81 private Map/*<String,Digester>*/ digesters;
83 public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository )
84 throws ProxyException, ResourceDoesNotExistException
86 return get( path, proxiedRepositories, managedRepository, null );
89 public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository, ProxyInfo wagonProxy )
90 throws ProxyException, ResourceDoesNotExistException
92 return get( managedRepository, path, proxiedRepositories, wagonProxy, false );
95 public File getAlways( String path, List proxiedRepositories, ArtifactRepository managedRepository )
96 throws ProxyException, ResourceDoesNotExistException
98 return getAlways( path, proxiedRepositories, managedRepository, null );
101 public File getAlways( String path, List proxiedRepositories, ArtifactRepository managedRepository,
102 ProxyInfo wagonProxy )
103 throws ResourceDoesNotExistException, ProxyException
105 return get( managedRepository, path, proxiedRepositories, wagonProxy, true );
108 private File get( ArtifactRepository managedRepository, String path, List proxiedRepositories, ProxyInfo wagonProxy,
110 throws ProxyException, ResourceDoesNotExistException
112 File target = new File( managedRepository.getBasedir(), path );
114 for ( Iterator i = proxiedRepositories.iterator(); i.hasNext(); )
116 ProxiedArtifactRepository repository = (ProxiedArtifactRepository) i.next();
118 if ( !force && repository.isCachedFailure( path ) )
120 processCachedRepositoryFailure( repository, "Cached failure found for: " + path );
124 get( path, target, repository, managedRepository, wagonProxy, force );
128 if ( !target.exists() )
130 throw new ResourceDoesNotExistException( "Could not find " + path + " in any of the repositories." );
136 private void get( String path, File target, ProxiedArtifactRepository repository,
137 ArtifactRepository managedRepository, ProxyInfo wagonProxy, boolean force )
138 throws ProxyException
140 ArtifactRepositoryPolicy policy;
142 if ( path.endsWith( ".md5" ) || path.endsWith( ".sha1" ) )
144 // always read from the managed repository, no need to make remote request
146 else if ( path.endsWith( "maven-metadata.xml" ) )
148 // TODO: merge the metadata!
149 policy = repository.getRepository().getReleases();
150 if ( force || !target.exists() || isOutOfDate( policy, target ) )
152 getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, target, policy,
158 Artifact artifact = null;
161 artifact = defaultArtifactDiscoverer.buildArtifact( path );
163 catch ( DiscovererException e )
165 getLogger().debug( "Failed to build artifact using default layout with message: " + e.getMessage() );
168 if ( artifact == null )
172 artifact = legacyArtifactDiscoverer.buildArtifact( path );
174 catch ( DiscovererException e )
176 getLogger().debug( "Failed to build artifact using legacy layout with message: " + e.getMessage() );
180 if ( artifact != null )
182 ArtifactRepository artifactRepository = repository.getRepository();
183 policy = artifact.isSnapshot() ? artifactRepository.getSnapshots() : artifactRepository.getReleases();
185 if ( !policy.isEnabled() )
187 getLogger().debug( "Skipping disabled repository " + repository.getName() );
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 ) )
194 getFileFromRepository( artifactRepository.pathOf( artifact ), repository,
195 managedRepository.getBasedir(), wagonProxy, target, policy, force );
201 // Some other unknown file in the repository, proxy as is
202 if ( force || !target.exists() )
204 policy = repository.getRepository().getReleases();
205 getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, target, policy,
211 if ( target.exists() )
213 // in case it previously failed and we've since found it
214 repository.clearFailure( path );
218 private void getFileFromRepository( String path, ProxiedArtifactRepository repository, String repositoryCachePath,
219 ProxyInfo httpProxy, File target, ArtifactRepositoryPolicy policy,
221 throws ProxyException
223 boolean connected = false;
224 Map checksums = null;
227 File temp = new File( target.getAbsolutePath() + ".tmp" );
232 String protocol = repository.getRepository().getProtocol();
233 wagon = (Wagon) wagons.get( protocol );
236 throw new ProxyException( "Unsupported remote protocol: " + protocol );
239 //@todo configure wagon (ssh settings, etc)
241 checksums = prepareChecksumListeners( wagon );
243 connected = connectToRepository( wagon, repository, httpProxy );
253 getLogger().debug( "Trying " + path + " from " + repository.getName() + "..." );
255 if ( force || !target.exists() )
257 wagon.get( path, temp );
261 wagon.getIfNewer( path, temp, target.lastModified() );
264 success = checkChecksum( checksums, path, wagon, repositoryCachePath );
266 if ( tries > 1 && !success )
268 processRepositoryFailure( repository, "Checksum failures occurred while downloading " + path,
275 // temp won't exist if we called getIfNewer and it was older, but its still a successful return
278 moveTempToTarget( temp, target );
281 //try next repository
283 catch ( TransferFailedException e )
285 processRepositoryFailure( repository, e, path, policy );
287 catch ( AuthorizationException e )
289 processRepositoryFailure( repository, e, path, policy );
291 catch ( ResourceDoesNotExistException e )
293 // hard failure setting doesn't affect "not found".
294 getLogger().debug( "Artifact not found in repository: " + repository.getName() + ": " + e.getMessage() );
300 if ( wagon != null && checksums != null )
302 releaseChecksumListeners( wagon, checksums );
307 disconnectWagon( wagon );
312 private static boolean isOutOfDate( ArtifactRepositoryPolicy policy, File target )
314 return policy != null && policy.checkOutOfDate( new Date( target.lastModified() ) );
318 * Used to add checksum observers as transfer listeners to the wagonManager object
320 * @param wagon the wagonManager object to use the checksum with
321 * @return map of ChecksumObservers added into the wagonManager transfer listeners
323 private Map prepareChecksumListeners( Wagon wagon )
325 Map checksums = new LinkedHashMap();
328 ChecksumObserver checksum = new ChecksumObserver( "SHA-1" );
329 wagon.addTransferListener( checksum );
330 checksums.put( "sha1", checksum );
332 checksum = new ChecksumObserver( "MD5" );
333 wagon.addTransferListener( checksum );
334 checksums.put( "md5", checksum );
336 catch ( NoSuchAlgorithmException e )
338 getLogger().info( "An error occurred while preparing checksum observers", e );
343 private void releaseChecksumListeners( Wagon wagon, Map checksumMap )
345 for ( Iterator checksums = checksumMap.values().iterator(); checksums.hasNext(); )
347 ChecksumObserver listener = (ChecksumObserver) checksums.next();
348 wagon.removeTransferListener( listener );
352 private boolean connectToRepository( Wagon wagon, ProxiedArtifactRepository repository, ProxyInfo httpProxy )
354 boolean connected = false;
357 ArtifactRepository artifactRepository = repository.getRepository();
358 Repository wagonRepository = new Repository( artifactRepository.getId(), artifactRepository.getUrl() );
359 if ( repository.isUseNetworkProxy() && httpProxy != null )
361 wagon.connect( wagonRepository, httpProxy );
365 wagon.connect( wagonRepository );
369 catch ( ConnectionException e )
371 getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
373 catch ( AuthenticationException e )
375 getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
381 private boolean checkChecksum( Map checksumMap, String path, Wagon wagon, String repositoryCachePath )
382 throws ProxyException
384 releaseChecksumListeners( wagon, checksumMap );
386 boolean correctChecksum = false;
388 boolean allNotFound = true;
390 for ( Iterator i = checksumMap.keySet().iterator(); i.hasNext() && !correctChecksum; )
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 );
397 File tempChecksumFile = new File( checksumFile.getAbsolutePath() + ".tmp" );
398 tempChecksumFile.deleteOnExit();
402 wagon.get( checksumPath, tempChecksumFile );
406 String remoteChecksum = DigestUtils.cleanChecksum( FileUtils.fileRead( tempChecksumFile ),
407 checksumExt.toUpperCase(),
408 path.substring( path.lastIndexOf( '/' ) ) );
410 String actualChecksum = checksum.getActualChecksum().toUpperCase();
411 remoteChecksum = remoteChecksum.toUpperCase();
413 if ( remoteChecksum.equals( actualChecksum ) )
415 moveTempToTarget( tempChecksumFile, checksumFile );
417 correctChecksum = true;
422 "The checksum '" + actualChecksum + "' did not match the remote value: " + remoteChecksum );
425 catch ( TransferFailedException e )
427 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
429 // do nothing try the next checksum
433 catch ( ResourceDoesNotExistException e )
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() )
440 checksumFile.delete();
443 catch ( AuthorizationException e )
445 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
447 // do nothing try the next checksum
451 catch ( IOException e )
453 getLogger().warn( "An error occurred while reading the temporary checksum file.", e );
454 // do nothing try the next checksum
458 catch ( DigesterException e )
460 getLogger().warn( "The checksum was invalid: " + checksumPath + ": " + e.getMessage(), e );
461 // do nothing try the next checksum
467 tempChecksumFile.delete();
470 return correctChecksum || allNotFound;
474 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles
475 * its downloaded files.
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
481 private void moveTempToTarget( File temp, File target )
482 throws ProxyException
484 if ( target.exists() && !target.delete() )
486 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
489 if ( !temp.renameTo( target ) )
491 getLogger().warn( "Unable to rename tmp file to its final name... resorting to copy command." );
495 FileUtils.copyFile( temp, target );
497 catch ( IOException e )
499 throw new ProxyException( "Cannot copy tmp file to its final location", e );
509 * Used to disconnect the wagonManager from its repository
511 * @param wagon the connected wagonManager object
513 private void disconnectWagon( Wagon wagon )
519 catch ( ConnectionException e )
521 getLogger().error( "Problem disconnecting from wagonManager - ignoring: " + e.getMessage() );
525 private void processRepositoryFailure( ProxiedArtifactRepository repository, Throwable t, String path,
526 ArtifactRepositoryPolicy policy )
527 throws ProxyException
529 repository.addFailure( path, policy );
531 String message = t.getMessage();
532 if ( repository.isHardFail() )
534 repository.addFailure( path, policy );
535 throw new ProxyException(
536 "An error occurred in hardfailing repository " + repository.getName() + "...\n " + message, t );
539 getLogger().warn( "Skipping repository " + repository.getName() + ": " + message );
540 getLogger().debug( "Cause", t );
543 private void processRepositoryFailure( ProxiedArtifactRepository repository, String message, String path,
544 ArtifactRepositoryPolicy policy )
545 throws ProxyException
547 repository.addFailure( path, policy );
549 processCachedRepositoryFailure( repository, message );
552 private void processCachedRepositoryFailure( ProxiedArtifactRepository repository, String message )
553 throws ProxyException
555 if ( repository.isHardFail() )
557 throw new ProxyException(
558 "An error occurred in hardfailing repository " + repository.getName() + "...\n " + message );
561 getLogger().warn( "Skipping repository " + repository.getName() + ": " + message );