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 org.apache.archiva.admin.model.RepositoryAdminException;
23 import org.apache.archiva.admin.model.beans.NetworkProxy;
24 import org.apache.archiva.admin.model.beans.ProxyConnectorRuleType;
25 import org.apache.archiva.admin.model.beans.RemoteRepository;
26 import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin;
27 import org.apache.archiva.common.filelock.FileLockException;
28 import org.apache.archiva.common.filelock.FileLockManager;
29 import org.apache.archiva.common.filelock.FileLockTimeoutException;
30 import org.apache.archiva.common.filelock.Lock;
31 import org.apache.archiva.configuration.ArchivaConfiguration;
32 import org.apache.archiva.configuration.Configuration;
33 import org.apache.archiva.configuration.ConfigurationNames;
34 import org.apache.archiva.configuration.NetworkProxyConfiguration;
35 import org.apache.archiva.configuration.ProxyConnectorConfiguration;
36 import org.apache.archiva.configuration.ProxyConnectorRuleConfiguration;
37 import org.apache.archiva.model.ArtifactReference;
38 import org.apache.archiva.model.Keys;
39 import org.apache.archiva.model.RepositoryURL;
40 import org.apache.archiva.policies.DownloadErrorPolicy;
41 import org.apache.archiva.policies.DownloadPolicy;
42 import org.apache.archiva.policies.PolicyConfigurationException;
43 import org.apache.archiva.policies.PolicyViolationException;
44 import org.apache.archiva.policies.PostDownloadPolicy;
45 import org.apache.archiva.policies.PreDownloadPolicy;
46 import org.apache.archiva.policies.ProxyDownloadException;
47 import org.apache.archiva.policies.urlcache.UrlFailureCache;
48 import org.apache.archiva.proxy.common.WagonFactory;
49 import org.apache.archiva.proxy.common.WagonFactoryException;
50 import org.apache.archiva.proxy.common.WagonFactoryRequest;
51 import org.apache.archiva.proxy.model.ProxyConnector;
52 import org.apache.archiva.proxy.model.ProxyFetchResult;
53 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
54 import org.apache.archiva.redback.components.registry.Registry;
55 import org.apache.archiva.redback.components.registry.RegistryListener;
56 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
57 import org.apache.archiva.repository.ManagedRepositoryContent;
58 import org.apache.archiva.repository.RemoteRepositoryContent;
59 import org.apache.archiva.repository.RepositoryContentFactory;
60 import org.apache.archiva.repository.RepositoryException;
61 import org.apache.archiva.repository.RepositoryNotFoundException;
62 import org.apache.archiva.repository.metadata.MetadataTools;
63 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
64 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
65 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
66 import org.apache.commons.collections.CollectionUtils;
67 import org.apache.commons.io.FilenameUtils;
68 import org.apache.commons.lang.StringUtils;
69 import org.apache.commons.lang.SystemUtils;
70 import org.apache.maven.wagon.ConnectionException;
71 import org.apache.maven.wagon.ResourceDoesNotExistException;
72 import org.apache.maven.wagon.Wagon;
73 import org.apache.maven.wagon.WagonException;
74 import org.apache.maven.wagon.authentication.AuthenticationException;
75 import org.apache.maven.wagon.authentication.AuthenticationInfo;
76 import org.apache.maven.wagon.proxy.ProxyInfo;
77 import org.apache.maven.wagon.repository.Repository;
78 import org.apache.tools.ant.types.selectors.SelectorUtils;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81 import org.slf4j.MarkerFactory;
82 import org.springframework.stereotype.Service;
84 import javax.annotation.PostConstruct;
85 import javax.inject.Inject;
86 import javax.inject.Named;
88 import java.io.IOException;
89 import java.nio.file.Files;
90 import java.nio.file.Path;
91 import java.nio.file.Paths;
92 import java.util.ArrayList;
93 import java.util.Collections;
94 import java.util.LinkedHashMap;
95 import java.util.List;
97 import java.util.Map.Entry;
98 import java.util.Properties;
99 import java.util.concurrent.ConcurrentHashMap;
100 import java.util.concurrent.ConcurrentMap;
101 import java.util.concurrent.TimeUnit;
104 * DefaultRepositoryProxyConnectors
105 * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
106 * your average brown onion
108 @Service("repositoryProxyConnectors#default")
109 public class DefaultRepositoryProxyConnectors
110 implements RepositoryProxyConnectors, RegistryListener
112 private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class );
115 @Named(value = "archivaConfiguration#default")
116 private ArchivaConfiguration archivaConfiguration;
119 @Named(value = "repositoryContentFactory#default")
120 private RepositoryContentFactory repositoryFactory;
123 @Named(value = "metadataTools#default")
124 private MetadataTools metadataTools;
127 private Map<String, PreDownloadPolicy> preDownloadPolicies;
130 private Map<String, PostDownloadPolicy> postDownloadPolicies;
133 private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
136 private UrlFailureCache urlFailureCache;
138 private ConcurrentMap<String, List<ProxyConnector>> proxyConnectorMap = new ConcurrentHashMap<>();
140 private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>();
143 private WagonFactory wagonFactory;
146 @Named(value = "archivaTaskScheduler#repository")
147 private ArchivaTaskScheduler scheduler;
150 private NetworkProxyAdmin networkProxyAdmin;
153 @Named(value = "fileLockManager#default")
154 private FileLockManager fileLockManager;
157 public void initialize()
159 initConnectorsAndNetworkProxies();
160 archivaConfiguration.addChangeListener( this );
164 @SuppressWarnings("unchecked")
165 private void initConnectorsAndNetworkProxies()
168 ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
169 this.proxyConnectorMap.clear();
171 Configuration configuration = archivaConfiguration.getConfiguration();
173 List<ProxyConnectorRuleConfiguration> allProxyConnectorRuleConfigurations =
174 configuration.getProxyConnectorRuleConfigurations();
176 List<ProxyConnectorConfiguration> proxyConfigs = configuration.getProxyConnectors();
177 for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
179 String key = proxyConfig.getSourceRepoId();
183 // Create connector object.
184 ProxyConnector connector = new ProxyConnector();
186 connector.setSourceRepository(
187 repositoryFactory.getManagedRepositoryContent( proxyConfig.getSourceRepoId() ) );
188 connector.setTargetRepository(
189 repositoryFactory.getRemoteRepositoryContent( proxyConfig.getTargetRepoId() ) );
191 connector.setProxyId( proxyConfig.getProxyId() );
192 connector.setPolicies( proxyConfig.getPolicies() );
193 connector.setOrder( proxyConfig.getOrder() );
194 connector.setDisabled( proxyConfig.isDisabled() );
196 // Copy any blacklist patterns.
197 List<String> blacklist = new ArrayList<>( 0 );
198 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
200 blacklist.addAll( proxyConfig.getBlackListPatterns() );
202 connector.setBlacklist( blacklist );
204 // Copy any whitelist patterns.
205 List<String> whitelist = new ArrayList<>( 0 );
206 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
208 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
210 connector.setWhitelist( whitelist );
212 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
213 findProxyConnectorRules( connector.getSourceRepository().getId(),
214 connector.getTargetRepository().getId(),
215 allProxyConnectorRuleConfigurations );
217 if ( !proxyConnectorRuleConfigurations.isEmpty() )
219 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : proxyConnectorRuleConfigurations )
221 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
222 ProxyConnectorRuleType.BLACK_LIST.getRuleType() ) )
224 connector.getBlacklist().add( proxyConnectorRuleConfiguration.getPattern() );
227 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
228 ProxyConnectorRuleType.WHITE_LIST.getRuleType() ) )
230 connector.getWhitelist().add( proxyConnectorRuleConfiguration.getPattern() );
235 // Get other connectors
236 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
237 if ( connectors == null )
239 // Create if we are the first.
240 connectors = new ArrayList<>( 1 );
243 // Add the connector.
244 connectors.add( connector );
246 // Ensure the list is sorted.
247 Collections.sort( connectors, proxyOrderSorter );
249 // Set the key to the list of connectors.
250 this.proxyConnectorMap.put( key, connectors );
252 catch ( RepositoryNotFoundException e )
254 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
256 catch ( RepositoryException e )
258 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
264 this.networkProxyMap.clear();
266 List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
267 for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
269 String key = networkProxyConfig.getId();
271 ProxyInfo proxy = new ProxyInfo();
273 proxy.setType( networkProxyConfig.getProtocol() );
274 proxy.setHost( networkProxyConfig.getHost() );
275 proxy.setPort( networkProxyConfig.getPort() );
276 proxy.setUserName( networkProxyConfig.getUsername() );
277 proxy.setPassword( networkProxyConfig.getPassword() );
279 this.networkProxyMap.put( key, proxy );
284 private List<ProxyConnectorRuleConfiguration> findProxyConnectorRules( String sourceRepository,
285 String targetRepository,
286 List<ProxyConnectorRuleConfiguration> all )
288 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations = new ArrayList<>();
290 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : all )
292 for ( ProxyConnectorConfiguration proxyConnector : proxyConnectorRuleConfiguration.getProxyConnectors() )
294 if ( StringUtils.equals( sourceRepository, proxyConnector.getSourceRepoId() ) && StringUtils.equals(
295 targetRepository, proxyConnector.getTargetRepoId() ) )
297 proxyConnectorRuleConfigurations.add( proxyConnectorRuleConfiguration );
302 return proxyConnectorRuleConfigurations;
306 public Path fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
307 throws ProxyDownloadException
309 Path localFile = toLocalFile( repository, artifact );
311 Properties requestProperties = new Properties();
312 requestProperties.setProperty( "filetype", "artifact" );
313 requestProperties.setProperty( "version", artifact.getVersion() );
314 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
316 List<ProxyConnector> connectors = getProxyConnectors( repository );
317 Map<String, Exception> previousExceptions = new LinkedHashMap<>();
318 for ( ProxyConnector connector : connectors )
320 if ( connector.isDisabled() )
325 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
326 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
328 String targetPath = targetRepository.toPath( artifact );
330 if ( SystemUtils.IS_OS_WINDOWS )
332 // toPath use system PATH_SEPARATOR so on windows url are \ which doesn't work very well :-)
333 targetPath = FilenameUtils.separatorsToUnix( targetPath );
338 Path downloadedFile =
339 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
342 if ( fileExists( downloadedFile ) )
344 log.debug( "Successfully transferred: {}", downloadedFile.toAbsolutePath() );
345 return downloadedFile;
348 catch ( NotFoundException e )
350 log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ),
351 targetRepository.getRepository().getId() );
353 catch ( NotModifiedException e )
355 log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ),
356 targetRepository.getRepository().getId() );
358 catch ( ProxyException | RepositoryAdminException e )
360 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
361 targetRepository, localFile, e, previousExceptions );
365 if ( !previousExceptions.isEmpty() )
367 throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
368 previousExceptions );
371 log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) );
377 public Path fetchFromProxies( ManagedRepositoryContent repository, String path )
379 Path localFile = Paths.get( repository.getRepoRoot(), path );
381 // no update policies for these paths
382 if ( Files.exists(localFile) )
387 Properties requestProperties = new Properties();
388 requestProperties.setProperty( "filetype", "resource" );
389 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
391 List<ProxyConnector> connectors = getProxyConnectors( repository );
392 for ( ProxyConnector connector : connectors )
394 if ( connector.isDisabled() )
399 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
400 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
402 String targetPath = path;
406 Path downloadedFile =
407 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
410 if ( fileExists( downloadedFile ) )
412 log.debug( "Successfully transferred: {}", downloadedFile.toAbsolutePath() );
413 return downloadedFile;
416 catch ( NotFoundException e )
418 log.debug( "Resource {} not found on repository \"{}\".", path,
419 targetRepository.getRepository().getId() );
421 catch ( NotModifiedException e )
423 log.debug( "Resource {} not updated on repository \"{}\".", path,
424 targetRepository.getRepository().getId() );
426 catch ( ProxyException e )
429 "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",
430 targetRepository.getRepository().getId(), path, e.getMessage() );
431 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
432 "Transfer error from repository \"{}"
433 + "\" for resource {}, continuing to next repository. Error message: {}",
434 targetRepository.getRepository().getId(), path, e.getMessage(), e );
436 catch ( RepositoryAdminException e )
438 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
439 "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",
440 targetRepository.getRepository().getId(), path, e.getMessage(), e );
441 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ), "Full stack trace", e );
445 log.debug( "Exhausted all target repositories, resource {} not found.", path );
451 public ProxyFetchResult fetchMetadataFromProxies( ManagedRepositoryContent repository, String logicalPath )
453 Path localFile = Paths.get( repository.getRepoRoot(), logicalPath );
455 Properties requestProperties = new Properties();
456 requestProperties.setProperty( "filetype", "metadata" );
457 boolean metadataNeedsUpdating = false;
458 long originalTimestamp = getLastModified( localFile );
460 List<ProxyConnector> connectors = new ArrayList<>( getProxyConnectors( repository ) );
461 for ( ProxyConnector connector : connectors )
463 if ( connector.isDisabled() )
468 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
470 Path localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
471 long originalMetadataTimestamp = getLastModified( localRepoFile );
475 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
478 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
480 metadataNeedsUpdating = true;
483 catch ( NotFoundException e )
486 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
487 targetRepository.getRepository().getId(), e );
490 catch ( NotModifiedException e )
493 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
494 targetRepository.getRepository().getId(), e );
497 catch ( ProxyException | RepositoryAdminException e )
500 "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
501 targetRepository.getRepository().getId(), logicalPath, e.getMessage() );
502 log.debug( "Full stack trace", e );
506 if ( hasBeenUpdated( localFile, originalTimestamp ) )
508 metadataNeedsUpdating = true;
511 if ( metadataNeedsUpdating || !Files.exists(localFile))
515 metadataTools.updateMetadata( repository, logicalPath );
517 catch ( RepositoryMetadataException e )
519 log.warn( "Unable to update metadata {}:{}", localFile.toAbsolutePath(), e.getMessage(), e );
524 if ( fileExists( localFile ) )
526 return new ProxyFetchResult( localFile, metadataNeedsUpdating );
529 return new ProxyFetchResult( null, false );
534 * @param remoteRepository
541 * @param workingDirectory
543 * @throws ProxyException
544 * @throws NotModifiedException
545 * @throws org.apache.archiva.admin.model.RepositoryAdminException
547 protected void transferResources( ProxyConnector connector, RemoteRepositoryContent remoteRepository, Path tmpMd5,
548 Path tmpSha1, Path tmpResource, String url, String remotePath, Path resource,
549 Path workingDirectory, ManagedRepositoryContent repository )
550 throws ProxyException, NotModifiedException, RepositoryAdminException
555 RepositoryURL repoUrl = remoteRepository.getURL();
556 String protocol = repoUrl.getProtocol();
557 NetworkProxy networkProxy = null;
558 if ( StringUtils.isNotBlank( connector.getProxyId() ) )
560 networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() );
562 WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest( "wagon#" + protocol,
563 remoteRepository.getRepository().getExtraHeaders() ).networkProxy(
565 wagon = wagonFactory.getWagon( wagonFactoryRequest );
568 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
573 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
576 boolean connected = connectToRepository( connector, wagon, remoteRepository );
579 transferArtifact( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
582 // TODO: these should be used to validate the download based on the policies, not always downloaded
584 // save on connections since md5 is rarely used
585 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".sha1",
587 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".md5",
591 catch ( NotFoundException e )
593 urlFailureCache.cacheFailure( url );
596 catch ( NotModifiedException e )
598 // Do not cache url here.
601 catch ( ProxyException e )
603 urlFailureCache.cacheFailure( url );
606 catch ( WagonFactoryException e )
608 throw new ProxyException( e.getMessage(), e );
618 catch ( ConnectionException e )
620 log.warn( "Unable to disconnect wagon.", e );
626 private void transferArtifact( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
627 ManagedRepositoryContent repository, Path resource, Path tmpDirectory,
629 throws ProxyException
631 transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, destFile );
634 private long getLastModified( Path file )
636 if ( !Files.exists(file) || !Files.isRegularFile(file) )
643 return Files.getLastModifiedTime(file).toMillis();
645 catch ( IOException e )
647 log.error("Could get the modified time of file {}", file.toAbsolutePath());
652 private boolean hasBeenUpdated( Path file, long originalLastModified )
654 if ( !Files.exists(file) || !Files.isRegularFile(file) )
659 long currentLastModified = getLastModified( file );
660 return ( currentLastModified > originalLastModified );
663 private Path toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
666 String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
667 return Paths.get( repository.getRepoRoot(), repoPath );
671 * Test if the provided ManagedRepositoryContent has any proxies configured for it.
674 public boolean hasProxies( ManagedRepositoryContent repository )
676 synchronized ( this.proxyConnectorMap )
678 return this.proxyConnectorMap.containsKey( repository.getId() );
682 private Path toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
684 return repository.toFile( artifact );
688 * Simple method to test if the file exists on the local disk.
690 * @param file the file to test. (may be null)
691 * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
693 private boolean fileExists( Path file )
700 if ( !Files.exists(file))
705 return Files.isRegularFile(file);
709 * Perform the transfer of the file.
711 * @param connector the connector configuration to use.
712 * @param remoteRepository the remote repository get the resource from.
713 * @param remotePath the path in the remote repository to the resource to get.
714 * @param repository the managed repository that will hold the file
715 * @param resource the local file to place the downloaded resource into
716 * @param requestProperties the request properties to utilize for policy handling.
717 * @param executeConsumers whether to execute the consumers after proxying
718 * @return the local file that was downloaded, or null if not downloaded.
719 * @throws NotFoundException if the file was not found on the remote repository.
720 * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but
721 * the remote resource is not newer than the local File.
722 * @throws ProxyException if transfer was unsuccessful.
724 private Path transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
725 ManagedRepositoryContent repository, Path resource, Properties requestProperties,
726 boolean executeConsumers )
727 throws ProxyException, NotModifiedException, RepositoryAdminException
729 String url = remoteRepository.getURL().getUrl();
730 if ( !url.endsWith( "/" ) )
734 url = url + remotePath;
735 requestProperties.setProperty( "url", url );
737 // Is a whitelist defined?
738 if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
740 // Path must belong to whitelist.
741 if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
743 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
744 remotePath, remoteRepository.getRepository().getName() );
749 // Is target path part of blacklist?
750 if ( matchesPattern( remotePath, connector.getBlacklist() ) )
752 log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
753 remoteRepository.getRepository().getName() );
757 // Handle pre-download policy
760 validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
762 catch ( PolicyViolationException e )
764 String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
765 if ( fileExists( resource ) )
767 log.debug( "{} : using already present local file.", emsg );
775 Path workingDirectory = createWorkingDirectory( repository );
776 Path tmpResource = workingDirectory.resolve(resource.getFileName());
777 Path tmpMd5 = workingDirectory.resolve(resource.getFileName().toString() + ".md5" );
778 Path tmpSha1 = workingDirectory.resolve( resource.getFileName().toString() + ".sha1" );
783 transferResources( connector, remoteRepository, tmpMd5, tmpSha1, tmpResource, url, remotePath, resource,
784 workingDirectory, repository );
786 // Handle post-download policies.
789 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
791 catch ( PolicyViolationException e )
793 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
794 executeConsumers = false;
795 if ( !fileExists( tmpResource ) )
801 if ( resource != null )
803 synchronized ( resource.toAbsolutePath().toString().intern() )
805 Path directory = resource.getParent();
806 moveFileIfExists( tmpMd5, directory );
807 moveFileIfExists( tmpSha1, directory );
808 moveFileIfExists( tmpResource, directory );
814 org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
817 if ( executeConsumers )
819 // Just-in-time update of the index and database by executing the consumers for this artifact
820 //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource );
821 queueRepositoryTask( connector.getSourceRepository().getRepository().getId(), resource );
827 private void queueRepositoryTask( String repositoryId, Path localFile )
829 RepositoryTask task = new RepositoryTask();
830 task.setRepositoryId( repositoryId );
831 task.setResourceFile( localFile.toFile() );
832 task.setUpdateRelatedArtifacts( true );
833 task.setScanAll( true );
837 scheduler.queueTask( task );
839 catch ( TaskQueueException e )
841 log.error( "Unable to queue repository task to execute consumers on resource file ['{}"
842 + "'].", localFile.getFileName() );
847 * Moves the file into repository location if it exists
849 * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
850 * @param directory directory to write files to
852 private void moveFileIfExists( Path fileToMove, Path directory )
853 throws ProxyException
855 if ( fileToMove != null && Files.exists(fileToMove) )
857 Path newLocation = directory.resolve(fileToMove.getFileName());
858 moveTempToTarget( fileToMove, newLocation );
864 * Quietly transfer the checksum file from the remote repository to the local file.
867 * @param wagon the wagon instance (should already be connected) to use.
868 * @param remoteRepository the remote repository to transfer from.
869 * @param remotePath the remote path to the resource to get.
870 * @param repository the managed repository that will hold the file
871 * @param resource the local file that should contain the downloaded contents
872 * @param tmpDirectory the temporary directory to download to
873 * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
874 * @throws ProxyException if copying the downloaded file into place did not succeed.
876 private void transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
877 ManagedRepositoryContent repository, Path resource, Path tmpDirectory, String ext,
879 throws ProxyException
881 String url = remoteRepository.getURL().getUrl() + remotePath + ext;
883 // Transfer checksum does not use the policy.
884 if ( urlFailureCache.hasFailedBefore( url ) )
891 transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile );
892 log.debug( "Checksum {} Downloaded: {} to move to {}", url, destFile, resource );
894 catch ( NotFoundException e )
896 urlFailureCache.cacheFailure( url );
897 log.debug( "Transfer failed, checksum not found: {}", url );
898 // Consume it, do not pass this on.
900 catch ( NotModifiedException e )
902 log.debug( "Transfer skipped, checksum not modified: {}", url );
903 // Consume it, do not pass this on.
905 catch ( ProxyException e )
907 urlFailureCache.cacheFailure( url );
908 log.warn( "Transfer failed on checksum: {} : {}", url, e.getMessage(), e );
909 // Critical issue, pass it on.
915 * Perform the transfer of the remote file to the local file specified.
917 * @param wagon the wagon instance to use.
918 * @param remoteRepository the remote repository to use
919 * @param remotePath the remote path to attempt to get
920 * @param repository the managed repository that will hold the file
921 * @param origFile the local file to save to
922 * @throws ProxyException if there was a problem moving the downloaded file into place.
924 private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
925 ManagedRepositoryContent repository, Path origFile, Path destFile )
926 throws ProxyException
928 assert ( remotePath != null );
930 // Transfer the file.
933 boolean success = false;
935 if ( !Files.exists(origFile))
937 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
938 wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile.toFile() );
941 // You wouldn't get here on failure, a WagonException would have been thrown.
942 log.debug( "Downloaded successfully." );
946 log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() );
949 success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile.toFile(),
950 Files.getLastModifiedTime(origFile).toMillis());
952 catch ( IOException e )
954 throw new ProxyException( "Failed to the modification time of "+origFile.toAbsolutePath() );
958 throw new NotModifiedException(
959 "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath() );
962 if ( Files.exists(destFile))
964 log.debug( "Downloaded successfully." );
968 catch ( ResourceDoesNotExistException e )
970 throw new NotFoundException(
971 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
974 catch ( WagonException e )
976 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
979 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
980 if ( e.getCause() != null )
982 msg += " (cause: " + e.getCause() + ")";
984 throw new ProxyException( msg, e );
989 * Apply the policies.
991 * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
992 * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy
994 * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}
996 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)})
997 * @throws PolicyViolationException
999 private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
1000 Properties request, Path localFile )
1001 throws PolicyViolationException
1003 for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
1005 // olamy with spring rolehint is now downloadPolicy#hint
1006 // so substring after last # to get the hint as with plexus
1007 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1008 DownloadPolicy policy = entry.getValue();
1009 String defaultSetting = policy.getDefaultOption();
1011 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1013 log.debug( "Applying [{}] policy with [{}]", key, setting );
1016 policy.applyPolicy( setting, request, localFile.toFile() );
1018 catch ( PolicyConfigurationException e )
1020 log.error( e.getMessage(), e );
1025 private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
1026 Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
1027 Path localFile, Exception exception, Map<String, Exception> previousExceptions )
1028 throws ProxyDownloadException
1030 boolean process = true;
1031 for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
1034 // olamy with spring rolehint is now downloadPolicy#hint
1035 // so substring after last # to get the hint as with plexus
1036 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1037 DownloadErrorPolicy policy = entry.getValue();
1038 String defaultSetting = policy.getDefaultOption();
1039 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1041 log.debug( "Applying [{}] policy with [{}]", key, setting );
1044 // all policies must approve the exception, any can cancel
1045 process = policy.applyPolicy( setting, request, localFile.toFile(), exception, previousExceptions );
1051 catch ( PolicyConfigurationException e )
1053 log.error( e.getMessage(), e );
1059 // if the exception was queued, don't throw it
1060 if ( !previousExceptions.containsKey( content.getId() ) )
1062 throw new ProxyDownloadException(
1063 "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
1064 content.getId(), exception );
1069 // if the exception was queued, but cancelled, remove it
1070 previousExceptions.remove( content.getId() );
1074 "Transfer error from repository {} for artifact {} , continuing to next repository. Error message: {}",
1075 content.getRepository().getId(), Keys.toKey( artifact ), exception.getMessage() );
1076 log.debug( "Full stack trace", exception );
1080 * Creates a working directory
1083 * @return file location of working directory
1085 private Path createWorkingDirectory( ManagedRepositoryContent repository )
1089 return Files.createTempDirectory( "temp" );
1091 catch ( IOException e )
1093 throw new RuntimeException( e.getMessage(), e );
1099 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
1102 * @param temp The completed download file
1103 * @param target The final location of the downloaded file
1104 * @throws ProxyException when the temp file cannot replace the target file
1106 private void moveTempToTarget( Path temp, Path target )
1107 throws ProxyException
1113 lock = fileLockManager.writeFileLock( target.toFile() );
1114 if ( lock.getFile().exists() && !lock.getFile().delete() )
1116 throw new ProxyException( "Unable to overwrite existing target file: " + target.toAbsolutePath() );
1119 lock.getFile().getParentFile().mkdirs();
1123 Files.move(temp, lock.getFile().toPath() );
1125 catch ( IOException e )
1127 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1131 Files.copy( temp, lock.getFile().toPath() );
1133 catch ( IOException e2 )
1135 if ( lock.getFile().exists() )
1137 log.debug( "Tried to copy file {} to {} but file with this name already exists.",
1138 temp.getFileName(), lock.getFile().getAbsolutePath() );
1142 throw new ProxyException(
1143 "Cannot copy tmp file " + temp.toAbsolutePath() + " to its final location", e2 );
1148 org.apache.archiva.common.utils.FileUtils.deleteQuietly( temp );
1153 catch ( FileLockException | FileLockTimeoutException e )
1155 throw new ProxyException( e.getMessage(), e );
1160 * Using wagon, connect to the remote repository.
1162 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1163 * @param wagon the wagon instance to establish the connection on.
1164 * @param remoteRepository the remote repository to connect to.
1165 * @return true if the connection was successful. false if not connected.
1167 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1168 RemoteRepositoryContent remoteRepository )
1170 boolean connected = false;
1172 final ProxyInfo networkProxy =
1173 connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1175 if ( log.isDebugEnabled() )
1177 if ( networkProxy != null )
1179 // TODO: move to proxyInfo.toString()
1180 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1181 + " to connect to remote repository " + remoteRepository.getURL();
1182 if ( networkProxy.getNonProxyHosts() != null )
1184 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1186 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1188 msg += "; as user: " + networkProxy.getUserName();
1194 AuthenticationInfo authInfo = null;
1195 String username = remoteRepository.getRepository().getUserName();
1196 String password = remoteRepository.getRepository().getPassword();
1198 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1200 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1201 authInfo = new AuthenticationInfo();
1202 authInfo.setUserName( username );
1203 authInfo.setPassword( password );
1206 // Convert seconds to milliseconds
1207 long timeoutInMilliseconds = TimeUnit.MILLISECONDS.convert( remoteRepository.getRepository().getTimeout(), //
1210 // Set timeout read and connect
1211 // FIXME olamy having 2 config values
1212 wagon.setReadTimeout( (int) timeoutInMilliseconds );
1213 wagon.setTimeout( (int) timeoutInMilliseconds );
1217 Repository wagonRepository =
1218 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1219 wagon.connect( wagonRepository, authInfo, networkProxy );
1222 catch ( ConnectionException | AuthenticationException e )
1224 log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1232 * Tests whitelist and blacklist patterns against path.
1234 * @param path the path to test.
1235 * @param patterns the list of patterns to check.
1236 * @return true if the path matches at least 1 pattern in the provided patterns list.
1238 private boolean matchesPattern( String path, List<String> patterns )
1240 if ( CollectionUtils.isEmpty( patterns ) )
1245 if ( !path.startsWith( "/" ) )
1250 for ( String pattern : patterns )
1252 if ( !pattern.startsWith( "/" ) )
1254 pattern = "/" + pattern;
1257 if ( SelectorUtils.matchPath( pattern, path, false ) )
1267 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1270 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1273 if ( !this.proxyConnectorMap.containsKey( repository.getId() ) )
1275 return Collections.emptyList();
1277 List<ProxyConnector> ret = new ArrayList<>( this.proxyConnectorMap.get( repository.getId() ) );
1279 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1285 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1287 if ( ConfigurationNames.isNetworkProxy( propertyName ) //
1288 || ConfigurationNames.isManagedRepositories( propertyName ) //
1289 || ConfigurationNames.isRemoteRepositories( propertyName ) //
1290 || ConfigurationNames.isProxyConnector( propertyName ) ) //
1292 initConnectorsAndNetworkProxies();
1296 protected String addParameters( String path, RemoteRepository remoteRepository )
1298 if ( remoteRepository.getExtraParameters().isEmpty() )
1303 boolean question = false;
1305 StringBuilder res = new StringBuilder( path == null ? "" : path );
1307 for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1311 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1315 return res.toString();
1320 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1325 public ArchivaConfiguration getArchivaConfiguration()
1327 return archivaConfiguration;
1330 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1332 this.archivaConfiguration = archivaConfiguration;
1335 public RepositoryContentFactory getRepositoryFactory()
1337 return repositoryFactory;
1340 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1342 this.repositoryFactory = repositoryFactory;
1345 public MetadataTools getMetadataTools()
1347 return metadataTools;
1350 public void setMetadataTools( MetadataTools metadataTools )
1352 this.metadataTools = metadataTools;
1355 public UrlFailureCache getUrlFailureCache()
1357 return urlFailureCache;
1360 public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1362 this.urlFailureCache = urlFailureCache;
1365 public WagonFactory getWagonFactory()
1367 return wagonFactory;
1370 public void setWagonFactory( WagonFactory wagonFactory )
1372 this.wagonFactory = wagonFactory;
1375 public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1377 return preDownloadPolicies;
1380 public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1382 this.preDownloadPolicies = preDownloadPolicies;
1385 public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1387 return postDownloadPolicies;
1390 public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1392 this.postDownloadPolicies = postDownloadPolicies;
1395 public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1397 return downloadErrorPolicies;
1400 public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1402 this.downloadErrorPolicies = downloadErrorPolicies;