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.common.utils.FileUtil;
32 import org.apache.archiva.configuration.ArchivaConfiguration;
33 import org.apache.archiva.configuration.Configuration;
34 import org.apache.archiva.configuration.ConfigurationNames;
35 import org.apache.archiva.configuration.NetworkProxyConfiguration;
36 import org.apache.archiva.configuration.ProxyConnectorConfiguration;
37 import org.apache.archiva.configuration.ProxyConnectorRuleConfiguration;
38 import org.apache.archiva.model.ArtifactReference;
39 import org.apache.archiva.model.Keys;
40 import org.apache.archiva.model.RepositoryURL;
41 import org.apache.archiva.policies.DownloadErrorPolicy;
42 import org.apache.archiva.policies.DownloadPolicy;
43 import org.apache.archiva.policies.PolicyConfigurationException;
44 import org.apache.archiva.policies.PolicyViolationException;
45 import org.apache.archiva.policies.PostDownloadPolicy;
46 import org.apache.archiva.policies.PreDownloadPolicy;
47 import org.apache.archiva.policies.ProxyDownloadException;
48 import org.apache.archiva.policies.urlcache.UrlFailureCache;
49 import org.apache.archiva.proxy.common.WagonFactory;
50 import org.apache.archiva.proxy.common.WagonFactoryException;
51 import org.apache.archiva.proxy.common.WagonFactoryRequest;
52 import org.apache.archiva.proxy.model.ProxyConnector;
53 import org.apache.archiva.proxy.model.ProxyFetchResult;
54 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
55 import org.apache.archiva.redback.components.registry.Registry;
56 import org.apache.archiva.redback.components.registry.RegistryListener;
57 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
58 import org.apache.archiva.repository.ManagedRepositoryContent;
59 import org.apache.archiva.repository.RemoteRepositoryContent;
60 import org.apache.archiva.repository.RepositoryContentFactory;
61 import org.apache.archiva.repository.RepositoryException;
62 import org.apache.archiva.repository.RepositoryNotFoundException;
63 import org.apache.archiva.repository.metadata.MetadataTools;
64 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
65 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
66 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
67 import org.apache.commons.collections.CollectionUtils;
68 import org.apache.commons.io.FileUtils;
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;
90 import java.io.IOException;
91 import java.lang.reflect.Proxy;
92 import java.nio.file.Files;
93 import java.nio.file.Path;
94 import java.nio.file.Paths;
95 import java.util.ArrayList;
96 import java.util.Collections;
97 import java.util.LinkedHashMap;
98 import java.util.List;
100 import java.util.Map.Entry;
101 import java.util.Properties;
102 import java.util.concurrent.ConcurrentHashMap;
103 import java.util.concurrent.ConcurrentMap;
104 import java.util.concurrent.TimeUnit;
107 * DefaultRepositoryProxyConnectors
108 * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
109 * your average brown onion
111 @Service("repositoryProxyConnectors#default")
112 public class DefaultRepositoryProxyConnectors
113 implements RepositoryProxyConnectors, RegistryListener
115 private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class );
118 @Named(value = "archivaConfiguration#default")
119 private ArchivaConfiguration archivaConfiguration;
122 @Named(value = "repositoryContentFactory#default")
123 private RepositoryContentFactory repositoryFactory;
126 @Named(value = "metadataTools#default")
127 private MetadataTools metadataTools;
130 private Map<String, PreDownloadPolicy> preDownloadPolicies;
133 private Map<String, PostDownloadPolicy> postDownloadPolicies;
136 private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
139 private UrlFailureCache urlFailureCache;
141 private ConcurrentMap<String, List<ProxyConnector>> proxyConnectorMap = new ConcurrentHashMap<>();
143 private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>();
146 private WagonFactory wagonFactory;
149 @Named(value = "archivaTaskScheduler#repository")
150 private ArchivaTaskScheduler scheduler;
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();
186 // Create connector object.
187 ProxyConnector connector = new ProxyConnector();
189 connector.setSourceRepository(
190 repositoryFactory.getManagedRepositoryContent( proxyConfig.getSourceRepoId() ) );
191 connector.setTargetRepository(
192 repositoryFactory.getRemoteRepositoryContent( proxyConfig.getTargetRepoId() ) );
194 connector.setProxyId( proxyConfig.getProxyId() );
195 connector.setPolicies( proxyConfig.getPolicies() );
196 connector.setOrder( proxyConfig.getOrder() );
197 connector.setDisabled( proxyConfig.isDisabled() );
199 // Copy any blacklist patterns.
200 List<String> blacklist = new ArrayList<>( 0 );
201 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
203 blacklist.addAll( proxyConfig.getBlackListPatterns() );
205 connector.setBlacklist( blacklist );
207 // Copy any whitelist patterns.
208 List<String> whitelist = new ArrayList<>( 0 );
209 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
211 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
213 connector.setWhitelist( whitelist );
215 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
216 findProxyConnectorRules( connector.getSourceRepository().getId(),
217 connector.getTargetRepository().getId(),
218 allProxyConnectorRuleConfigurations );
220 if ( !proxyConnectorRuleConfigurations.isEmpty() )
222 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : proxyConnectorRuleConfigurations )
224 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
225 ProxyConnectorRuleType.BLACK_LIST.getRuleType() ) )
227 connector.getBlacklist().add( proxyConnectorRuleConfiguration.getPattern() );
230 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
231 ProxyConnectorRuleType.WHITE_LIST.getRuleType() ) )
233 connector.getWhitelist().add( proxyConnectorRuleConfiguration.getPattern() );
238 // Get other connectors
239 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
240 if ( connectors == null )
242 // Create if we are the first.
243 connectors = new ArrayList<>( 1 );
246 // Add the connector.
247 connectors.add( connector );
249 // Ensure the list is sorted.
250 Collections.sort( connectors, proxyOrderSorter );
252 // Set the key to the list of connectors.
253 this.proxyConnectorMap.put( key, connectors );
255 catch ( RepositoryNotFoundException e )
257 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
259 catch ( RepositoryException e )
261 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
267 this.networkProxyMap.clear();
269 List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
270 for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
272 String key = networkProxyConfig.getId();
274 ProxyInfo proxy = new ProxyInfo();
276 proxy.setType( networkProxyConfig.getProtocol() );
277 proxy.setHost( networkProxyConfig.getHost() );
278 proxy.setPort( networkProxyConfig.getPort() );
279 proxy.setUserName( networkProxyConfig.getUsername() );
280 proxy.setPassword( networkProxyConfig.getPassword() );
282 this.networkProxyMap.put( key, proxy );
287 private List<ProxyConnectorRuleConfiguration> findProxyConnectorRules( String sourceRepository,
288 String targetRepository,
289 List<ProxyConnectorRuleConfiguration> all )
291 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations = new ArrayList<>();
293 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : all )
295 for ( ProxyConnectorConfiguration proxyConnector : proxyConnectorRuleConfiguration.getProxyConnectors() )
297 if ( StringUtils.equals( sourceRepository, proxyConnector.getSourceRepoId() ) && StringUtils.equals(
298 targetRepository, proxyConnector.getTargetRepoId() ) )
300 proxyConnectorRuleConfigurations.add( proxyConnectorRuleConfiguration );
305 return proxyConnectorRuleConfigurations;
309 public Path fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
310 throws ProxyDownloadException
312 Path localFile = toLocalFile( repository, artifact );
314 Properties requestProperties = new Properties();
315 requestProperties.setProperty( "filetype", "artifact" );
316 requestProperties.setProperty( "version", artifact.getVersion() );
317 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
319 List<ProxyConnector> connectors = getProxyConnectors( repository );
320 Map<String, Exception> previousExceptions = new LinkedHashMap<>();
321 for ( ProxyConnector connector : connectors )
323 if ( connector.isDisabled() )
328 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
329 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
331 String targetPath = targetRepository.toPath( artifact );
333 if ( SystemUtils.IS_OS_WINDOWS )
335 // toPath use system PATH_SEPARATOR so on windows url are \ which doesn't work very well :-)
336 targetPath = FilenameUtils.separatorsToUnix( targetPath );
341 Path downloadedFile =
342 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
345 if ( fileExists( downloadedFile ) )
347 log.debug( "Successfully transferred: {}", downloadedFile.toAbsolutePath() );
348 return downloadedFile;
351 catch ( NotFoundException e )
353 log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ),
354 targetRepository.getRepository().getId() );
356 catch ( NotModifiedException e )
358 log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ),
359 targetRepository.getRepository().getId() );
361 catch ( ProxyException | RepositoryAdminException e )
363 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
364 targetRepository, localFile, e, previousExceptions );
368 if ( !previousExceptions.isEmpty() )
370 throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
371 previousExceptions );
374 log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) );
380 public Path fetchFromProxies( ManagedRepositoryContent repository, String path )
382 Path localFile = Paths.get( repository.getRepoRoot(), path );
384 // no update policies for these paths
385 if ( Files.exists(localFile) )
390 Properties requestProperties = new Properties();
391 requestProperties.setProperty( "filetype", "resource" );
392 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
394 List<ProxyConnector> connectors = getProxyConnectors( repository );
395 for ( ProxyConnector connector : connectors )
397 if ( connector.isDisabled() )
402 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
403 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
405 String targetPath = path;
409 Path downloadedFile =
410 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
413 if ( fileExists( downloadedFile ) )
415 log.debug( "Successfully transferred: {}", downloadedFile.toAbsolutePath() );
416 return downloadedFile;
419 catch ( NotFoundException e )
421 log.debug( "Resource {} not found on repository \"{}\".", path,
422 targetRepository.getRepository().getId() );
424 catch ( NotModifiedException e )
426 log.debug( "Resource {} not updated on repository \"{}\".", path,
427 targetRepository.getRepository().getId() );
429 catch ( ProxyException e )
432 "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",
433 targetRepository.getRepository().getId(), path, e.getMessage() );
434 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
435 "Transfer error from repository \"{}"
436 + "\" for resource {}, continuing to next repository. Error message: {}",
437 targetRepository.getRepository().getId(), path, e.getMessage(), e );
439 catch ( RepositoryAdminException e )
441 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
442 "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",
443 targetRepository.getRepository().getId(), path, e.getMessage(), e );
444 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ), "Full stack trace", e );
448 log.debug( "Exhausted all target repositories, resource {} not found.", path );
454 public ProxyFetchResult fetchMetadataFromProxies( ManagedRepositoryContent repository, String logicalPath )
456 Path localFile = Paths.get( repository.getRepoRoot(), logicalPath );
458 Properties requestProperties = new Properties();
459 requestProperties.setProperty( "filetype", "metadata" );
460 boolean metadataNeedsUpdating = false;
461 long originalTimestamp = getLastModified( localFile );
463 List<ProxyConnector> connectors = new ArrayList<>( getProxyConnectors( repository ) );
464 for ( ProxyConnector connector : connectors )
466 if ( connector.isDisabled() )
471 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
473 Path localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
474 long originalMetadataTimestamp = getLastModified( localRepoFile );
478 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
481 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
483 metadataNeedsUpdating = true;
486 catch ( NotFoundException e )
489 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
490 targetRepository.getRepository().getId(), e );
493 catch ( NotModifiedException e )
496 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
497 targetRepository.getRepository().getId(), e );
500 catch ( ProxyException | RepositoryAdminException e )
503 "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
504 targetRepository.getRepository().getId(), logicalPath, e.getMessage() );
505 log.debug( "Full stack trace", e );
509 if ( hasBeenUpdated( localFile, originalTimestamp ) )
511 metadataNeedsUpdating = true;
514 if ( metadataNeedsUpdating || !Files.exists(localFile))
518 metadataTools.updateMetadata( repository, logicalPath );
520 catch ( RepositoryMetadataException e )
522 log.warn( "Unable to update metadata {}:{}", localFile.toAbsolutePath(), e.getMessage(), e );
527 if ( fileExists( localFile ) )
529 return new ProxyFetchResult( localFile, metadataNeedsUpdating );
532 return new ProxyFetchResult( null, false );
537 * @param remoteRepository
544 * @param workingDirectory
546 * @throws ProxyException
547 * @throws NotModifiedException
548 * @throws org.apache.archiva.admin.model.RepositoryAdminException
550 protected void transferResources( ProxyConnector connector, RemoteRepositoryContent remoteRepository, Path tmpMd5,
551 Path tmpSha1, Path tmpResource, String url, String remotePath, Path resource,
552 Path workingDirectory, ManagedRepositoryContent repository )
553 throws ProxyException, NotModifiedException, RepositoryAdminException
558 RepositoryURL repoUrl = remoteRepository.getURL();
559 String protocol = repoUrl.getProtocol();
560 NetworkProxy networkProxy = null;
561 if ( StringUtils.isNotBlank( connector.getProxyId() ) )
563 networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() );
565 WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest( "wagon#" + protocol,
566 remoteRepository.getRepository().getExtraHeaders() ).networkProxy(
568 wagon = wagonFactory.getWagon( wagonFactoryRequest );
571 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
576 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
579 boolean connected = connectToRepository( connector, wagon, remoteRepository );
582 transferArtifact( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
585 // TODO: these should be used to validate the download based on the policies, not always downloaded
587 // save on connections since md5 is rarely used
588 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".sha1",
590 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".md5",
594 catch ( NotFoundException e )
596 urlFailureCache.cacheFailure( url );
599 catch ( NotModifiedException e )
601 // Do not cache url here.
604 catch ( ProxyException e )
606 urlFailureCache.cacheFailure( url );
609 catch ( WagonFactoryException e )
611 throw new ProxyException( e.getMessage(), e );
621 catch ( ConnectionException e )
623 log.warn( "Unable to disconnect wagon.", e );
629 private void transferArtifact( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
630 ManagedRepositoryContent repository, Path resource, Path tmpDirectory,
632 throws ProxyException
634 transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, destFile );
637 private long getLastModified( Path file )
639 if ( !Files.exists(file) || !Files.isRegularFile(file) )
646 return Files.getLastModifiedTime(file).toMillis();
648 catch ( IOException e )
650 log.error("Could get the modified time of file {}", file.toAbsolutePath());
655 private boolean hasBeenUpdated( Path file, long originalLastModified )
657 if ( !Files.exists(file) || !Files.isRegularFile(file) )
662 long currentLastModified = getLastModified( file );
663 return ( currentLastModified > originalLastModified );
666 private Path toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
669 String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
670 return Paths.get( repository.getRepoRoot(), repoPath );
674 * Test if the provided ManagedRepositoryContent has any proxies configured for it.
677 public boolean hasProxies( ManagedRepositoryContent repository )
679 synchronized ( this.proxyConnectorMap )
681 return this.proxyConnectorMap.containsKey( repository.getId() );
685 private Path toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
687 return repository.toFile( artifact );
691 * Simple method to test if the file exists on the local disk.
693 * @param file the file to test. (may be null)
694 * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
696 private boolean fileExists( Path file )
703 if ( !Files.exists(file))
708 return Files.isRegularFile(file);
712 * Perform the transfer of the file.
714 * @param connector the connector configuration to use.
715 * @param remoteRepository the remote repository get the resource from.
716 * @param remotePath the path in the remote repository to the resource to get.
717 * @param repository the managed repository that will hold the file
718 * @param resource the local file to place the downloaded resource into
719 * @param requestProperties the request properties to utilize for policy handling.
720 * @param executeConsumers whether to execute the consumers after proxying
721 * @return the local file that was downloaded, or null if not downloaded.
722 * @throws NotFoundException if the file was not found on the remote repository.
723 * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but
724 * the remote resource is not newer than the local File.
725 * @throws ProxyException if transfer was unsuccessful.
727 private Path transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
728 ManagedRepositoryContent repository, Path resource, Properties requestProperties,
729 boolean executeConsumers )
730 throws ProxyException, NotModifiedException, RepositoryAdminException
732 String url = remoteRepository.getURL().getUrl();
733 if ( !url.endsWith( "/" ) )
737 url = url + remotePath;
738 requestProperties.setProperty( "url", url );
740 // Is a whitelist defined?
741 if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
743 // Path must belong to whitelist.
744 if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
746 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
747 remotePath, remoteRepository.getRepository().getName() );
752 // Is target path part of blacklist?
753 if ( matchesPattern( remotePath, connector.getBlacklist() ) )
755 log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
756 remoteRepository.getRepository().getName() );
760 // Handle pre-download policy
763 validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
765 catch ( PolicyViolationException e )
767 String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
768 if ( fileExists( resource ) )
770 log.debug( "{} : using already present local file.", emsg );
778 Path workingDirectory = createWorkingDirectory( repository );
779 Path tmpResource = workingDirectory.resolve(resource.getFileName());
780 Path tmpMd5 = workingDirectory.resolve(resource.getFileName().toString() + ".md5" );
781 Path tmpSha1 = workingDirectory.resolve( resource.getFileName().toString() + ".sha1" );
786 transferResources( connector, remoteRepository, tmpMd5, tmpSha1, tmpResource, url, remotePath, resource,
787 workingDirectory, repository );
789 // Handle post-download policies.
792 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
794 catch ( PolicyViolationException e )
796 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
797 executeConsumers = false;
798 if ( !fileExists( tmpResource ) )
804 if ( resource != null )
806 synchronized ( resource.toAbsolutePath().toString().intern() )
808 Path directory = resource.getParent();
809 moveFileIfExists( tmpMd5, directory );
810 moveFileIfExists( tmpSha1, directory );
811 moveFileIfExists( tmpResource, directory );
817 org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
820 if ( executeConsumers )
822 // Just-in-time update of the index and database by executing the consumers for this artifact
823 //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource );
824 queueRepositoryTask( connector.getSourceRepository().getRepository().getId(), resource );
830 private void queueRepositoryTask( String repositoryId, Path localFile )
832 RepositoryTask task = new RepositoryTask();
833 task.setRepositoryId( repositoryId );
834 task.setResourceFile( localFile.toFile() );
835 task.setUpdateRelatedArtifacts( true );
836 task.setScanAll( true );
840 scheduler.queueTask( task );
842 catch ( TaskQueueException e )
844 log.error( "Unable to queue repository task to execute consumers on resource file ['{}"
845 + "'].", localFile.getFileName() );
850 * Moves the file into repository location if it exists
852 * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
853 * @param directory directory to write files to
855 private void moveFileIfExists( Path fileToMove, Path directory )
856 throws ProxyException
858 if ( fileToMove != null && Files.exists(fileToMove) )
860 Path newLocation = directory.resolve(fileToMove.getFileName());
861 moveTempToTarget( fileToMove, newLocation );
867 * Quietly transfer the checksum file from the remote repository to the local file.
870 * @param wagon the wagon instance (should already be connected) to use.
871 * @param remoteRepository the remote repository to transfer from.
872 * @param remotePath the remote path to the resource to get.
873 * @param repository the managed repository that will hold the file
874 * @param resource the local file that should contain the downloaded contents
875 * @param tmpDirectory the temporary directory to download to
876 * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
877 * @throws ProxyException if copying the downloaded file into place did not succeed.
879 private void transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
880 ManagedRepositoryContent repository, Path resource, Path tmpDirectory, String ext,
882 throws ProxyException
884 String url = remoteRepository.getURL().getUrl() + remotePath + ext;
886 // Transfer checksum does not use the policy.
887 if ( urlFailureCache.hasFailedBefore( url ) )
894 transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile );
895 log.debug( "Checksum {} Downloaded: {} to move to {}", url, destFile, resource );
897 catch ( NotFoundException e )
899 urlFailureCache.cacheFailure( url );
900 log.debug( "Transfer failed, checksum not found: {}", url );
901 // Consume it, do not pass this on.
903 catch ( NotModifiedException e )
905 log.debug( "Transfer skipped, checksum not modified: {}", url );
906 // Consume it, do not pass this on.
908 catch ( ProxyException e )
910 urlFailureCache.cacheFailure( url );
911 log.warn( "Transfer failed on checksum: {} : {}", url, e.getMessage(), e );
912 // Critical issue, pass it on.
918 * Perform the transfer of the remote file to the local file specified.
920 * @param wagon the wagon instance to use.
921 * @param remoteRepository the remote repository to use
922 * @param remotePath the remote path to attempt to get
923 * @param repository the managed repository that will hold the file
924 * @param origFile the local file to save to
925 * @throws ProxyException if there was a problem moving the downloaded file into place.
927 private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
928 ManagedRepositoryContent repository, Path origFile, Path destFile )
929 throws ProxyException
931 assert ( remotePath != null );
933 // Transfer the file.
936 boolean success = false;
938 if ( !Files.exists(origFile))
940 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
941 wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile.toFile() );
944 // You wouldn't get here on failure, a WagonException would have been thrown.
945 log.debug( "Downloaded successfully." );
949 log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() );
952 success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile.toFile(),
953 Files.getLastModifiedTime(origFile).toMillis());
955 catch ( IOException e )
957 throw new ProxyException( "Failed to the modification time of "+origFile.toAbsolutePath() );
961 throw new NotModifiedException(
962 "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath() );
965 if ( Files.exists(destFile))
967 log.debug( "Downloaded successfully." );
971 catch ( ResourceDoesNotExistException e )
973 throw new NotFoundException(
974 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
977 catch ( WagonException e )
979 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
982 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
983 if ( e.getCause() != null )
985 msg += " (cause: " + e.getCause() + ")";
987 throw new ProxyException( msg, e );
992 * Apply the policies.
994 * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
995 * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy
997 * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}
999 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)})
1000 * @throws PolicyViolationException
1002 private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
1003 Properties request, Path localFile )
1004 throws PolicyViolationException
1006 for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
1008 // olamy with spring rolehint is now downloadPolicy#hint
1009 // so substring after last # to get the hint as with plexus
1010 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1011 DownloadPolicy policy = entry.getValue();
1012 String defaultSetting = policy.getDefaultOption();
1014 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1016 log.debug( "Applying [{}] policy with [{}]", key, setting );
1019 policy.applyPolicy( setting, request, localFile.toFile() );
1021 catch ( PolicyConfigurationException e )
1023 log.error( e.getMessage(), e );
1028 private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
1029 Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
1030 Path localFile, Exception exception, Map<String, Exception> previousExceptions )
1031 throws ProxyDownloadException
1033 boolean process = true;
1034 for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
1037 // olamy with spring rolehint is now downloadPolicy#hint
1038 // so substring after last # to get the hint as with plexus
1039 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1040 DownloadErrorPolicy policy = entry.getValue();
1041 String defaultSetting = policy.getDefaultOption();
1042 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1044 log.debug( "Applying [{}] policy with [{}]", key, setting );
1047 // all policies must approve the exception, any can cancel
1048 process = policy.applyPolicy( setting, request, localFile.toFile(), exception, previousExceptions );
1054 catch ( PolicyConfigurationException e )
1056 log.error( e.getMessage(), e );
1062 // if the exception was queued, don't throw it
1063 if ( !previousExceptions.containsKey( content.getId() ) )
1065 throw new ProxyDownloadException(
1066 "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
1067 content.getId(), exception );
1072 // if the exception was queued, but cancelled, remove it
1073 previousExceptions.remove( content.getId() );
1077 "Transfer error from repository {} for artifact {} , continuing to next repository. Error message: {}",
1078 content.getRepository().getId(), Keys.toKey( artifact ), exception.getMessage() );
1079 log.debug( "Full stack trace", exception );
1083 * Creates a working directory
1086 * @return file location of working directory
1088 private Path createWorkingDirectory( ManagedRepositoryContent repository )
1092 return Files.createTempDirectory( "temp" );
1094 catch ( IOException e )
1096 throw new RuntimeException( e.getMessage(), e );
1102 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
1105 * @param temp The completed download file
1106 * @param target The final location of the downloaded file
1107 * @throws ProxyException when the temp file cannot replace the target file
1109 private void moveTempToTarget( Path temp, Path target )
1110 throws ProxyException
1116 lock = fileLockManager.writeFileLock( target.toFile() );
1117 if ( lock.getFile().exists() && !lock.getFile().delete() )
1119 throw new ProxyException( "Unable to overwrite existing target file: " + target.toAbsolutePath() );
1122 lock.getFile().getParentFile().mkdirs();
1126 Files.move(temp, lock.getFile().toPath() );
1128 catch ( IOException e )
1130 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1134 Files.copy( temp, lock.getFile().toPath() );
1136 catch ( IOException e2 )
1138 if ( lock.getFile().exists() )
1140 log.debug( "Tried to copy file {} to {} but file with this name already exists.",
1141 temp.getFileName(), lock.getFile().getAbsolutePath() );
1145 throw new ProxyException(
1146 "Cannot copy tmp file " + temp.toAbsolutePath() + " to its final location", e2 );
1151 org.apache.archiva.common.utils.FileUtils.deleteQuietly( temp );
1156 catch ( FileLockException | FileLockTimeoutException e )
1158 throw new ProxyException( e.getMessage(), e );
1163 * Using wagon, connect to the remote repository.
1165 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1166 * @param wagon the wagon instance to establish the connection on.
1167 * @param remoteRepository the remote repository to connect to.
1168 * @return true if the connection was successful. false if not connected.
1170 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1171 RemoteRepositoryContent remoteRepository )
1173 boolean connected = false;
1175 final ProxyInfo networkProxy =
1176 connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1178 if ( log.isDebugEnabled() )
1180 if ( networkProxy != null )
1182 // TODO: move to proxyInfo.toString()
1183 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1184 + " to connect to remote repository " + remoteRepository.getURL();
1185 if ( networkProxy.getNonProxyHosts() != null )
1187 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1189 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1191 msg += "; as user: " + networkProxy.getUserName();
1197 AuthenticationInfo authInfo = null;
1198 String username = remoteRepository.getRepository().getUserName();
1199 String password = remoteRepository.getRepository().getPassword();
1201 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1203 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1204 authInfo = new AuthenticationInfo();
1205 authInfo.setUserName( username );
1206 authInfo.setPassword( password );
1209 // Convert seconds to milliseconds
1210 long timeoutInMilliseconds = TimeUnit.MILLISECONDS.convert( remoteRepository.getRepository().getTimeout(), //
1213 // Set timeout read and connect
1214 // FIXME olamy having 2 config values
1215 wagon.setReadTimeout( (int) timeoutInMilliseconds );
1216 wagon.setTimeout( (int) timeoutInMilliseconds );
1220 Repository wagonRepository =
1221 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1222 wagon.connect( wagonRepository, authInfo, networkProxy );
1225 catch ( ConnectionException | AuthenticationException e )
1227 log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1235 * Tests whitelist and blacklist patterns against path.
1237 * @param path the path to test.
1238 * @param patterns the list of patterns to check.
1239 * @return true if the path matches at least 1 pattern in the provided patterns list.
1241 private boolean matchesPattern( String path, List<String> patterns )
1243 if ( CollectionUtils.isEmpty( patterns ) )
1248 if ( !path.startsWith( "/" ) )
1253 for ( String pattern : patterns )
1255 if ( !pattern.startsWith( "/" ) )
1257 pattern = "/" + pattern;
1260 if ( SelectorUtils.matchPath( pattern, path, false ) )
1270 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1273 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1276 if ( !this.proxyConnectorMap.containsKey( repository.getId() ) )
1278 return Collections.emptyList();
1280 List<ProxyConnector> ret = new ArrayList<>( this.proxyConnectorMap.get( repository.getId() ) );
1282 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1288 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1290 if ( ConfigurationNames.isNetworkProxy( propertyName ) //
1291 || ConfigurationNames.isManagedRepositories( propertyName ) //
1292 || ConfigurationNames.isRemoteRepositories( propertyName ) //
1293 || ConfigurationNames.isProxyConnector( propertyName ) ) //
1295 initConnectorsAndNetworkProxies();
1299 protected String addParameters( String path, RemoteRepository remoteRepository )
1301 if ( remoteRepository.getExtraParameters().isEmpty() )
1306 boolean question = false;
1308 StringBuilder res = new StringBuilder( path == null ? "" : path );
1310 for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1314 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1318 return res.toString();
1323 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1328 public ArchivaConfiguration getArchivaConfiguration()
1330 return archivaConfiguration;
1333 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1335 this.archivaConfiguration = archivaConfiguration;
1338 public RepositoryContentFactory getRepositoryFactory()
1340 return repositoryFactory;
1343 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1345 this.repositoryFactory = repositoryFactory;
1348 public MetadataTools getMetadataTools()
1350 return metadataTools;
1353 public void setMetadataTools( MetadataTools metadataTools )
1355 this.metadataTools = metadataTools;
1358 public UrlFailureCache getUrlFailureCache()
1360 return urlFailureCache;
1363 public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1365 this.urlFailureCache = urlFailureCache;
1368 public WagonFactory getWagonFactory()
1370 return wagonFactory;
1373 public void setWagonFactory( WagonFactory wagonFactory )
1375 this.wagonFactory = wagonFactory;
1378 public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1380 return preDownloadPolicies;
1383 public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1385 this.preDownloadPolicies = preDownloadPolicies;
1388 public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1390 return postDownloadPolicies;
1393 public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1395 this.postDownloadPolicies = postDownloadPolicies;
1398 public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1400 return downloadErrorPolicies;
1403 public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1405 this.downloadErrorPolicies = downloadErrorPolicies;