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.*;
32 import org.apache.archiva.model.ArtifactReference;
33 import org.apache.archiva.model.Keys;
34 import org.apache.archiva.model.RepositoryURL;
35 import org.apache.archiva.policies.*;
36 import org.apache.archiva.policies.urlcache.UrlFailureCache;
37 import org.apache.archiva.proxy.common.WagonFactory;
38 import org.apache.archiva.proxy.common.WagonFactoryException;
39 import org.apache.archiva.proxy.common.WagonFactoryRequest;
40 import org.apache.archiva.proxy.model.ProxyConnector;
41 import org.apache.archiva.proxy.model.ProxyFetchResult;
42 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
43 import org.apache.archiva.redback.components.registry.Registry;
44 import org.apache.archiva.redback.components.registry.RegistryListener;
45 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
46 import org.apache.archiva.repository.*;
47 import org.apache.archiva.repository.metadata.MetadataTools;
48 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
49 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
50 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
51 import org.apache.commons.collections.CollectionUtils;
52 import org.apache.commons.io.FilenameUtils;
53 import org.apache.commons.lang.StringUtils;
54 import org.apache.commons.lang.SystemUtils;
55 import org.apache.maven.wagon.ConnectionException;
56 import org.apache.maven.wagon.ResourceDoesNotExistException;
57 import org.apache.maven.wagon.Wagon;
58 import org.apache.maven.wagon.WagonException;
59 import org.apache.maven.wagon.authentication.AuthenticationException;
60 import org.apache.maven.wagon.authentication.AuthenticationInfo;
61 import org.apache.maven.wagon.proxy.ProxyInfo;
62 import org.apache.maven.wagon.repository.Repository;
63 import org.apache.tools.ant.types.selectors.SelectorUtils;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66 import org.slf4j.MarkerFactory;
67 import org.springframework.stereotype.Service;
69 import javax.annotation.PostConstruct;
70 import javax.inject.Inject;
71 import javax.inject.Named;
72 import java.io.IOException;
73 import java.nio.file.Files;
74 import java.nio.file.Path;
75 import java.nio.file.Paths;
77 import java.util.Map.Entry;
78 import java.util.concurrent.ConcurrentHashMap;
79 import java.util.concurrent.ConcurrentMap;
80 import java.util.concurrent.TimeUnit;
83 * DefaultRepositoryProxyConnectors
84 * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
85 * your average brown onion
87 @Service("repositoryProxyConnectors#default")
88 public class DefaultRepositoryProxyConnectors
89 implements RepositoryProxyConnectors, RegistryListener
91 private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class );
94 @Named(value = "archivaConfiguration#default")
95 private ArchivaConfiguration archivaConfiguration;
98 @Named(value = "repositoryContentFactory#default")
99 private RepositoryContentFactory repositoryFactory;
102 @Named(value = "metadataTools#default")
103 private MetadataTools metadataTools;
106 private Map<String, PreDownloadPolicy> preDownloadPolicies;
109 private Map<String, PostDownloadPolicy> postDownloadPolicies;
112 private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
115 private UrlFailureCache urlFailureCache;
117 private ConcurrentMap<String, List<ProxyConnector>> proxyConnectorMap = new ConcurrentHashMap<>();
119 private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>();
122 private WagonFactory wagonFactory;
125 @Named(value = "archivaTaskScheduler#repository")
126 private ArchivaTaskScheduler scheduler;
129 private NetworkProxyAdmin networkProxyAdmin;
132 @Named(value = "fileLockManager#default")
133 private FileLockManager fileLockManager;
136 public void initialize()
138 initConnectorsAndNetworkProxies();
139 archivaConfiguration.addChangeListener( this );
143 @SuppressWarnings("unchecked")
144 private void initConnectorsAndNetworkProxies()
147 ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
148 this.proxyConnectorMap.clear();
150 Configuration configuration = archivaConfiguration.getConfiguration();
152 List<ProxyConnectorRuleConfiguration> allProxyConnectorRuleConfigurations =
153 configuration.getProxyConnectorRuleConfigurations();
155 List<ProxyConnectorConfiguration> proxyConfigs = configuration.getProxyConnectors();
156 for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
158 String key = proxyConfig.getSourceRepoId();
162 // Create connector object.
163 ProxyConnector connector = new ProxyConnector();
165 connector.setSourceRepository(
166 repositoryFactory.getManagedRepositoryContent( proxyConfig.getSourceRepoId() ) );
167 connector.setTargetRepository(
168 repositoryFactory.getRemoteRepositoryContent( proxyConfig.getTargetRepoId() ) );
170 connector.setProxyId( proxyConfig.getProxyId() );
171 connector.setPolicies( proxyConfig.getPolicies() );
172 connector.setOrder( proxyConfig.getOrder() );
173 connector.setDisabled( proxyConfig.isDisabled() );
175 // Copy any blacklist patterns.
176 List<String> blacklist = new ArrayList<>( 0 );
177 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
179 blacklist.addAll( proxyConfig.getBlackListPatterns() );
181 connector.setBlacklist( blacklist );
183 // Copy any whitelist patterns.
184 List<String> whitelist = new ArrayList<>( 0 );
185 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
187 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
189 connector.setWhitelist( whitelist );
191 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
192 findProxyConnectorRules( connector.getSourceRepository().getId(),
193 connector.getTargetRepository().getId(),
194 allProxyConnectorRuleConfigurations );
196 if ( !proxyConnectorRuleConfigurations.isEmpty() )
198 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : proxyConnectorRuleConfigurations )
200 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
201 ProxyConnectorRuleType.BLACK_LIST.getRuleType() ) )
203 connector.getBlacklist().add( proxyConnectorRuleConfiguration.getPattern() );
206 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
207 ProxyConnectorRuleType.WHITE_LIST.getRuleType() ) )
209 connector.getWhitelist().add( proxyConnectorRuleConfiguration.getPattern() );
214 // Get other connectors
215 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
216 if ( connectors == null )
218 // Create if we are the first.
219 connectors = new ArrayList<>( 1 );
222 // Add the connector.
223 connectors.add( connector );
225 // Ensure the list is sorted.
226 Collections.sort( connectors, proxyOrderSorter );
228 // Set the key to the list of connectors.
229 this.proxyConnectorMap.put( key, connectors );
231 catch ( RepositoryNotFoundException e )
233 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
235 catch ( RepositoryException e )
237 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
243 this.networkProxyMap.clear();
245 List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
246 for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
248 String key = networkProxyConfig.getId();
250 ProxyInfo proxy = new ProxyInfo();
252 proxy.setType( networkProxyConfig.getProtocol() );
253 proxy.setHost( networkProxyConfig.getHost() );
254 proxy.setPort( networkProxyConfig.getPort() );
255 proxy.setUserName( networkProxyConfig.getUsername() );
256 proxy.setPassword( networkProxyConfig.getPassword() );
258 this.networkProxyMap.put( key, proxy );
263 private List<ProxyConnectorRuleConfiguration> findProxyConnectorRules( String sourceRepository,
264 String targetRepository,
265 List<ProxyConnectorRuleConfiguration> all )
267 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations = new ArrayList<>();
269 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : all )
271 for ( ProxyConnectorConfiguration proxyConnector : proxyConnectorRuleConfiguration.getProxyConnectors() )
273 if ( StringUtils.equals( sourceRepository, proxyConnector.getSourceRepoId() ) && StringUtils.equals(
274 targetRepository, proxyConnector.getTargetRepoId() ) )
276 proxyConnectorRuleConfigurations.add( proxyConnectorRuleConfiguration );
281 return proxyConnectorRuleConfigurations;
285 public Path fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
286 throws ProxyDownloadException
288 Path localFile = toLocalFile( repository, artifact );
290 Properties requestProperties = new Properties();
291 requestProperties.setProperty( "filetype", "artifact" );
292 requestProperties.setProperty( "version", artifact.getVersion() );
293 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
295 List<ProxyConnector> connectors = getProxyConnectors( repository );
296 Map<String, Exception> previousExceptions = new LinkedHashMap<>();
297 for ( ProxyConnector connector : connectors )
299 if ( connector.isDisabled() )
304 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
305 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
307 String targetPath = targetRepository.toPath( artifact );
309 if ( SystemUtils.IS_OS_WINDOWS )
311 // toPath use system PATH_SEPARATOR so on windows url are \ which doesn't work very well :-)
312 targetPath = FilenameUtils.separatorsToUnix( targetPath );
317 Path downloadedFile =
318 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
321 if ( fileExists( downloadedFile ) )
323 log.debug( "Successfully transferred: {}", downloadedFile.toAbsolutePath() );
324 return downloadedFile;
327 catch ( NotFoundException e )
329 log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ),
330 targetRepository.getRepository().getId() );
332 catch ( NotModifiedException e )
334 log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ),
335 targetRepository.getRepository().getId() );
337 catch ( ProxyException | RepositoryAdminException e )
339 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
340 targetRepository, localFile, e, previousExceptions );
344 if ( !previousExceptions.isEmpty() )
346 throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
347 previousExceptions );
350 log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) );
356 public Path fetchFromProxies( ManagedRepositoryContent repository, String path )
358 Path localFile = Paths.get( repository.getRepoRoot(), path );
360 // no update policies for these paths
361 if ( Files.exists(localFile) )
366 Properties requestProperties = new Properties();
367 requestProperties.setProperty( "filetype", "resource" );
368 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
370 List<ProxyConnector> connectors = getProxyConnectors( repository );
371 for ( ProxyConnector connector : connectors )
373 if ( connector.isDisabled() )
378 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
379 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
381 String targetPath = path;
385 Path downloadedFile =
386 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
389 if ( fileExists( downloadedFile ) )
391 log.debug( "Successfully transferred: {}", downloadedFile.toAbsolutePath() );
392 return downloadedFile;
395 catch ( NotFoundException e )
397 log.debug( "Resource {} not found on repository \"{}\".", path,
398 targetRepository.getRepository().getId() );
400 catch ( NotModifiedException e )
402 log.debug( "Resource {} not updated on repository \"{}\".", path,
403 targetRepository.getRepository().getId() );
405 catch ( ProxyException e )
408 "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",
409 targetRepository.getRepository().getId(), path, e.getMessage() );
410 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
411 "Transfer error from repository \"{}"
412 + "\" for resource {}, continuing to next repository. Error message: {}",
413 targetRepository.getRepository().getId(), path, e.getMessage(), e );
415 catch ( RepositoryAdminException e )
417 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
418 "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",
419 targetRepository.getRepository().getId(), path, e.getMessage(), e );
420 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ), "Full stack trace", e );
424 log.debug( "Exhausted all target repositories, resource {} not found.", path );
430 public ProxyFetchResult fetchMetadataFromProxies( ManagedRepositoryContent repository, String logicalPath )
432 Path localFile = Paths.get( repository.getRepoRoot(), logicalPath );
434 Properties requestProperties = new Properties();
435 requestProperties.setProperty( "filetype", "metadata" );
436 boolean metadataNeedsUpdating = false;
437 long originalTimestamp = getLastModified( localFile );
439 List<ProxyConnector> connectors = new ArrayList<>( getProxyConnectors( repository ) );
440 for ( ProxyConnector connector : connectors )
442 if ( connector.isDisabled() )
447 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
449 Path localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
450 long originalMetadataTimestamp = getLastModified( localRepoFile );
454 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
457 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
459 metadataNeedsUpdating = true;
462 catch ( NotFoundException e )
465 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
466 targetRepository.getRepository().getId(), e );
469 catch ( NotModifiedException e )
472 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
473 targetRepository.getRepository().getId(), e );
476 catch ( ProxyException | RepositoryAdminException e )
479 "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
480 targetRepository.getRepository().getId(), logicalPath, e.getMessage() );
481 log.debug( "Full stack trace", e );
485 if ( hasBeenUpdated( localFile, originalTimestamp ) )
487 metadataNeedsUpdating = true;
490 if ( metadataNeedsUpdating || !Files.exists(localFile))
494 metadataTools.updateMetadata( repository, logicalPath );
496 catch ( RepositoryMetadataException e )
498 log.warn( "Unable to update metadata {}:{}", localFile.toAbsolutePath(), e.getMessage(), e );
503 if ( fileExists( localFile ) )
505 return new ProxyFetchResult( localFile, metadataNeedsUpdating );
508 return new ProxyFetchResult( null, false );
513 * @param remoteRepository
520 * @param workingDirectory
522 * @throws ProxyException
523 * @throws NotModifiedException
524 * @throws org.apache.archiva.admin.model.RepositoryAdminException
526 protected void transferResources( ProxyConnector connector, RemoteRepositoryContent remoteRepository, Path tmpMd5,
527 Path tmpSha1, Path tmpResource, String url, String remotePath, Path resource,
528 Path workingDirectory, ManagedRepositoryContent repository )
529 throws ProxyException, NotModifiedException, RepositoryAdminException
534 RepositoryURL repoUrl = remoteRepository.getURL();
535 String protocol = repoUrl.getProtocol();
536 NetworkProxy networkProxy = null;
537 if ( StringUtils.isNotBlank( connector.getProxyId() ) )
539 networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() );
541 WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest( "wagon#" + protocol,
542 remoteRepository.getRepository().getExtraHeaders() ).networkProxy(
544 wagon = wagonFactory.getWagon( wagonFactoryRequest );
547 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
552 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
555 boolean connected = connectToRepository( connector, wagon, remoteRepository );
558 transferArtifact( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
561 // TODO: these should be used to validate the download based on the policies, not always downloaded
563 // save on connections since md5 is rarely used
564 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".sha1",
566 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".md5",
570 catch ( NotFoundException e )
572 urlFailureCache.cacheFailure( url );
575 catch ( NotModifiedException e )
577 // Do not cache url here.
580 catch ( ProxyException e )
582 urlFailureCache.cacheFailure( url );
585 catch ( WagonFactoryException e )
587 throw new ProxyException( e.getMessage(), e );
597 catch ( ConnectionException e )
599 log.warn( "Unable to disconnect wagon.", e );
605 private void transferArtifact( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
606 ManagedRepositoryContent repository, Path resource, Path tmpDirectory,
608 throws ProxyException
610 transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, destFile );
613 private long getLastModified( Path file )
615 if ( !Files.exists(file) || !Files.isRegularFile(file) )
622 return Files.getLastModifiedTime(file).toMillis();
624 catch ( IOException e )
626 log.error("Could get the modified time of file {}", file.toAbsolutePath());
631 private boolean hasBeenUpdated( Path file, long originalLastModified )
633 if ( !Files.exists(file) || !Files.isRegularFile(file) )
638 long currentLastModified = getLastModified( file );
639 return ( currentLastModified > originalLastModified );
642 private Path toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
645 String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
646 return Paths.get( repository.getRepoRoot(), repoPath );
650 * Test if the provided ManagedRepositoryContent has any proxies configured for it.
653 public boolean hasProxies( ManagedRepositoryContent repository )
655 synchronized ( this.proxyConnectorMap )
657 return this.proxyConnectorMap.containsKey( repository.getId() );
661 private Path toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
663 return repository.toFile( artifact );
667 * Simple method to test if the file exists on the local disk.
669 * @param file the file to test. (may be null)
670 * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
672 private boolean fileExists( Path file )
679 if ( !Files.exists(file))
684 return Files.isRegularFile(file);
688 * Perform the transfer of the file.
690 * @param connector the connector configuration to use.
691 * @param remoteRepository the remote repository get the resource from.
692 * @param remotePath the path in the remote repository to the resource to get.
693 * @param repository the managed repository that will hold the file
694 * @param resource the local file to place the downloaded resource into
695 * @param requestProperties the request properties to utilize for policy handling.
696 * @param executeConsumers whether to execute the consumers after proxying
697 * @return the local file that was downloaded, or null if not downloaded.
698 * @throws NotFoundException if the file was not found on the remote repository.
699 * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but
700 * the remote resource is not newer than the local File.
701 * @throws ProxyException if transfer was unsuccessful.
703 private Path transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
704 ManagedRepositoryContent repository, Path resource, Properties requestProperties,
705 boolean executeConsumers )
706 throws ProxyException, NotModifiedException, RepositoryAdminException
708 String url = remoteRepository.getURL().getUrl();
709 if ( !url.endsWith( "/" ) )
713 url = url + remotePath;
714 requestProperties.setProperty( "url", url );
716 // Is a whitelist defined?
717 if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
719 // Path must belong to whitelist.
720 if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
722 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
723 remotePath, remoteRepository.getRepository().getName() );
728 // Is target path part of blacklist?
729 if ( matchesPattern( remotePath, connector.getBlacklist() ) )
731 log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
732 remoteRepository.getRepository().getName() );
736 // Handle pre-download policy
739 validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
741 catch ( PolicyViolationException e )
743 String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
744 if ( fileExists( resource ) )
746 log.debug( "{} : using already present local file.", emsg );
754 Path workingDirectory = createWorkingDirectory( repository );
755 Path tmpResource = workingDirectory.resolve(resource.getFileName());
756 Path tmpMd5 = workingDirectory.resolve(resource.getFileName().toString() + ".md5" );
757 Path tmpSha1 = workingDirectory.resolve( resource.getFileName().toString() + ".sha1" );
762 transferResources( connector, remoteRepository, tmpMd5, tmpSha1, tmpResource, url, remotePath, resource,
763 workingDirectory, repository );
765 // Handle post-download policies.
768 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
770 catch ( PolicyViolationException e )
772 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
773 executeConsumers = false;
774 if ( !fileExists( tmpResource ) )
780 if ( resource != null )
782 synchronized ( resource.toAbsolutePath().toString().intern() )
784 Path directory = resource.getParent();
785 moveFileIfExists( tmpMd5, directory );
786 moveFileIfExists( tmpSha1, directory );
787 moveFileIfExists( tmpResource, directory );
793 org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
796 if ( executeConsumers )
798 // Just-in-time update of the index and database by executing the consumers for this artifact
799 //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource );
800 queueRepositoryTask( connector.getSourceRepository().getRepository().getId(), resource );
806 private void queueRepositoryTask( String repositoryId, Path localFile )
808 RepositoryTask task = new RepositoryTask();
809 task.setRepositoryId( repositoryId );
810 task.setResourceFile( localFile.toFile() );
811 task.setUpdateRelatedArtifacts( true );
812 task.setScanAll( true );
816 scheduler.queueTask( task );
818 catch ( TaskQueueException e )
820 log.error( "Unable to queue repository task to execute consumers on resource file ['{}"
821 + "'].", localFile.getFileName() );
826 * Moves the file into repository location if it exists
828 * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
829 * @param directory directory to write files to
831 private void moveFileIfExists( Path fileToMove, Path directory )
832 throws ProxyException
834 if ( fileToMove != null && Files.exists(fileToMove) )
836 Path newLocation = directory.resolve(fileToMove.getFileName());
837 moveTempToTarget( fileToMove, newLocation );
843 * Quietly transfer the checksum file from the remote repository to the local file.
846 * @param wagon the wagon instance (should already be connected) to use.
847 * @param remoteRepository the remote repository to transfer from.
848 * @param remotePath the remote path to the resource to get.
849 * @param repository the managed repository that will hold the file
850 * @param resource the local file that should contain the downloaded contents
851 * @param tmpDirectory the temporary directory to download to
852 * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
853 * @throws ProxyException if copying the downloaded file into place did not succeed.
855 private void transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
856 ManagedRepositoryContent repository, Path resource, Path tmpDirectory, String ext,
858 throws ProxyException
860 String url = remoteRepository.getURL().getUrl() + remotePath + ext;
862 // Transfer checksum does not use the policy.
863 if ( urlFailureCache.hasFailedBefore( url ) )
870 transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile );
871 log.debug( "Checksum {} Downloaded: {} to move to {}", url, destFile, resource );
873 catch ( NotFoundException e )
875 urlFailureCache.cacheFailure( url );
876 log.debug( "Transfer failed, checksum not found: {}", url );
877 // Consume it, do not pass this on.
879 catch ( NotModifiedException e )
881 log.debug( "Transfer skipped, checksum not modified: {}", url );
882 // Consume it, do not pass this on.
884 catch ( ProxyException e )
886 urlFailureCache.cacheFailure( url );
887 log.warn( "Transfer failed on checksum: {} : {}", url, e.getMessage(), e );
888 // Critical issue, pass it on.
894 * Perform the transfer of the remote file to the local file specified.
896 * @param wagon the wagon instance to use.
897 * @param remoteRepository the remote repository to use
898 * @param remotePath the remote path to attempt to get
899 * @param repository the managed repository that will hold the file
900 * @param origFile the local file to save to
901 * @throws ProxyException if there was a problem moving the downloaded file into place.
903 private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
904 ManagedRepositoryContent repository, Path origFile, Path destFile )
905 throws ProxyException
907 assert ( remotePath != null );
909 // Transfer the file.
912 boolean success = false;
914 if ( !Files.exists(origFile))
916 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
917 wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile.toFile() );
920 // You wouldn't get here on failure, a WagonException would have been thrown.
921 log.debug( "Downloaded successfully." );
925 log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() );
928 success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile.toFile(),
929 Files.getLastModifiedTime(origFile).toMillis());
931 catch ( IOException e )
933 throw new ProxyException( "Failed to the modification time of "+origFile.toAbsolutePath() );
937 throw new NotModifiedException(
938 "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath() );
941 if ( Files.exists(destFile))
943 log.debug( "Downloaded successfully." );
947 catch ( ResourceDoesNotExistException e )
949 throw new NotFoundException(
950 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
953 catch ( WagonException e )
955 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
958 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
959 if ( e.getCause() != null )
961 msg += " (cause: " + e.getCause() + ")";
963 throw new ProxyException( msg, e );
968 * Apply the policies.
970 * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
971 * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy
973 * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, Path)}
975 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, Path)})
976 * @throws PolicyViolationException
978 private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
979 Properties request, Path localFile )
980 throws PolicyViolationException
982 for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
984 // olamy with spring rolehint is now downloadPolicy#hint
985 // so substring after last # to get the hint as with plexus
986 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
987 DownloadPolicy policy = entry.getValue();
988 String defaultSetting = policy.getDefaultOption();
990 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
992 log.debug( "Applying [{}] policy with [{}]", key, setting );
995 policy.applyPolicy( setting, request, localFile );
997 catch ( PolicyConfigurationException e )
999 log.error( e.getMessage(), e );
1004 private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
1005 Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
1006 Path localFile, Exception exception, Map<String, Exception> previousExceptions )
1007 throws ProxyDownloadException
1009 boolean process = true;
1010 for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
1013 // olamy with spring rolehint is now downloadPolicy#hint
1014 // so substring after last # to get the hint as with plexus
1015 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1016 DownloadErrorPolicy policy = entry.getValue();
1017 String defaultSetting = policy.getDefaultOption();
1018 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1020 log.debug( "Applying [{}] policy with [{}]", key, setting );
1023 // all policies must approve the exception, any can cancel
1024 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
1030 catch ( PolicyConfigurationException e )
1032 log.error( e.getMessage(), e );
1038 // if the exception was queued, don't throw it
1039 if ( !previousExceptions.containsKey( content.getId() ) )
1041 throw new ProxyDownloadException(
1042 "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
1043 content.getId(), exception );
1048 // if the exception was queued, but cancelled, remove it
1049 previousExceptions.remove( content.getId() );
1053 "Transfer error from repository {} for artifact {} , continuing to next repository. Error message: {}",
1054 content.getRepository().getId(), Keys.toKey( artifact ), exception.getMessage() );
1055 log.debug( "Full stack trace", exception );
1059 * Creates a working directory
1062 * @return file location of working directory
1064 private Path createWorkingDirectory( ManagedRepositoryContent repository )
1068 return Files.createTempDirectory( "temp" );
1070 catch ( IOException e )
1072 throw new RuntimeException( e.getMessage(), e );
1078 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
1081 * @param temp The completed download file
1082 * @param target The final location of the downloaded file
1083 * @throws ProxyException when the temp file cannot replace the target file
1085 private void moveTempToTarget( Path temp, Path target )
1086 throws ProxyException
1092 lock = fileLockManager.writeFileLock( target.toFile() );
1093 if ( lock.getFile().exists() && !lock.getFile().delete() )
1095 throw new ProxyException( "Unable to overwrite existing target file: " + target.toAbsolutePath() );
1098 lock.getFile().getParentFile().mkdirs();
1102 Files.move(temp, lock.getFile().toPath() );
1104 catch ( IOException e )
1106 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1110 Files.copy( temp, lock.getFile().toPath() );
1112 catch ( IOException e2 )
1114 if ( lock.getFile().exists() )
1116 log.debug( "Tried to copy file {} to {} but file with this name already exists.",
1117 temp.getFileName(), lock.getFile().getAbsolutePath() );
1121 throw new ProxyException(
1122 "Cannot copy tmp file " + temp.toAbsolutePath() + " to its final location", e2 );
1127 org.apache.archiva.common.utils.FileUtils.deleteQuietly( temp );
1132 catch ( FileLockException | FileLockTimeoutException e )
1134 throw new ProxyException( e.getMessage(), e );
1139 * Using wagon, connect to the remote repository.
1141 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1142 * @param wagon the wagon instance to establish the connection on.
1143 * @param remoteRepository the remote repository to connect to.
1144 * @return true if the connection was successful. false if not connected.
1146 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1147 RemoteRepositoryContent remoteRepository )
1149 boolean connected = false;
1151 final ProxyInfo networkProxy =
1152 connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1154 if ( log.isDebugEnabled() )
1156 if ( networkProxy != null )
1158 // TODO: move to proxyInfo.toString()
1159 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1160 + " to connect to remote repository " + remoteRepository.getURL();
1161 if ( networkProxy.getNonProxyHosts() != null )
1163 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1165 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1167 msg += "; as user: " + networkProxy.getUserName();
1173 AuthenticationInfo authInfo = null;
1174 String username = remoteRepository.getRepository().getUserName();
1175 String password = remoteRepository.getRepository().getPassword();
1177 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1179 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1180 authInfo = new AuthenticationInfo();
1181 authInfo.setUserName( username );
1182 authInfo.setPassword( password );
1185 // Convert seconds to milliseconds
1186 long timeoutInMilliseconds = TimeUnit.MILLISECONDS.convert( remoteRepository.getRepository().getTimeout(), //
1189 // Set timeout read and connect
1190 // FIXME olamy having 2 config values
1191 wagon.setReadTimeout( (int) timeoutInMilliseconds );
1192 wagon.setTimeout( (int) timeoutInMilliseconds );
1196 Repository wagonRepository =
1197 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1198 wagon.connect( wagonRepository, authInfo, networkProxy );
1201 catch ( ConnectionException | AuthenticationException e )
1203 log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1211 * Tests whitelist and blacklist patterns against path.
1213 * @param path the path to test.
1214 * @param patterns the list of patterns to check.
1215 * @return true if the path matches at least 1 pattern in the provided patterns list.
1217 private boolean matchesPattern( String path, List<String> patterns )
1219 if ( CollectionUtils.isEmpty( patterns ) )
1224 if ( !path.startsWith( "/" ) )
1229 for ( String pattern : patterns )
1231 if ( !pattern.startsWith( "/" ) )
1233 pattern = "/" + pattern;
1236 if ( SelectorUtils.matchPath( pattern, path, false ) )
1246 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1249 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1252 if ( !this.proxyConnectorMap.containsKey( repository.getId() ) )
1254 return Collections.emptyList();
1256 List<ProxyConnector> ret = new ArrayList<>( this.proxyConnectorMap.get( repository.getId() ) );
1258 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1264 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1266 if ( ConfigurationNames.isNetworkProxy( propertyName ) //
1267 || ConfigurationNames.isManagedRepositories( propertyName ) //
1268 || ConfigurationNames.isRemoteRepositories( propertyName ) //
1269 || ConfigurationNames.isProxyConnector( propertyName ) ) //
1271 initConnectorsAndNetworkProxies();
1275 protected String addParameters( String path, RemoteRepository remoteRepository )
1277 if ( remoteRepository.getExtraParameters().isEmpty() )
1282 boolean question = false;
1284 StringBuilder res = new StringBuilder( path == null ? "" : path );
1286 for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1290 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1294 return res.toString();
1299 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1304 public ArchivaConfiguration getArchivaConfiguration()
1306 return archivaConfiguration;
1309 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1311 this.archivaConfiguration = archivaConfiguration;
1314 public RepositoryContentFactory getRepositoryFactory()
1316 return repositoryFactory;
1319 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1321 this.repositoryFactory = repositoryFactory;
1324 public MetadataTools getMetadataTools()
1326 return metadataTools;
1329 public void setMetadataTools( MetadataTools metadataTools )
1331 this.metadataTools = metadataTools;
1334 public UrlFailureCache getUrlFailureCache()
1336 return urlFailureCache;
1339 public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1341 this.urlFailureCache = urlFailureCache;
1344 public WagonFactory getWagonFactory()
1346 return wagonFactory;
1349 public void setWagonFactory( WagonFactory wagonFactory )
1351 this.wagonFactory = wagonFactory;
1354 public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1356 return preDownloadPolicies;
1359 public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1361 this.preDownloadPolicies = preDownloadPolicies;
1364 public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1366 return postDownloadPolicies;
1369 public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1371 this.postDownloadPolicies = postDownloadPolicies;
1374 public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1376 return downloadErrorPolicies;
1379 public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1381 this.downloadErrorPolicies = downloadErrorPolicies;