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