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.networkproxy.NetworkProxyAdmin;
26 import org.apache.archiva.common.filelock.FileLockException;
27 import org.apache.archiva.common.filelock.FileLockManager;
28 import org.apache.archiva.common.filelock.FileLockTimeoutException;
29 import org.apache.archiva.common.filelock.Lock;
30 import org.apache.archiva.configuration.ArchivaConfiguration;
31 import org.apache.archiva.configuration.Configuration;
32 import org.apache.archiva.configuration.ConfigurationNames;
33 import org.apache.archiva.configuration.NetworkProxyConfiguration;
34 import org.apache.archiva.configuration.ProxyConnectorConfiguration;
35 import org.apache.archiva.configuration.ProxyConnectorRuleConfiguration;
36 import org.apache.archiva.model.ArtifactReference;
37 import org.apache.archiva.model.Keys;
38 import org.apache.archiva.model.RepositoryURL;
39 import org.apache.archiva.policies.DownloadErrorPolicy;
40 import org.apache.archiva.policies.DownloadPolicy;
41 import org.apache.archiva.policies.PolicyConfigurationException;
42 import org.apache.archiva.policies.PolicyViolationException;
43 import org.apache.archiva.policies.PostDownloadPolicy;
44 import org.apache.archiva.policies.PreDownloadPolicy;
45 import org.apache.archiva.policies.ProxyDownloadException;
46 import org.apache.archiva.policies.urlcache.UrlFailureCache;
47 import org.apache.archiva.proxy.common.WagonFactory;
48 import org.apache.archiva.proxy.common.WagonFactoryException;
49 import org.apache.archiva.proxy.common.WagonFactoryRequest;
50 import org.apache.archiva.proxy.model.ProxyConnector;
51 import org.apache.archiva.proxy.model.ProxyFetchResult;
52 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
53 import org.apache.archiva.redback.components.registry.Registry;
54 import org.apache.archiva.redback.components.registry.RegistryListener;
55 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
56 import org.apache.archiva.repository.ManagedRepository;
57 import org.apache.archiva.repository.ManagedRepositoryContent;
58 import org.apache.archiva.repository.PasswordCredentials;
59 import org.apache.archiva.repository.RemoteRepository;
60 import org.apache.archiva.repository.RemoteRepositoryContent;
61 import org.apache.archiva.repository.RepositoryContentFactory;
62 import org.apache.archiva.repository.RepositoryCredentials;
63 import org.apache.archiva.repository.RepositoryRegistry;
64 import org.apache.archiva.repository.metadata.MetadataTools;
65 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
66 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
67 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
68 import org.apache.commons.collections.CollectionUtils;
69 import org.apache.commons.io.FilenameUtils;
70 import org.apache.commons.lang.StringUtils;
71 import org.apache.commons.lang.SystemUtils;
72 import org.apache.maven.wagon.ConnectionException;
73 import org.apache.maven.wagon.ResourceDoesNotExistException;
74 import org.apache.maven.wagon.Wagon;
75 import org.apache.maven.wagon.WagonException;
76 import org.apache.maven.wagon.authentication.AuthenticationException;
77 import org.apache.maven.wagon.authentication.AuthenticationInfo;
78 import org.apache.maven.wagon.proxy.ProxyInfo;
79 import org.apache.maven.wagon.repository.Repository;
80 import org.apache.tools.ant.types.selectors.SelectorUtils;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83 import org.slf4j.MarkerFactory;
84 import org.springframework.stereotype.Service;
86 import javax.annotation.PostConstruct;
87 import javax.inject.Inject;
88 import javax.inject.Named;
89 import java.io.IOException;
90 import java.nio.file.Files;
91 import java.nio.file.Path;
92 import java.nio.file.Paths;
93 import java.util.ArrayList;
94 import java.util.Collections;
95 import java.util.LinkedHashMap;
96 import java.util.List;
98 import java.util.Map.Entry;
99 import java.util.Properties;
100 import java.util.concurrent.ConcurrentHashMap;
101 import java.util.concurrent.ConcurrentMap;
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 RepositoryRegistry repositoryRegistry;
153 private NetworkProxyAdmin networkProxyAdmin;
156 @Named(value = "fileLockManager#default")
157 private FileLockManager fileLockManager;
160 public void initialize()
162 initConnectorsAndNetworkProxies();
163 archivaConfiguration.addChangeListener( this );
167 @SuppressWarnings("unchecked")
168 private void initConnectorsAndNetworkProxies()
171 ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
172 this.proxyConnectorMap.clear();
174 Configuration configuration = archivaConfiguration.getConfiguration();
176 List<ProxyConnectorRuleConfiguration> allProxyConnectorRuleConfigurations =
177 configuration.getProxyConnectorRuleConfigurations();
179 List<ProxyConnectorConfiguration> proxyConfigs = configuration.getProxyConnectors();
180 for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
182 String key = proxyConfig.getSourceRepoId();
184 // Create connector object.
185 ProxyConnector connector = new ProxyConnector();
187 ManagedRepository repo = repositoryRegistry.getManagedRepository( proxyConfig.getSourceRepoId( ) );
189 log.error("Cannot find source repository after config change "+proxyConfig.getSourceRepoId());
192 connector.setSourceRepository(repo.getContent());
193 RemoteRepository rRepo = repositoryRegistry.getRemoteRepository( proxyConfig.getTargetRepoId() );
195 log.error("Cannot find target repository after config change "+proxyConfig.getSourceRepoId());
198 connector.setTargetRepository(rRepo.getContent());
200 connector.setProxyId( proxyConfig.getProxyId() );
201 connector.setPolicies( proxyConfig.getPolicies() );
202 connector.setOrder( proxyConfig.getOrder() );
203 connector.setDisabled( proxyConfig.isDisabled() );
205 // Copy any blacklist patterns.
206 List<String> blacklist = new ArrayList<>( 0 );
207 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
209 blacklist.addAll( proxyConfig.getBlackListPatterns() );
211 connector.setBlacklist( blacklist );
213 // Copy any whitelist patterns.
214 List<String> whitelist = new ArrayList<>( 0 );
215 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
217 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
219 connector.setWhitelist( whitelist );
221 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
222 findProxyConnectorRules( connector.getSourceRepository().getId(),
223 connector.getTargetRepository().getId(),
224 allProxyConnectorRuleConfigurations );
226 if ( !proxyConnectorRuleConfigurations.isEmpty() )
228 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : proxyConnectorRuleConfigurations )
230 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
231 ProxyConnectorRuleType.BLACK_LIST.getRuleType() ) )
233 connector.getBlacklist().add( proxyConnectorRuleConfiguration.getPattern() );
236 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
237 ProxyConnectorRuleType.WHITE_LIST.getRuleType() ) )
239 connector.getWhitelist().add( proxyConnectorRuleConfiguration.getPattern() );
244 // Get other connectors
245 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
246 if ( connectors == null )
248 // Create if we are the first.
249 connectors = new ArrayList<>( 1 );
252 // Add the connector.
253 connectors.add( connector );
255 // Ensure the list is sorted.
256 Collections.sort( connectors, proxyOrderSorter );
258 // Set the key to the list of connectors.
259 this.proxyConnectorMap.put( key, connectors );
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 );
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, Path)}
996 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, Path)})
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 );
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, 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 );
1115 Files.deleteIfExists(lock.getFile());
1116 } catch (IOException e) {
1117 throw new ProxyException( "Unable to overwrite existing target file: " + target.toAbsolutePath() );
1121 Files.createDirectories(lock.getFile().getParent());
1122 } catch (IOException e) {
1123 throw new ProxyException("Unable to create parent directory "+lock.getFile().getParent());
1128 Files.move(temp, lock.getFile() );
1130 catch ( IOException e )
1132 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1136 Files.copy( temp, lock.getFile());
1138 catch ( IOException e2 )
1140 if ( Files.exists(lock.getFile()) )
1142 log.debug( "Tried to copy file {} to {} but file with this name already exists.",
1143 temp.getFileName(), lock.getFile().toAbsolutePath() );
1147 throw new ProxyException(
1148 "Cannot copy tmp file " + temp.toAbsolutePath() + " to its final location", e2 );
1153 org.apache.archiva.common.utils.FileUtils.deleteQuietly( temp );
1158 catch ( FileLockException | FileLockTimeoutException e )
1160 throw new ProxyException( e.getMessage(), e );
1165 * Using wagon, connect to the remote repository.
1167 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1168 * @param wagon the wagon instance to establish the connection on.
1169 * @param remoteRepository the remote repository to connect to.
1170 * @return true if the connection was successful. false if not connected.
1172 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1173 RemoteRepositoryContent remoteRepository )
1175 boolean connected = false;
1177 final ProxyInfo networkProxy =
1178 connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1180 if ( log.isDebugEnabled() )
1182 if ( networkProxy != null )
1184 // TODO: move to proxyInfo.toString()
1185 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1186 + " to connect to remote repository " + remoteRepository.getURL();
1187 if ( networkProxy.getNonProxyHosts() != null )
1189 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1191 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1193 msg += "; as user: " + networkProxy.getUserName();
1199 AuthenticationInfo authInfo = null;
1200 String username = "";
1201 String password = "";
1202 RepositoryCredentials repCred = remoteRepository.getRepository().getLoginCredentials();
1203 if (repCred!=null && repCred instanceof PasswordCredentials) {
1204 PasswordCredentials pwdCred = (PasswordCredentials) repCred;
1205 username = pwdCred.getUsername();
1206 password = pwdCred.getPassword()==null ? "" : new String(pwdCred.getPassword());
1209 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1211 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1212 authInfo = new AuthenticationInfo();
1213 authInfo.setUserName( username );
1214 authInfo.setPassword( password );
1217 // Convert seconds to milliseconds
1219 long timeoutInMilliseconds = remoteRepository.getRepository().getTimeout().toMillis();
1221 // Set timeout read and connect
1222 // FIXME olamy having 2 config values
1223 wagon.setReadTimeout( (int) timeoutInMilliseconds );
1224 wagon.setTimeout( (int) timeoutInMilliseconds );
1228 Repository wagonRepository =
1229 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1230 wagon.connect( wagonRepository, authInfo, networkProxy );
1233 catch ( ConnectionException | AuthenticationException e )
1235 log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1243 * Tests whitelist and blacklist patterns against path.
1245 * @param path the path to test.
1246 * @param patterns the list of patterns to check.
1247 * @return true if the path matches at least 1 pattern in the provided patterns list.
1249 private boolean matchesPattern( String path, List<String> patterns )
1251 if ( CollectionUtils.isEmpty( patterns ) )
1256 if ( !path.startsWith( "/" ) )
1261 for ( String pattern : patterns )
1263 if ( !pattern.startsWith( "/" ) )
1265 pattern = "/" + pattern;
1268 if ( SelectorUtils.matchPath( pattern, path, false ) )
1278 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1281 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1284 if ( !this.proxyConnectorMap.containsKey( repository.getId() ) )
1286 return Collections.emptyList();
1288 List<ProxyConnector> ret = new ArrayList<>( this.proxyConnectorMap.get( repository.getId() ) );
1290 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1296 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1298 if ( ConfigurationNames.isNetworkProxy( propertyName ) //
1299 || ConfigurationNames.isManagedRepositories( propertyName ) //
1300 || ConfigurationNames.isRemoteRepositories( propertyName ) //
1301 || ConfigurationNames.isProxyConnector( propertyName ) ) //
1303 initConnectorsAndNetworkProxies();
1307 protected String addParameters( String path, RemoteRepository remoteRepository )
1309 if ( remoteRepository.getExtraParameters().isEmpty() )
1314 boolean question = false;
1316 StringBuilder res = new StringBuilder( path == null ? "" : path );
1318 for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1322 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1326 return res.toString();
1331 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1336 public ArchivaConfiguration getArchivaConfiguration()
1338 return archivaConfiguration;
1341 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1343 this.archivaConfiguration = archivaConfiguration;
1346 public RepositoryContentFactory getRepositoryFactory()
1348 return repositoryFactory;
1351 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1353 this.repositoryFactory = repositoryFactory;
1356 public MetadataTools getMetadataTools()
1358 return metadataTools;
1361 public void setMetadataTools( MetadataTools metadataTools )
1363 this.metadataTools = metadataTools;
1366 public UrlFailureCache getUrlFailureCache()
1368 return urlFailureCache;
1371 public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1373 this.urlFailureCache = urlFailureCache;
1376 public WagonFactory getWagonFactory()
1378 return wagonFactory;
1381 public void setWagonFactory( WagonFactory wagonFactory )
1383 this.wagonFactory = wagonFactory;
1386 public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1388 return preDownloadPolicies;
1391 public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1393 this.preDownloadPolicies = preDownloadPolicies;
1396 public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1398 return postDownloadPolicies;
1401 public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1403 this.postDownloadPolicies = postDownloadPolicies;
1406 public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1408 return downloadErrorPolicies;
1411 public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1413 this.downloadErrorPolicies = downloadErrorPolicies;