package org.apache.archiva.proxy; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.archiva.admin.model.RepositoryAdminException; import org.apache.archiva.admin.model.beans.NetworkProxy; import org.apache.archiva.admin.model.beans.ProxyConnectorRuleType; import org.apache.archiva.admin.model.beans.RemoteRepository; import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin; import org.apache.archiva.common.filelock.FileLockException; import org.apache.archiva.common.filelock.FileLockManager; import org.apache.archiva.common.filelock.FileLockTimeoutException; import org.apache.archiva.common.filelock.Lock; import org.apache.archiva.configuration.ArchivaConfiguration; import org.apache.archiva.configuration.Configuration; import org.apache.archiva.configuration.ConfigurationNames; import org.apache.archiva.configuration.NetworkProxyConfiguration; import org.apache.archiva.configuration.ProxyConnectorConfiguration; import org.apache.archiva.configuration.ProxyConnectorRuleConfiguration; import org.apache.archiva.model.ArtifactReference; import org.apache.archiva.model.Keys; import org.apache.archiva.model.RepositoryURL; import org.apache.archiva.policies.DownloadErrorPolicy; import org.apache.archiva.policies.DownloadPolicy; import org.apache.archiva.policies.PolicyConfigurationException; import org.apache.archiva.policies.PolicyViolationException; import org.apache.archiva.policies.PostDownloadPolicy; import org.apache.archiva.policies.PreDownloadPolicy; import org.apache.archiva.policies.ProxyDownloadException; import org.apache.archiva.policies.urlcache.UrlFailureCache; import org.apache.archiva.proxy.common.WagonFactory; import org.apache.archiva.proxy.common.WagonFactoryException; import org.apache.archiva.proxy.common.WagonFactoryRequest; import org.apache.archiva.proxy.model.ProxyConnector; import org.apache.archiva.proxy.model.ProxyFetchResult; import org.apache.archiva.proxy.model.RepositoryProxyConnectors; import org.apache.archiva.redback.components.registry.Registry; import org.apache.archiva.redback.components.registry.RegistryListener; import org.apache.archiva.redback.components.taskqueue.TaskQueueException; import org.apache.archiva.repository.ManagedRepositoryContent; import org.apache.archiva.repository.RemoteRepositoryContent; import org.apache.archiva.repository.RepositoryContentFactory; import org.apache.archiva.repository.RepositoryException; import org.apache.archiva.repository.RepositoryNotFoundException; import org.apache.archiva.repository.metadata.MetadataTools; import org.apache.archiva.repository.metadata.RepositoryMetadataException; import org.apache.archiva.scheduler.ArchivaTaskScheduler; import org.apache.archiva.scheduler.repository.model.RepositoryTask; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.apache.maven.wagon.ConnectionException; import org.apache.maven.wagon.ResourceDoesNotExistException; import org.apache.maven.wagon.Wagon; import org.apache.maven.wagon.WagonException; import org.apache.maven.wagon.authentication.AuthenticationException; import org.apache.maven.wagon.authentication.AuthenticationInfo; import org.apache.maven.wagon.proxy.ProxyInfo; import org.apache.maven.wagon.repository.Repository; import org.apache.tools.ant.types.selectors.SelectorUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MarkerFactory; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Named; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; /** * DefaultRepositoryProxyConnectors * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than * your average brown onion */ @Service("repositoryProxyConnectors#default") public class DefaultRepositoryProxyConnectors implements RepositoryProxyConnectors, RegistryListener { private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class ); @Inject @Named(value = "archivaConfiguration#default") private ArchivaConfiguration archivaConfiguration; @Inject @Named(value = "repositoryContentFactory#default") private RepositoryContentFactory repositoryFactory; @Inject @Named(value = "metadataTools#default") private MetadataTools metadataTools; @Inject private Map<String, PreDownloadPolicy> preDownloadPolicies; @Inject private Map<String, PostDownloadPolicy> postDownloadPolicies; @Inject private Map<String, DownloadErrorPolicy> downloadErrorPolicies; @Inject private UrlFailureCache urlFailureCache; private ConcurrentMap<String, List<ProxyConnector>> proxyConnectorMap = new ConcurrentHashMap<>(); private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>(); @Inject private WagonFactory wagonFactory; @Inject @Named(value = "archivaTaskScheduler#repository") private ArchivaTaskScheduler scheduler; @Inject private NetworkProxyAdmin networkProxyAdmin; @Inject @Named(value = "fileLockManager#default") private FileLockManager fileLockManager; @PostConstruct public void initialize() { initConnectorsAndNetworkProxies(); archivaConfiguration.addChangeListener( this ); } @SuppressWarnings("unchecked") private void initConnectorsAndNetworkProxies() { ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator(); this.proxyConnectorMap.clear(); Configuration configuration = archivaConfiguration.getConfiguration(); List<ProxyConnectorRuleConfiguration> allProxyConnectorRuleConfigurations = configuration.getProxyConnectorRuleConfigurations(); List<ProxyConnectorConfiguration> proxyConfigs = configuration.getProxyConnectors(); for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs ) { String key = proxyConfig.getSourceRepoId(); try { // Create connector object. ProxyConnector connector = new ProxyConnector(); connector.setSourceRepository( repositoryFactory.getManagedRepositoryContent( proxyConfig.getSourceRepoId() ) ); connector.setTargetRepository( repositoryFactory.getRemoteRepositoryContent( proxyConfig.getTargetRepoId() ) ); connector.setProxyId( proxyConfig.getProxyId() ); connector.setPolicies( proxyConfig.getPolicies() ); connector.setOrder( proxyConfig.getOrder() ); connector.setDisabled( proxyConfig.isDisabled() ); // Copy any blacklist patterns. List<String> blacklist = new ArrayList<>( 0 ); if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) ) { blacklist.addAll( proxyConfig.getBlackListPatterns() ); } connector.setBlacklist( blacklist ); // Copy any whitelist patterns. List<String> whitelist = new ArrayList<>( 0 ); if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) ) { whitelist.addAll( proxyConfig.getWhiteListPatterns() ); } connector.setWhitelist( whitelist ); List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations = findProxyConnectorRules( connector.getSourceRepository().getId(), connector.getTargetRepository().getId(), allProxyConnectorRuleConfigurations ); if ( !proxyConnectorRuleConfigurations.isEmpty() ) { for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : proxyConnectorRuleConfigurations ) { if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(), ProxyConnectorRuleType.BLACK_LIST.getRuleType() ) ) { connector.getBlacklist().add( proxyConnectorRuleConfiguration.getPattern() ); } if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(), ProxyConnectorRuleType.WHITE_LIST.getRuleType() ) ) { connector.getWhitelist().add( proxyConnectorRuleConfiguration.getPattern() ); } } } // Get other connectors List<ProxyConnector> connectors = this.proxyConnectorMap.get( key ); if ( connectors == null ) { // Create if we are the first. connectors = new ArrayList<>( 1 ); } // Add the connector. connectors.add( connector ); // Ensure the list is sorted. Collections.sort( connectors, proxyOrderSorter ); // Set the key to the list of connectors. this.proxyConnectorMap.put( key, connectors ); } catch ( RepositoryNotFoundException e ) { log.warn( "Unable to use proxy connector: {}", e.getMessage(), e ); } catch ( RepositoryException e ) { log.warn( "Unable to use proxy connector: {}", e.getMessage(), e ); } } this.networkProxyMap.clear(); List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies(); for ( NetworkProxyConfiguration networkProxyConfig : networkProxies ) { String key = networkProxyConfig.getId(); ProxyInfo proxy = new ProxyInfo(); proxy.setType( networkProxyConfig.getProtocol() ); proxy.setHost( networkProxyConfig.getHost() ); proxy.setPort( networkProxyConfig.getPort() ); proxy.setUserName( networkProxyConfig.getUsername() ); proxy.setPassword( networkProxyConfig.getPassword() ); this.networkProxyMap.put( key, proxy ); } } private List<ProxyConnectorRuleConfiguration> findProxyConnectorRules( String sourceRepository, String targetRepository, List<ProxyConnectorRuleConfiguration> all ) { List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations = new ArrayList<>(); for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : all ) { for ( ProxyConnectorConfiguration proxyConnector : proxyConnectorRuleConfiguration.getProxyConnectors() ) { if ( StringUtils.equals( sourceRepository, proxyConnector.getSourceRepoId() ) && StringUtils.equals( targetRepository, proxyConnector.getTargetRepoId() ) ) { proxyConnectorRuleConfigurations.add( proxyConnectorRuleConfiguration ); } } } return proxyConnectorRuleConfigurations; } @Override public File fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact ) throws ProxyDownloadException { File localFile = toLocalFile( repository, artifact ); Properties requestProperties = new Properties(); requestProperties.setProperty( "filetype", "artifact" ); requestProperties.setProperty( "version", artifact.getVersion() ); requestProperties.setProperty( "managedRepositoryId", repository.getId() ); List<ProxyConnector> connectors = getProxyConnectors( repository ); Map<String, Exception> previousExceptions = new LinkedHashMap<>(); for ( ProxyConnector connector : connectors ) { if ( connector.isDisabled() ) { continue; } RemoteRepositoryContent targetRepository = connector.getTargetRepository(); requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() ); String targetPath = targetRepository.toPath( artifact ); if ( SystemUtils.IS_OS_WINDOWS ) { // toPath use system PATH_SEPARATOR so on windows url are \ which doesn't work very well :-) targetPath = FilenameUtils.separatorsToUnix( targetPath ); } try { File downloadedFile = transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties, true ); if ( fileExists( downloadedFile ) ) { log.debug( "Successfully transferred: {}", downloadedFile.getAbsolutePath() ); return downloadedFile; } } catch ( NotFoundException e ) { log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ), targetRepository.getRepository().getId() ); } catch ( NotModifiedException e ) { log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ), targetRepository.getRepository().getId() ); } catch ( ProxyException | RepositoryAdminException e ) { validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact, targetRepository, localFile, e, previousExceptions ); } } if ( !previousExceptions.isEmpty() ) { throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories", previousExceptions ); } log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) ); return null; } @Override public File fetchFromProxies( ManagedRepositoryContent repository, String path ) { File localFile = new File( repository.getRepoRoot(), path ); // no update policies for these paths if ( localFile.exists() ) { return null; } Properties requestProperties = new Properties(); requestProperties.setProperty( "filetype", "resource" ); requestProperties.setProperty( "managedRepositoryId", repository.getId() ); List<ProxyConnector> connectors = getProxyConnectors( repository ); for ( ProxyConnector connector : connectors ) { if ( connector.isDisabled() ) { continue; } RemoteRepositoryContent targetRepository = connector.getTargetRepository(); requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() ); String targetPath = path; try { File downloadedFile = transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties, false ); if ( fileExists( downloadedFile ) ) { log.debug( "Successfully transferred: {}", downloadedFile.getAbsolutePath() ); return downloadedFile; } } catch ( NotFoundException e ) { log.debug( "Resource {} not found on repository \"{}\".", path, targetRepository.getRepository().getId() ); } catch ( NotModifiedException e ) { log.debug( "Resource {} not updated on repository \"{}\".", path, targetRepository.getRepository().getId() ); } catch ( ProxyException e ) { log.warn( "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}", targetRepository.getRepository().getId(), path, e.getMessage() ); log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ), "Transfer error from repository \"" + targetRepository.getRepository().getId() + "\" for resource " + path + ", continuing to next repository. Error message: {}", e.getMessage(), e ); } catch ( RepositoryAdminException e ) { log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ), "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}", targetRepository.getRepository().getId(), path, e.getMessage(), e ); log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ), "Full stack trace", e ); } } log.debug( "Exhausted all target repositories, resource {} not found.", path ); return null; } @Override public ProxyFetchResult fetchMetadataFromProxies( ManagedRepositoryContent repository, String logicalPath ) { File localFile = new File( repository.getRepoRoot(), logicalPath ); Properties requestProperties = new Properties(); requestProperties.setProperty( "filetype", "metadata" ); boolean metadataNeedsUpdating = false; long originalTimestamp = getLastModified( localFile ); List<ProxyConnector> connectors = new ArrayList<>( getProxyConnectors( repository ) ); for ( ProxyConnector connector : connectors ) { if ( connector.isDisabled() ) { continue; } RemoteRepositoryContent targetRepository = connector.getTargetRepository(); File localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath ); long originalMetadataTimestamp = getLastModified( localRepoFile ); try { transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties, true ); if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) ) { metadataNeedsUpdating = true; } } catch ( NotFoundException e ) { log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath, targetRepository.getRepository().getId(), e ); } catch ( NotModifiedException e ) { log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath, targetRepository.getRepository().getId(), e ); } catch ( ProxyException | RepositoryAdminException e ) { log.warn( "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}", targetRepository.getRepository().getId(), logicalPath, e.getMessage() ); log.debug( "Full stack trace", e ); } } if ( hasBeenUpdated( localFile, originalTimestamp ) ) { metadataNeedsUpdating = true; } if ( metadataNeedsUpdating || !localFile.exists() ) { try { metadataTools.updateMetadata( repository, logicalPath ); } catch ( RepositoryMetadataException e ) { log.warn( "Unable to update metadata {}:{}", localFile.getAbsolutePath(), e.getMessage(), e ); } } if ( fileExists( localFile ) ) { return new ProxyFetchResult( localFile, metadataNeedsUpdating ); } return new ProxyFetchResult( null, false ); } /** * @param connector * @param remoteRepository * @param tmpMd5 * @param tmpSha1 * @param tmpResource * @param url * @param remotePath * @param resource * @param workingDirectory * @param repository * @throws ProxyException * @throws NotModifiedException * @throws org.apache.archiva.admin.model.RepositoryAdminException */ protected void transferResources( ProxyConnector connector, RemoteRepositoryContent remoteRepository, File tmpMd5, File tmpSha1, File tmpResource, String url, String remotePath, File resource, File workingDirectory, ManagedRepositoryContent repository ) throws ProxyException, NotModifiedException, RepositoryAdminException { Wagon wagon = null; try { RepositoryURL repoUrl = remoteRepository.getURL(); String protocol = repoUrl.getProtocol(); NetworkProxy networkProxy = null; if ( StringUtils.isNotBlank( connector.getProxyId() ) ) { networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() ); } WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getRepository().getExtraHeaders() ).networkProxy( networkProxy ); wagon = wagonFactory.getWagon( wagonFactoryRequest ); if ( wagon == null ) { throw new ProxyException( "Unsupported target repository protocol: " + protocol ); } if ( wagon == null ) { throw new ProxyException( "Unsupported target repository protocol: " + protocol ); } boolean connected = connectToRepository( connector, wagon, remoteRepository ); if ( connected ) { transferArtifact( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, tmpResource ); // TODO: these should be used to validate the download based on the policies, not always downloaded // to // save on connections since md5 is rarely used transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".sha1", tmpSha1 ); transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".md5", tmpMd5 ); } } catch ( NotFoundException e ) { urlFailureCache.cacheFailure( url ); throw e; } catch ( NotModifiedException e ) { // Do not cache url here. throw e; } catch ( ProxyException e ) { urlFailureCache.cacheFailure( url ); throw e; } catch ( WagonFactoryException e ) { throw new ProxyException( e.getMessage(), e ); } finally { if ( wagon != null ) { try { wagon.disconnect(); } catch ( ConnectionException e ) { log.warn( "Unable to disconnect wagon.", e ); } } } } private void transferArtifact( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath, ManagedRepositoryContent repository, File resource, File tmpDirectory, File destFile ) throws ProxyException { transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, destFile ); } private long getLastModified( File file ) { if ( !file.exists() || !file.isFile() ) { return 0; } return file.lastModified(); } private boolean hasBeenUpdated( File file, long originalLastModified ) { if ( !file.exists() || !file.isFile() ) { return false; } long currentLastModified = getLastModified( file ); return ( currentLastModified > originalLastModified ); } private File toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository, String targetPath ) { String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath ); return new File( repository.getRepoRoot(), repoPath ); } /** * Test if the provided ManagedRepositoryContent has any proxies configured for it. */ @Override public boolean hasProxies( ManagedRepositoryContent repository ) { synchronized ( this.proxyConnectorMap ) { return this.proxyConnectorMap.containsKey( repository.getId() ); } } private File toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact ) { return repository.toFile( artifact ); } /** * Simple method to test if the file exists on the local disk. * * @param file the file to test. (may be null) * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File. */ private boolean fileExists( File file ) { if ( file == null ) { return false; } if ( !file.exists() ) { return false; } return file.isFile(); } /** * Perform the transfer of the file. * * @param connector the connector configuration to use. * @param remoteRepository the remote repository get the resource from. * @param remotePath the path in the remote repository to the resource to get. * @param repository the managed repository that will hold the file * @param resource the local file to place the downloaded resource into * @param requestProperties the request properties to utilize for policy handling. * @param executeConsumers whether to execute the consumers after proxying * @return the local file that was downloaded, or null if not downloaded. * @throws NotFoundException if the file was not found on the remote repository. * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but * the remote resource is not newer than the local File. * @throws ProxyException if transfer was unsuccessful. */ private File transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath, ManagedRepositoryContent repository, File resource, Properties requestProperties, boolean executeConsumers ) throws ProxyException, NotModifiedException, RepositoryAdminException { String url = remoteRepository.getURL().getUrl(); if ( !url.endsWith( "/" ) ) { url = url + "/"; } url = url + remotePath; requestProperties.setProperty( "url", url ); // Is a whitelist defined? if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) ) { // Path must belong to whitelist. if ( !matchesPattern( remotePath, connector.getWhitelist() ) ) { log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).", remotePath, remoteRepository.getRepository().getName() ); return null; } } // Is target path part of blacklist? if ( matchesPattern( remotePath, connector.getBlacklist() ) ) { log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath, remoteRepository.getRepository().getName() ); return null; } // Handle pre-download policy try { validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource ); } catch ( PolicyViolationException e ) { String emsg = "Transfer not attempted on " + url + " : " + e.getMessage(); if ( fileExists( resource ) ) { log.debug( "{} : using already present local file.", emsg ); return resource; } log.debug( emsg ); return null; } File workingDirectory = createWorkingDirectory( repository ); File tmpResource = new File( workingDirectory, resource.getName() ); File tmpMd5 = new File( workingDirectory, resource.getName() + ".md5" ); File tmpSha1 = new File( workingDirectory, resource.getName() + ".sha1" ); try { transferResources( connector, remoteRepository, tmpMd5, tmpSha1, tmpResource, url, remotePath, resource, workingDirectory, repository ); // Handle post-download policies. try { validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource ); } catch ( PolicyViolationException e ) { log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() ); executeConsumers = false; if ( !fileExists( tmpResource ) ) { resource = null; } } if ( resource != null ) { synchronized ( resource.getAbsolutePath().intern() ) { File directory = resource.getParentFile(); moveFileIfExists( tmpMd5, directory ); moveFileIfExists( tmpSha1, directory ); moveFileIfExists( tmpResource, directory ); } } } finally { FileUtils.deleteQuietly( workingDirectory ); } if ( executeConsumers ) { // Just-in-time update of the index and database by executing the consumers for this artifact //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource ); queueRepositoryTask( connector.getSourceRepository().getRepository().getId(), resource ); } return resource; } private void queueRepositoryTask( String repositoryId, File localFile ) { RepositoryTask task = new RepositoryTask(); task.setRepositoryId( repositoryId ); task.setResourceFile( localFile ); task.setUpdateRelatedArtifacts( true ); task.setScanAll( true ); try { scheduler.queueTask( task ); } catch ( TaskQueueException e ) { log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName() + "']." ); } } /** * Moves the file into repository location if it exists * * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file. * @param directory directory to write files to */ private void moveFileIfExists( File fileToMove, File directory ) throws ProxyException { if ( fileToMove != null && fileToMove.exists() ) { File newLocation = new File( directory, fileToMove.getName() ); moveTempToTarget( fileToMove, newLocation ); } } /** * <p> * Quietly transfer the checksum file from the remote repository to the local file. * </p> * * @param wagon the wagon instance (should already be connected) to use. * @param remoteRepository the remote repository to transfer from. * @param remotePath the remote path to the resource to get. * @param repository the managed repository that will hold the file * @param resource the local file that should contain the downloaded contents * @param tmpDirectory the temporary directory to download to * @param ext the type of checksum to transfer (example: ".md5" or ".sha1") * @throws ProxyException if copying the downloaded file into place did not succeed. */ private void transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath, ManagedRepositoryContent repository, File resource, File tmpDirectory, String ext, File destFile ) throws ProxyException { String url = remoteRepository.getURL().getUrl() + remotePath + ext; // Transfer checksum does not use the policy. if ( urlFailureCache.hasFailedBefore( url ) ) { return; } try { transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile ); log.debug( "Checksum {} Downloaded: {} to move to {}", url, destFile, resource ); } catch ( NotFoundException e ) { urlFailureCache.cacheFailure( url ); log.debug( "Transfer failed, checksum not found: {}", url ); // Consume it, do not pass this on. } catch ( NotModifiedException e ) { log.debug( "Transfer skipped, checksum not modified: {}", url ); // Consume it, do not pass this on. } catch ( ProxyException e ) { urlFailureCache.cacheFailure( url ); log.warn( "Transfer failed on checksum: {} : {}", url, e.getMessage(), e ); // Critical issue, pass it on. throw e; } } /** * Perform the transfer of the remote file to the local file specified. * * @param wagon the wagon instance to use. * @param remoteRepository the remote repository to use * @param remotePath the remote path to attempt to get * @param repository the managed repository that will hold the file * @param origFile the local file to save to * @throws ProxyException if there was a problem moving the downloaded file into place. */ private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath, ManagedRepositoryContent repository, File origFile, File destFile ) throws ProxyException { assert ( remotePath != null ); // Transfer the file. try { boolean success = false; if ( !origFile.exists() ) { log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() ); wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile ); success = true; // You wouldn't get here on failure, a WagonException would have been thrown. log.debug( "Downloaded successfully." ); } else { log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() ); success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile, origFile.lastModified() ); if ( !success ) { throw new NotModifiedException( "Not downloaded, as local file is newer than remote side: " + origFile.getAbsolutePath() ); } if ( destFile.exists() ) { log.debug( "Downloaded successfully." ); } } } catch ( ResourceDoesNotExistException e ) { throw new NotFoundException( "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(), e ); } catch ( WagonException e ) { // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough String msg = "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage(); if ( e.getCause() != null ) { msg += " (cause: " + e.getCause() + ")"; } throw new ProxyException( msg, e ); } } /** * Apply the policies. * * @param policies the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects) * @param settings the map of settings for the policies to execute. (Map of String policy keys, to String policy * setting) * @param request the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)} * ) * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}) * @throws PolicyViolationException */ private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings, Properties request, File localFile ) throws PolicyViolationException { for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() ) { // olamy with spring rolehint is now downloadPolicy#hint // so substring after last # to get the hint as with plexus String key = StringUtils.substringAfterLast( entry.getKey(), "#" ); DownloadPolicy policy = entry.getValue(); String defaultSetting = policy.getDefaultOption(); String setting = StringUtils.defaultString( settings.get( key ), defaultSetting ); log.debug( "Applying [{}] policy with [{}]", key, setting ); try { policy.applyPolicy( setting, request, localFile ); } catch ( PolicyConfigurationException e ) { log.error( e.getMessage(), e ); } } } private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings, Properties request, ArtifactReference artifact, RemoteRepositoryContent content, File localFile, Exception exception, Map<String, Exception> previousExceptions ) throws ProxyDownloadException { boolean process = true; for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() ) { // olamy with spring rolehint is now downloadPolicy#hint // so substring after last # to get the hint as with plexus String key = StringUtils.substringAfterLast( entry.getKey(), "#" ); DownloadErrorPolicy policy = entry.getValue(); String defaultSetting = policy.getDefaultOption(); String setting = StringUtils.defaultString( settings.get( key ), defaultSetting ); log.debug( "Applying [{}] policy with [{}]", key, setting ); try { // all policies must approve the exception, any can cancel process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions ); if ( !process ) { break; } } catch ( PolicyConfigurationException e ) { log.error( e.getMessage(), e ); } } if ( process ) { // if the exception was queued, don't throw it if ( !previousExceptions.containsKey( content.getId() ) ) { throw new ProxyDownloadException( "An error occurred in downloading from the remote repository, and the policy is to fail immediately", content.getId(), exception ); } } else { // if the exception was queued, but cancelled, remove it previousExceptions.remove( content.getId() ); } log.warn( "Transfer error from repository {} for artifact {} , continuing to next repository. Error message: {}", content.getRepository().getId(), Keys.toKey( artifact ), exception.getMessage() ); log.debug( "Full stack trace", exception ); } /** * Creates a working directory * * @param repository * @return file location of working directory */ private File createWorkingDirectory( ManagedRepositoryContent repository ) { try { return Files.createTempDirectory( "temp" ).toFile(); } catch ( IOException e ) { throw new RuntimeException( e.getMessage(), e ); } } /** * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its * downloaded files. * * @param temp The completed download file * @param target The final location of the downloaded file * @throws ProxyException when the temp file cannot replace the target file */ private void moveTempToTarget( File temp, File target ) throws ProxyException { Lock lock; try { lock = fileLockManager.writeFileLock( target ); if ( lock.getFile().exists() && !lock.getFile().delete() ) { throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() ); } lock.getFile().getParentFile().mkdirs(); if ( !temp.renameTo( lock.getFile() ) ) { log.warn( "Unable to rename tmp file to its final name... resorting to copy command." ); try { FileUtils.copyFile( temp, lock.getFile() ); } catch ( IOException e ) { if ( lock.getFile().exists() ) { log.debug( "Tried to copy file {} to {} but file with this name already exists.", temp.getName(), lock.getFile().getAbsolutePath() ); } else { throw new ProxyException( "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e ); } } finally { FileUtils.deleteQuietly( temp ); } } } catch ( FileLockException | FileLockTimeoutException e ) { throw new ProxyException( e.getMessage(), e ); } } /** * Using wagon, connect to the remote repository. * * @param connector the connector configuration to utilize (for obtaining network proxy configuration from) * @param wagon the wagon instance to establish the connection on. * @param remoteRepository the remote repository to connect to. * @return true if the connection was successful. false if not connected. */ private boolean connectToRepository( ProxyConnector connector, Wagon wagon, RemoteRepositoryContent remoteRepository ) { boolean connected = false; final ProxyInfo networkProxy = connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() ); if ( log.isDebugEnabled() ) { if ( networkProxy != null ) { // TODO: move to proxyInfo.toString() String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort() + " to connect to remote repository " + remoteRepository.getURL(); if ( networkProxy.getNonProxyHosts() != null ) { msg += "; excluding hosts: " + networkProxy.getNonProxyHosts(); } if ( StringUtils.isNotBlank( networkProxy.getUserName() ) ) { msg += "; as user: " + networkProxy.getUserName(); } log.debug( msg ); } } AuthenticationInfo authInfo = null; String username = remoteRepository.getRepository().getUserName(); String password = remoteRepository.getRepository().getPassword(); if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) ) { log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() ); authInfo = new AuthenticationInfo(); authInfo.setUserName( username ); authInfo.setPassword( password ); } // Convert seconds to milliseconds long timeoutInMilliseconds = TimeUnit.MILLISECONDS.convert( remoteRepository.getRepository().getTimeout(), // TimeUnit.SECONDS ); // Set timeout read and connect // FIXME olamy having 2 config values wagon.setReadTimeout( (int) timeoutInMilliseconds ); wagon.setTimeout( (int) timeoutInMilliseconds ); try { Repository wagonRepository = new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() ); wagon.connect( wagonRepository, authInfo, networkProxy ); connected = true; } catch ( ConnectionException | AuthenticationException e ) { log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() ); connected = false; } return connected; } /** * Tests whitelist and blacklist patterns against path. * * @param path the path to test. * @param patterns the list of patterns to check. * @return true if the path matches at least 1 pattern in the provided patterns list. */ private boolean matchesPattern( String path, List<String> patterns ) { if ( CollectionUtils.isEmpty( patterns ) ) { return false; } if ( !path.startsWith( "/" ) ) { path = "/" + path; } for ( String pattern : patterns ) { if ( !pattern.startsWith( "/" ) ) { pattern = "/" + pattern; } if ( SelectorUtils.matchPath( pattern, path, false ) ) { return true; } } return false; } /** * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477 */ @Override public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository ) { if ( !this.proxyConnectorMap.containsKey( repository.getId() ) ) { return Collections.emptyList(); } List<ProxyConnector> ret = new ArrayList<>( this.proxyConnectorMap.get( repository.getId() ) ); Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() ); return ret; } @Override public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue ) { if ( ConfigurationNames.isNetworkProxy( propertyName ) // || ConfigurationNames.isManagedRepositories( propertyName ) // || ConfigurationNames.isRemoteRepositories( propertyName ) // || ConfigurationNames.isProxyConnector( propertyName ) ) // { initConnectorsAndNetworkProxies(); } } protected String addParameters( String path, RemoteRepository remoteRepository ) { if ( remoteRepository.getExtraParameters().isEmpty() ) { return path; } boolean question = false; StringBuilder res = new StringBuilder( path == null ? "" : path ); for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() ) { if ( !question ) { res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() ); } } return res.toString(); } @Override public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue ) { /* do nothing */ } public ArchivaConfiguration getArchivaConfiguration() { return archivaConfiguration; } public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration ) { this.archivaConfiguration = archivaConfiguration; } public RepositoryContentFactory getRepositoryFactory() { return repositoryFactory; } public void setRepositoryFactory( RepositoryContentFactory repositoryFactory ) { this.repositoryFactory = repositoryFactory; } public MetadataTools getMetadataTools() { return metadataTools; } public void setMetadataTools( MetadataTools metadataTools ) { this.metadataTools = metadataTools; } public UrlFailureCache getUrlFailureCache() { return urlFailureCache; } public void setUrlFailureCache( UrlFailureCache urlFailureCache ) { this.urlFailureCache = urlFailureCache; } public WagonFactory getWagonFactory() { return wagonFactory; } public void setWagonFactory( WagonFactory wagonFactory ) { this.wagonFactory = wagonFactory; } public Map<String, PreDownloadPolicy> getPreDownloadPolicies() { return preDownloadPolicies; } public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies ) { this.preDownloadPolicies = preDownloadPolicies; } public Map<String, PostDownloadPolicy> getPostDownloadPolicies() { return postDownloadPolicies; } public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies ) { this.postDownloadPolicies = postDownloadPolicies; } public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies() { return downloadErrorPolicies; } public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies ) { this.downloadErrorPolicies = downloadErrorPolicies; } }