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