]> source.dussan.org Git - archiva.git/blob
5869f673080c66adc548e5a351f5d9be0c197f92
[archiva.git] /
1 package org.apache.archiva.proxy;
2
3 /*
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
11  *
12  *  http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  * under the License.
20  */
21
22 import org.apache.archiva.admin.model.RepositoryAdminException;
23 import org.apache.archiva.admin.model.beans.NetworkProxy;
24 import org.apache.archiva.admin.model.beans.ProxyConnectorRuleType;
25 import org.apache.archiva.admin.model.beans.RemoteRepository;
26 import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin;
27 import org.apache.archiva.common.filelock.FileLockException;
28 import org.apache.archiva.common.filelock.FileLockManager;
29 import org.apache.archiva.common.filelock.FileLockTimeoutException;
30 import org.apache.archiva.common.filelock.Lock;
31 import org.apache.archiva.common.utils.FileUtil;
32 import org.apache.archiva.configuration.ArchivaConfiguration;
33 import org.apache.archiva.configuration.Configuration;
34 import org.apache.archiva.configuration.ConfigurationNames;
35 import org.apache.archiva.configuration.NetworkProxyConfiguration;
36 import org.apache.archiva.configuration.ProxyConnectorConfiguration;
37 import org.apache.archiva.configuration.ProxyConnectorRuleConfiguration;
38 import org.apache.archiva.model.ArtifactReference;
39 import org.apache.archiva.model.Keys;
40 import org.apache.archiva.model.RepositoryURL;
41 import org.apache.archiva.policies.DownloadErrorPolicy;
42 import org.apache.archiva.policies.DownloadPolicy;
43 import org.apache.archiva.policies.PolicyConfigurationException;
44 import org.apache.archiva.policies.PolicyViolationException;
45 import org.apache.archiva.policies.PostDownloadPolicy;
46 import org.apache.archiva.policies.PreDownloadPolicy;
47 import org.apache.archiva.policies.ProxyDownloadException;
48 import org.apache.archiva.policies.urlcache.UrlFailureCache;
49 import org.apache.archiva.proxy.common.WagonFactory;
50 import org.apache.archiva.proxy.common.WagonFactoryException;
51 import org.apache.archiva.proxy.common.WagonFactoryRequest;
52 import org.apache.archiva.proxy.model.ProxyConnector;
53 import org.apache.archiva.proxy.model.ProxyFetchResult;
54 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
55 import org.apache.archiva.redback.components.registry.Registry;
56 import org.apache.archiva.redback.components.registry.RegistryListener;
57 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
58 import org.apache.archiva.repository.ManagedRepositoryContent;
59 import org.apache.archiva.repository.RemoteRepositoryContent;
60 import org.apache.archiva.repository.RepositoryContentFactory;
61 import org.apache.archiva.repository.RepositoryException;
62 import org.apache.archiva.repository.RepositoryNotFoundException;
63 import org.apache.archiva.repository.metadata.MetadataTools;
64 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
65 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
66 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
67 import org.apache.commons.collections.CollectionUtils;
68 import org.apache.commons.io.FileUtils;
69 import org.apache.commons.io.FilenameUtils;
70 import org.apache.commons.lang.StringUtils;
71 import org.apache.commons.lang.SystemUtils;
72 import org.apache.maven.wagon.ConnectionException;
73 import org.apache.maven.wagon.ResourceDoesNotExistException;
74 import org.apache.maven.wagon.Wagon;
75 import org.apache.maven.wagon.WagonException;
76 import org.apache.maven.wagon.authentication.AuthenticationException;
77 import org.apache.maven.wagon.authentication.AuthenticationInfo;
78 import org.apache.maven.wagon.proxy.ProxyInfo;
79 import org.apache.maven.wagon.repository.Repository;
80 import org.apache.tools.ant.types.selectors.SelectorUtils;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83 import org.slf4j.MarkerFactory;
84 import org.springframework.stereotype.Service;
85
86 import javax.annotation.PostConstruct;
87 import javax.inject.Inject;
88 import javax.inject.Named;
89 import java.io.File;
90 import java.io.IOException;
91 import java.lang.reflect.Proxy;
92 import java.nio.file.Files;
93 import java.nio.file.Path;
94 import java.nio.file.Paths;
95 import java.util.ArrayList;
96 import java.util.Collections;
97 import java.util.LinkedHashMap;
98 import java.util.List;
99 import java.util.Map;
100 import java.util.Map.Entry;
101 import java.util.Properties;
102 import java.util.concurrent.ConcurrentHashMap;
103 import java.util.concurrent.ConcurrentMap;
104 import java.util.concurrent.TimeUnit;
105
106 /**
107  * DefaultRepositoryProxyConnectors
108  * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
109  * your average brown onion
110  */
111 @Service("repositoryProxyConnectors#default")
112 public class DefaultRepositoryProxyConnectors
113     implements RepositoryProxyConnectors, RegistryListener
114 {
115     private Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyConnectors.class );
116
117     @Inject
118     @Named(value = "archivaConfiguration#default")
119     private ArchivaConfiguration archivaConfiguration;
120
121     @Inject
122     @Named(value = "repositoryContentFactory#default")
123     private RepositoryContentFactory repositoryFactory;
124
125     @Inject
126     @Named(value = "metadataTools#default")
127     private MetadataTools metadataTools;
128
129     @Inject
130     private Map<String, PreDownloadPolicy> preDownloadPolicies;
131
132     @Inject
133     private Map<String, PostDownloadPolicy> postDownloadPolicies;
134
135     @Inject
136     private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
137
138     @Inject
139     private UrlFailureCache urlFailureCache;
140
141     private ConcurrentMap<String, List<ProxyConnector>> proxyConnectorMap = new ConcurrentHashMap<>();
142
143     private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>();
144
145     @Inject
146     private WagonFactory wagonFactory;
147
148     @Inject
149     @Named(value = "archivaTaskScheduler#repository")
150     private ArchivaTaskScheduler scheduler;
151
152     @Inject
153     private NetworkProxyAdmin networkProxyAdmin;
154
155     @Inject
156     @Named(value = "fileLockManager#default")
157     private FileLockManager fileLockManager;
158
159     @PostConstruct
160     public void initialize()
161     {
162         initConnectorsAndNetworkProxies();
163         archivaConfiguration.addChangeListener( this );
164
165     }
166
167     @SuppressWarnings("unchecked")
168     private void initConnectorsAndNetworkProxies()
169     {
170
171         ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
172         this.proxyConnectorMap.clear();
173
174         Configuration configuration = archivaConfiguration.getConfiguration();
175
176         List<ProxyConnectorRuleConfiguration> allProxyConnectorRuleConfigurations =
177             configuration.getProxyConnectorRuleConfigurations();
178
179         List<ProxyConnectorConfiguration> proxyConfigs = configuration.getProxyConnectors();
180         for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
181         {
182             String key = proxyConfig.getSourceRepoId();
183
184             try
185             {
186                 // Create connector object.
187                 ProxyConnector connector = new ProxyConnector();
188
189                 connector.setSourceRepository(
190                     repositoryFactory.getManagedRepositoryContent( proxyConfig.getSourceRepoId() ) );
191                 connector.setTargetRepository(
192                     repositoryFactory.getRemoteRepositoryContent( proxyConfig.getTargetRepoId() ) );
193
194                 connector.setProxyId( proxyConfig.getProxyId() );
195                 connector.setPolicies( proxyConfig.getPolicies() );
196                 connector.setOrder( proxyConfig.getOrder() );
197                 connector.setDisabled( proxyConfig.isDisabled() );
198
199                 // Copy any blacklist patterns.
200                 List<String> blacklist = new ArrayList<>( 0 );
201                 if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
202                 {
203                     blacklist.addAll( proxyConfig.getBlackListPatterns() );
204                 }
205                 connector.setBlacklist( blacklist );
206
207                 // Copy any whitelist patterns.
208                 List<String> whitelist = new ArrayList<>( 0 );
209                 if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
210                 {
211                     whitelist.addAll( proxyConfig.getWhiteListPatterns() );
212                 }
213                 connector.setWhitelist( whitelist );
214
215                 List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations =
216                     findProxyConnectorRules( connector.getSourceRepository().getId(),
217                                              connector.getTargetRepository().getId(),
218                                              allProxyConnectorRuleConfigurations );
219
220                 if ( !proxyConnectorRuleConfigurations.isEmpty() )
221                 {
222                     for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : proxyConnectorRuleConfigurations )
223                     {
224                         if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
225                                                  ProxyConnectorRuleType.BLACK_LIST.getRuleType() ) )
226                         {
227                             connector.getBlacklist().add( proxyConnectorRuleConfiguration.getPattern() );
228                         }
229
230                         if ( StringUtils.equals( proxyConnectorRuleConfiguration.getRuleType(),
231                                                  ProxyConnectorRuleType.WHITE_LIST.getRuleType() ) )
232                         {
233                             connector.getWhitelist().add( proxyConnectorRuleConfiguration.getPattern() );
234                         }
235                     }
236                 }
237
238                 // Get other connectors
239                 List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
240                 if ( connectors == null )
241                 {
242                     // Create if we are the first.
243                     connectors = new ArrayList<>( 1 );
244                 }
245
246                 // Add the connector.
247                 connectors.add( connector );
248
249                 // Ensure the list is sorted.
250                 Collections.sort( connectors, proxyOrderSorter );
251
252                 // Set the key to the list of connectors.
253                 this.proxyConnectorMap.put( key, connectors );
254             }
255             catch ( RepositoryNotFoundException e )
256             {
257                 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
258             }
259             catch ( RepositoryException e )
260             {
261                 log.warn( "Unable to use proxy connector: {}", e.getMessage(), e );
262             }
263
264
265         }
266
267         this.networkProxyMap.clear();
268
269         List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
270         for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
271         {
272             String key = networkProxyConfig.getId();
273
274             ProxyInfo proxy = new ProxyInfo();
275
276             proxy.setType( networkProxyConfig.getProtocol() );
277             proxy.setHost( networkProxyConfig.getHost() );
278             proxy.setPort( networkProxyConfig.getPort() );
279             proxy.setUserName( networkProxyConfig.getUsername() );
280             proxy.setPassword( networkProxyConfig.getPassword() );
281
282             this.networkProxyMap.put( key, proxy );
283         }
284
285     }
286
287     private List<ProxyConnectorRuleConfiguration> findProxyConnectorRules( String sourceRepository,
288                                                                            String targetRepository,
289                                                                            List<ProxyConnectorRuleConfiguration> all )
290     {
291         List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations = new ArrayList<>();
292
293         for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : all )
294         {
295             for ( ProxyConnectorConfiguration proxyConnector : proxyConnectorRuleConfiguration.getProxyConnectors() )
296             {
297                 if ( StringUtils.equals( sourceRepository, proxyConnector.getSourceRepoId() ) && StringUtils.equals(
298                     targetRepository, proxyConnector.getTargetRepoId() ) )
299                 {
300                     proxyConnectorRuleConfigurations.add( proxyConnectorRuleConfiguration );
301                 }
302             }
303         }
304
305         return proxyConnectorRuleConfigurations;
306     }
307
308     @Override
309     public Path fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
310         throws ProxyDownloadException
311     {
312         Path localFile = toLocalFile( repository, artifact );
313
314         Properties requestProperties = new Properties();
315         requestProperties.setProperty( "filetype", "artifact" );
316         requestProperties.setProperty( "version", artifact.getVersion() );
317         requestProperties.setProperty( "managedRepositoryId", repository.getId() );
318
319         List<ProxyConnector> connectors = getProxyConnectors( repository );
320         Map<String, Exception> previousExceptions = new LinkedHashMap<>();
321         for ( ProxyConnector connector : connectors )
322         {
323             if ( connector.isDisabled() )
324             {
325                 continue;
326             }
327
328             RemoteRepositoryContent targetRepository = connector.getTargetRepository();
329             requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
330
331             String targetPath = targetRepository.toPath( artifact );
332
333             if ( SystemUtils.IS_OS_WINDOWS )
334             {
335                 // toPath use system PATH_SEPARATOR so on windows url are \ which doesn't work very well :-)
336                 targetPath = FilenameUtils.separatorsToUnix( targetPath );
337             }
338
339             try
340             {
341                 Path downloadedFile =
342                     transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
343                                   true );
344
345                 if ( fileExists( downloadedFile ) )
346                 {
347                     log.debug( "Successfully transferred: {}", downloadedFile.toAbsolutePath() );
348                     return downloadedFile;
349                 }
350             }
351             catch ( NotFoundException e )
352             {
353                 log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ),
354                            targetRepository.getRepository().getId() );
355             }
356             catch ( NotModifiedException e )
357             {
358                 log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ),
359                            targetRepository.getRepository().getId() );
360             }
361             catch ( ProxyException | RepositoryAdminException e )
362             {
363                 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
364                                   targetRepository, localFile, e, previousExceptions );
365             }
366         }
367
368         if ( !previousExceptions.isEmpty() )
369         {
370             throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
371                                               previousExceptions );
372         }
373
374         log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) );
375
376         return null;
377     }
378
379     @Override
380     public Path fetchFromProxies( ManagedRepositoryContent repository, String path )
381     {
382         Path localFile = Paths.get( repository.getRepoRoot(), path );
383
384         // no update policies for these paths
385         if ( Files.exists(localFile) )
386         {
387             return null;
388         }
389
390         Properties requestProperties = new Properties();
391         requestProperties.setProperty( "filetype", "resource" );
392         requestProperties.setProperty( "managedRepositoryId", repository.getId() );
393
394         List<ProxyConnector> connectors = getProxyConnectors( repository );
395         for ( ProxyConnector connector : connectors )
396         {
397             if ( connector.isDisabled() )
398             {
399                 continue;
400             }
401
402             RemoteRepositoryContent targetRepository = connector.getTargetRepository();
403             requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
404
405             String targetPath = path;
406
407             try
408             {
409                 Path downloadedFile =
410                     transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
411                                   false );
412
413                 if ( fileExists( downloadedFile ) )
414                 {
415                     log.debug( "Successfully transferred: {}", downloadedFile.toAbsolutePath() );
416                     return downloadedFile;
417                 }
418             }
419             catch ( NotFoundException e )
420             {
421                 log.debug( "Resource {} not found on repository \"{}\".", path,
422                            targetRepository.getRepository().getId() );
423             }
424             catch ( NotModifiedException e )
425             {
426                 log.debug( "Resource {} not updated on repository \"{}\".", path,
427                            targetRepository.getRepository().getId() );
428             }
429             catch ( ProxyException e )
430             {
431                 log.warn(
432                     "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",
433                     targetRepository.getRepository().getId(), path, e.getMessage() );
434                 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
435                            "Transfer error from repository \"{}"
436                                + "\" for resource {}, continuing to next repository. Error message: {}",
437                            targetRepository.getRepository().getId(), path, e.getMessage(), e );
438             }
439             catch ( RepositoryAdminException e )
440             {
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 );
445             }
446         }
447
448         log.debug( "Exhausted all target repositories, resource {} not found.", path );
449
450         return null;
451     }
452
453     @Override
454     public ProxyFetchResult fetchMetadataFromProxies( ManagedRepositoryContent repository, String logicalPath )
455     {
456         Path localFile = Paths.get( repository.getRepoRoot(), logicalPath );
457
458         Properties requestProperties = new Properties();
459         requestProperties.setProperty( "filetype", "metadata" );
460         boolean metadataNeedsUpdating = false;
461         long originalTimestamp = getLastModified( localFile );
462
463         List<ProxyConnector> connectors = new ArrayList<>( getProxyConnectors( repository ) );
464         for ( ProxyConnector connector : connectors )
465         {
466             if ( connector.isDisabled() )
467             {
468                 continue;
469             }
470
471             RemoteRepositoryContent targetRepository = connector.getTargetRepository();
472
473             Path localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
474             long originalMetadataTimestamp = getLastModified( localRepoFile );
475
476             try
477             {
478                 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
479                               true );
480
481                 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
482                 {
483                     metadataNeedsUpdating = true;
484                 }
485             }
486             catch ( NotFoundException e )
487             {
488
489                 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
490                            targetRepository.getRepository().getId(), e );
491
492             }
493             catch ( NotModifiedException e )
494             {
495
496                 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
497                            targetRepository.getRepository().getId(), e );
498
499             }
500             catch ( ProxyException | RepositoryAdminException e )
501             {
502                 log.warn(
503                     "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
504                     targetRepository.getRepository().getId(), logicalPath, e.getMessage() );
505                 log.debug( "Full stack trace", e );
506             }
507         }
508
509         if ( hasBeenUpdated( localFile, originalTimestamp ) )
510         {
511             metadataNeedsUpdating = true;
512         }
513
514         if ( metadataNeedsUpdating || !Files.exists(localFile))
515         {
516             try
517             {
518                 metadataTools.updateMetadata( repository, logicalPath );
519             }
520             catch ( RepositoryMetadataException e )
521             {
522                 log.warn( "Unable to update metadata {}:{}", localFile.toAbsolutePath(), e.getMessage(), e );
523             }
524
525         }
526
527         if ( fileExists( localFile ) )
528         {
529             return new ProxyFetchResult( localFile, metadataNeedsUpdating );
530         }
531
532         return new ProxyFetchResult( null, false );
533     }
534
535     /**
536      * @param connector
537      * @param remoteRepository
538      * @param tmpMd5
539      * @param tmpSha1
540      * @param tmpResource
541      * @param url
542      * @param remotePath
543      * @param resource
544      * @param workingDirectory
545      * @param repository
546      * @throws ProxyException
547      * @throws NotModifiedException
548      * @throws org.apache.archiva.admin.model.RepositoryAdminException
549      */
550     protected void transferResources( ProxyConnector connector, RemoteRepositoryContent remoteRepository, Path tmpMd5,
551                                       Path tmpSha1, Path tmpResource, String url, String remotePath, Path resource,
552                                       Path workingDirectory, ManagedRepositoryContent repository )
553         throws ProxyException, NotModifiedException, RepositoryAdminException
554     {
555         Wagon wagon = null;
556         try
557         {
558             RepositoryURL repoUrl = remoteRepository.getURL();
559             String protocol = repoUrl.getProtocol();
560             NetworkProxy networkProxy = null;
561             if ( StringUtils.isNotBlank( connector.getProxyId() ) )
562             {
563                 networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() );
564             }
565             WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest( "wagon#" + protocol,
566                                                                                remoteRepository.getRepository().getExtraHeaders() ).networkProxy(
567                 networkProxy );
568             wagon = wagonFactory.getWagon( wagonFactoryRequest );
569             if ( wagon == null )
570             {
571                 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
572             }
573
574             if ( wagon == null )
575             {
576                 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
577             }
578
579             boolean connected = connectToRepository( connector, wagon, remoteRepository );
580             if ( connected )
581             {
582                 transferArtifact( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
583                                   tmpResource );
584
585                 // TODO: these should be used to validate the download based on the policies, not always downloaded
586                 // to
587                 // save on connections since md5 is rarely used
588                 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".sha1",
589                                   tmpSha1 );
590                 transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".md5",
591                                   tmpMd5 );
592             }
593         }
594         catch ( NotFoundException e )
595         {
596             urlFailureCache.cacheFailure( url );
597             throw e;
598         }
599         catch ( NotModifiedException e )
600         {
601             // Do not cache url here.
602             throw e;
603         }
604         catch ( ProxyException e )
605         {
606             urlFailureCache.cacheFailure( url );
607             throw e;
608         }
609         catch ( WagonFactoryException e )
610         {
611             throw new ProxyException( e.getMessage(), e );
612         }
613         finally
614         {
615             if ( wagon != null )
616             {
617                 try
618                 {
619                     wagon.disconnect();
620                 }
621                 catch ( ConnectionException e )
622                 {
623                     log.warn( "Unable to disconnect wagon.", e );
624                 }
625             }
626         }
627     }
628
629     private void transferArtifact( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
630                                    ManagedRepositoryContent repository, Path resource, Path tmpDirectory,
631                                    Path destFile )
632         throws ProxyException
633     {
634         transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, destFile );
635     }
636
637     private long getLastModified( Path file )
638     {
639         if ( !Files.exists(file) || !Files.isRegularFile(file) )
640         {
641             return 0;
642         }
643
644         try
645         {
646             return Files.getLastModifiedTime(file).toMillis();
647         }
648         catch ( IOException e )
649         {
650             log.error("Could get the modified time of file {}", file.toAbsolutePath());
651             return 0;
652         }
653     }
654
655     private boolean hasBeenUpdated( Path file, long originalLastModified )
656     {
657         if ( !Files.exists(file) || !Files.isRegularFile(file) )
658         {
659             return false;
660         }
661
662         long currentLastModified = getLastModified( file );
663         return ( currentLastModified > originalLastModified );
664     }
665
666     private Path toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
667                                   String targetPath )
668     {
669         String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
670         return Paths.get( repository.getRepoRoot(), repoPath );
671     }
672
673     /**
674      * Test if the provided ManagedRepositoryContent has any proxies configured for it.
675      */
676     @Override
677     public boolean hasProxies( ManagedRepositoryContent repository )
678     {
679         synchronized ( this.proxyConnectorMap )
680         {
681             return this.proxyConnectorMap.containsKey( repository.getId() );
682         }
683     }
684
685     private Path toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
686     {
687         return repository.toFile( artifact );
688     }
689
690     /**
691      * Simple method to test if the file exists on the local disk.
692      *
693      * @param file the file to test. (may be null)
694      * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
695      */
696     private boolean fileExists( Path file )
697     {
698         if ( file == null )
699         {
700             return false;
701         }
702
703         if ( !Files.exists(file))
704         {
705             return false;
706         }
707
708         return Files.isRegularFile(file);
709     }
710
711     /**
712      * Perform the transfer of the file.
713      *
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.
726      */
727     private Path transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
728                                ManagedRepositoryContent repository, Path resource, Properties requestProperties,
729                                boolean executeConsumers )
730         throws ProxyException, NotModifiedException, RepositoryAdminException
731     {
732         String url = remoteRepository.getURL().getUrl();
733         if ( !url.endsWith( "/" ) )
734         {
735             url = url + "/";
736         }
737         url = url + remotePath;
738         requestProperties.setProperty( "url", url );
739
740         // Is a whitelist defined?
741         if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
742         {
743             // Path must belong to whitelist.
744             if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
745             {
746                 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
747                            remotePath, remoteRepository.getRepository().getName() );
748                 return null;
749             }
750         }
751
752         // Is target path part of blacklist?
753         if ( matchesPattern( remotePath, connector.getBlacklist() ) )
754         {
755             log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
756                        remoteRepository.getRepository().getName() );
757             return null;
758         }
759
760         // Handle pre-download policy
761         try
762         {
763             validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
764         }
765         catch ( PolicyViolationException e )
766         {
767             String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
768             if ( fileExists( resource ) )
769             {
770                 log.debug( "{} : using already present local file.", emsg );
771                 return resource;
772             }
773
774             log.debug( emsg );
775             return null;
776         }
777
778         Path workingDirectory = createWorkingDirectory( repository );
779         Path tmpResource = workingDirectory.resolve(resource.getFileName());
780         Path tmpMd5 = workingDirectory.resolve(resource.getFileName().toString() + ".md5" );
781         Path tmpSha1 = workingDirectory.resolve( resource.getFileName().toString() + ".sha1" );
782
783         try
784         {
785
786             transferResources( connector, remoteRepository, tmpMd5, tmpSha1, tmpResource, url, remotePath, resource,
787                                workingDirectory, repository );
788
789             // Handle post-download policies.
790             try
791             {
792                 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
793             }
794             catch ( PolicyViolationException e )
795             {
796                 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
797                 executeConsumers = false;
798                 if ( !fileExists( tmpResource ) )
799                 {
800                     resource = null;
801                 }
802             }
803
804             if ( resource != null )
805             {
806                 synchronized ( resource.toAbsolutePath().toString().intern() )
807                 {
808                     Path directory = resource.getParent();
809                     moveFileIfExists( tmpMd5, directory );
810                     moveFileIfExists( tmpSha1, directory );
811                     moveFileIfExists( tmpResource, directory );
812                 }
813             }
814         }
815         finally
816         {
817             org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
818         }
819
820         if ( executeConsumers )
821         {
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 );
825         }
826
827         return resource;
828     }
829
830     private void queueRepositoryTask( String repositoryId, Path localFile )
831     {
832         RepositoryTask task = new RepositoryTask();
833         task.setRepositoryId( repositoryId );
834         task.setResourceFile( localFile.toFile() );
835         task.setUpdateRelatedArtifacts( true );
836         task.setScanAll( true );
837
838         try
839         {
840             scheduler.queueTask( task );
841         }
842         catch ( TaskQueueException e )
843         {
844             log.error( "Unable to queue repository task to execute consumers on resource file ['{}"
845                            + "'].", localFile.getFileName() );
846         }
847     }
848
849     /**
850      * Moves the file into repository location if it exists
851      *
852      * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
853      * @param directory  directory to write files to
854      */
855     private void moveFileIfExists( Path fileToMove, Path directory )
856         throws ProxyException
857     {
858         if ( fileToMove != null && Files.exists(fileToMove) )
859         {
860             Path newLocation = directory.resolve(fileToMove.getFileName());
861             moveTempToTarget( fileToMove, newLocation );
862         }
863     }
864
865     /**
866      * <p>
867      * Quietly transfer the checksum file from the remote repository to the local file.
868      * </p>
869      *
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.
878      */
879     private void transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
880                                    ManagedRepositoryContent repository, Path resource, Path tmpDirectory, String ext,
881                                    Path destFile )
882         throws ProxyException
883     {
884         String url = remoteRepository.getURL().getUrl() + remotePath + ext;
885
886         // Transfer checksum does not use the policy.
887         if ( urlFailureCache.hasFailedBefore( url ) )
888         {
889             return;
890         }
891
892         try
893         {
894             transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile );
895             log.debug( "Checksum {} Downloaded: {} to move to {}", url, destFile, resource );
896         }
897         catch ( NotFoundException e )
898         {
899             urlFailureCache.cacheFailure( url );
900             log.debug( "Transfer failed, checksum not found: {}", url );
901             // Consume it, do not pass this on.
902         }
903         catch ( NotModifiedException e )
904         {
905             log.debug( "Transfer skipped, checksum not modified: {}", url );
906             // Consume it, do not pass this on.
907         }
908         catch ( ProxyException e )
909         {
910             urlFailureCache.cacheFailure( url );
911             log.warn( "Transfer failed on checksum: {} : {}", url, e.getMessage(), e );
912             // Critical issue, pass it on.
913             throw e;
914         }
915     }
916
917     /**
918      * Perform the transfer of the remote file to the local file specified.
919      *
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.
926      */
927     private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
928                                      ManagedRepositoryContent repository, Path origFile, Path destFile )
929         throws ProxyException
930     {
931         assert ( remotePath != null );
932
933         // Transfer the file.
934         try
935         {
936             boolean success = false;
937
938             if ( !Files.exists(origFile))
939             {
940                 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
941                 wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile.toFile() );
942                 success = true;
943
944                 // You wouldn't get here on failure, a WagonException would have been thrown.
945                 log.debug( "Downloaded successfully." );
946             }
947             else
948             {
949                 log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() );
950                 try
951                 {
952                     success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile.toFile(),
953                                                 Files.getLastModifiedTime(origFile).toMillis());
954                 }
955                 catch ( IOException e )
956                 {
957                     throw new ProxyException( "Failed to the modification time of "+origFile.toAbsolutePath() );
958                 }
959                 if ( !success )
960                 {
961                     throw new NotModifiedException(
962                         "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath() );
963                 }
964
965                 if ( Files.exists(destFile))
966                 {
967                     log.debug( "Downloaded successfully." );
968                 }
969             }
970         }
971         catch ( ResourceDoesNotExistException e )
972         {
973             throw new NotFoundException(
974                 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
975                 e );
976         }
977         catch ( WagonException e )
978         {
979             // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
980
981             String msg =
982                 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
983             if ( e.getCause() != null )
984             {
985                 msg += " (cause: " + e.getCause() + ")";
986             }
987             throw new ProxyException( msg, e );
988         }
989     }
990
991     /**
992      * Apply the policies.
993      *
994      * @param policies  the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
995      * @param settings  the map of settings for the policies to execute. (Map of String policy keys, to String policy
996      *                  setting)
997      * @param request   the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}
998      *                  )
999      * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)})
1000      * @throws PolicyViolationException
1001      */
1002     private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
1003                                    Properties request, Path localFile )
1004         throws PolicyViolationException
1005     {
1006         for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
1007         {
1008             // olamy with spring rolehint is now downloadPolicy#hint
1009             // so substring after last # to get the hint as with plexus
1010             String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1011             DownloadPolicy policy = entry.getValue();
1012             String defaultSetting = policy.getDefaultOption();
1013
1014             String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1015
1016             log.debug( "Applying [{}] policy with [{}]", key, setting );
1017             try
1018             {
1019                 policy.applyPolicy( setting, request, localFile.toFile() );
1020             }
1021             catch ( PolicyConfigurationException e )
1022             {
1023                 log.error( e.getMessage(), e );
1024             }
1025         }
1026     }
1027
1028     private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
1029                                    Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
1030                                    Path localFile, Exception exception, Map<String, Exception> previousExceptions )
1031         throws ProxyDownloadException
1032     {
1033         boolean process = true;
1034         for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
1035         {
1036
1037             // olamy with spring rolehint is now downloadPolicy#hint
1038             // so substring after last # to get the hint as with plexus
1039             String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1040             DownloadErrorPolicy policy = entry.getValue();
1041             String defaultSetting = policy.getDefaultOption();
1042             String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1043
1044             log.debug( "Applying [{}] policy with [{}]", key, setting );
1045             try
1046             {
1047                 // all policies must approve the exception, any can cancel
1048                 process = policy.applyPolicy( setting, request, localFile.toFile(), exception, previousExceptions );
1049                 if ( !process )
1050                 {
1051                     break;
1052                 }
1053             }
1054             catch ( PolicyConfigurationException e )
1055             {
1056                 log.error( e.getMessage(), e );
1057             }
1058         }
1059
1060         if ( process )
1061         {
1062             // if the exception was queued, don't throw it
1063             if ( !previousExceptions.containsKey( content.getId() ) )
1064             {
1065                 throw new ProxyDownloadException(
1066                     "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
1067                     content.getId(), exception );
1068             }
1069         }
1070         else
1071         {
1072             // if the exception was queued, but cancelled, remove it
1073             previousExceptions.remove( content.getId() );
1074         }
1075
1076         log.warn(
1077             "Transfer error from repository {} for artifact {} , continuing to next repository. Error message: {}",
1078             content.getRepository().getId(), Keys.toKey( artifact ), exception.getMessage() );
1079         log.debug( "Full stack trace", exception );
1080     }
1081
1082     /**
1083      * Creates a working directory
1084      *
1085      * @param repository
1086      * @return file location of working directory
1087      */
1088     private Path createWorkingDirectory( ManagedRepositoryContent repository )
1089     {
1090         try
1091         {
1092             return Files.createTempDirectory( "temp" );
1093         }
1094         catch ( IOException e )
1095         {
1096             throw new RuntimeException( e.getMessage(), e );
1097         }
1098
1099     }
1100
1101     /**
1102      * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
1103      * downloaded files.
1104      *
1105      * @param temp   The completed download file
1106      * @param target The final location of the downloaded file
1107      * @throws ProxyException when the temp file cannot replace the target file
1108      */
1109     private void moveTempToTarget( Path temp, Path target )
1110         throws ProxyException
1111     {
1112
1113         Lock lock;
1114         try
1115         {
1116             lock = fileLockManager.writeFileLock( target.toFile() );
1117             if ( lock.getFile().exists() && !lock.getFile().delete() )
1118             {
1119                 throw new ProxyException( "Unable to overwrite existing target file: " + target.toAbsolutePath() );
1120             }
1121
1122             lock.getFile().getParentFile().mkdirs();
1123
1124             try
1125             {
1126                 Files.move(temp, lock.getFile().toPath() );
1127             }
1128             catch ( IOException e )
1129             {
1130                 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1131
1132                 try
1133                 {
1134                     Files.copy( temp, lock.getFile().toPath() );
1135                 }
1136                 catch ( IOException e2 )
1137                 {
1138                     if ( lock.getFile().exists() )
1139                     {
1140                         log.debug( "Tried to copy file {} to {} but file with this name already exists.",
1141                             temp.getFileName(), lock.getFile().getAbsolutePath() );
1142                     }
1143                     else
1144                     {
1145                         throw new ProxyException(
1146                             "Cannot copy tmp file " + temp.toAbsolutePath() + " to its final location", e2 );
1147                     }
1148                 }
1149                 finally
1150                 {
1151                     org.apache.archiva.common.utils.FileUtils.deleteQuietly( temp );
1152                 }
1153             }
1154
1155         }
1156         catch ( FileLockException | FileLockTimeoutException e )
1157         {
1158             throw new ProxyException( e.getMessage(), e );
1159         }
1160     }
1161
1162     /**
1163      * Using wagon, connect to the remote repository.
1164      *
1165      * @param connector        the connector configuration to utilize (for obtaining network proxy configuration from)
1166      * @param wagon            the wagon instance to establish the connection on.
1167      * @param remoteRepository the remote repository to connect to.
1168      * @return true if the connection was successful. false if not connected.
1169      */
1170     private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1171                                          RemoteRepositoryContent remoteRepository )
1172     {
1173         boolean connected = false;
1174
1175         final ProxyInfo networkProxy =
1176             connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1177
1178         if ( log.isDebugEnabled() )
1179         {
1180             if ( networkProxy != null )
1181             {
1182                 // TODO: move to proxyInfo.toString()
1183                 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1184                     + " to connect to remote repository " + remoteRepository.getURL();
1185                 if ( networkProxy.getNonProxyHosts() != null )
1186                 {
1187                     msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1188                 }
1189                 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1190                 {
1191                     msg += "; as user: " + networkProxy.getUserName();
1192                 }
1193                 log.debug( msg );
1194             }
1195         }
1196
1197         AuthenticationInfo authInfo = null;
1198         String username = remoteRepository.getRepository().getUserName();
1199         String password = remoteRepository.getRepository().getPassword();
1200
1201         if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1202         {
1203             log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1204             authInfo = new AuthenticationInfo();
1205             authInfo.setUserName( username );
1206             authInfo.setPassword( password );
1207         }
1208
1209         // Convert seconds to milliseconds
1210         long timeoutInMilliseconds = TimeUnit.MILLISECONDS.convert( remoteRepository.getRepository().getTimeout(), //
1211                                                                     TimeUnit.SECONDS );
1212
1213         // Set timeout  read and connect
1214         // FIXME olamy having 2 config values
1215         wagon.setReadTimeout( (int) timeoutInMilliseconds );
1216         wagon.setTimeout( (int)  timeoutInMilliseconds );
1217
1218         try
1219         {
1220             Repository wagonRepository =
1221                 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1222             wagon.connect( wagonRepository, authInfo, networkProxy );
1223             connected = true;
1224         }
1225         catch ( ConnectionException | AuthenticationException e )
1226         {
1227             log.warn( "Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage() );
1228             connected = false;
1229         }
1230
1231         return connected;
1232     }
1233
1234     /**
1235      * Tests whitelist and blacklist patterns against path.
1236      *
1237      * @param path     the path to test.
1238      * @param patterns the list of patterns to check.
1239      * @return true if the path matches at least 1 pattern in the provided patterns list.
1240      */
1241     private boolean matchesPattern( String path, List<String> patterns )
1242     {
1243         if ( CollectionUtils.isEmpty( patterns ) )
1244         {
1245             return false;
1246         }
1247
1248         if ( !path.startsWith( "/" ) )
1249         {
1250             path = "/" + path;
1251         }
1252
1253         for ( String pattern : patterns )
1254         {
1255             if ( !pattern.startsWith( "/" ) )
1256             {
1257                 pattern = "/" + pattern;
1258             }
1259
1260             if ( SelectorUtils.matchPath( pattern, path, false ) )
1261             {
1262                 return true;
1263             }
1264         }
1265
1266         return false;
1267     }
1268
1269     /**
1270      * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1271      */
1272     @Override
1273     public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1274     {
1275
1276         if ( !this.proxyConnectorMap.containsKey( repository.getId() ) )
1277         {
1278             return Collections.emptyList();
1279         }
1280         List<ProxyConnector> ret = new ArrayList<>( this.proxyConnectorMap.get( repository.getId() ) );
1281
1282         Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1283         return ret;
1284
1285     }
1286
1287     @Override
1288     public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1289     {
1290         if ( ConfigurationNames.isNetworkProxy( propertyName ) //
1291             || ConfigurationNames.isManagedRepositories( propertyName ) //
1292             || ConfigurationNames.isRemoteRepositories( propertyName ) //
1293             || ConfigurationNames.isProxyConnector( propertyName ) ) //
1294         {
1295             initConnectorsAndNetworkProxies();
1296         }
1297     }
1298
1299     protected String addParameters( String path, RemoteRepository remoteRepository )
1300     {
1301         if ( remoteRepository.getExtraParameters().isEmpty() )
1302         {
1303             return path;
1304         }
1305
1306         boolean question = false;
1307
1308         StringBuilder res = new StringBuilder( path == null ? "" : path );
1309
1310         for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1311         {
1312             if ( !question )
1313             {
1314                 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1315             }
1316         }
1317
1318         return res.toString();
1319     }
1320
1321
1322     @Override
1323     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1324     {
1325         /* do nothing */
1326     }
1327
1328     public ArchivaConfiguration getArchivaConfiguration()
1329     {
1330         return archivaConfiguration;
1331     }
1332
1333     public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1334     {
1335         this.archivaConfiguration = archivaConfiguration;
1336     }
1337
1338     public RepositoryContentFactory getRepositoryFactory()
1339     {
1340         return repositoryFactory;
1341     }
1342
1343     public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1344     {
1345         this.repositoryFactory = repositoryFactory;
1346     }
1347
1348     public MetadataTools getMetadataTools()
1349     {
1350         return metadataTools;
1351     }
1352
1353     public void setMetadataTools( MetadataTools metadataTools )
1354     {
1355         this.metadataTools = metadataTools;
1356     }
1357
1358     public UrlFailureCache getUrlFailureCache()
1359     {
1360         return urlFailureCache;
1361     }
1362
1363     public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1364     {
1365         this.urlFailureCache = urlFailureCache;
1366     }
1367
1368     public WagonFactory getWagonFactory()
1369     {
1370         return wagonFactory;
1371     }
1372
1373     public void setWagonFactory( WagonFactory wagonFactory )
1374     {
1375         this.wagonFactory = wagonFactory;
1376     }
1377
1378     public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1379     {
1380         return preDownloadPolicies;
1381     }
1382
1383     public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1384     {
1385         this.preDownloadPolicies = preDownloadPolicies;
1386     }
1387
1388     public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1389     {
1390         return postDownloadPolicies;
1391     }
1392
1393     public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1394     {
1395         this.postDownloadPolicies = postDownloadPolicies;
1396     }
1397
1398     public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1399     {
1400         return downloadErrorPolicies;
1401     }
1402
1403     public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1404     {
1405         this.downloadErrorPolicies = downloadErrorPolicies;
1406     }
1407 }