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
22 import com.google.common.collect.Lists;
23 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
24 import org.apache.archiva.scheduler.repository.RepositoryTask;
25 import org.apache.commons.collections.CollectionUtils;
26 import org.apache.commons.io.FileUtils;
27 import org.apache.commons.lang.StringUtils;
28 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
29 import org.apache.maven.archiva.configuration.ConfigurationNames;
30 import org.apache.maven.archiva.configuration.NetworkProxyConfiguration;
31 import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
32 import org.apache.maven.archiva.model.ArtifactReference;
33 import org.apache.maven.archiva.model.Keys;
34 import org.apache.maven.archiva.model.RepositoryURL;
35 import org.apache.maven.archiva.policies.DownloadErrorPolicy;
36 import org.apache.maven.archiva.policies.DownloadPolicy;
37 import org.apache.maven.archiva.policies.PolicyConfigurationException;
38 import org.apache.maven.archiva.policies.PolicyViolationException;
39 import org.apache.maven.archiva.policies.PostDownloadPolicy;
40 import org.apache.maven.archiva.policies.PreDownloadPolicy;
41 import org.apache.maven.archiva.policies.ProxyDownloadException;
42 import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
43 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
44 import org.apache.maven.archiva.repository.RemoteRepositoryContent;
45 import org.apache.maven.archiva.repository.RepositoryContentFactory;
46 import org.apache.maven.archiva.repository.RepositoryException;
47 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
48 import org.apache.maven.archiva.repository.metadata.MetadataTools;
49 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
50 import org.apache.maven.wagon.ConnectionException;
51 import org.apache.maven.wagon.ResourceDoesNotExistException;
52 import org.apache.maven.wagon.Wagon;
53 import org.apache.maven.wagon.WagonException;
54 import org.apache.maven.wagon.authentication.AuthenticationException;
55 import org.apache.maven.wagon.authentication.AuthenticationInfo;
56 import org.apache.maven.wagon.proxy.ProxyInfo;
57 import org.apache.maven.wagon.repository.Repository;
58 import org.codehaus.plexus.registry.Registry;
59 import org.codehaus.plexus.registry.RegistryListener;
60 import org.codehaus.plexus.taskqueue.TaskQueueException;
61 import org.codehaus.plexus.util.SelectorUtils;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64 import org.springframework.context.ApplicationContext;
65 import org.springframework.stereotype.Service;
67 import javax.annotation.PostConstruct;
68 import javax.inject.Inject;
69 import javax.inject.Named;
71 import java.io.IOException;
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.Collections;
75 import java.util.HashMap;
76 import java.util.LinkedHashMap;
77 import java.util.List;
79 import java.util.Map.Entry;
80 import java.util.Properties;
83 * DefaultRepositoryProxyConnectors
86 * @todo exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
87 * your average brown onion
88 * plexus.component role-hint="default"
90 @Service( "repositoryProxyConnectors#default" )
91 public class DefaultRepositoryProxyConnectors
92 implements RepositoryProxyConnectors, RegistryListener
94 private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class );
100 @Named( value = "archivaConfiguration#default" )
101 private ArchivaConfiguration archivaConfiguration;
107 @Named( value = "repositoryContentFactory#default" )
108 private RepositoryContentFactory repositoryFactory;
114 @Named( value = "metadataTools#default" )
115 private MetadataTools metadataTools;
118 private ApplicationContext applicationContext;
121 * plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
123 private Map<String, PreDownloadPolicy> preDownloadPolicies;
126 * plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
128 private Map<String, PostDownloadPolicy> postDownloadPolicies;
131 * plexus.requirement role="org.apache.maven.archiva.policies.DownloadErrorPolicy"
133 private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
136 * plexus.requirement role-hint="default"
139 private UrlFailureCache urlFailureCache;
141 private Map<String, List<ProxyConnector>> proxyConnectorMap = new HashMap<String, List<ProxyConnector>>();
143 private Map<String, ProxyInfo> networkProxyMap = new HashMap<String, ProxyInfo>();
149 private WagonFactory wagonFactory;
152 * plexus.requirement role="org.apache.archiva.scheduler.ArchivaTaskScheduler" role-hint="repository"
155 @Named( value = "archivaTaskScheduler#repository" )
156 private ArchivaTaskScheduler scheduler;
159 public void initialize()
161 initConnectorsAndNetworkProxies();
162 archivaConfiguration.addChangeListener( this );
163 this.postDownloadPolicies = applicationContext.getBeansOfType( PostDownloadPolicy.class );
164 this.preDownloadPolicies = applicationContext.getBeansOfType( PreDownloadPolicy.class );
165 this.downloadErrorPolicies = applicationContext.getBeansOfType( DownloadErrorPolicy.class );
168 @SuppressWarnings( "unchecked" )
169 private void initConnectorsAndNetworkProxies()
171 synchronized ( this.proxyConnectorMap )
173 ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
174 this.proxyConnectorMap.clear();
176 List<ProxyConnectorConfiguration> proxyConfigs =
177 archivaConfiguration.getConfiguration().getProxyConnectors();
178 for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
180 String key = proxyConfig.getSourceRepoId();
184 // Create connector object.
185 ProxyConnector connector = new ProxyConnector();
187 connector.setSourceRepository(
188 repositoryFactory.getManagedRepositoryContent( proxyConfig.getSourceRepoId() ) );
189 connector.setTargetRepository(
190 repositoryFactory.getRemoteRepositoryContent( proxyConfig.getTargetRepoId() ) );
192 connector.setProxyId( proxyConfig.getProxyId() );
193 connector.setPolicies( proxyConfig.getPolicies() );
194 connector.setOrder( proxyConfig.getOrder() );
195 connector.setDisabled( proxyConfig.isDisabled() );
197 // Copy any blacklist patterns.
198 List<String> blacklist = new ArrayList<String>();
199 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
201 blacklist.addAll( proxyConfig.getBlackListPatterns() );
203 connector.setBlacklist( blacklist );
205 // Copy any whitelist patterns.
206 List<String> whitelist = new ArrayList<String>();
207 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
209 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
211 connector.setWhitelist( whitelist );
213 // Get other connectors
214 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
215 if ( connectors == null )
217 // Create if we are the first.
218 connectors = new ArrayList<ProxyConnector>();
221 // Add the connector.
222 connectors.add( connector );
224 // Ensure the list is sorted.
225 Collections.sort( connectors, proxyOrderSorter );
227 // Set the key to the list of connectors.
228 this.proxyConnectorMap.put( key, connectors );
230 catch ( RepositoryNotFoundException e )
232 log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
234 catch ( RepositoryException e )
236 log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
242 synchronized ( this.networkProxyMap )
244 this.networkProxyMap.clear();
246 List<NetworkProxyConfiguration> networkProxies =
247 archivaConfiguration.getConfiguration().getNetworkProxies();
248 for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
250 String key = networkProxyConfig.getId();
252 ProxyInfo proxy = new ProxyInfo();
254 proxy.setType( networkProxyConfig.getProtocol() );
255 proxy.setHost( networkProxyConfig.getHost() );
256 proxy.setPort( networkProxyConfig.getPort() );
257 proxy.setUserName( networkProxyConfig.getUsername() );
258 proxy.setPassword( networkProxyConfig.getPassword() );
260 this.networkProxyMap.put( key, proxy );
265 public File fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
266 throws ProxyDownloadException
268 File localFile = toLocalFile( repository, artifact );
270 Properties requestProperties = new Properties();
271 requestProperties.setProperty( "filetype", "artifact" );
272 requestProperties.setProperty( "version", artifact.getVersion() );
273 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
275 List<ProxyConnector> connectors = getProxyConnectors( repository );
276 Map<String, Exception> previousExceptions = new LinkedHashMap<String, Exception>();
277 for ( ProxyConnector connector : connectors )
279 if ( connector.isDisabled() )
284 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
285 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
287 String targetPath = targetRepository.toPath( artifact );
291 File downloadedFile =
292 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
295 if ( fileExists( downloadedFile ) )
297 log.debug( "Successfully transferred: {}", downloadedFile.getAbsolutePath() );
298 return downloadedFile;
301 catch ( NotFoundException e )
303 log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ),
304 targetRepository.getRepository().getId() );
306 catch ( NotModifiedException e )
308 log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ),
309 targetRepository.getRepository().getId() );
311 catch ( ProxyException e )
313 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
314 targetRepository, localFile, e, previousExceptions );
318 if ( !previousExceptions.isEmpty() )
320 throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
321 previousExceptions );
324 log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) );
329 public File fetchFromProxies( ManagedRepositoryContent repository, String path )
331 File localFile = new File( repository.getRepoRoot(), path );
333 // no update policies for these paths
334 if ( localFile.exists() )
339 Properties requestProperties = new Properties();
340 requestProperties.setProperty( "filetype", "resource" );
341 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
343 List<ProxyConnector> connectors = getProxyConnectors( repository );
344 for ( ProxyConnector connector : connectors )
346 if ( connector.isDisabled() )
351 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
352 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
354 String targetPath = path;
358 File downloadedFile =
359 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
362 if ( fileExists( downloadedFile ) )
364 log.debug( "Successfully transferred: {}", downloadedFile.getAbsolutePath() );
365 return downloadedFile;
368 catch ( NotFoundException e )
370 log.debug( "Resource {} not found on repository \"{}\".", path,
371 targetRepository.getRepository().getId() );
373 catch ( NotModifiedException e )
375 log.debug( "Resource {} not updated on repository \"{}\".", path,
376 targetRepository.getRepository().getId() );
378 catch ( ProxyException e )
381 "Transfer error from repository \"" + targetRepository.getRepository().getId() + "\" for resource "
382 + path + ", continuing to next repository. Error message: " + e.getMessage() );
383 log.debug( "Full stack trace", e );
387 log.debug( "Exhausted all target repositories, resource {} not found.", path );
392 public File fetchMetatadaFromProxies( ManagedRepositoryContent repository, String logicalPath )
394 File localFile = new File( repository.getRepoRoot(), logicalPath );
396 Properties requestProperties = new Properties();
397 requestProperties.setProperty( "filetype", "metadata" );
398 boolean metadataNeedsUpdating = false;
399 long originalTimestamp = getLastModified( localFile );
401 List<ProxyConnector> connectors = getProxyConnectors( repository );
402 for ( ProxyConnector connector : connectors )
404 if ( connector.isDisabled() )
409 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
411 File localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
412 long originalMetadataTimestamp = getLastModified( localRepoFile );
416 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
419 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
421 metadataNeedsUpdating = true;
424 catch ( NotFoundException e )
426 if ( log.isDebugEnabled() )
428 log.debug( "Metadata {} not found on remote repository \"{}\".",
429 Lists.<Object>newArrayList( logicalPath, targetRepository.getRepository().getId() ), e );
432 catch ( NotModifiedException e )
434 if ( log.isDebugEnabled() )
436 log.debug( "Metadata {} not updated on remote repository \"{}\".",
437 Lists.<Object>newArrayList( logicalPath, targetRepository.getRepository().getId() ), e );
440 catch ( ProxyException e )
442 log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId()
443 + "\" for versioned Metadata " + logicalPath
444 + ", continuing to next repository. Error message: " + e.getMessage() );
445 log.debug( "Full stack trace", e );
449 if ( hasBeenUpdated( localFile, originalTimestamp ) )
451 metadataNeedsUpdating = true;
454 if ( metadataNeedsUpdating || !localFile.exists() )
458 metadataTools.updateMetadata( repository, logicalPath );
460 catch ( RepositoryMetadataException e )
462 log.warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
466 if ( fileExists( localFile ) )
474 private long getLastModified( File file )
476 if ( !file.exists() || !file.isFile() )
481 return file.lastModified();
484 private boolean hasBeenUpdated( File file, long originalLastModified )
486 if ( !file.exists() || !file.isFile() )
491 long currentLastModified = getLastModified( file );
492 return ( currentLastModified > originalLastModified );
495 private File toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
498 String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
499 return new File( repository.getRepoRoot(), repoPath );
503 * Test if the provided ManagedRepositoryContent has any proxies configured for it.
505 public boolean hasProxies( ManagedRepositoryContent repository )
507 synchronized ( this.proxyConnectorMap )
509 return this.proxyConnectorMap.containsKey( repository.getId() );
513 private File toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
515 return repository.toFile( artifact );
519 * Simple method to test if the file exists on the local disk.
521 * @param file the file to test. (may be null)
522 * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
524 private boolean fileExists( File file )
531 if ( !file.exists() )
536 if ( !file.isFile() )
545 * Perform the transfer of the file.
547 * @param connector the connector configuration to use.
548 * @param remoteRepository the remote repository get the resource from.
549 * @param remotePath the path in the remote repository to the resource to get.
550 * @param repository the managed repository that will hold the file
551 * @param resource the local file to place the downloaded resource into
552 * @param requestProperties the request properties to utilize for policy handling.
553 * @param executeConsumers whether to execute the consumers after proxying
554 * @return the local file that was downloaded, or null if not downloaded.
555 * @throws NotFoundException if the file was not found on the remote repository.
556 * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but
557 * the remote resource is not newer than the local File.
558 * @throws ProxyException if transfer was unsuccessful.
560 private File transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
561 ManagedRepositoryContent repository, File resource, Properties requestProperties,
562 boolean executeConsumers )
563 throws ProxyException, NotModifiedException
565 String url = remoteRepository.getURL().getUrl();
566 if ( !url.endsWith( "/" ) )
570 url = url + remotePath;
571 requestProperties.setProperty( "url", url );
573 // Is a whitelist defined?
574 if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
576 // Path must belong to whitelist.
577 if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
579 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
580 remotePath, remoteRepository.getRepository().getName() );
585 // Is target path part of blacklist?
586 if ( matchesPattern( remotePath, connector.getBlacklist() ) )
588 log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
589 remoteRepository.getRepository().getName() );
593 // Handle pre-download policy
596 validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
598 catch ( PolicyViolationException e )
600 String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
601 if ( fileExists( resource ) )
603 log.debug( "{} : using already present local file.", emsg );
613 File tmpResource = null;
615 File workingDirectory = createWorkingDirectory( repository );
621 RepositoryURL repoUrl = remoteRepository.getURL();
622 String protocol = repoUrl.getProtocol();
623 wagon = wagonFactory.getWagon( "wagon#" + protocol );
626 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
629 boolean connected = connectToRepository( connector, wagon, remoteRepository );
632 tmpResource = new File( workingDirectory, resource.getName() );
633 transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, tmpResource );
635 // TODO: these should be used to validate the download based on the policies, not always downloaded
637 // save on connections since md5 is rarely used
639 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
642 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
646 catch ( NotFoundException e )
648 urlFailureCache.cacheFailure( url );
651 catch ( NotModifiedException e )
653 // Do not cache url here.
656 catch ( ProxyException e )
658 urlFailureCache.cacheFailure( url );
661 catch ( WagonFactoryException e )
663 throw new ProxyException( e.getMessage(), e );
673 catch ( ConnectionException e )
675 log.warn( "Unable to disconnect wagon.", e );
680 // Handle post-download policies.
683 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
685 catch ( PolicyViolationException e )
687 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
688 executeConsumers = false;
689 if ( !fileExists( tmpResource ) )
695 if ( resource != null )
697 synchronized ( resource.getAbsolutePath().intern() )
699 File directory = resource.getParentFile();
700 moveFileIfExists( tmpMd5, directory );
701 moveFileIfExists( tmpSha1, directory );
702 moveFileIfExists( tmpResource, directory );
708 FileUtils.deleteQuietly( workingDirectory );
711 if ( executeConsumers )
713 // Just-in-time update of the index and database by executing the consumers for this artifact
714 //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource );
715 queueRepositoryTask( connector.getSourceRepository().getRepository().getId(), resource );
721 private void queueRepositoryTask( String repositoryId, File localFile )
723 RepositoryTask task = new RepositoryTask();
724 task.setRepositoryId( repositoryId );
725 task.setResourceFile( localFile );
726 task.setUpdateRelatedArtifacts( true );
727 task.setScanAll( true );
731 scheduler.queueTask( task );
733 catch ( TaskQueueException e )
735 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
741 * Moves the file into repository location if it exists
743 * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
744 * @param directory directory to write files to
746 private void moveFileIfExists( File fileToMove, File directory )
747 throws ProxyException
749 if ( fileToMove != null && fileToMove.exists() )
751 File newLocation = new File( directory, fileToMove.getName() );
752 moveTempToTarget( fileToMove, newLocation );
758 * Quietly transfer the checksum file from the remote repository to the local file.
761 * @param wagon the wagon instance (should already be connected) to use.
762 * @param remoteRepository the remote repository to transfer from.
763 * @param remotePath the remote path to the resource to get.
764 * @param repository the managed repository that will hold the file
765 * @param resource the local file that should contain the downloaded contents
766 * @param tmpDirectory the temporary directory to download to
767 * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
768 * @throws ProxyException if copying the downloaded file into place did not succeed.
770 private File transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
771 ManagedRepositoryContent repository, File resource, File tmpDirectory, String ext )
772 throws ProxyException
774 String url = remoteRepository.getURL().getUrl() + remotePath + ext;
776 // Transfer checksum does not use the policy.
777 if ( urlFailureCache.hasFailedBefore( url ) )
782 File destFile = new File( tmpDirectory, resource.getName() + ext );
786 transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile );
787 log.debug( "Checksum {} Downloaded: {} to move to {}", Arrays.asList( url, destFile, resource ).toArray() );
789 catch ( NotFoundException e )
791 urlFailureCache.cacheFailure( url );
792 log.debug( "Transfer failed, checksum not found: {}", url );
793 // Consume it, do not pass this on.
795 catch ( NotModifiedException e )
797 log.debug( "Transfer skipped, checksum not modified: {}", url );
798 // Consume it, do not pass this on.
800 catch ( ProxyException e )
802 urlFailureCache.cacheFailure( url );
803 log.warn( "Transfer failed on checksum: " + url + " : " + e.getMessage(), e );
804 // Critical issue, pass it on.
811 * Perform the transfer of the remote file to the local file specified.
813 * @param wagon the wagon instance to use.
814 * @param remoteRepository the remote repository to use
815 * @param remotePath the remote path to attempt to get
816 * @param repository the managed repository that will hold the file
817 * @param origFile the local file to save to
818 * @return The local file that was transfered.
819 * @throws ProxyException if there was a problem moving the downloaded file into place.
820 * @throws WagonException if there was a problem tranfering the file.
822 private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
823 ManagedRepositoryContent repository, File origFile, File destFile )
824 throws ProxyException
826 assert ( remotePath != null );
828 // Transfer the file.
831 boolean success = false;
833 if ( !origFile.exists() )
835 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
836 wagon.get( remotePath, destFile );
839 // You wouldn't get here on failure, a WagonException would have been thrown.
840 log.debug( "Downloaded successfully." );
844 log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() );
845 success = wagon.getIfNewer( remotePath, destFile, origFile.lastModified() );
848 throw new NotModifiedException(
849 "Not downloaded, as local file is newer than remote side: " + origFile.getAbsolutePath() );
852 if ( destFile.exists() )
854 log.debug( "Downloaded successfully." );
858 catch ( ResourceDoesNotExistException e )
860 throw new NotFoundException(
861 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
864 catch ( WagonException e )
866 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
869 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
870 if ( e.getCause() != null )
872 msg += " (cause: " + e.getCause() + ")";
874 throw new ProxyException( msg, e );
879 * Apply the policies.
881 * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
882 * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy
884 * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}
886 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)})
888 private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
889 Properties request, File localFile )
890 throws PolicyViolationException
892 for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
894 // olamy with spring rolehint is now downloadPolicy#hint
895 // so substring after last # to get the hint as with plexus
896 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
897 DownloadPolicy policy = entry.getValue();
898 String defaultSetting = policy.getDefaultOption();
900 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
902 log.debug( "Applying [{}] policy with [{}]", key, setting );
905 policy.applyPolicy( setting, request, localFile );
907 catch ( PolicyConfigurationException e )
909 log.error( e.getMessage(), e );
914 private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
915 Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
916 File localFile, ProxyException exception, Map<String, Exception> previousExceptions )
917 throws ProxyDownloadException
919 boolean process = true;
920 for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
923 // olamy with spring rolehint is now downloadPolicy#hint
924 // so substring after last # to get the hint as with plexus
925 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
926 DownloadErrorPolicy policy = entry.getValue();
927 String defaultSetting = policy.getDefaultOption();
928 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
930 log.debug( "Applying [{}] policy with [{}]", key, setting );
933 // all policies must approve the exception, any can cancel
934 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
940 catch ( PolicyConfigurationException e )
942 log.error( e.getMessage(), e );
948 // if the exception was queued, don't throw it
949 if ( !previousExceptions.containsKey( content.getId() ) )
951 throw new ProxyDownloadException(
952 "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
953 content.getId(), exception );
958 // if the exception was queued, but cancelled, remove it
959 previousExceptions.remove( content.getId() );
962 log.warn( "Transfer error from repository \"" + content.getRepository().getId() + "\" for artifact "
963 + Keys.toKey( artifact ) + ", continuing to next repository. Error message: "
964 + exception.getMessage() );
965 log.debug( "Full stack trace", exception );
969 * Creates a working directory in the repository root for this request
972 * @return file location of working directory
973 * @throws IOException
975 private File createWorkingDirectory( ManagedRepositoryContent repository )
977 // TODO: This is ugly - lets actually clean this up when we get the new repository api
980 File tmpDir = File.createTempFile( ".workingdirectory", null, new File( repository.getRepoRoot() ) );
985 catch ( IOException e )
987 throw new RuntimeException( "Could not create working directory for this request", e );
992 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
995 * @param temp The completed download file
996 * @param target The final location of the downloaded file
997 * @throws ProxyException when the temp file cannot replace the target file
999 private void moveTempToTarget( File temp, File target )
1000 throws ProxyException
1002 if ( target.exists() && !target.delete() )
1004 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
1007 target.getParentFile().mkdirs();
1008 if ( !temp.renameTo( target ) )
1010 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1014 FileUtils.copyFile( temp, target );
1016 catch ( IOException e )
1018 if ( target.exists() )
1020 log.debug( "Tried to copy file {} to {} but file with this name already exists.", temp.getName(),
1021 target.getAbsolutePath() );
1025 throw new ProxyException(
1026 "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e );
1031 FileUtils.deleteQuietly( temp );
1037 * Using wagon, connect to the remote repository.
1039 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1040 * @param wagon the wagon instance to establish the connection on.
1041 * @param remoteRepository the remote repository to connect to.
1042 * @return true if the connection was successful. false if not connected.
1044 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1045 RemoteRepositoryContent remoteRepository )
1047 boolean connected = false;
1049 final ProxyInfo networkProxy;
1050 synchronized ( this.networkProxyMap )
1052 networkProxy = (ProxyInfo) this.networkProxyMap.get( connector.getProxyId() );
1055 if ( log.isDebugEnabled() )
1057 if ( networkProxy != null )
1059 // TODO: move to proxyInfo.toString()
1060 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1061 + " to connect to remote repository " + remoteRepository.getURL();
1062 if ( networkProxy.getNonProxyHosts() != null )
1064 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1066 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1068 msg += "; as user: " + networkProxy.getUserName();
1074 AuthenticationInfo authInfo = null;
1075 String username = remoteRepository.getRepository().getUsername();
1076 String password = remoteRepository.getRepository().getPassword();
1078 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1080 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1081 authInfo = new AuthenticationInfo();
1082 authInfo.setUserName( username );
1083 authInfo.setPassword( password );
1086 // Convert seconds to milliseconds
1087 int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
1090 wagon.setTimeout( timeoutInMilliseconds );
1094 Repository wagonRepository =
1095 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1096 wagon.connect( wagonRepository, authInfo, networkProxy );
1099 catch ( ConnectionException e )
1101 log.warn( "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
1104 catch ( AuthenticationException e )
1106 log.warn( "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
1114 * Tests whitelist and blacklist patterns against path.
1116 * @param path the path to test.
1117 * @param patterns the list of patterns to check.
1118 * @return true if the path matches at least 1 pattern in the provided patterns list.
1120 private boolean matchesPattern( String path, List<String> patterns )
1122 if ( CollectionUtils.isEmpty( patterns ) )
1127 if ( !path.startsWith( "/" ) )
1132 for ( String pattern : patterns )
1134 if ( !pattern.startsWith( "/" ) )
1136 pattern = "/" + pattern;
1139 if ( SelectorUtils.matchPath( pattern, path, false ) )
1149 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1151 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1153 synchronized ( this.proxyConnectorMap )
1155 List<ProxyConnector> ret = (List<ProxyConnector>) this.proxyConnectorMap.get( repository.getId() );
1158 return Collections.emptyList();
1161 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1166 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1168 if ( ConfigurationNames.isNetworkProxy( propertyName )
1169 || ConfigurationNames.isManagedRepositories( propertyName )
1170 || ConfigurationNames.isRemoteRepositories( propertyName ) || ConfigurationNames.isProxyConnector(
1173 initConnectorsAndNetworkProxies();
1177 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1182 public ArchivaConfiguration getArchivaConfiguration()
1184 return archivaConfiguration;
1187 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1189 this.archivaConfiguration = archivaConfiguration;
1192 public RepositoryContentFactory getRepositoryFactory()
1194 return repositoryFactory;
1197 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1199 this.repositoryFactory = repositoryFactory;
1202 public MetadataTools getMetadataTools()
1204 return metadataTools;
1207 public void setMetadataTools( MetadataTools metadataTools )
1209 this.metadataTools = metadataTools;
1212 public UrlFailureCache getUrlFailureCache()
1214 return urlFailureCache;
1217 public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1219 this.urlFailureCache = urlFailureCache;
1222 public WagonFactory getWagonFactory()
1224 return wagonFactory;
1227 public void setWagonFactory( WagonFactory wagonFactory )
1229 this.wagonFactory = wagonFactory;
1232 public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1234 return preDownloadPolicies;
1237 public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1239 this.preDownloadPolicies = preDownloadPolicies;
1242 public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1244 return postDownloadPolicies;
1247 public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1249 this.postDownloadPolicies = postDownloadPolicies;
1252 public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1254 return downloadErrorPolicies;
1257 public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1259 this.downloadErrorPolicies = downloadErrorPolicies;