1 package org.apache.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.io.Files;
23 import org.apache.archiva.admin.model.RepositoryAdminException;
24 import org.apache.archiva.admin.model.beans.NetworkProxy;
25 import org.apache.archiva.admin.model.beans.ProxyConnectorRuleType;
26 import org.apache.archiva.admin.model.beans.RemoteRepository;
27 import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin;
28 import org.apache.archiva.configuration.ArchivaConfiguration;
29 import org.apache.archiva.configuration.Configuration;
30 import org.apache.archiva.configuration.ConfigurationNames;
31 import org.apache.archiva.configuration.NetworkProxyConfiguration;
32 import org.apache.archiva.configuration.ProxyConnectorConfiguration;
33 import org.apache.archiva.configuration.ProxyConnectorRuleConfiguration;
34 import org.apache.archiva.model.ArtifactReference;
35 import org.apache.archiva.model.Keys;
36 import org.apache.archiva.model.RepositoryURL;
37 import org.apache.archiva.policies.DownloadErrorPolicy;
38 import org.apache.archiva.policies.DownloadPolicy;
39 import org.apache.archiva.policies.PolicyConfigurationException;
40 import org.apache.archiva.policies.PolicyViolationException;
41 import org.apache.archiva.policies.PostDownloadPolicy;
42 import org.apache.archiva.policies.PreDownloadPolicy;
43 import org.apache.archiva.policies.ProxyDownloadException;
44 import org.apache.archiva.policies.urlcache.UrlFailureCache;
45 import org.apache.archiva.proxy.common.WagonFactory;
46 import org.apache.archiva.proxy.common.WagonFactoryException;
47 import org.apache.archiva.proxy.common.WagonFactoryRequest;
48 import org.apache.archiva.proxy.model.ProxyConnector;
49 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
50 import org.apache.archiva.redback.components.registry.Registry;
51 import org.apache.archiva.redback.components.registry.RegistryListener;
52 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
53 import org.apache.archiva.repository.ManagedRepositoryContent;
54 import org.apache.archiva.repository.RemoteRepositoryContent;
55 import org.apache.archiva.repository.RepositoryContentFactory;
56 import org.apache.archiva.repository.RepositoryException;
57 import org.apache.archiva.repository.RepositoryNotFoundException;
58 import org.apache.archiva.repository.metadata.MetadataTools;
59 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
60 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
61 import org.apache.archiva.scheduler.repository.RepositoryTask;
62 import org.apache.commons.collections.CollectionUtils;
63 import org.apache.commons.io.FileUtils;
64 import org.apache.commons.io.FilenameUtils;
65 import org.apache.commons.lang.StringUtils;
66 import org.apache.commons.lang.SystemUtils;
67 import org.apache.maven.wagon.ConnectionException;
68 import org.apache.maven.wagon.ResourceDoesNotExistException;
69 import org.apache.maven.wagon.Wagon;
70 import org.apache.maven.wagon.WagonException;
71 import org.apache.maven.wagon.authentication.AuthenticationException;
72 import org.apache.maven.wagon.authentication.AuthenticationInfo;
73 import org.apache.maven.wagon.proxy.ProxyInfo;
74 import org.apache.maven.wagon.repository.Repository;
75 import org.apache.tools.ant.types.selectors.SelectorUtils;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78 import org.springframework.stereotype.Service;
80 import javax.annotation.PostConstruct;
81 import javax.inject.Inject;
82 import javax.inject.Named;
84 import java.io.IOException;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.Collections;
88 import java.util.HashMap;
89 import java.util.LinkedHashMap;
90 import java.util.List;
92 import java.util.Map.Entry;
93 import java.util.Properties;
94 import java.util.concurrent.ConcurrentHashMap;
97 * DefaultRepositoryProxyConnectors
99 * @todo exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
100 * your average brown onion
102 @Service ( "repositoryProxyConnectors#default" )
103 public class DefaultRepositoryProxyConnectors
104 implements RepositoryProxyConnectors, RegistryListener
106 private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class );
112 @Named ( value = "archivaConfiguration#default" )
113 private ArchivaConfiguration archivaConfiguration;
119 @Named ( value = "repositoryContentFactory#default" )
120 private RepositoryContentFactory repositoryFactory;
126 @Named ( value = "metadataTools#default" )
127 private MetadataTools metadataTools;
133 private Map<String, PreDownloadPolicy> preDownloadPolicies;
139 private Map<String, PostDownloadPolicy> postDownloadPolicies;
145 private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
151 private UrlFailureCache urlFailureCache;
153 private Map<String, List<ProxyConnector>> proxyConnectorMap = new HashMap<String, List<ProxyConnector>>();
155 private Map<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<String, ProxyInfo>();
161 private WagonFactory wagonFactory;
167 @Named ( value = "archivaTaskScheduler#repository" )
168 private ArchivaTaskScheduler scheduler;
171 private NetworkProxyAdmin networkProxyAdmin;
174 public void initialize()
176 initConnectorsAndNetworkProxies();
177 archivaConfiguration.addChangeListener( this );
181 @SuppressWarnings ( "unchecked" )
182 private void initConnectorsAndNetworkProxies()
185 ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
186 this.proxyConnectorMap.clear();
188 Configuration configuration = archivaConfiguration.getConfiguration();
190 List<ProxyConnectorRuleConfiguration> allProxyConnectorRuleConfigurations =
191 configuration.getProxyConnectorRuleConfigurations();
193 List<ProxyConnectorConfiguration> proxyConfigs = configuration.getProxyConnectors();
194 for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
196 String key = proxyConfig.getSourceRepoId();
200 // Create connector object.
201 ProxyConnector connector = new ProxyConnector();
203 connector.setSourceRepository(
204 repositoryFactory.getManagedRepositoryContent( proxyConfig.getSourceRepoId() ) );
205 connector.setTargetRepository(
206 repositoryFactory.getRemoteRepositoryContent( proxyConfig.getTargetRepoId() ) );
208 connector.setProxyId( proxyConfig.getProxyId() );
209 connector.setPolicies( proxyConfig.getPolicies() );
210 connector.setOrder( proxyConfig.getOrder() );
211 connector.setDisabled( proxyConfig.isDisabled() );
213 // Copy any blacklist patterns.
214 List<String> blacklist = new ArrayList<String>( 0 );
215 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
217 blacklist.addAll( proxyConfig.getBlackListPatterns() );
219 connector.setBlacklist( blacklist );
221 // Copy any whitelist patterns.
222 List<String> whitelist = new ArrayList<String>( 0 );
223 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
225 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
227 connector.setWhitelist( whitelist );
229 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
230 findProxyConnectorRules( connector.getSourceRepository().getId(),
231 connector.getTargetRepository().getId(),
232 allProxyConnectorRuleConfigurations );
234 if ( !proxyConnectorRuleConfigurations.isEmpty() )
236 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : proxyConnectorRuleConfigurations )
238 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
239 ProxyConnectorRuleType.BLACK_LIST.getRuleType() ) )
241 connector.getBlacklist().add( proxyConnectorRuleConfiguration.getPattern() );
244 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
245 ProxyConnectorRuleType.WHITE_LIST.getRuleType() ) )
247 connector.getWhitelist().add( proxyConnectorRuleConfiguration.getPattern() );
252 // Get other connectors
253 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
254 if ( connectors == null )
256 // Create if we are the first.
257 connectors = new ArrayList<ProxyConnector>( 1 );
260 // Add the connector.
261 connectors.add( connector );
263 // Ensure the list is sorted.
264 Collections.sort( connectors, proxyOrderSorter );
266 // Set the key to the list of connectors.
267 this.proxyConnectorMap.put( key, connectors );
269 catch ( RepositoryNotFoundException e )
271 log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
273 catch ( RepositoryException e )
275 log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
281 this.networkProxyMap.clear();
283 List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
284 for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
286 String key = networkProxyConfig.getId();
288 ProxyInfo proxy = new ProxyInfo();
290 proxy.setType( networkProxyConfig.getProtocol() );
291 proxy.setHost( networkProxyConfig.getHost() );
292 proxy.setPort( networkProxyConfig.getPort() );
293 proxy.setUserName( networkProxyConfig.getUsername() );
294 proxy.setPassword( networkProxyConfig.getPassword() );
296 this.networkProxyMap.put( key, proxy );
301 private List<ProxyConnectorRuleConfiguration> findProxyConnectorRules( String sourceRepository,
302 String targetRepository,
303 List<ProxyConnectorRuleConfiguration> all )
305 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
306 new ArrayList<ProxyConnectorRuleConfiguration>();
308 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : all )
310 for ( ProxyConnectorConfiguration proxyConnector : proxyConnectorRuleConfiguration.getProxyConnectors() )
312 if ( StringUtils.equals( sourceRepository, proxyConnector.getSourceRepoId() ) && StringUtils.equals(
313 targetRepository, proxyConnector.getTargetRepoId() ) )
315 proxyConnectorRuleConfigurations.add( proxyConnectorRuleConfiguration );
320 return proxyConnectorRuleConfigurations;
323 public File fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
324 throws ProxyDownloadException
326 File localFile = toLocalFile( repository, artifact );
328 Properties requestProperties = new Properties();
329 requestProperties.setProperty( "filetype", "artifact" );
330 requestProperties.setProperty( "version", artifact.getVersion() );
331 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
333 List<ProxyConnector> connectors = getProxyConnectors( repository );
334 Map<String, Exception> previousExceptions = new LinkedHashMap<String, Exception>();
335 for ( ProxyConnector connector : connectors )
337 if ( connector.isDisabled() )
342 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
343 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
345 String targetPath = targetRepository.toPath( artifact );
347 if ( SystemUtils.IS_OS_WINDOWS )
349 // toPath use system PATH_SEPARATOR so on windows url are \ which doesn't work very well :-)
350 targetPath = FilenameUtils.separatorsToUnix( targetPath );
355 File downloadedFile =
356 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
359 if ( fileExists( downloadedFile ) )
361 log.debug( "Successfully transferred: {}", downloadedFile.getAbsolutePath() );
362 return downloadedFile;
365 catch ( NotFoundException e )
367 log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ),
368 targetRepository.getRepository().getId() );
370 catch ( NotModifiedException e )
372 log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ),
373 targetRepository.getRepository().getId() );
375 catch ( ProxyException e )
377 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
378 targetRepository, localFile, e, previousExceptions );
380 catch ( RepositoryAdminException e )
382 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
383 targetRepository, localFile, e, previousExceptions );
387 if ( !previousExceptions.isEmpty() )
389 throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
390 previousExceptions );
393 log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) );
398 public File fetchFromProxies( ManagedRepositoryContent repository, String path )
400 File localFile = new File( repository.getRepoRoot(), path );
402 // no update policies for these paths
403 if ( localFile.exists() )
408 Properties requestProperties = new Properties();
409 requestProperties.setProperty( "filetype", "resource" );
410 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
412 List<ProxyConnector> connectors = getProxyConnectors( repository );
413 for ( ProxyConnector connector : connectors )
415 if ( connector.isDisabled() )
420 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
421 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
423 String targetPath = path;
427 File downloadedFile =
428 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
431 if ( fileExists( downloadedFile ) )
433 log.debug( "Successfully transferred: {}", downloadedFile.getAbsolutePath() );
434 return downloadedFile;
437 catch ( NotFoundException e )
439 log.debug( "Resource {} not found on repository \"{}\".", path,
440 targetRepository.getRepository().getId() );
442 catch ( NotModifiedException e )
444 log.debug( "Resource {} not updated on repository \"{}\".", path,
445 targetRepository.getRepository().getId() );
447 catch ( ProxyException e )
450 "Transfer error from repository \"" + targetRepository.getRepository().getId() + "\" for resource "
451 + path + ", continuing to next repository. Error message: " + e.getMessage() );
452 log.debug( "Full stack trace", e );
454 catch ( RepositoryAdminException e )
457 "Transfer error from repository \"" + targetRepository.getRepository().getId() + "\" for resource "
458 + path + ", continuing to next repository. Error message: " + e.getMessage() );
459 log.debug( "Full stack trace", e );
463 log.debug( "Exhausted all target repositories, resource {} not found.", path );
468 public File fetchMetatadaFromProxies( ManagedRepositoryContent repository, String logicalPath )
470 File localFile = new File( repository.getRepoRoot(), logicalPath );
472 Properties requestProperties = new Properties();
473 requestProperties.setProperty( "filetype", "metadata" );
474 boolean metadataNeedsUpdating = false;
475 long originalTimestamp = getLastModified( localFile );
477 List<ProxyConnector> connectors = getProxyConnectors( repository );
478 for ( ProxyConnector connector : connectors )
480 if ( connector.isDisabled() )
485 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
487 File localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
488 long originalMetadataTimestamp = getLastModified( localRepoFile );
492 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
495 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
497 metadataNeedsUpdating = true;
500 catch ( NotFoundException e )
503 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
504 targetRepository.getRepository().getId(), e );
507 catch ( NotModifiedException e )
510 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
511 targetRepository.getRepository().getId(), e );
514 catch ( ProxyException e )
516 log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId()
517 + "\" for versioned Metadata " + logicalPath
518 + ", continuing to next repository. Error message: " + e.getMessage() );
519 log.debug( "Full stack trace", e );
521 catch ( RepositoryAdminException e )
523 log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId()
524 + "\" for versioned Metadata " + logicalPath
525 + ", continuing to next repository. Error message: " + e.getMessage() );
526 log.debug( "Full stack trace", e );
530 if ( hasBeenUpdated( localFile, originalTimestamp ) )
532 metadataNeedsUpdating = true;
535 if ( metadataNeedsUpdating || !localFile.exists() )
539 metadataTools.updateMetadata( repository, logicalPath );
541 catch ( RepositoryMetadataException e )
543 log.warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
547 if ( fileExists( localFile ) )
555 private long getLastModified( File file )
557 if ( !file.exists() || !file.isFile() )
562 return file.lastModified();
565 private boolean hasBeenUpdated( File file, long originalLastModified )
567 if ( !file.exists() || !file.isFile() )
572 long currentLastModified = getLastModified( file );
573 return ( currentLastModified > originalLastModified );
576 private File toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
579 String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
580 return new File( repository.getRepoRoot(), repoPath );
584 * Test if the provided ManagedRepositoryContent has any proxies configured for it.
586 public boolean hasProxies( ManagedRepositoryContent repository )
588 synchronized ( this.proxyConnectorMap )
590 return this.proxyConnectorMap.containsKey( repository.getId() );
594 private File toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
596 return repository.toFile( artifact );
600 * Simple method to test if the file exists on the local disk.
602 * @param file the file to test. (may be null)
603 * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
605 private boolean fileExists( File file )
612 if ( !file.exists() )
617 if ( !file.isFile() )
626 * Perform the transfer of the file.
628 * @param connector the connector configuration to use.
629 * @param remoteRepository the remote repository get the resource from.
630 * @param remotePath the path in the remote repository to the resource to get.
631 * @param repository the managed repository that will hold the file
632 * @param resource the local file to place the downloaded resource into
633 * @param requestProperties the request properties to utilize for policy handling.
634 * @param executeConsumers whether to execute the consumers after proxying
635 * @return the local file that was downloaded, or null if not downloaded.
636 * @throws NotFoundException if the file was not found on the remote repository.
637 * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but
638 * the remote resource is not newer than the local File.
639 * @throws ProxyException if transfer was unsuccessful.
641 private File transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
642 ManagedRepositoryContent repository, File resource, Properties requestProperties,
643 boolean executeConsumers )
644 throws ProxyException, NotModifiedException, RepositoryAdminException
646 String url = remoteRepository.getURL().getUrl();
647 if ( !url.endsWith( "/" ) )
651 url = url + remotePath;
652 requestProperties.setProperty( "url", url );
654 // Is a whitelist defined?
655 if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
657 // Path must belong to whitelist.
658 if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
660 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
661 remotePath, remoteRepository.getRepository().getName() );
666 // Is target path part of blacklist?
667 if ( matchesPattern( remotePath, connector.getBlacklist() ) )
669 log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
670 remoteRepository.getRepository().getName() );
674 // Handle pre-download policy
677 validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
679 catch ( PolicyViolationException e )
681 String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
682 if ( fileExists( resource ) )
684 log.debug( "{} : using already present local file.", emsg );
694 File tmpResource = null;
696 File workingDirectory = createWorkingDirectory( repository );
702 RepositoryURL repoUrl = remoteRepository.getURL();
703 String protocol = repoUrl.getProtocol();
704 NetworkProxy networkProxy = null;
705 if ( StringUtils.isNotBlank( connector.getProxyId() ) )
707 networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() );
710 wagon = ( networkProxy != null && networkProxy.isUseNtlm() )
711 ? wagonFactory.getWagon( new WagonFactoryRequest( "wagon#" + protocol + "-ntlm",
712 remoteRepository.getRepository().getExtraHeaders() ) )
713 : wagonFactory.getWagon( new WagonFactoryRequest( "wagon#" + protocol,
714 remoteRepository.getRepository().getExtraHeaders() ) );
717 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
720 boolean connected = connectToRepository( connector, wagon, remoteRepository );
723 tmpResource = new File( workingDirectory, resource.getName() );
724 transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, tmpResource );
726 // TODO: these should be used to validate the download based on the policies, not always downloaded
728 // save on connections since md5 is rarely used
730 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
733 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
737 catch ( NotFoundException e )
739 urlFailureCache.cacheFailure( url );
742 catch ( NotModifiedException e )
744 // Do not cache url here.
747 catch ( ProxyException e )
749 urlFailureCache.cacheFailure( url );
752 catch ( WagonFactoryException e )
754 throw new ProxyException( e.getMessage(), e );
764 catch ( ConnectionException e )
766 log.warn( "Unable to disconnect wagon.", e );
771 // Handle post-download policies.
774 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
776 catch ( PolicyViolationException e )
778 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
779 executeConsumers = false;
780 if ( !fileExists( tmpResource ) )
786 if ( resource != null )
788 synchronized ( resource.getAbsolutePath().intern() )
790 File directory = resource.getParentFile();
791 moveFileIfExists( tmpMd5, directory );
792 moveFileIfExists( tmpSha1, directory );
793 moveFileIfExists( tmpResource, directory );
799 FileUtils.deleteQuietly( workingDirectory );
802 if ( executeConsumers )
804 // Just-in-time update of the index and database by executing the consumers for this artifact
805 //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource );
806 queueRepositoryTask( connector.getSourceRepository().getRepository().getId(), resource );
812 private void queueRepositoryTask( String repositoryId, File localFile )
814 RepositoryTask task = new RepositoryTask();
815 task.setRepositoryId( repositoryId );
816 task.setResourceFile( localFile );
817 task.setUpdateRelatedArtifacts( true );
818 task.setScanAll( true );
822 scheduler.queueTask( task );
824 catch ( TaskQueueException e )
826 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
832 * Moves the file into repository location if it exists
834 * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
835 * @param directory directory to write files to
837 private void moveFileIfExists( File fileToMove, File directory )
838 throws ProxyException
840 if ( fileToMove != null && fileToMove.exists() )
842 File newLocation = new File( directory, fileToMove.getName() );
843 moveTempToTarget( fileToMove, newLocation );
849 * Quietly transfer the checksum file from the remote repository to the local file.
852 * @param wagon the wagon instance (should already be connected) to use.
853 * @param remoteRepository the remote repository to transfer from.
854 * @param remotePath the remote path to the resource to get.
855 * @param repository the managed repository that will hold the file
856 * @param resource the local file that should contain the downloaded contents
857 * @param tmpDirectory the temporary directory to download to
858 * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
859 * @throws ProxyException if copying the downloaded file into place did not succeed.
861 private File transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
862 ManagedRepositoryContent repository, File resource, File tmpDirectory, String ext )
863 throws ProxyException
865 String url = remoteRepository.getURL().getUrl() + remotePath + ext;
867 // Transfer checksum does not use the policy.
868 if ( urlFailureCache.hasFailedBefore( url ) )
873 File destFile = new File( tmpDirectory, resource.getName() + ext );
877 transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile );
878 log.debug( "Checksum {} Downloaded: {} to move to {}", Arrays.asList( url, destFile, resource ).toArray() );
880 catch ( NotFoundException e )
882 urlFailureCache.cacheFailure( url );
883 log.debug( "Transfer failed, checksum not found: {}", url );
884 // Consume it, do not pass this on.
886 catch ( NotModifiedException e )
888 log.debug( "Transfer skipped, checksum not modified: {}", url );
889 // Consume it, do not pass this on.
891 catch ( ProxyException e )
893 urlFailureCache.cacheFailure( url );
894 log.warn( "Transfer failed on checksum: " + url + " : " + e.getMessage(), e );
895 // Critical issue, pass it on.
902 * Perform the transfer of the remote file to the local file specified.
904 * @param wagon the wagon instance to use.
905 * @param remoteRepository the remote repository to use
906 * @param remotePath the remote path to attempt to get
907 * @param repository the managed repository that will hold the file
908 * @param origFile the local file to save to
909 * @return The local file that was transfered.
910 * @throws ProxyException if there was a problem moving the downloaded file into place.
911 * @throws WagonException if there was a problem tranfering the file.
913 private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
914 ManagedRepositoryContent repository, File origFile, File destFile )
915 throws ProxyException
917 assert ( remotePath != null );
919 // Transfer the file.
922 boolean success = false;
924 if ( !origFile.exists() )
926 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
927 wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile );
930 // You wouldn't get here on failure, a WagonException would have been thrown.
931 log.debug( "Downloaded successfully." );
935 log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() );
936 success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile,
937 origFile.lastModified() );
940 throw new NotModifiedException(
941 "Not downloaded, as local file is newer than remote side: " + origFile.getAbsolutePath() );
944 if ( destFile.exists() )
946 log.debug( "Downloaded successfully." );
950 catch ( ResourceDoesNotExistException e )
952 throw new NotFoundException(
953 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
956 catch ( WagonException e )
958 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
961 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
962 if ( e.getCause() != null )
964 msg += " (cause: " + e.getCause() + ")";
966 throw new ProxyException( msg, e );
971 * Apply the policies.
973 * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
974 * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy
976 * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}
978 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)})
980 private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
981 Properties request, File localFile )
982 throws PolicyViolationException
984 for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
986 // olamy with spring rolehint is now downloadPolicy#hint
987 // so substring after last # to get the hint as with plexus
988 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
989 DownloadPolicy policy = entry.getValue();
990 String defaultSetting = policy.getDefaultOption();
992 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
994 log.debug( "Applying [{}] policy with [{}]", key, setting );
997 policy.applyPolicy( setting, request, localFile );
999 catch ( PolicyConfigurationException e )
1001 log.error( e.getMessage(), e );
1006 private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
1007 Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
1008 File localFile, Exception exception, Map<String, Exception> previousExceptions )
1009 throws ProxyDownloadException
1011 boolean process = true;
1012 for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
1015 // olamy with spring rolehint is now downloadPolicy#hint
1016 // so substring after last # to get the hint as with plexus
1017 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1018 DownloadErrorPolicy policy = entry.getValue();
1019 String defaultSetting = policy.getDefaultOption();
1020 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1022 log.debug( "Applying [{}] policy with [{}]", key, setting );
1025 // all policies must approve the exception, any can cancel
1026 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
1032 catch ( PolicyConfigurationException e )
1034 log.error( e.getMessage(), e );
1040 // if the exception was queued, don't throw it
1041 if ( !previousExceptions.containsKey( content.getId() ) )
1043 throw new ProxyDownloadException(
1044 "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
1045 content.getId(), exception );
1050 // if the exception was queued, but cancelled, remove it
1051 previousExceptions.remove( content.getId() );
1055 "Transfer error from repository \"" + content.getRepository().getId() + "\" for artifact " + Keys.toKey(
1056 artifact ) + ", continuing to next repository. Error message: " + exception.getMessage() );
1057 log.debug( "Full stack trace", exception );
1061 * Creates a working directory
1064 * @return file location of working directory
1065 * @throws IOException
1067 private File createWorkingDirectory( ManagedRepositoryContent repository )
1069 return Files.createTempDir();
1073 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
1076 * @param temp The completed download file
1077 * @param target The final location of the downloaded file
1078 * @throws ProxyException when the temp file cannot replace the target file
1080 private void moveTempToTarget( File temp, File target )
1081 throws ProxyException
1083 if ( target.exists() && !target.delete() )
1085 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
1088 target.getParentFile().mkdirs();
1089 if ( !temp.renameTo( target ) )
1091 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1095 FileUtils.copyFile( temp, target );
1097 catch ( IOException e )
1099 if ( target.exists() )
1101 log.debug( "Tried to copy file {} to {} but file with this name already exists.", temp.getName(),
1102 target.getAbsolutePath() );
1106 throw new ProxyException(
1107 "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e );
1112 FileUtils.deleteQuietly( temp );
1118 * Using wagon, connect to the remote repository.
1120 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1121 * @param wagon the wagon instance to establish the connection on.
1122 * @param remoteRepository the remote repository to connect to.
1123 * @return true if the connection was successful. false if not connected.
1125 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1126 RemoteRepositoryContent remoteRepository )
1128 boolean connected = false;
1130 final ProxyInfo networkProxy =
1131 connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1133 if ( log.isDebugEnabled() )
1135 if ( networkProxy != null )
1137 // TODO: move to proxyInfo.toString()
1138 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1139 + " to connect to remote repository " + remoteRepository.getURL();
1140 if ( networkProxy.getNonProxyHosts() != null )
1142 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1144 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1146 msg += "; as user: " + networkProxy.getUserName();
1152 AuthenticationInfo authInfo = null;
1153 String username = remoteRepository.getRepository().getUserName();
1154 String password = remoteRepository.getRepository().getPassword();
1156 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1158 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1159 authInfo = new AuthenticationInfo();
1160 authInfo.setUserName( username );
1161 authInfo.setPassword( password );
1164 // Convert seconds to milliseconds
1165 int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
1167 // Set timeout read and connect
1168 // FIXME olamy having 2 config values
1169 wagon.setReadTimeout( timeoutInMilliseconds );
1170 wagon.setTimeout( timeoutInMilliseconds );
1174 Repository wagonRepository =
1175 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1176 wagon.connect( wagonRepository, authInfo, networkProxy );
1179 catch ( ConnectionException e )
1181 log.warn( "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
1184 catch ( AuthenticationException e )
1186 log.warn( "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
1194 * Tests whitelist and blacklist patterns against path.
1196 * @param path the path to test.
1197 * @param patterns the list of patterns to check.
1198 * @return true if the path matches at least 1 pattern in the provided patterns list.
1200 private boolean matchesPattern( String path, List<String> patterns )
1202 if ( CollectionUtils.isEmpty( patterns ) )
1207 if ( !path.startsWith( "/" ) )
1212 for ( String pattern : patterns )
1214 if ( !pattern.startsWith( "/" ) )
1216 pattern = "/" + pattern;
1219 if ( SelectorUtils.matchPath( pattern, path, false ) )
1229 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1231 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1233 synchronized ( this.proxyConnectorMap )
1235 List<ProxyConnector> ret = this.proxyConnectorMap.get( repository.getId() );
1238 return Collections.emptyList();
1241 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1246 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1248 if ( ConfigurationNames.isNetworkProxy( propertyName ) || ConfigurationNames.isManagedRepositories(
1249 propertyName ) || ConfigurationNames.isRemoteRepositories( propertyName )
1250 || ConfigurationNames.isProxyConnector( propertyName ) )
1252 initConnectorsAndNetworkProxies();
1256 protected String addParameters( String path, RemoteRepository remoteRepository )
1258 if ( remoteRepository.getExtraParameters().isEmpty() )
1263 boolean question = false;
1265 StringBuilder res = new StringBuilder( path == null ? "" : path );
1267 for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1271 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1275 return res.toString();
1279 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1284 public ArchivaConfiguration getArchivaConfiguration()
1286 return archivaConfiguration;
1289 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1291 this.archivaConfiguration = archivaConfiguration;
1294 public RepositoryContentFactory getRepositoryFactory()
1296 return repositoryFactory;
1299 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1301 this.repositoryFactory = repositoryFactory;
1304 public MetadataTools getMetadataTools()
1306 return metadataTools;
1309 public void setMetadataTools( MetadataTools metadataTools )
1311 this.metadataTools = metadataTools;
1314 public UrlFailureCache getUrlFailureCache()
1316 return urlFailureCache;
1319 public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1321 this.urlFailureCache = urlFailureCache;
1324 public WagonFactory getWagonFactory()
1326 return wagonFactory;
1329 public void setWagonFactory( WagonFactory wagonFactory )
1331 this.wagonFactory = wagonFactory;
1334 public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1336 return preDownloadPolicies;
1339 public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1341 this.preDownloadPolicies = preDownloadPolicies;
1344 public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1346 return postDownloadPolicies;
1349 public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1351 this.postDownloadPolicies = postDownloadPolicies;
1354 public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1356 return downloadErrorPolicies;
1359 public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1361 this.downloadErrorPolicies = downloadErrorPolicies;