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