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.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;
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;
46 * An implementation of the proxy handler.
48 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
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)
55 public class DefaultProxyRequestHandler
56 extends AbstractLogEnabled
57 implements ProxyRequestHandler
60 * @plexus.requirement role-hint="default"
61 * @todo use a map, and have priorities in them
63 private ArtifactDiscoverer defaultArtifactDiscoverer;
66 * @plexus.requirement role-hint="legacy"
68 private ArtifactDiscoverer legacyArtifactDiscoverer;
71 * @plexus.requirement role="org.apache.maven.wagon.Wagon"
73 private Map/*<String,Wagon>*/ wagons;
75 public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository )
76 throws ProxyException, ResourceDoesNotExistException
78 return get( path, proxiedRepositories, managedRepository, null );
81 public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository, ProxyInfo wagonProxy )
82 throws ProxyException, ResourceDoesNotExistException
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() )
89 cachedFile = getAlways( path, proxiedRepositories, managedRepository, wagonProxy );
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 File target = new File( managedRepository.getBasedir(), path );
107 for ( Iterator i = proxiedRepositories.iterator(); i.hasNext(); )
109 ProxiedArtifactRepository repository = (ProxiedArtifactRepository) i.next();
111 if ( repository.isCachedFailure( path ) )
113 processRepositoryFailure( repository, "Cached failure found" );
119 get( path, target, repository, managedRepository, wagonProxy );
121 if ( !target.exists() )
123 repository.addFailure( path );
127 // in case it previously failed and we've since found it
128 repository.clearFailure( path );
131 catch ( ProxyException e )
133 repository.addFailure( path );
139 if ( !target.exists() )
141 throw new ResourceDoesNotExistException( "Could not find " + path + " in any of the repositories." );
147 private void get( String path, File target, ProxiedArtifactRepository repository,
148 ArtifactRepository managedRepository, ProxyInfo wagonProxy )
149 throws ProxyException
151 if ( path.endsWith( ".md5" ) || path.endsWith( ".sha1" ) )
153 // always read from the managed repository, no need to make remote request
155 else if ( path.endsWith( "maven-metadata.xml" ) )
157 // TODO: this is not always!
158 if ( !target.exists() || isOutOfDate( repository.getRepository().getReleases(), target ) )
160 getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, target );
165 Artifact artifact = null;
168 artifact = defaultArtifactDiscoverer.buildArtifact( path );
170 catch ( DiscovererException e )
172 getLogger().debug( "Failed to build artifact using default layout with message: " + e.getMessage() );
175 if ( artifact == null )
179 artifact = legacyArtifactDiscoverer.buildArtifact( path );
181 catch ( DiscovererException e )
183 getLogger().debug( "Failed to build artifact using legacy layout with message: " + e.getMessage() );
187 if ( artifact != null )
189 getArtifactFromRepository( artifact, repository, managedRepository, wagonProxy, target );
193 // Some other unknown file in the repository, proxy as is
194 // TODO: this is not always!
195 if ( !target.exists() )
197 getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, target );
203 private void getFileFromRepository( String path, ProxiedArtifactRepository repository, String repositoryCachePath,
204 ProxyInfo httpProxy, File target )
205 throws ProxyException
207 boolean connected = false;
208 Map checksums = null;
213 String protocol = repository.getRepository().getProtocol();
214 wagon = (Wagon) wagons.get( protocol );
217 throw new ProxyException( "Unsupported remote protocol: " + protocol );
220 //@todo configure wagon (ssh settings, etc)
222 checksums = prepareChecksumListeners( wagon );
224 connected = connectToRepository( wagon, repository, httpProxy );
227 File temp = new File( target.getAbsolutePath() + ".tmp" );
237 getLogger().debug( "Trying " + path + " from " + repository.getName() + "..." );
239 if ( !target.exists() )
241 wagon.get( path, temp );
245 wagon.getIfNewer( path, temp, target.lastModified() );
248 success = checkChecksum( checksums, path, wagon, repositoryCachePath );
250 if ( tries > 1 && !success )
252 processRepositoryFailure( repository, "Checksum failures occurred while downloading " + path );
256 // temp won't exist if we called getIfNewer and it was older, but its still a successful return
259 moveTempToTarget( temp, target );
264 //try next repository
266 catch ( TransferFailedException e )
268 processRepositoryFailure( repository, e );
270 catch ( AuthorizationException e )
272 processRepositoryFailure( repository, e );
274 catch ( ResourceDoesNotExistException e )
276 // hard failure setting doesn't affect "not found".
277 getLogger().debug( "Artifact not found in repository: " + repository.getName() + ": " + e.getMessage() );
281 if ( wagon != null && checksums != null )
283 releaseChecksumListeners( wagon, checksums );
288 disconnectWagon( wagon );
293 private static boolean isOutOfDate( ArtifactRepositoryPolicy policy, File target )
295 return policy != null && policy.checkOutOfDate( new Date( target.lastModified() ) );
299 * Used to add checksum observers as transfer listeners to the wagonManager object
301 * @param wagon the wagonManager object to use the checksum with
302 * @return map of ChecksumObservers added into the wagonManager transfer listeners
304 private Map prepareChecksumListeners( Wagon wagon )
306 Map checksums = new LinkedHashMap();
309 ChecksumObserver checksum = new ChecksumObserver( "SHA-1" );
310 wagon.addTransferListener( checksum );
311 checksums.put( "sha1", checksum );
313 checksum = new ChecksumObserver( "MD5" );
314 wagon.addTransferListener( checksum );
315 checksums.put( "md5", checksum );
317 catch ( NoSuchAlgorithmException e )
319 getLogger().info( "An error occurred while preparing checksum observers", e );
324 private void releaseChecksumListeners( Wagon wagon, Map checksumMap )
326 for ( Iterator checksums = checksumMap.values().iterator(); checksums.hasNext(); )
328 ChecksumObserver listener = (ChecksumObserver) checksums.next();
329 wagon.removeTransferListener( listener );
333 private boolean connectToRepository( Wagon wagon, ProxiedArtifactRepository repository, ProxyInfo httpProxy )
335 boolean connected = false;
338 ArtifactRepository artifactRepository = repository.getRepository();
339 Repository wagonRepository = new Repository( artifactRepository.getId(), artifactRepository.getUrl() );
340 if ( repository.isUseNetworkProxy() && httpProxy != null )
342 wagon.connect( wagonRepository, httpProxy );
346 wagon.connect( wagonRepository );
350 catch ( ConnectionException e )
352 getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
354 catch ( AuthenticationException e )
356 getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
362 private boolean checkChecksum( Map checksumMap, String path, Wagon wagon, String repositoryCachePath )
363 throws ProxyException
365 releaseChecksumListeners( wagon, checksumMap );
366 for ( Iterator checksums = checksumMap.keySet().iterator(); checksums.hasNext(); )
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 );
375 File tempChecksumFile = new File( checksumFile.getAbsolutePath() + ".tmp" );
377 wagon.get( checksumPath, tempChecksumFile );
379 String remoteChecksum = FileUtils.fileRead( tempChecksumFile ).trim();
380 if ( remoteChecksum.indexOf( ' ' ) > 0 )
382 remoteChecksum = remoteChecksum.substring( 0, remoteChecksum.indexOf( ' ' ) );
385 String actualChecksum = checksum.getActualChecksum().toUpperCase();
386 remoteChecksum = remoteChecksum.toUpperCase();
388 boolean checksumCheck;
389 if ( remoteChecksum.equals( actualChecksum ) )
391 moveTempToTarget( tempChecksumFile, checksumFile );
393 checksumCheck = true;
398 "The checksum '" + actualChecksum + "' did not match the remote value: " + remoteChecksum );
399 checksumCheck = false;
401 return checksumCheck;
403 catch ( TransferFailedException e )
405 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
407 // do nothing try the next checksum
409 catch ( ResourceDoesNotExistException e )
411 getLogger().debug( "The checksum did not exist: " + checksumPath, e );
412 // do nothing try the next checksum
414 catch ( AuthorizationException e )
416 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
418 // do nothing try the next checksum
420 catch ( IOException e )
422 getLogger().warn( "An error occurred while reading the temporary checksum file.", e );
427 getLogger().debug( "No remote checksums available." );
433 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles
434 * its downloaded files.
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
440 private void moveTempToTarget( File temp, File target )
441 throws ProxyException
443 if ( target.exists() && !target.delete() )
445 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
448 if ( !temp.renameTo( target ) )
450 getLogger().warn( "Unable to rename tmp file to its final name... resorting to copy command." );
454 FileUtils.copyFile( temp, target );
456 catch ( IOException e )
458 throw new ProxyException( "Cannot copy tmp file to its final location", e );
468 * Used to disconnect the wagonManager from its repository
470 * @param wagon the connected wagonManager object
472 private void disconnectWagon( Wagon wagon )
478 catch ( ConnectionException e )
480 getLogger().error( "Problem disconnecting from wagonManager - ignoring: " + e.getMessage() );
484 private void processRepositoryFailure( ProxiedArtifactRepository repository, Throwable t )
485 throws ProxyException
487 if ( repository.isHardFail() )
489 throw new ProxyException(
490 "An error occurred in hardfailing repository " + repository.getName() + "...\n " + t.getMessage(),
495 getLogger().warn( "Skipping repository " + repository.getName() + ": " + t.getMessage() );
496 getLogger().debug( "Cause", t );
500 private void processRepositoryFailure( ProxiedArtifactRepository repository, String message )
501 throws ProxyException
503 if ( repository.isHardFail() )
505 throw new ProxyException(
506 "An error occurred in hardfailing repository " + repository.getName() + "...\n " + message );
510 getLogger().warn( "Skipping repository " + repository.getName() + ": " + message );
514 private void getArtifactFromRepository( Artifact artifact, ProxiedArtifactRepository repository,
515 ArtifactRepository managedRepository, ProxyInfo httpProxy, File remoteFile )
516 throws ProxyException
518 ArtifactRepository artifactRepository = repository.getRepository();
519 ArtifactRepositoryPolicy policy =
520 artifact.isSnapshot() ? artifactRepository.getSnapshots() : artifactRepository.getReleases();
522 if ( !policy.isEnabled() )
524 getLogger().debug( "Skipping disabled repository " + repository.getName() );
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 ) )
533 getFileFromRepository( artifactRepository.pathOf( artifact ), repository,
534 managedRepository.getBasedir(), httpProxy, remoteFile );
536 getLogger().debug( " Artifact resolved" );
538 artifact.setResolved( true );