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