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.artifact.repository.metadata.Metadata;
23 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
24 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
25 import org.apache.maven.repository.digest.DigestUtils;
26 import org.apache.maven.repository.digest.DigesterException;
27 import org.apache.maven.repository.discovery.ArtifactDiscoverer;
28 import org.apache.maven.repository.discovery.DiscovererException;
29 import org.apache.maven.wagon.ConnectionException;
30 import org.apache.maven.wagon.ResourceDoesNotExistException;
31 import org.apache.maven.wagon.TransferFailedException;
32 import org.apache.maven.wagon.Wagon;
33 import org.apache.maven.wagon.authentication.AuthenticationException;
34 import org.apache.maven.wagon.authorization.AuthorizationException;
35 import org.apache.maven.wagon.observers.ChecksumObserver;
36 import org.apache.maven.wagon.proxy.ProxyInfo;
37 import org.apache.maven.wagon.repository.Repository;
38 import org.codehaus.plexus.logging.AbstractLogEnabled;
39 import org.codehaus.plexus.util.FileUtils;
40 import org.codehaus.plexus.util.IOUtil;
41 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
44 import java.io.FileReader;
45 import java.io.FileWriter;
46 import java.io.IOException;
47 import java.security.NoSuchAlgorithmException;
48 import java.util.Date;
49 import java.util.Iterator;
50 import java.util.LinkedHashMap;
51 import java.util.List;
55 * An implementation of the proxy handler.
57 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
59 * @todo use wagonManager for cache use file:// as URL
60 * @todo this currently duplicates a lot of the wagon manager, and doesn't do things like snapshot resolution, etc.
61 * The checksum handling is inconsistent with that of the wagon manager.
62 * Should we have a more artifact based one? This will merge metadata so should behave correctly, and it is able to
63 * correct some limitations of the wagon manager (eg, it can retrieve newer SNAPSHOT files without metadata)
65 public class DefaultProxyRequestHandler
66 extends AbstractLogEnabled
67 implements ProxyRequestHandler
70 * @plexus.requirement role-hint="default"
71 * @todo use a map, and have priorities in them
73 private ArtifactDiscoverer defaultArtifactDiscoverer;
76 * @plexus.requirement role-hint="legacy"
78 private ArtifactDiscoverer legacyArtifactDiscoverer;
81 * @plexus.requirement role="org.apache.maven.wagon.Wagon"
83 private Map/*<String,Wagon>*/ wagons;
86 * @plexus.requirement role="org.apache.maven.repository.digest.Digester"
88 private Map/*<String,Digester>*/ digesters;
90 public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository )
91 throws ProxyException, ResourceDoesNotExistException
93 return get( path, proxiedRepositories, managedRepository, null );
96 public File get( String path, List proxiedRepositories, ArtifactRepository managedRepository, ProxyInfo wagonProxy )
97 throws ProxyException, ResourceDoesNotExistException
99 return get( managedRepository, path, proxiedRepositories, wagonProxy, false );
102 public File getAlways( String path, List proxiedRepositories, ArtifactRepository managedRepository )
103 throws ProxyException, ResourceDoesNotExistException
105 return getAlways( path, proxiedRepositories, managedRepository, null );
108 public File getAlways( String path, List proxiedRepositories, ArtifactRepository managedRepository,
109 ProxyInfo wagonProxy )
110 throws ResourceDoesNotExistException, ProxyException
112 return get( managedRepository, path, proxiedRepositories, wagonProxy, true );
115 private File get( ArtifactRepository managedRepository, String path, List proxiedRepositories, ProxyInfo wagonProxy,
117 throws ProxyException, ResourceDoesNotExistException
119 File target = new File( managedRepository.getBasedir(), path );
121 for ( Iterator i = proxiedRepositories.iterator(); i.hasNext(); )
123 ProxiedArtifactRepository repository = (ProxiedArtifactRepository) i.next();
125 if ( !force && repository.isCachedFailure( path ) )
127 processCachedRepositoryFailure( repository, "Cached failure found for: " + path );
131 get( path, target, repository, managedRepository, wagonProxy, force );
135 if ( !target.exists() )
137 throw new ResourceDoesNotExistException( "Could not find " + path + " in any of the repositories." );
143 private void get( String path, File target, ProxiedArtifactRepository repository,
144 ArtifactRepository managedRepository, ProxyInfo wagonProxy, boolean force )
145 throws ProxyException
147 ArtifactRepositoryPolicy policy;
149 if ( path.endsWith( ".md5" ) || path.endsWith( ".sha1" ) )
151 // always read from the managed repository, no need to make remote request
153 else if ( path.endsWith( "maven-metadata.xml" ) )
155 File metadataFile = new File( target.getParentFile(), ".metadata-" + repository.getRepository().getId() );
157 policy = repository.getRepository().getReleases();
159 // if it is snapshot metadata, use a different policy
160 if ( path.endsWith( "-SNAPSHOT/maven-metadata.xml" ) )
162 policy = repository.getRepository().getSnapshots();
165 if ( force || !metadataFile.exists() || isOutOfDate( policy, metadataFile ) )
167 getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, metadataFile,
170 mergeMetadataFiles( target, metadataFile );
175 Artifact artifact = null;
178 artifact = defaultArtifactDiscoverer.buildArtifact( path );
180 catch ( DiscovererException e )
182 getLogger().debug( "Failed to build artifact using default layout with message: " + e.getMessage() );
185 if ( artifact == null )
189 artifact = legacyArtifactDiscoverer.buildArtifact( path );
191 catch ( DiscovererException e )
193 getLogger().debug( "Failed to build artifact using legacy layout with message: " + e.getMessage() );
197 if ( artifact != null )
199 ArtifactRepository artifactRepository = repository.getRepository();
201 // we use the release policy for tracking failures, but only check for updates on snapshots
202 // also, we don't look for updates on timestamp snapshot files, only non-unique-version ones
203 policy = artifact.isSnapshot() ? artifactRepository.getSnapshots() : artifactRepository.getReleases();
205 boolean needsUpdate = false;
206 if ( artifact.getVersion().endsWith( "-SNAPSHOT" ) && isOutOfDate( policy, target ) )
211 if ( needsUpdate || force || !target.exists() )
213 getFileFromRepository( artifactRepository.pathOf( artifact ), repository,
214 managedRepository.getBasedir(), wagonProxy, target, policy, force );
219 // Some other unknown file in the repository, proxy as is
220 if ( force || !target.exists() )
222 policy = repository.getRepository().getReleases();
223 getFileFromRepository( path, repository, managedRepository.getBasedir(), wagonProxy, target, policy,
229 if ( target.exists() )
231 // in case it previously failed and we've since found it
232 repository.clearFailure( path );
236 private void mergeMetadataFiles( File target, File metadataFile )
237 throws ProxyException
239 if ( target.exists() )
241 MetadataXpp3Reader reader = new MetadataXpp3Reader();
243 FileReader fileReader = null;
246 fileReader = new FileReader( target );
247 metadata = reader.read( fileReader );
249 catch ( XmlPullParserException e )
251 throw new ProxyException( "Unable to parse existing metadata: " + e.getMessage(), e );
253 catch ( IOException e )
255 throw new ProxyException( "Unable to read existing metadata: " + e.getMessage(), e );
259 IOUtil.close( fileReader );
263 boolean changed = false;
266 fileReader = new FileReader( metadataFile );
267 Metadata newMetadata = reader.read( fileReader );
269 changed = metadata.merge( newMetadata );
271 catch ( IOException e )
273 // ignore the merged file
274 getLogger().warn( "Unable to read new metadata: " + e.getMessage() );
276 catch ( XmlPullParserException e )
278 // ignore the merged file
279 getLogger().warn( "Unable to parse new metadata: " + e.getMessage() );
283 IOUtil.close( fileReader );
288 FileWriter fileWriter = null;
291 fileWriter = new FileWriter( target );
292 new MetadataXpp3Writer().write( fileWriter, metadata );
294 catch ( IOException e )
296 getLogger().warn( "Unable to store new metadata: " + e.getMessage() );
300 IOUtil.close( fileWriter );
308 FileUtils.copyFile( metadataFile, target );
310 catch ( IOException e )
313 getLogger().warn( "Unable to copy metadata: " + metadataFile + " to " + target );
318 private void getFileFromRepository( String path, ProxiedArtifactRepository repository, String repositoryCachePath,
319 ProxyInfo httpProxy, File target, ArtifactRepositoryPolicy policy,
321 throws ProxyException
323 if ( !policy.isEnabled() )
325 getLogger().debug( "Skipping disabled repository " + repository.getName() );
329 Map checksums = null;
332 File temp = new File( target.getAbsolutePath() + ".tmp" );
335 boolean connected = false;
338 String protocol = repository.getRepository().getProtocol();
339 wagon = (Wagon) wagons.get( protocol );
342 throw new ProxyException( "Unsupported remote protocol: " + protocol );
345 //@todo configure wagon (ssh settings, etc)
347 checksums = prepareChecksumListeners( wagon );
349 connected = connectToRepository( wagon, repository, httpProxy );
359 getLogger().debug( "Trying " + path + " from " + repository.getName() + "..." );
361 boolean downloaded = true;
362 if ( force || !target.exists() )
364 wagon.get( path, temp );
368 downloaded = wagon.getIfNewer( path, temp, target.lastModified() );
373 success = checkChecksum( checksums, path, wagon, repositoryCachePath );
375 if ( tries > 1 && !success )
377 processRepositoryFailure( repository,
378 "Checksum failures occurred while downloading " + path, path,
385 // getIfNewer determined we were up to date
391 // temp won't exist if we called getIfNewer and it was older, but its still a successful return
394 moveTempToTarget( temp, target );
397 //try next repository
399 catch ( TransferFailedException e )
401 processRepositoryFailure( repository, e, path, policy );
403 catch ( AuthorizationException e )
405 processRepositoryFailure( repository, e, path, policy );
407 catch ( ResourceDoesNotExistException e )
409 // hard failure setting doesn't affect "not found".
410 getLogger().debug( "Artifact not found in repository: " + repository.getName() + ": " + e.getMessage() );
416 if ( wagon != null && checksums != null )
418 releaseChecksumListeners( wagon, checksums );
423 disconnectWagon( wagon );
428 private static boolean isOutOfDate( ArtifactRepositoryPolicy policy, File target )
430 return policy != null && policy.checkOutOfDate( new Date( target.lastModified() ) );
434 * Used to add checksum observers as transfer listeners to the wagonManager object
436 * @param wagon the wagonManager object to use the checksum with
437 * @return map of ChecksumObservers added into the wagonManager transfer listeners
439 private Map prepareChecksumListeners( Wagon wagon )
441 Map checksums = new LinkedHashMap();
444 ChecksumObserver checksum = new ChecksumObserver( "SHA-1" );
445 wagon.addTransferListener( checksum );
446 checksums.put( "sha1", checksum );
448 checksum = new ChecksumObserver( "MD5" );
449 wagon.addTransferListener( checksum );
450 checksums.put( "md5", checksum );
452 catch ( NoSuchAlgorithmException e )
454 getLogger().info( "An error occurred while preparing checksum observers", e );
459 private void releaseChecksumListeners( Wagon wagon, Map checksumMap )
461 for ( Iterator checksums = checksumMap.values().iterator(); checksums.hasNext(); )
463 ChecksumObserver listener = (ChecksumObserver) checksums.next();
464 wagon.removeTransferListener( listener );
468 private boolean connectToRepository( Wagon wagon, ProxiedArtifactRepository repository, ProxyInfo httpProxy )
470 boolean connected = false;
473 ArtifactRepository artifactRepository = repository.getRepository();
474 Repository wagonRepository = new Repository( artifactRepository.getId(), artifactRepository.getUrl() );
475 if ( repository.isUseNetworkProxy() && httpProxy != null )
477 wagon.connect( wagonRepository, httpProxy );
481 wagon.connect( wagonRepository );
485 catch ( ConnectionException e )
487 getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
489 catch ( AuthenticationException e )
491 getLogger().info( "Could not connect to " + repository.getName() + ": " + e.getMessage() );
497 private boolean checkChecksum( Map checksumMap, String path, Wagon wagon, String repositoryCachePath )
498 throws ProxyException
500 releaseChecksumListeners( wagon, checksumMap );
502 boolean correctChecksum = false;
504 boolean allNotFound = true;
506 for ( Iterator i = checksumMap.keySet().iterator(); i.hasNext() && !correctChecksum; )
508 String checksumExt = (String) i.next();
509 ChecksumObserver checksum = (ChecksumObserver) checksumMap.get( checksumExt );
510 String checksumPath = path + "." + checksumExt;
511 File checksumFile = new File( repositoryCachePath, checksumPath );
513 File tempChecksumFile = new File( checksumFile.getAbsolutePath() + ".tmp" );
514 tempChecksumFile.deleteOnExit();
518 wagon.get( checksumPath, tempChecksumFile );
522 String remoteChecksum = DigestUtils.cleanChecksum( FileUtils.fileRead( tempChecksumFile ),
523 checksumExt.toUpperCase(),
524 path.substring( path.lastIndexOf( '/' ) ) );
526 String actualChecksum = checksum.getActualChecksum().toUpperCase();
527 remoteChecksum = remoteChecksum.toUpperCase();
529 if ( remoteChecksum.equals( actualChecksum ) )
531 moveTempToTarget( tempChecksumFile, checksumFile );
533 correctChecksum = true;
538 "The checksum '" + actualChecksum + "' did not match the remote value: " + remoteChecksum );
541 catch ( TransferFailedException e )
543 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
545 // do nothing try the next checksum
549 catch ( ResourceDoesNotExistException e )
551 getLogger().debug( "The checksum did not exist: " + checksumPath, e );
552 // do nothing try the next checksum
553 // remove it if it is present locally in case there is an old incorrect one
554 if ( checksumFile.exists() )
556 checksumFile.delete();
559 catch ( AuthorizationException e )
561 getLogger().warn( "An error occurred during the download of " + checksumPath + ": " + e.getMessage(),
563 // do nothing try the next checksum
567 catch ( IOException e )
569 getLogger().warn( "An error occurred while reading the temporary checksum file.", e );
570 // do nothing try the next checksum
574 catch ( DigesterException e )
576 getLogger().warn( "The checksum was invalid: " + checksumPath + ": " + e.getMessage(), e );
577 // do nothing try the next checksum
583 tempChecksumFile.delete();
586 return correctChecksum || allNotFound;
590 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles
591 * its downloaded files.
593 * @param temp The completed download file
594 * @param target The final location of the downloaded file
595 * @throws ProxyException when the temp file cannot replace the target file
597 private void moveTempToTarget( File temp, File target )
598 throws ProxyException
600 if ( target.exists() && !target.delete() )
602 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
605 if ( !temp.renameTo( target ) )
607 getLogger().warn( "Unable to rename tmp file to its final name... resorting to copy command." );
611 FileUtils.copyFile( temp, target );
613 catch ( IOException e )
615 throw new ProxyException( "Cannot copy tmp file to its final location", e );
625 * Used to disconnect the wagonManager from its repository
627 * @param wagon the connected wagonManager object
629 private void disconnectWagon( Wagon wagon )
635 catch ( ConnectionException e )
637 getLogger().error( "Problem disconnecting from wagonManager - ignoring: " + e.getMessage() );
641 private void processRepositoryFailure( ProxiedArtifactRepository repository, Throwable t, String path,
642 ArtifactRepositoryPolicy policy )
643 throws ProxyException
645 repository.addFailure( path, policy );
647 String message = t.getMessage();
648 if ( repository.isHardFail() )
650 repository.addFailure( path, policy );
651 throw new ProxyException(
652 "An error occurred in hardfailing repository " + repository.getName() + "...\n " + message, t );
655 getLogger().warn( "Skipping repository " + repository.getName() + ": " + message );
656 getLogger().debug( "Cause", t );
659 private void processRepositoryFailure( ProxiedArtifactRepository repository, String message, String path,
660 ArtifactRepositoryPolicy policy )
661 throws ProxyException
663 repository.addFailure( path, policy );
665 processCachedRepositoryFailure( repository, message );
668 private void processCachedRepositoryFailure( ProxiedArtifactRepository repository, String message )
669 throws ProxyException
671 if ( repository.isHardFail() )
673 throw new ProxyException(
674 "An error occurred in hardfailing repository " + repository.getName() + "...\n " + message );
677 getLogger().warn( "Skipping repository " + repository.getName() + ": " + message );