1 package org.apache.maven.archiva.proxy;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.LinkedHashMap;
28 import java.util.List;
30 import java.util.Properties;
31 import java.util.Map.Entry;
33 import org.apache.commons.collections.CollectionUtils;
34 import org.apache.commons.io.FileUtils;
35 import org.apache.commons.io.FilenameUtils;
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
38 import org.apache.maven.archiva.configuration.ConfigurationNames;
39 import org.apache.maven.archiva.configuration.NetworkProxyConfiguration;
40 import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
41 import org.apache.maven.archiva.model.ArtifactReference;
42 import org.apache.maven.archiva.model.Keys;
43 import org.apache.maven.archiva.model.ProjectReference;
44 import org.apache.maven.archiva.model.RepositoryURL;
45 import org.apache.maven.archiva.model.VersionedReference;
46 import org.apache.maven.archiva.policies.DownloadErrorPolicy;
47 import org.apache.maven.archiva.policies.DownloadPolicy;
48 import org.apache.maven.archiva.policies.PolicyConfigurationException;
49 import org.apache.maven.archiva.policies.PolicyViolationException;
50 import org.apache.maven.archiva.policies.PostDownloadPolicy;
51 import org.apache.maven.archiva.policies.PreDownloadPolicy;
52 import org.apache.maven.archiva.policies.ProxyDownloadException;
53 import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
54 import org.apache.maven.archiva.repository.ContentNotFoundException;
55 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
56 import org.apache.maven.archiva.repository.RemoteRepositoryContent;
57 import org.apache.maven.archiva.repository.RepositoryContentFactory;
58 import org.apache.maven.archiva.repository.RepositoryException;
59 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
60 import org.apache.maven.archiva.repository.layout.LayoutException;
61 import org.apache.maven.archiva.repository.metadata.MetadataTools;
62 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
63 import org.apache.maven.archiva.repository.scanner.RepositoryContentConsumers;
64 import org.apache.maven.wagon.ConnectionException;
65 import org.apache.maven.wagon.ResourceDoesNotExistException;
66 import org.apache.maven.wagon.Wagon;
67 import org.apache.maven.wagon.WagonException;
68 import org.apache.maven.wagon.authentication.AuthenticationException;
69 import org.apache.maven.wagon.authentication.AuthenticationInfo;
70 import org.apache.maven.wagon.proxy.ProxyInfo;
71 import org.apache.maven.wagon.repository.Repository;
72 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
73 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
74 import org.codehaus.plexus.registry.Registry;
75 import org.codehaus.plexus.registry.RegistryListener;
76 import org.codehaus.plexus.util.SelectorUtils;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
81 * DefaultRepositoryProxyConnectors
83 * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
85 * @todo exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than your average brown onion
86 * @plexus.component role-hint="default"
88 public class DefaultRepositoryProxyConnectors
89 implements RepositoryProxyConnectors, RegistryListener, Initializable
91 private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class );
96 private ArchivaConfiguration archivaConfiguration;
101 private RepositoryContentFactory repositoryFactory;
104 * @plexus.requirement
106 private MetadataTools metadataTools;
109 * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
111 private Map<String, PreDownloadPolicy> preDownloadPolicies;
114 * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
116 private Map<String, PostDownloadPolicy> postDownloadPolicies;
119 * @plexus.requirement role="org.apache.maven.archiva.policies.DownloadErrorPolicy"
121 private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
124 * @plexus.requirement role-hint="default"
126 private UrlFailureCache urlFailureCache;
128 private Map<String, List<ProxyConnector>> proxyConnectorMap = new HashMap<String, List<ProxyConnector>>();
130 private Map<String, ProxyInfo> networkProxyMap = new HashMap<String, ProxyInfo>();
133 * @plexus.requirement
135 private RepositoryContentConsumers consumers;
138 * @plexus.requirement
140 private WagonFactory wagonFactory;
142 public File fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
143 throws ProxyDownloadException
145 File workingDirectory = createWorkingDirectory(repository);
148 File localFile = toLocalFile( repository, artifact );
150 Properties requestProperties = new Properties();
151 requestProperties.setProperty( "filetype", "artifact" );
152 requestProperties.setProperty( "version", artifact.getVersion() );
153 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
155 List<ProxyConnector> connectors = getProxyConnectors( repository );
156 Map<String, Exception> previousExceptions = new LinkedHashMap<String, Exception>();
157 for ( ProxyConnector connector : connectors )
159 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
160 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
162 String targetPath = targetRepository.toPath( artifact );
166 File downloadedFile =
167 transferFile( connector, targetRepository, targetPath, repository, workingDirectory, localFile, requestProperties,
170 if ( fileExists( downloadedFile ) )
172 log.debug( "Successfully transferred: " + downloadedFile.getAbsolutePath() );
173 return downloadedFile;
176 catch ( NotFoundException e )
178 log.debug( "Artifact " + Keys.toKey( artifact ) + " not found on repository \""
179 + targetRepository.getRepository().getId() + "\"." );
181 catch ( NotModifiedException e )
183 log.debug( "Artifact " + Keys.toKey( artifact ) + " not updated on repository \""
184 + targetRepository.getRepository().getId() + "\"." );
186 catch ( ProxyException e )
188 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
189 targetRepository, localFile, e, previousExceptions );
193 if ( !previousExceptions.isEmpty() )
195 throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
196 previousExceptions );
199 log.debug( "Exhausted all target repositories, artifact " + Keys.toKey( artifact ) + " not found." );
203 FileUtils.deleteQuietly(workingDirectory);
209 public File fetchFromProxies( ManagedRepositoryContent repository, String path )
211 File workingDir = createWorkingDirectory(repository);
214 File localFile = new File( repository.getRepoRoot(), path );
216 // no update policies for these paths
217 if ( localFile.exists() )
222 Properties requestProperties = new Properties();
223 requestProperties.setProperty( "filetype", "resource" );
224 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
226 List<ProxyConnector> connectors = getProxyConnectors( repository );
227 for ( ProxyConnector connector : connectors )
229 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
230 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
232 String targetPath = path;
236 File downloadedFile =
237 transferFile( connector, targetRepository, targetPath, repository, workingDir, localFile, requestProperties, false );
239 if ( fileExists( downloadedFile ) )
241 log.debug( "Successfully transferred: " + downloadedFile.getAbsolutePath() );
242 return downloadedFile;
245 catch ( NotFoundException e )
247 log.debug( "Resource " + path + " not found on repository \""
248 + targetRepository.getRepository().getId() + "\"." );
250 catch ( NotModifiedException e )
252 log.debug( "Resource " + path + " not updated on repository \""
253 + targetRepository.getRepository().getId() + "\"." );
255 catch ( ProxyException e )
257 log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId()
258 + "\" for resource " + path + ", continuing to next repository. Error message: " + e.getMessage() );
259 log.debug( "Full stack trace", e );
263 log.debug( "Exhausted all target repositories, resource " + path + " not found." );
267 FileUtils.deleteQuietly(workingDir);
273 public File fetchFromProxies( ManagedRepositoryContent repository, VersionedReference metadata )
275 File workingDir = createWorkingDirectory(repository);
278 File localFile = toLocalFile( repository, metadata );
280 Properties requestProperties = new Properties();
281 requestProperties.setProperty( "filetype", "metadata" );
282 boolean metadataNeedsUpdating = false;
283 long originalTimestamp = getLastModified( localFile );
285 List<ProxyConnector> connectors = getProxyConnectors( repository );
286 for ( ProxyConnector connector : connectors )
288 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
289 String targetPath = metadataTools.toPath( metadata );
291 File localRepoFile = toLocalRepoFile( repository, targetRepository, targetPath );
292 long originalMetadataTimestamp = getLastModified( localRepoFile );
296 transferFile( connector, targetRepository, targetPath, repository, workingDir, localRepoFile, requestProperties, true );
298 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
300 metadataNeedsUpdating = true;
303 catch ( NotFoundException e )
305 log.debug( "Versioned Metadata " + Keys.toKey( metadata )
306 + " not found on remote repository \""
307 + targetRepository.getRepository().getId() + "\"." );
309 catch ( NotModifiedException e )
311 log.debug( "Versioned Metadata " + Keys.toKey( metadata )
312 + " not updated on remote repository \""
313 + targetRepository.getRepository().getId() + "\"." );
315 catch ( ProxyException e )
317 log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId() +
318 "\" for versioned Metadata " + Keys.toKey( metadata ) +
319 ", continuing to next repository. Error message: " + e.getMessage() );
320 log.debug( "Full stack trace", e );
324 if ( hasBeenUpdated( localFile, originalTimestamp ) )
326 metadataNeedsUpdating = true;
329 if ( metadataNeedsUpdating )
333 metadataTools.updateMetadata( repository, metadata );
335 catch ( LayoutException e )
337 log.warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage() );
338 // TODO: add into repository report?
340 catch ( RepositoryMetadataException e )
342 log.warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
343 // TODO: add into repository report?
345 catch ( IOException e )
347 log.warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
348 // TODO: add into repository report?
350 catch ( ContentNotFoundException e )
352 log.warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
353 // TODO: add into repository report?
357 if ( fileExists( localFile ) )
364 FileUtils.deleteQuietly(workingDir);
370 private long getLastModified( File file )
372 if ( !file.exists() || !file.isFile() )
377 return file.lastModified();
380 private boolean hasBeenUpdated( File file, long originalLastModified )
382 if ( !file.exists() || !file.isFile() )
387 long currentLastModified = getLastModified( file );
388 return ( currentLastModified > originalLastModified );
391 public File fetchFromProxies( ManagedRepositoryContent repository, ProjectReference metadata )
393 File workingDir = createWorkingDirectory(repository);
396 File localFile = toLocalFile( repository, metadata );
398 Properties requestProperties = new Properties();
399 requestProperties.setProperty( "filetype", "metadata" );
400 boolean metadataNeedsUpdating = false;
401 long originalTimestamp = getLastModified( localFile );
403 List<ProxyConnector> connectors = getProxyConnectors( repository );
404 for ( ProxyConnector connector : connectors )
406 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
407 String targetPath = metadataTools.toPath( metadata );
409 File localRepoFile = toLocalRepoFile( repository, targetRepository, targetPath );
410 long originalMetadataTimestamp = getLastModified( localRepoFile );
413 transferFile( connector, targetRepository, targetPath, repository, workingDir, localRepoFile, requestProperties, true );
415 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
417 metadataNeedsUpdating = true;
420 catch ( NotFoundException e )
422 log.debug( "Project Metadata " + Keys.toKey( metadata ) + " not found on remote repository \""
423 + targetRepository.getRepository().getId() + "\"." );
425 catch ( NotModifiedException e )
427 log.debug( "Project Metadata " + Keys.toKey( metadata )
428 + " not updated on remote repository \""
429 + targetRepository.getRepository().getId() + "\"." );
431 catch ( ProxyException e )
433 log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId() +
434 "\" for project metadata " + Keys.toKey( metadata ) +
435 ", continuing to next repository. Error message: " + e.getMessage() );
436 log.debug( "Full stack trace", e );
441 if ( hasBeenUpdated( localFile, originalTimestamp ) )
443 metadataNeedsUpdating = true;
446 if ( metadataNeedsUpdating )
450 metadataTools.updateMetadata( repository, metadata );
452 catch ( LayoutException e )
454 log.warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage() );
455 // TODO: add into repository report?
457 catch ( RepositoryMetadataException e )
460 .warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
461 // TODO: add into repository report?
463 catch ( IOException e )
466 .warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
467 // TODO: add into repository report?
469 catch ( ContentNotFoundException e )
472 .warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
473 // TODO: add into repository report?
477 if ( fileExists( localFile ) )
484 FileUtils.deleteQuietly(workingDir);
490 private File toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
493 String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
494 return new File( repository.getRepoRoot(), repoPath );
498 * Test if the provided ManagedRepositoryContent has any proxies configured for it.
500 public boolean hasProxies( ManagedRepositoryContent repository )
502 synchronized ( this.proxyConnectorMap )
504 return this.proxyConnectorMap.containsKey( repository.getId() );
508 private File toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
510 return repository.toFile( artifact );
513 private File toLocalFile( ManagedRepositoryContent repository, ProjectReference metadata )
515 String sourcePath = metadataTools.toPath( metadata );
516 return new File( repository.getRepoRoot(), sourcePath );
519 private File toLocalFile( ManagedRepositoryContent repository, VersionedReference metadata )
521 String sourcePath = metadataTools.toPath( metadata );
522 return new File( repository.getRepoRoot(), sourcePath );
526 * Simple method to test if the file exists on the local disk.
528 * @param file the file to test. (may be null)
529 * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
531 private boolean fileExists( File file )
538 if ( !file.exists() )
543 if ( !file.isFile() )
552 * Perform the transfer of the file.
554 * @param connector the connector configuration to use.
555 * @param remoteRepository the remote repository get the resource from.
556 * @param remotePath the path in the remote repository to the resource to get.
557 * @param repository the managed repository that will hold the file
558 * @param localFile the local file to place the downloaded resource into
559 * @param requestProperties the request properties to utilize for policy handling.
560 * @param executeConsumers whether to execute the consumers after proxying
561 * @return the local file that was downloaded, or null if not downloaded.
562 * @throws NotFoundException if the file was not found on the remote repository.
563 * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository,
564 * but the remote resource is not newer than the local File.
565 * @throws ProxyException if transfer was unsuccessful.
567 private File transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
568 ManagedRepositoryContent repository, File workingDirectory, File localFile, Properties requestProperties,
569 boolean executeConsumers )
570 throws ProxyException, NotModifiedException
572 String url = remoteRepository.getURL().getUrl();
573 if ( !url.endsWith( "/" ) )
577 url = url + remotePath;
578 requestProperties.setProperty( "url", url );
580 // Is a whitelist defined?
581 if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
583 // Path must belong to whitelist.
584 if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
586 log.debug( "Path [" + remotePath +
587 "] is not part of defined whitelist (skipping transfer from repository [" +
588 remoteRepository.getRepository().getName() + "])." );
593 // Is target path part of blacklist?
594 if ( matchesPattern( remotePath, connector.getBlacklist() ) )
596 log.debug( "Path [" + remotePath + "] is part of blacklist (skipping transfer from repository [" +
597 remoteRepository.getRepository().getName() + "])." );
601 // Handle pre-download policy
604 validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, localFile );
606 catch ( PolicyViolationException e )
608 String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
609 if ( fileExists( localFile ) )
611 log.info( emsg + ": using already present local file." );
619 // MRM-631 - the lightweight wagon does not reset these - remove if we switch to httpclient based wagon
620 String previousHttpProxyHost = System.getProperty( "http.proxyHost" );
621 String previousHttpProxyPort = System.getProperty( "http.proxyPort" );
622 String previousProxyExclusions = System.getProperty( "http.nonProxyHosts" );
626 File tmpLocalFile = null;
631 RepositoryURL repoUrl = remoteRepository.getURL();
632 String protocol = repoUrl.getProtocol();
633 wagon = (Wagon) wagonFactory.getWagon( "wagon#" + protocol );
636 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
639 boolean connected = connectToRepository( connector, wagon, remoteRepository );
642 tmpLocalFile = transferSimpleFile( wagon, remoteRepository, remotePath, repository, workingDirectory, localFile );
644 // TODO: these should be used to validate the download based on the policies, not always downloaded to
645 // save on connections since md5 is rarely used
646 tmpSha1 = transferChecksum( wagon, remoteRepository, remotePath, repository, workingDirectory, localFile, ".sha1" );
647 tmpMd5 = transferChecksum( wagon, remoteRepository, remotePath, repository, workingDirectory, localFile, ".md5" );
650 catch ( NotFoundException e )
652 urlFailureCache.cacheFailure( url );
655 catch ( NotModifiedException e )
657 // Do not cache url here.
660 catch ( ProxyException e )
662 urlFailureCache.cacheFailure( url );
673 // MRM-631 - the lightweight wagon does not reset these - remove if we switch to httpclient based wagon
674 if ( previousHttpProxyHost != null )
676 System.setProperty( "http.proxyHost", previousHttpProxyHost );
680 System.getProperties().remove( "http.proxyHost" );
682 if ( previousHttpProxyPort != null )
684 System.setProperty( "http.proxyPort", previousHttpProxyPort );
688 System.getProperties().remove( "http.proxyPort" );
690 if ( previousProxyExclusions != null )
692 System.setProperty( "http.nonProxyHosts", previousProxyExclusions );
696 System.getProperties().remove( "http.nonProxyHosts" );
699 catch ( ConnectionException e )
701 log.warn( "Unable to disconnect wagon.", e );
706 // Handle post-download policies.
709 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpLocalFile );
711 catch ( PolicyViolationException e )
713 log.info( "Transfer invalidated from " + url + " : " + e.getMessage() );
714 executeConsumers = false;
715 if ( !fileExists( tmpLocalFile ) )
721 if (localFile != null)
723 moveFileIfExists(tmpMd5, localFile);
724 moveFileIfExists(tmpSha1, localFile);
725 moveFileIfExists(tmpLocalFile, localFile);
728 if ( executeConsumers )
730 // Just-in-time update of the index and database by executing the consumers for this artifact
731 consumers.executeConsumers( connector.getSourceRepository().getRepository(), localFile );
740 * Moves the file into repository location if it exists
742 * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
743 * @param localFile this is always the main artifact
745 private void moveFileIfExists(File fileToMove, File localFile) throws ProxyException
747 if (fileToMove != null && fileToMove.exists())
749 moveTempToTarget(fileToMove, new File(localFile.getParentFile(), fileToMove.getName()));
755 * Quietly transfer the checksum file from the remote repository to the local file.
758 * @param wagon the wagon instance (should already be connected) to use.
759 * @param remoteRepository the remote repository to transfer from.
760 * @param remotePath the remote path to the resource to get.
761 * @param repository the managed repository that will hold the file
762 * @param localFile the local file that should contain the downloaded contents
763 * @param type the type of checksum to transfer (example: ".md5" or ".sha1")
764 * @throws ProxyException if copying the downloaded file into place did not succeed.
766 private File transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
767 ManagedRepositoryContent repository, File workingDirectory, File localFile, String type )
768 throws ProxyException
770 File hashFile = new File( localFile.getAbsolutePath() + type );
771 File tmpChecksum = new File(workingDirectory, hashFile.getName());
772 String url = remoteRepository.getURL().getUrl() + remotePath;
774 // Transfer checksum does not use the policy.
775 if ( urlFailureCache.hasFailedBefore( url + type ) )
782 transferSimpleFile( wagon, remoteRepository, remotePath + type, repository, workingDirectory, hashFile );
783 log.debug( "Checksum" + type + " Downloaded: " + hashFile );
785 catch ( NotFoundException e )
787 urlFailureCache.cacheFailure( url + type );
788 log.debug( "Transfer failed, checksum not found: " + url );
789 // Consume it, do not pass this on.
791 catch ( NotModifiedException e )
793 log.debug( "Transfer skipped, checksum not modified: " + url );
794 // Consume it, do not pass this on.
796 catch ( ProxyException e )
798 urlFailureCache.cacheFailure( url + type );
799 log.warn( "Transfer failed on checksum: " + url + " : " + e.getMessage(), e );
800 // Critical issue, pass it on.
807 * Perform the transfer of the remote file to the local file specified.
809 * @param wagon the wagon instance to use.
810 * @param remoteRepository the remote repository to use
811 * @param remotePath the remote path to attempt to get
812 * @param repository the managed repository that will hold the file
813 * @param localFile the local file to save to
814 * @return The local file that was transfered.
815 * @throws ProxyException if there was a problem moving the downloaded file into place.
816 * @throws WagonException if there was a problem tranfering the file.
818 private File transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
819 ManagedRepositoryContent repository, File workingDirectory, File localFile )
820 throws ProxyException
822 assert ( remotePath != null );
824 // Transfer the file.
829 temp = new File(workingDirectory, localFile.getName());
831 boolean success = false;
833 if ( !localFile.exists() )
835 log.debug( "Retrieving " + remotePath + " from " + remoteRepository.getRepository().getName() );
836 wagon.get( remotePath, temp );
839 // You wouldn't get here on failure, a WagonException would have been thrown.
840 log.debug( "Downloaded successfully." );
844 log.debug( "Retrieving " + remotePath + " from " + remoteRepository.getRepository().getName()
846 success = wagon.getIfNewer( remotePath, temp, localFile.lastModified() );
849 throw new NotModifiedException(
850 "Not downloaded, as local file is newer than remote side: " + localFile.getAbsolutePath() );
855 log.debug( "Downloaded successfully." );
861 catch ( ResourceDoesNotExistException e )
863 throw new NotFoundException(
864 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
867 catch ( WagonException e )
869 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
872 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
873 if ( e.getCause() != null )
875 msg += " (cause: " + e.getCause() + ")";
877 throw new ProxyException( msg, e );
882 * Apply the policies.
884 * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
885 * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy setting)
886 * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String,Properties,File)})
887 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String,Properties,File)})
889 private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
890 Properties request, File localFile )
891 throws PolicyViolationException
893 for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
895 String key = entry.getKey();
896 DownloadPolicy policy = entry.getValue();
897 String defaultSetting = policy.getDefaultOption();
898 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
900 log.debug( "Applying [" + key + "] policy with [" + setting + "]" );
903 policy.applyPolicy( setting, request, localFile );
905 catch ( PolicyConfigurationException e )
907 log.error( e.getMessage(), e );
912 private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
913 Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
914 File localFile, ProxyException exception, Map<String, Exception> previousExceptions )
915 throws ProxyDownloadException
917 boolean process = true;
918 for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
920 String key = entry.getKey();
921 DownloadErrorPolicy policy = entry.getValue();
922 String defaultSetting = policy.getDefaultOption();
923 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
925 log.debug( "Applying [" + key + "] policy with [" + setting + "]" );
928 // all policies must approve the exception, any can cancel
929 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
935 catch ( PolicyConfigurationException e )
937 log.error( e.getMessage(), e );
943 // if the exception was queued, don't throw it
944 if ( !previousExceptions.containsKey( content.getId() ) )
946 throw new ProxyDownloadException(
947 "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
948 content.getId(), exception );
953 // if the exception was queued, but cancelled, remove it
954 previousExceptions.remove( content.getId() );
957 log.warn( "Transfer error from repository \"" + content.getRepository().getId() + "\" for artifact " +
958 Keys.toKey( artifact ) + ", continuing to next repository. Error message: " + exception.getMessage() );
959 log.debug( "Full stack trace", exception );
963 * Creates a working directory in the repository root for this request
965 * @return file location of working directory
967 private File createWorkingDirectory(ManagedRepositoryContent repository)
969 //TODO: This is ugly - lets actually clean this up when we get the new repository api
972 File tmpDir = File.createTempFile(".workingdirectory", null, new File(repository.getRepoRoot()));
977 catch (IOException e)
979 throw new RuntimeException("Could not create working directory for this request", e);
984 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles
985 * its downloaded files.
987 * @param temp The completed download file
988 * @param target The final location of the downloaded file
989 * @throws ProxyException when the temp file cannot replace the target file
991 private void moveTempToTarget( File temp, File target )
992 throws ProxyException
994 if ( target.exists() && !target.delete() )
996 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
999 target.getParentFile().mkdirs();
1000 if ( !temp.renameTo( target ) )
1002 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1006 FileUtils.copyFile( temp, target );
1008 catch ( IOException e )
1010 if (target.exists())
1012 log.debug("Tried to copy file " + temp.getName() + " to " + target.getAbsolutePath() + " but file with this name already exists.");
1016 throw new ProxyException( "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e );
1021 FileUtils.deleteQuietly(temp);
1027 * Using wagon, connect to the remote repository.
1029 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1030 * @param wagon the wagon instance to establish the connection on.
1031 * @param remoteRepository the remote repository to connect to.
1032 * @return true if the connection was successful. false if not connected.
1034 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1035 RemoteRepositoryContent remoteRepository )
1037 boolean connected = false;
1039 final ProxyInfo networkProxy;
1040 synchronized ( this.networkProxyMap )
1042 networkProxy = (ProxyInfo) this.networkProxyMap.get( connector.getProxyId() );
1045 if ( log.isDebugEnabled() )
1047 if ( networkProxy != null )
1049 // TODO: move to proxyInfo.toString()
1051 "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1052 + " to connect to remote repository " + remoteRepository.getURL();
1053 if ( networkProxy.getNonProxyHosts() != null )
1055 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1057 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1059 msg += "; as user: " + networkProxy.getUserName();
1065 AuthenticationInfo authInfo = null;
1066 String username = remoteRepository.getRepository().getUsername();
1067 String password = remoteRepository.getRepository().getPassword();
1069 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1071 log.debug( "Using username " + username + " to connect to remote repository "
1072 + remoteRepository.getURL() );
1073 authInfo = new AuthenticationInfo();
1074 authInfo.setUserName( username );
1075 authInfo.setPassword( password );
1078 //Convert seconds to milliseconds
1079 int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
1082 wagon.setTimeout(timeoutInMilliseconds);
1086 Repository wagonRepository = new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1087 wagon.connect( wagonRepository, authInfo, networkProxy );
1090 catch ( ConnectionException e )
1093 "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
1096 catch ( AuthenticationException e )
1099 "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
1107 * Tests whitelist and blacklist patterns against path.
1109 * @param path the path to test.
1110 * @param patterns the list of patterns to check.
1111 * @return true if the path matches at least 1 pattern in the provided patterns list.
1113 private boolean matchesPattern( String path, List<String> patterns )
1115 if ( CollectionUtils.isEmpty( patterns ) )
1120 for ( String pattern : patterns )
1122 if ( SelectorUtils.matchPath( pattern, path, false ) )
1132 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1134 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1136 synchronized ( this.proxyConnectorMap )
1138 List<ProxyConnector> ret = (List<ProxyConnector>) this.proxyConnectorMap.get( repository.getId() );
1141 return Collections.emptyList();
1144 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1149 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1151 if ( ConfigurationNames.isNetworkProxy( propertyName ) ||
1152 ConfigurationNames.isManagedRepositories( propertyName ) ||
1153 ConfigurationNames.isRemoteRepositories( propertyName ) ||
1154 ConfigurationNames.isProxyConnector( propertyName ) )
1156 initConnectorsAndNetworkProxies();
1160 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1165 @SuppressWarnings("unchecked")
1166 private void initConnectorsAndNetworkProxies()
1168 synchronized ( this.proxyConnectorMap )
1170 ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
1171 this.proxyConnectorMap.clear();
1173 List<ProxyConnectorConfiguration> proxyConfigs = archivaConfiguration.getConfiguration()
1174 .getProxyConnectors();
1175 for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
1177 String key = proxyConfig.getSourceRepoId();
1181 // Create connector object.
1182 ProxyConnector connector = new ProxyConnector();
1184 connector.setSourceRepository( repositoryFactory.getManagedRepositoryContent( proxyConfig
1185 .getSourceRepoId() ) );
1186 connector.setTargetRepository( repositoryFactory.getRemoteRepositoryContent( proxyConfig
1187 .getTargetRepoId() ) );
1189 connector.setProxyId( proxyConfig.getProxyId() );
1190 connector.setPolicies( proxyConfig.getPolicies() );
1191 connector.setOrder( proxyConfig.getOrder() );
1193 // Copy any blacklist patterns.
1194 List<String> blacklist = new ArrayList<String>();
1195 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
1197 blacklist.addAll( proxyConfig.getBlackListPatterns() );
1199 connector.setBlacklist( blacklist );
1201 // Copy any whitelist patterns.
1202 List<String> whitelist = new ArrayList<String>();
1203 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
1205 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
1207 connector.setWhitelist( whitelist );
1209 // Get other connectors
1210 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
1211 if ( connectors == null )
1213 // Create if we are the first.
1214 connectors = new ArrayList<ProxyConnector>();
1217 // Add the connector.
1218 connectors.add( connector );
1220 // Ensure the list is sorted.
1221 Collections.sort( connectors, proxyOrderSorter );
1223 // Set the key to the list of connectors.
1224 this.proxyConnectorMap.put( key, connectors );
1226 catch ( RepositoryNotFoundException e )
1228 log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
1230 catch ( RepositoryException e )
1232 log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
1238 synchronized ( this.networkProxyMap )
1240 this.networkProxyMap.clear();
1242 List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
1243 for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
1245 String key = networkProxyConfig.getId();
1247 ProxyInfo proxy = new ProxyInfo();
1249 proxy.setType( networkProxyConfig.getProtocol() );
1250 proxy.setHost( networkProxyConfig.getHost() );
1251 proxy.setPort( networkProxyConfig.getPort() );
1252 proxy.setUserName( networkProxyConfig.getUsername() );
1253 proxy.setPassword( networkProxyConfig.getPassword() );
1255 this.networkProxyMap.put( key, proxy );
1260 public void initialize()
1261 throws InitializationException
1263 initConnectorsAndNetworkProxies();
1264 archivaConfiguration.addChangeListener( this );