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.model.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.slf4j.MarkerFactory;
79 import org.springframework.stereotype.Service;
81 import javax.annotation.PostConstruct;
82 import javax.inject.Inject;
83 import javax.inject.Named;
85 import java.io.IOException;
86 import java.util.ArrayList;
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 {} for resource {}, continuing to next repository. Error message: {}",
451 targetRepository.getRepository().getId(), path, e.getMessage() );
452 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
453 "Transfer error from repository \"" + targetRepository.getRepository().getId()
454 + "\" for resource " + path + ", continuing to next repository. Error message: {}",
457 catch ( RepositoryAdminException e )
459 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
460 "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",targetRepository.getRepository().getId(), path,
462 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ), "Full stack trace", e );
466 log.debug( "Exhausted all target repositories, resource {} not found.", path );
471 public File fetchMetatadaFromProxies( ManagedRepositoryContent repository, String logicalPath )
473 File localFile = new File( repository.getRepoRoot(), logicalPath );
475 Properties requestProperties = new Properties();
476 requestProperties.setProperty( "filetype", "metadata" );
477 boolean metadataNeedsUpdating = false;
478 long originalTimestamp = getLastModified( localFile );
480 List<ProxyConnector> connectors = getProxyConnectors( repository );
481 for ( ProxyConnector connector : connectors )
483 if ( connector.isDisabled() )
488 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
490 File localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
491 long originalMetadataTimestamp = getLastModified( localRepoFile );
495 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
498 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
500 metadataNeedsUpdating = true;
503 catch ( NotFoundException e )
506 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
507 targetRepository.getRepository().getId(), e );
510 catch ( NotModifiedException e )
513 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
514 targetRepository.getRepository().getId(), e );
517 catch ( ProxyException e )
519 log.warn( "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
520 targetRepository.getRepository().getId(), logicalPath, e.getMessage() );
521 log.debug( "Full stack trace", e );
523 catch ( RepositoryAdminException e )
525 log.warn( "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
526 targetRepository.getRepository().getId(), logicalPath, e.getMessage() );
527 log.debug( "Full stack trace", e );
531 if ( hasBeenUpdated( localFile, originalTimestamp ) )
533 metadataNeedsUpdating = true;
536 if ( metadataNeedsUpdating || !localFile.exists() )
540 metadataTools.updateMetadata( repository, logicalPath );
542 catch ( RepositoryMetadataException e )
544 log.warn( "Unable to update metadata {}:{}", localFile.getAbsolutePath(), e.getMessage(), e );
548 if ( fileExists( localFile ) )
558 * @param remoteRepository
565 * @param workingDirectory
567 * @throws ProxyException
568 * @throws NotModifiedException
570 protected void transferResources( ProxyConnector connector, RemoteRepositoryContent remoteRepository, File tmpMd5,
571 File tmpSha1, File tmpResource, String url, String remotePath, File resource,
572 File workingDirectory, ManagedRepositoryContent repository )
573 throws ProxyException, NotModifiedException, RepositoryAdminException
578 RepositoryURL repoUrl = remoteRepository.getURL();
579 String protocol = repoUrl.getProtocol();
580 NetworkProxy networkProxy = null;
581 if ( StringUtils.isNotBlank( connector.getProxyId() ) )
583 networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() );
585 WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest( "wagon#" + protocol,
586 remoteRepository.getRepository().getExtraHeaders() ).networkProxy(
588 wagon = wagonFactory.getWagon( wagonFactoryRequest );
591 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
596 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
599 boolean connected = connectToRepository( connector, wagon, remoteRepository );
602 transferArtifact( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
605 // TODO: these should be used to validate the download based on the policies, not always downloaded
607 // save on connections since md5 is rarely used
608 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".sha1",
610 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".md5",
614 catch ( NotFoundException e )
616 urlFailureCache.cacheFailure( url );
619 catch ( NotModifiedException e )
621 // Do not cache url here.
624 catch ( ProxyException e )
626 urlFailureCache.cacheFailure( url );
629 catch ( WagonFactoryException e )
631 throw new ProxyException( e.getMessage(), e );
641 catch ( ConnectionException e )
643 log.warn( "Unable to disconnect wagon.", e );
649 private void transferArtifact( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
650 ManagedRepositoryContent repository, File resource, File tmpDirectory,
652 throws ProxyException
654 transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, destFile );
657 private long getLastModified( File file )
659 if ( !file.exists() || !file.isFile() )
664 return file.lastModified();
667 private boolean hasBeenUpdated( File file, long originalLastModified )
669 if ( !file.exists() || !file.isFile() )
674 long currentLastModified = getLastModified( file );
675 return ( currentLastModified > originalLastModified );
678 private File toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
681 String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
682 return new File( repository.getRepoRoot(), repoPath );
686 * Test if the provided ManagedRepositoryContent has any proxies configured for it.
688 public boolean hasProxies( ManagedRepositoryContent repository )
690 synchronized ( this.proxyConnectorMap )
692 return this.proxyConnectorMap.containsKey( repository.getId() );
696 private File toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
698 return repository.toFile( artifact );
702 * Simple method to test if the file exists on the local disk.
704 * @param file the file to test. (may be null)
705 * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
707 private boolean fileExists( File file )
714 if ( !file.exists() )
719 if ( !file.isFile() )
728 * Perform the transfer of the file.
730 * @param connector the connector configuration to use.
731 * @param remoteRepository the remote repository get the resource from.
732 * @param remotePath the path in the remote repository to the resource to get.
733 * @param repository the managed repository that will hold the file
734 * @param resource the local file to place the downloaded resource into
735 * @param requestProperties the request properties to utilize for policy handling.
736 * @param executeConsumers whether to execute the consumers after proxying
737 * @return the local file that was downloaded, or null if not downloaded.
738 * @throws NotFoundException if the file was not found on the remote repository.
739 * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but
740 * the remote resource is not newer than the local File.
741 * @throws ProxyException if transfer was unsuccessful.
743 private File transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
744 ManagedRepositoryContent repository, File resource, Properties requestProperties,
745 boolean executeConsumers )
746 throws ProxyException, NotModifiedException, RepositoryAdminException
748 String url = remoteRepository.getURL().getUrl();
749 if ( !url.endsWith( "/" ) )
753 url = url + remotePath;
754 requestProperties.setProperty( "url", url );
756 // Is a whitelist defined?
757 if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
759 // Path must belong to whitelist.
760 if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
762 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
763 remotePath, remoteRepository.getRepository().getName() );
768 // Is target path part of blacklist?
769 if ( matchesPattern( remotePath, connector.getBlacklist() ) )
771 log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
772 remoteRepository.getRepository().getName() );
776 // Handle pre-download policy
779 validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
781 catch ( PolicyViolationException e )
783 String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
784 if ( fileExists( resource ) )
786 log.debug( "{} : using already present local file.", emsg );
794 File workingDirectory = createWorkingDirectory( repository );
795 File tmpResource = new File( workingDirectory, resource.getName() );
796 File tmpMd5 = new File( workingDirectory, resource.getName() + ".md5" );
797 File tmpSha1 = new File( workingDirectory, resource.getName() + ".sha1" );
802 transferResources( connector, remoteRepository, tmpMd5, tmpSha1, tmpResource, url, remotePath, resource,
803 workingDirectory, repository );
805 // Handle post-download policies.
808 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
810 catch ( PolicyViolationException e )
812 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
813 executeConsumers = false;
814 if ( !fileExists( tmpResource ) )
820 if ( resource != null )
822 synchronized ( resource.getAbsolutePath().intern() )
824 File directory = resource.getParentFile();
825 moveFileIfExists( tmpMd5, directory );
826 moveFileIfExists( tmpSha1, directory );
827 moveFileIfExists( tmpResource, directory );
833 FileUtils.deleteQuietly( workingDirectory );
836 if ( executeConsumers )
838 // Just-in-time update of the index and database by executing the consumers for this artifact
839 //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource );
840 queueRepositoryTask( connector.getSourceRepository().getRepository().getId(), resource );
846 private void queueRepositoryTask( String repositoryId, File localFile )
848 RepositoryTask task = new RepositoryTask();
849 task.setRepositoryId( repositoryId );
850 task.setResourceFile( localFile );
851 task.setUpdateRelatedArtifacts( true );
852 task.setScanAll( true );
856 scheduler.queueTask( task );
858 catch ( TaskQueueException e )
860 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
866 * Moves the file into repository location if it exists
868 * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
869 * @param directory directory to write files to
871 private void moveFileIfExists( File fileToMove, File directory )
872 throws ProxyException
874 if ( fileToMove != null && fileToMove.exists() )
876 File newLocation = new File( directory, fileToMove.getName() );
877 moveTempToTarget( fileToMove, newLocation );
883 * Quietly transfer the checksum file from the remote repository to the local file.
886 * @param wagon the wagon instance (should already be connected) to use.
887 * @param remoteRepository the remote repository to transfer from.
888 * @param remotePath the remote path to the resource to get.
889 * @param repository the managed repository that will hold the file
890 * @param resource the local file that should contain the downloaded contents
891 * @param tmpDirectory the temporary directory to download to
892 * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
893 * @throws ProxyException if copying the downloaded file into place did not succeed.
895 private void transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
896 ManagedRepositoryContent repository, File resource, File tmpDirectory, String ext,
898 throws ProxyException
900 String url = remoteRepository.getURL().getUrl() + remotePath + ext;
902 // Transfer checksum does not use the policy.
903 if ( urlFailureCache.hasFailedBefore( url ) )
910 transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile );
911 log.debug( "Checksum {} Downloaded: {} to move to {}", url, destFile, resource );
913 catch ( NotFoundException e )
915 urlFailureCache.cacheFailure( url );
916 log.debug( "Transfer failed, checksum not found: {}", url );
917 // Consume it, do not pass this on.
919 catch ( NotModifiedException e )
921 log.debug( "Transfer skipped, checksum not modified: {}", url );
922 // Consume it, do not pass this on.
924 catch ( ProxyException e )
926 urlFailureCache.cacheFailure( url );
927 log.warn( "Transfer failed on checksum: {} : {}",url ,e.getMessage(), e );
928 // Critical issue, pass it on.
934 * Perform the transfer of the remote file to the local file specified.
936 * @param wagon the wagon instance to use.
937 * @param remoteRepository the remote repository to use
938 * @param remotePath the remote path to attempt to get
939 * @param repository the managed repository that will hold the file
940 * @param origFile the local file to save to
941 * @return The local file that was transfered.
942 * @throws ProxyException if there was a problem moving the downloaded file into place.
943 * @throws WagonException if there was a problem tranfering the file.
945 private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
946 ManagedRepositoryContent repository, File origFile, File destFile )
947 throws ProxyException
949 assert ( remotePath != null );
951 // Transfer the file.
954 boolean success = false;
956 if ( !origFile.exists() )
958 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
959 wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile );
962 // You wouldn't get here on failure, a WagonException would have been thrown.
963 log.debug( "Downloaded successfully." );
967 log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() );
968 success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile,
969 origFile.lastModified() );
972 throw new NotModifiedException(
973 "Not downloaded, as local file is newer than remote side: " + origFile.getAbsolutePath() );
976 if ( destFile.exists() )
978 log.debug( "Downloaded successfully." );
982 catch ( ResourceDoesNotExistException e )
984 throw new NotFoundException(
985 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
988 catch ( WagonException e )
990 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
993 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
994 if ( e.getCause() != null )
996 msg += " (cause: " + e.getCause() + ")";
998 throw new ProxyException( msg, e );
1003 * Apply the policies.
1005 * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
1006 * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy
1008 * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}
1010 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)})
1012 private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
1013 Properties request, File localFile )
1014 throws PolicyViolationException
1016 for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
1018 // olamy with spring rolehint is now downloadPolicy#hint
1019 // so substring after last # to get the hint as with plexus
1020 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1021 DownloadPolicy policy = entry.getValue();
1022 String defaultSetting = policy.getDefaultOption();
1024 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1026 log.debug( "Applying [{}] policy with [{}]", key, setting );
1029 policy.applyPolicy( setting, request, localFile );
1031 catch ( PolicyConfigurationException e )
1033 log.error( e.getMessage(), e );
1038 private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
1039 Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
1040 File localFile, Exception exception, Map<String, Exception> previousExceptions )
1041 throws ProxyDownloadException
1043 boolean process = true;
1044 for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
1047 // olamy with spring rolehint is now downloadPolicy#hint
1048 // so substring after last # to get the hint as with plexus
1049 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1050 DownloadErrorPolicy policy = entry.getValue();
1051 String defaultSetting = policy.getDefaultOption();
1052 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1054 log.debug( "Applying [{}] policy with [{}]", key, setting );
1057 // all policies must approve the exception, any can cancel
1058 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
1064 catch ( PolicyConfigurationException e )
1066 log.error( e.getMessage(), e );
1072 // if the exception was queued, don't throw it
1073 if ( !previousExceptions.containsKey( content.getId() ) )
1075 throw new ProxyDownloadException(
1076 "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
1077 content.getId(), exception );
1082 // if the exception was queued, but cancelled, remove it
1083 previousExceptions.remove( content.getId() );
1087 "Transfer error from repository {} for artifact {} , continuing to next repository. Error message: {}",
1088 content.getRepository().getId(), Keys.toKey( artifact), exception.getMessage() );
1089 log.debug( "Full stack trace", exception );
1093 * Creates a working directory
1096 * @return file location of working directory
1097 * @throws IOException
1099 private File createWorkingDirectory( ManagedRepositoryContent repository )
1101 return Files.createTempDir();
1105 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
1108 * @param temp The completed download file
1109 * @param target The final location of the downloaded file
1110 * @throws ProxyException when the temp file cannot replace the target file
1112 private void moveTempToTarget( File temp, File target )
1113 throws ProxyException
1115 if ( target.exists() && !target.delete() )
1117 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
1120 target.getParentFile().mkdirs();
1121 if ( !temp.renameTo( target ) )
1123 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1127 FileUtils.copyFile( temp, target );
1129 catch ( IOException e )
1131 if ( target.exists() )
1133 log.debug( "Tried to copy file {} to {} but file with this name already exists.", temp.getName(),
1134 target.getAbsolutePath() );
1138 throw new ProxyException(
1139 "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e );
1144 FileUtils.deleteQuietly( temp );
1150 * Using wagon, connect to the remote repository.
1152 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1153 * @param wagon the wagon instance to establish the connection on.
1154 * @param remoteRepository the remote repository to connect to.
1155 * @return true if the connection was successful. false if not connected.
1157 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1158 RemoteRepositoryContent remoteRepository )
1160 boolean connected = false;
1162 final ProxyInfo networkProxy =
1163 connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1165 if ( log.isDebugEnabled() )
1167 if ( networkProxy != null )
1169 // TODO: move to proxyInfo.toString()
1170 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1171 + " to connect to remote repository " + remoteRepository.getURL();
1172 if ( networkProxy.getNonProxyHosts() != null )
1174 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1176 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1178 msg += "; as user: " + networkProxy.getUserName();
1184 AuthenticationInfo authInfo = null;
1185 String username = remoteRepository.getRepository().getUserName();
1186 String password = remoteRepository.getRepository().getPassword();
1188 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1190 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1191 authInfo = new AuthenticationInfo();
1192 authInfo.setUserName( username );
1193 authInfo.setPassword( password );
1196 // Convert seconds to milliseconds
1197 int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
1199 // Set timeout read and connect
1200 // FIXME olamy having 2 config values
1201 wagon.setReadTimeout( timeoutInMilliseconds );
1202 wagon.setTimeout( timeoutInMilliseconds );
1206 Repository wagonRepository =
1207 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1208 wagon.connect( wagonRepository, authInfo, networkProxy );
1211 catch ( ConnectionException e )
1213 log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1216 catch ( AuthenticationException e )
1218 log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1226 * Tests whitelist and blacklist patterns against path.
1228 * @param path the path to test.
1229 * @param patterns the list of patterns to check.
1230 * @return true if the path matches at least 1 pattern in the provided patterns list.
1232 private boolean matchesPattern( String path, List<String> patterns )
1234 if ( CollectionUtils.isEmpty( patterns ) )
1239 if ( !path.startsWith( "/" ) )
1244 for ( String pattern : patterns )
1246 if ( !pattern.startsWith( "/" ) )
1248 pattern = "/" + pattern;
1251 if ( SelectorUtils.matchPath( pattern, path, false ) )
1261 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1263 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1265 synchronized ( this.proxyConnectorMap )
1267 List<ProxyConnector> ret = this.proxyConnectorMap.get( repository.getId() );
1270 return Collections.emptyList();
1273 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1278 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1280 if ( ConfigurationNames.isNetworkProxy( propertyName ) || ConfigurationNames.isManagedRepositories(
1281 propertyName ) || ConfigurationNames.isRemoteRepositories( propertyName )
1282 || ConfigurationNames.isProxyConnector( propertyName ) )
1284 initConnectorsAndNetworkProxies();
1288 protected String addParameters( String path, RemoteRepository remoteRepository )
1290 if ( remoteRepository.getExtraParameters().isEmpty() )
1295 boolean question = false;
1297 StringBuilder res = new StringBuilder( path == null ? "" : path );
1299 for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1303 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1307 return res.toString();
1311 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1316 public ArchivaConfiguration getArchivaConfiguration()
1318 return archivaConfiguration;
1321 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1323 this.archivaConfiguration = archivaConfiguration;
1326 public RepositoryContentFactory getRepositoryFactory()
1328 return repositoryFactory;
1331 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1333 this.repositoryFactory = repositoryFactory;
1336 public MetadataTools getMetadataTools()
1338 return metadataTools;
1341 public void setMetadataTools( MetadataTools metadataTools )
1343 this.metadataTools = metadataTools;
1346 public UrlFailureCache getUrlFailureCache()
1348 return urlFailureCache;
1351 public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1353 this.urlFailureCache = urlFailureCache;
1356 public WagonFactory getWagonFactory()
1358 return wagonFactory;
1361 public void setWagonFactory( WagonFactory wagonFactory )
1363 this.wagonFactory = wagonFactory;
1366 public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1368 return preDownloadPolicies;
1371 public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1373 this.preDownloadPolicies = preDownloadPolicies;
1376 public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1378 return postDownloadPolicies;
1381 public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1383 this.postDownloadPolicies = postDownloadPolicies;
1386 public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1388 return downloadErrorPolicies;
1391 public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1393 this.downloadErrorPolicies = downloadErrorPolicies;