1 package org.apache.archiva.proxy;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
22 import com.google.common.io.Files;
23 import org.apache.archiva.admin.model.RepositoryAdminException;
24 import org.apache.archiva.admin.model.beans.NetworkProxy;
25 import org.apache.archiva.admin.model.beans.ProxyConnectorRuleType;
26 import org.apache.archiva.admin.model.beans.RemoteRepository;
27 import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin;
28 import org.apache.archiva.common.filelock.FileLockException;
29 import org.apache.archiva.common.filelock.FileLockManager;
30 import org.apache.archiva.common.filelock.FileLockTimeoutException;
31 import org.apache.archiva.common.filelock.Lock;
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.RepositoryProxyConnectors;
54 import org.apache.archiva.redback.components.registry.Registry;
55 import org.apache.archiva.redback.components.registry.RegistryListener;
56 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
57 import org.apache.archiva.repository.ManagedRepositoryContent;
58 import org.apache.archiva.repository.RemoteRepositoryContent;
59 import org.apache.archiva.repository.RepositoryContentFactory;
60 import org.apache.archiva.repository.RepositoryException;
61 import org.apache.archiva.repository.RepositoryNotFoundException;
62 import org.apache.archiva.repository.metadata.MetadataTools;
63 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
64 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
65 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
66 import org.apache.commons.collections.CollectionUtils;
67 import org.apache.commons.io.FileUtils;
68 import org.apache.commons.io.FilenameUtils;
69 import org.apache.commons.lang.StringUtils;
70 import org.apache.commons.lang.SystemUtils;
71 import org.apache.maven.wagon.ConnectionException;
72 import org.apache.maven.wagon.ResourceDoesNotExistException;
73 import org.apache.maven.wagon.Wagon;
74 import org.apache.maven.wagon.WagonException;
75 import org.apache.maven.wagon.authentication.AuthenticationException;
76 import org.apache.maven.wagon.authentication.AuthenticationInfo;
77 import org.apache.maven.wagon.proxy.ProxyInfo;
78 import org.apache.maven.wagon.repository.Repository;
79 import org.apache.tools.ant.types.selectors.SelectorUtils;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
82 import org.slf4j.MarkerFactory;
83 import org.springframework.stereotype.Service;
85 import javax.annotation.PostConstruct;
86 import javax.inject.Inject;
87 import javax.inject.Named;
89 import java.io.IOException;
90 import java.io.RandomAccessFile;
91 import java.util.ArrayList;
92 import java.util.Collections;
93 import java.util.HashMap;
94 import java.util.LinkedHashMap;
95 import java.util.List;
97 import java.util.Map.Entry;
98 import java.util.Properties;
99 import java.util.concurrent.ConcurrentHashMap;
102 * DefaultRepositoryProxyConnectors
104 * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
105 * your average brown onion
107 @Service("repositoryProxyConnectors#default")
108 public class DefaultRepositoryProxyConnectors
109 implements RepositoryProxyConnectors, RegistryListener
111 private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class );
114 @Named(value = "archivaConfiguration#default")
115 private ArchivaConfiguration archivaConfiguration;
118 @Named(value = "repositoryContentFactory#default")
119 private RepositoryContentFactory repositoryFactory;
122 @Named(value = "metadataTools#default")
123 private MetadataTools metadataTools;
126 private Map<String, PreDownloadPolicy> preDownloadPolicies;
129 private Map<String, PostDownloadPolicy> postDownloadPolicies;
132 private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
135 private UrlFailureCache urlFailureCache;
137 private Map<String, List<ProxyConnector>> proxyConnectorMap = new HashMap<>();
139 private Map<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<String, ProxyInfo>();
142 private WagonFactory wagonFactory;
145 @Named(value = "archivaTaskScheduler#repository")
146 private ArchivaTaskScheduler scheduler;
149 private NetworkProxyAdmin networkProxyAdmin;
152 @Named( value = "fileLockManager#default" )
153 private FileLockManager fileLockManager;
156 public void initialize()
158 initConnectorsAndNetworkProxies();
159 archivaConfiguration.addChangeListener( this );
163 @SuppressWarnings("unchecked")
164 private void initConnectorsAndNetworkProxies()
167 ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
168 this.proxyConnectorMap.clear();
170 Configuration configuration = archivaConfiguration.getConfiguration();
172 List<ProxyConnectorRuleConfiguration> allProxyConnectorRuleConfigurations =
173 configuration.getProxyConnectorRuleConfigurations();
175 List<ProxyConnectorConfiguration> proxyConfigs = configuration.getProxyConnectors();
176 for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
178 String key = proxyConfig.getSourceRepoId();
182 // Create connector object.
183 ProxyConnector connector = new ProxyConnector();
185 connector.setSourceRepository(
186 repositoryFactory.getManagedRepositoryContent( proxyConfig.getSourceRepoId() ) );
187 connector.setTargetRepository(
188 repositoryFactory.getRemoteRepositoryContent( proxyConfig.getTargetRepoId() ) );
190 connector.setProxyId( proxyConfig.getProxyId() );
191 connector.setPolicies( proxyConfig.getPolicies() );
192 connector.setOrder( proxyConfig.getOrder() );
193 connector.setDisabled( proxyConfig.isDisabled() );
195 // Copy any blacklist patterns.
196 List<String> blacklist = new ArrayList<>( 0 );
197 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
199 blacklist.addAll( proxyConfig.getBlackListPatterns() );
201 connector.setBlacklist( blacklist );
203 // Copy any whitelist patterns.
204 List<String> whitelist = new ArrayList<>( 0 );
205 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
207 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
209 connector.setWhitelist( whitelist );
211 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
212 findProxyConnectorRules( connector.getSourceRepository().getId(),
213 connector.getTargetRepository().getId(),
214 allProxyConnectorRuleConfigurations );
216 if ( !proxyConnectorRuleConfigurations.isEmpty() )
218 for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : proxyConnectorRuleConfigurations )
220 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
221 ProxyConnectorRuleType.BLACK_LIST.getRuleType() ) )
223 connector.getBlacklist().add( proxyConnectorRuleConfiguration.getPattern() );
226 if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
227 ProxyConnectorRuleType.WHITE_LIST.getRuleType() ) )
229 connector.getWhitelist().add( proxyConnectorRuleConfiguration.getPattern() );
234 // Get other connectors
235 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
236 if ( connectors == null )
238 // Create if we are the first.
239 connectors = new ArrayList<>( 1 );
242 // Add the connector.
243 connectors.add( connector );
245 // Ensure the list is sorted.
246 Collections.sort( connectors, proxyOrderSorter );
248 // Set the key to the list of connectors.
249 this.proxyConnectorMap.put( key, connectors );
251 catch ( RepositoryNotFoundException e )
253 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
255 catch ( RepositoryException e )
257 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
263 this.networkProxyMap.clear();
265 List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
266 for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
268 String key = networkProxyConfig.getId();
270 ProxyInfo proxy = new ProxyInfo();
272 proxy.setType( networkProxyConfig.getProtocol() );
273 proxy.setHost( networkProxyConfig.getHost() );
274 proxy.setPort( networkProxyConfig.getPort() );
275 proxy.setUserName( networkProxyConfig.getUsername() );
276 proxy.setPassword( networkProxyConfig.getPassword() );
278 this.networkProxyMap.put( key, proxy );
283 private List<ProxyConnectorRuleConfiguration> findProxyConnectorRules( String sourceRepository,
284 String targetRepository,
285 List<ProxyConnectorRuleConfiguration> all )
287 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
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;
305 public File fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
306 throws ProxyDownloadException
308 File localFile = toLocalFile( repository, artifact );
310 Properties requestProperties = new Properties();
311 requestProperties.setProperty( "filetype", "artifact" );
312 requestProperties.setProperty( "version", artifact.getVersion() );
313 requestProperties.setProperty( "managedRepositoryId", repository.getId() );
315 List<ProxyConnector> connectors = getProxyConnectors( repository );
316 Map<String, Exception> previousExceptions = new LinkedHashMap<String, Exception>();
317 for ( ProxyConnector connector : connectors )
319 if ( connector.isDisabled() )
324 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
325 requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
327 String targetPath = targetRepository.toPath( artifact );
329 if ( SystemUtils.IS_OS_WINDOWS )
331 // toPath use system PATH_SEPARATOR so on windows url are \ which doesn't work very well :-)
332 targetPath = FilenameUtils.separatorsToUnix( targetPath );
337 File downloadedFile =
338 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
341 if ( fileExists( downloadedFile ) )
343 log.debug( "Successfully transferred: {}", downloadedFile.getAbsolutePath() );
344 return downloadedFile;
347 catch ( NotFoundException e )
349 log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ),
350 targetRepository.getRepository().getId() );
352 catch ( NotModifiedException e )
354 log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ),
355 targetRepository.getRepository().getId() );
357 catch ( ProxyException e )
359 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
360 targetRepository, localFile, e, previousExceptions );
362 catch ( RepositoryAdminException e )
364 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
365 targetRepository, localFile, e, previousExceptions );
369 if ( !previousExceptions.isEmpty() )
371 throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
372 previousExceptions );
375 log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) );
380 public File fetchFromProxies( ManagedRepositoryContent repository, String path )
382 File localFile = new File( repository.getRepoRoot(), path );
384 // no update policies for these paths
385 if ( localFile.exists() )
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 File downloadedFile =
410 transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
413 if ( fileExists( downloadedFile ) )
415 log.debug( "Successfully transferred: {}", downloadedFile.getAbsolutePath() );
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 \"" + targetRepository.getRepository().getId()
436 + "\" for resource " + path + ", continuing to next repository. Error message: {}",
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 );
453 public File fetchMetatadaFromProxies( ManagedRepositoryContent repository, String logicalPath )
455 File localFile = new File( repository.getRepoRoot(), logicalPath );
457 Properties requestProperties = new Properties();
458 requestProperties.setProperty( "filetype", "metadata" );
459 boolean metadataNeedsUpdating = false;
460 long originalTimestamp = getLastModified( localFile );
462 List<ProxyConnector> connectors = getProxyConnectors( repository );
463 for ( ProxyConnector connector : connectors )
465 if ( connector.isDisabled() )
470 RemoteRepositoryContent targetRepository = connector.getTargetRepository();
472 File localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
473 long originalMetadataTimestamp = getLastModified( localRepoFile );
477 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
480 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
482 metadataNeedsUpdating = true;
485 catch ( NotFoundException e )
488 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
489 targetRepository.getRepository().getId(), e );
492 catch ( NotModifiedException e )
495 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
496 targetRepository.getRepository().getId(), e );
499 catch ( ProxyException e )
502 "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
503 targetRepository.getRepository().getId(), logicalPath, e.getMessage() );
504 log.debug( "Full stack trace", e );
506 catch ( RepositoryAdminException e )
509 "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
510 targetRepository.getRepository().getId(), logicalPath, e.getMessage() );
511 log.debug( "Full stack trace", e );
515 if ( hasBeenUpdated( localFile, originalTimestamp ) )
517 metadataNeedsUpdating = true;
520 if ( metadataNeedsUpdating || !localFile.exists() )
524 metadataTools.updateMetadata( repository, logicalPath );
526 catch ( RepositoryMetadataException e )
528 log.warn( "Unable to update metadata {}:{}", localFile.getAbsolutePath(), e.getMessage(), e );
532 if ( fileExists( localFile ) )
542 * @param remoteRepository
549 * @param workingDirectory
551 * @throws ProxyException
552 * @throws NotModifiedException
554 protected void transferResources( ProxyConnector connector, RemoteRepositoryContent remoteRepository, File tmpMd5,
555 File tmpSha1, File tmpResource, String url, String remotePath, File resource,
556 File workingDirectory, ManagedRepositoryContent repository )
557 throws ProxyException, NotModifiedException, RepositoryAdminException
562 RepositoryURL repoUrl = remoteRepository.getURL();
563 String protocol = repoUrl.getProtocol();
564 NetworkProxy networkProxy = null;
565 if ( StringUtils.isNotBlank( connector.getProxyId() ) )
567 networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() );
569 WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest( "wagon#" + protocol,
570 remoteRepository.getRepository().getExtraHeaders() ).networkProxy(
572 wagon = wagonFactory.getWagon( wagonFactoryRequest );
575 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
580 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
583 boolean connected = connectToRepository( connector, wagon, remoteRepository );
586 transferArtifact( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
589 // TODO: these should be used to validate the download based on the policies, not always downloaded
591 // save on connections since md5 is rarely used
592 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".sha1",
594 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".md5",
598 catch ( NotFoundException e )
600 urlFailureCache.cacheFailure( url );
603 catch ( NotModifiedException e )
605 // Do not cache url here.
608 catch ( ProxyException e )
610 urlFailureCache.cacheFailure( url );
613 catch ( WagonFactoryException e )
615 throw new ProxyException( e.getMessage(), e );
625 catch ( ConnectionException e )
627 log.warn( "Unable to disconnect wagon.", e );
633 private void transferArtifact( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
634 ManagedRepositoryContent repository, File resource, File tmpDirectory,
636 throws ProxyException
638 transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, destFile );
641 private long getLastModified( File file )
643 if ( !file.exists() || !file.isFile() )
648 return file.lastModified();
651 private boolean hasBeenUpdated( File file, long originalLastModified )
653 if ( !file.exists() || !file.isFile() )
658 long currentLastModified = getLastModified( file );
659 return ( currentLastModified > originalLastModified );
662 private File toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
665 String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
666 return new File( repository.getRepoRoot(), repoPath );
670 * Test if the provided ManagedRepositoryContent has any proxies configured for it.
672 public boolean hasProxies( ManagedRepositoryContent repository )
674 synchronized ( this.proxyConnectorMap )
676 return this.proxyConnectorMap.containsKey( repository.getId() );
680 private File toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
682 return repository.toFile( artifact );
686 * Simple method to test if the file exists on the local disk.
688 * @param file the file to test. (may be null)
689 * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
691 private boolean fileExists( File file )
698 if ( !file.exists() )
703 if ( !file.isFile() )
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 File transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
728 ManagedRepositoryContent repository, File 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 File workingDirectory = createWorkingDirectory( repository );
779 File tmpResource = new File( workingDirectory, resource.getName() );
780 File tmpMd5 = new File( workingDirectory, resource.getName() + ".md5" );
781 File tmpSha1 = new File( workingDirectory, resource.getName() + ".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.getAbsolutePath().intern() )
808 File directory = resource.getParentFile();
809 moveFileIfExists( tmpMd5, directory );
810 moveFileIfExists( tmpSha1, directory );
811 moveFileIfExists( tmpResource, directory );
817 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, File localFile )
832 RepositoryTask task = new RepositoryTask();
833 task.setRepositoryId( repositoryId );
834 task.setResourceFile( localFile );
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 ['" + localFile.getName()
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( File fileToMove, File directory )
856 throws ProxyException
858 if ( fileToMove != null && fileToMove.exists() )
860 File newLocation = new File( directory, fileToMove.getName() );
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, File resource, File 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, File origFile, File destFile )
929 throws ProxyException
931 assert ( remotePath != null );
933 // Transfer the file.
936 boolean success = false;
938 if ( !origFile.exists() )
940 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
941 wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile );
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() );
950 success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile,
951 origFile.lastModified() );
954 throw new NotModifiedException(
955 "Not downloaded, as local file is newer than remote side: " + origFile.getAbsolutePath() );
958 if ( destFile.exists() )
960 log.debug( "Downloaded successfully." );
964 catch ( ResourceDoesNotExistException e )
966 throw new NotFoundException(
967 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
970 catch ( WagonException e )
972 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
975 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
976 if ( e.getCause() != null )
978 msg += " (cause: " + e.getCause() + ")";
980 throw new ProxyException( msg, e );
985 * Apply the policies.
987 * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
988 * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy
990 * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}
992 * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)})
993 * @throws PolicyViolationException
995 private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
996 Properties request, File localFile )
997 throws PolicyViolationException
999 for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
1001 // olamy with spring rolehint is now downloadPolicy#hint
1002 // so substring after last # to get the hint as with plexus
1003 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1004 DownloadPolicy policy = entry.getValue();
1005 String defaultSetting = policy.getDefaultOption();
1007 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1009 log.debug( "Applying [{}] policy with [{}]", key, setting );
1012 policy.applyPolicy( setting, request, localFile );
1014 catch ( PolicyConfigurationException e )
1016 log.error( e.getMessage(), e );
1021 private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
1022 Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
1023 File localFile, Exception exception, Map<String, Exception> previousExceptions )
1024 throws ProxyDownloadException
1026 boolean process = true;
1027 for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
1030 // olamy with spring rolehint is now downloadPolicy#hint
1031 // so substring after last # to get the hint as with plexus
1032 String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1033 DownloadErrorPolicy policy = entry.getValue();
1034 String defaultSetting = policy.getDefaultOption();
1035 String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1037 log.debug( "Applying [{}] policy with [{}]", key, setting );
1040 // all policies must approve the exception, any can cancel
1041 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
1047 catch ( PolicyConfigurationException e )
1049 log.error( e.getMessage(), e );
1055 // if the exception was queued, don't throw it
1056 if ( !previousExceptions.containsKey( content.getId() ) )
1058 throw new ProxyDownloadException(
1059 "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
1060 content.getId(), exception );
1065 // if the exception was queued, but cancelled, remove it
1066 previousExceptions.remove( content.getId() );
1070 "Transfer error from repository {} for artifact {} , continuing to next repository. Error message: {}",
1071 content.getRepository().getId(), Keys.toKey( artifact ), exception.getMessage() );
1072 log.debug( "Full stack trace", exception );
1076 * Creates a working directory
1079 * @return file location of working directory
1081 private File createWorkingDirectory( ManagedRepositoryContent repository )
1083 return Files.createTempDir();
1087 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
1090 * @param temp The completed download file
1091 * @param target The final location of the downloaded file
1092 * @throws ProxyException when the temp file cannot replace the target file
1094 private void moveTempToTarget( File temp, File target )
1095 throws ProxyException
1098 // TODO file lock library
1102 lock = fileLockManager.writeFileLock( target );
1103 if ( lock.getFile().exists() && !lock.getFile().delete() )
1105 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
1108 lock.getFile().getParentFile().mkdirs();
1110 if ( !temp.renameTo( lock.getFile() ) )
1112 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1116 FileUtils.copyFile( temp, lock.getFile() );
1118 catch ( IOException e )
1120 if ( lock.getFile().exists() )
1122 log.debug( "Tried to copy file {} to {} but file with this name already exists.",
1123 temp.getName(), lock.getFile().getAbsolutePath() );
1127 throw new ProxyException(
1128 "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e );
1133 FileUtils.deleteQuietly( temp );
1136 } catch( FileLockException e)
1138 throw new ProxyException( e.getMessage(), e );
1139 } catch (FileLockTimeoutException e)
1141 throw new ProxyException( e.getMessage(), e );
1146 * Using wagon, connect to the remote repository.
1148 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
1149 * @param wagon the wagon instance to establish the connection on.
1150 * @param remoteRepository the remote repository to connect to.
1151 * @return true if the connection was successful. false if not connected.
1153 private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1154 RemoteRepositoryContent remoteRepository )
1156 boolean connected = false;
1158 final ProxyInfo networkProxy =
1159 connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1161 if ( log.isDebugEnabled() )
1163 if ( networkProxy != null )
1165 // TODO: move to proxyInfo.toString()
1166 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1167 + " to connect to remote repository " + remoteRepository.getURL();
1168 if ( networkProxy.getNonProxyHosts() != null )
1170 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1172 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1174 msg += "; as user: " + networkProxy.getUserName();
1180 AuthenticationInfo authInfo = null;
1181 String username = remoteRepository.getRepository().getUserName();
1182 String password = remoteRepository.getRepository().getPassword();
1184 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1186 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1187 authInfo = new AuthenticationInfo();
1188 authInfo.setUserName( username );
1189 authInfo.setPassword( password );
1192 // Convert seconds to milliseconds
1193 int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
1195 // Set timeout read and connect
1196 // FIXME olamy having 2 config values
1197 wagon.setReadTimeout( timeoutInMilliseconds );
1198 wagon.setTimeout( timeoutInMilliseconds );
1202 Repository wagonRepository =
1203 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1204 wagon.connect( wagonRepository, authInfo, networkProxy );
1207 catch ( ConnectionException e )
1209 log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1212 catch ( AuthenticationException e )
1214 log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1222 * Tests whitelist and blacklist patterns against path.
1224 * @param path the path to test.
1225 * @param patterns the list of patterns to check.
1226 * @return true if the path matches at least 1 pattern in the provided patterns list.
1228 private boolean matchesPattern( String path, List<String> patterns )
1230 if ( CollectionUtils.isEmpty( patterns ) )
1235 if ( !path.startsWith( "/" ) )
1240 for ( String pattern : patterns )
1242 if ( !pattern.startsWith( "/" ) )
1244 pattern = "/" + pattern;
1247 if ( SelectorUtils.matchPath( pattern, path, false ) )
1257 * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1259 public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1261 synchronized ( this.proxyConnectorMap )
1263 List<ProxyConnector> ret = this.proxyConnectorMap.get( repository.getId() );
1266 return Collections.emptyList();
1269 Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1274 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1276 if ( ConfigurationNames.isNetworkProxy( propertyName ) || ConfigurationNames.isManagedRepositories(
1277 propertyName ) || ConfigurationNames.isRemoteRepositories( propertyName )
1278 || ConfigurationNames.isProxyConnector( propertyName ) )
1280 initConnectorsAndNetworkProxies();
1284 protected String addParameters( String path, RemoteRepository remoteRepository )
1286 if ( remoteRepository.getExtraParameters().isEmpty() )
1291 boolean question = false;
1293 StringBuilder res = new StringBuilder( path == null ? "" : path );
1295 for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1299 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1303 return res.toString();
1307 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1312 public ArchivaConfiguration getArchivaConfiguration()
1314 return archivaConfiguration;
1317 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1319 this.archivaConfiguration = archivaConfiguration;
1322 public RepositoryContentFactory getRepositoryFactory()
1324 return repositoryFactory;
1327 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1329 this.repositoryFactory = repositoryFactory;
1332 public MetadataTools getMetadataTools()
1334 return metadataTools;
1337 public void setMetadataTools( MetadataTools metadataTools )
1339 this.metadataTools = metadataTools;
1342 public UrlFailureCache getUrlFailureCache()
1344 return urlFailureCache;
1347 public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1349 this.urlFailureCache = urlFailureCache;
1352 public WagonFactory getWagonFactory()
1354 return wagonFactory;
1357 public void setWagonFactory( WagonFactory wagonFactory )
1359 this.wagonFactory = wagonFactory;
1362 public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1364 return preDownloadPolicies;
1367 public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1369 this.preDownloadPolicies = preDownloadPolicies;
1372 public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1374 return postDownloadPolicies;
1377 public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1379 this.postDownloadPolicies = postDownloadPolicies;
1382 public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1384 return downloadErrorPolicies;
1387 public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1389 this.downloadErrorPolicies = downloadErrorPolicies;