]> source.dussan.org Git - archiva.git/blob
1b6d7831e7138c6695446deb2630a42d582031b8
[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.springframework.stereotype.Service;
79
80 import javax.annotation.PostConstruct;
81 import javax.inject.Inject;
82 import javax.inject.Named;
83 import java.io.File;
84 import java.io.IOException;
85 import java.util.ArrayList;
86 import java.util.Arrays;
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 \"" + targetRepository.getRepository().getId() + "\" for resource "
451                         + path + ", continuing to next repository. Error message: " + e.getMessage() );
452                 log.debug( "Full stack trace", e );
453             }
454             catch ( RepositoryAdminException e )
455             {
456                 log.warn(
457                     "Transfer error from repository \"" + targetRepository.getRepository().getId() + "\" for resource "
458                         + path + ", continuing to next repository. Error message: " + e.getMessage() );
459                 log.debug( "Full stack trace", e );
460             }
461         }
462
463         log.debug( "Exhausted all target repositories, resource {} not found.", path );
464
465         return null;
466     }
467
468     public File fetchMetatadaFromProxies( ManagedRepositoryContent repository, String logicalPath )
469     {
470         File localFile = new File( repository.getRepoRoot(), logicalPath );
471
472         Properties requestProperties = new Properties();
473         requestProperties.setProperty( "filetype", "metadata" );
474         boolean metadataNeedsUpdating = false;
475         long originalTimestamp = getLastModified( localFile );
476
477         List<ProxyConnector> connectors = getProxyConnectors( repository );
478         for ( ProxyConnector connector : connectors )
479         {
480             if ( connector.isDisabled() )
481             {
482                 continue;
483             }
484
485             RemoteRepositoryContent targetRepository = connector.getTargetRepository();
486
487             File localRepoFile = toLocalRepoFile( repository, targetRepository, logicalPath );
488             long originalMetadataTimestamp = getLastModified( localRepoFile );
489
490             try
491             {
492                 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
493                               true );
494
495                 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
496                 {
497                     metadataNeedsUpdating = true;
498                 }
499             }
500             catch ( NotFoundException e )
501             {
502
503                 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
504                            targetRepository.getRepository().getId(), e );
505
506             }
507             catch ( NotModifiedException e )
508             {
509
510                 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
511                            targetRepository.getRepository().getId(), e );
512
513             }
514             catch ( ProxyException e )
515             {
516                 log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId()
517                               + "\" for versioned Metadata " + logicalPath
518                               + ", continuing to next repository. Error message: " + e.getMessage() );
519                 log.debug( "Full stack trace", e );
520             }
521             catch ( RepositoryAdminException e )
522             {
523                 log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId()
524                               + "\" for versioned Metadata " + logicalPath
525                               + ", continuing to next repository. Error message: " + e.getMessage() );
526                 log.debug( "Full stack trace", e );
527             }
528         }
529
530         if ( hasBeenUpdated( localFile, originalTimestamp ) )
531         {
532             metadataNeedsUpdating = true;
533         }
534
535         if ( metadataNeedsUpdating || !localFile.exists() )
536         {
537             try
538             {
539                 metadataTools.updateMetadata( repository, logicalPath );
540             }
541             catch ( RepositoryMetadataException e )
542             {
543                 log.warn( "Unable to update metadata " + localFile.getAbsolutePath() + ": " + e.getMessage(), e );
544             }
545         }
546
547         if ( fileExists( localFile ) )
548         {
549             return localFile;
550         }
551
552         return null;
553     }
554
555     private long getLastModified( File file )
556     {
557         if ( !file.exists() || !file.isFile() )
558         {
559             return 0;
560         }
561
562         return file.lastModified();
563     }
564
565     private boolean hasBeenUpdated( File file, long originalLastModified )
566     {
567         if ( !file.exists() || !file.isFile() )
568         {
569             return false;
570         }
571
572         long currentLastModified = getLastModified( file );
573         return ( currentLastModified > originalLastModified );
574     }
575
576     private File toLocalRepoFile( ManagedRepositoryContent repository, RemoteRepositoryContent targetRepository,
577                                   String targetPath )
578     {
579         String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
580         return new File( repository.getRepoRoot(), repoPath );
581     }
582
583     /**
584      * Test if the provided ManagedRepositoryContent has any proxies configured for it.
585      */
586     public boolean hasProxies( ManagedRepositoryContent repository )
587     {
588         synchronized ( this.proxyConnectorMap )
589         {
590             return this.proxyConnectorMap.containsKey( repository.getId() );
591         }
592     }
593
594     private File toLocalFile( ManagedRepositoryContent repository, ArtifactReference artifact )
595     {
596         return repository.toFile( artifact );
597     }
598
599     /**
600      * Simple method to test if the file exists on the local disk.
601      *
602      * @param file the file to test. (may be null)
603      * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
604      */
605     private boolean fileExists( File file )
606     {
607         if ( file == null )
608         {
609             return false;
610         }
611
612         if ( !file.exists() )
613         {
614             return false;
615         }
616
617         if ( !file.isFile() )
618         {
619             return false;
620         }
621
622         return true;
623     }
624
625     /**
626      * Perform the transfer of the file.
627      *
628      * @param connector         the connector configuration to use.
629      * @param remoteRepository  the remote repository get the resource from.
630      * @param remotePath        the path in the remote repository to the resource to get.
631      * @param repository        the managed repository that will hold the file
632      * @param resource          the local file to place the downloaded resource into
633      * @param requestProperties the request properties to utilize for policy handling.
634      * @param executeConsumers  whether to execute the consumers after proxying
635      * @return the local file that was downloaded, or null if not downloaded.
636      * @throws NotFoundException    if the file was not found on the remote repository.
637      * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but
638      *                              the remote resource is not newer than the local File.
639      * @throws ProxyException       if transfer was unsuccessful.
640      */
641     private File transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
642                                ManagedRepositoryContent repository, File resource, Properties requestProperties,
643                                boolean executeConsumers )
644         throws ProxyException, NotModifiedException, RepositoryAdminException
645     {
646         String url = remoteRepository.getURL().getUrl();
647         if ( !url.endsWith( "/" ) )
648         {
649             url = url + "/";
650         }
651         url = url + remotePath;
652         requestProperties.setProperty( "url", url );
653
654         // Is a whitelist defined?
655         if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
656         {
657             // Path must belong to whitelist.
658             if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
659             {
660                 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
661                            remotePath, remoteRepository.getRepository().getName() );
662                 return null;
663             }
664         }
665
666         // Is target path part of blacklist?
667         if ( matchesPattern( remotePath, connector.getBlacklist() ) )
668         {
669             log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
670                        remoteRepository.getRepository().getName() );
671             return null;
672         }
673
674         // Handle pre-download policy
675         try
676         {
677             validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
678         }
679         catch ( PolicyViolationException e )
680         {
681             String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
682             if ( fileExists( resource ) )
683             {
684                 log.debug( "{} : using already present local file.", emsg );
685                 return resource;
686             }
687
688             log.debug( emsg );
689             return null;
690         }
691
692         File tmpMd5 = null;
693         File tmpSha1 = null;
694         File tmpResource = null;
695
696         File workingDirectory = createWorkingDirectory( repository );
697         try
698         {
699             Wagon wagon = null;
700             try
701             {
702                 RepositoryURL repoUrl = remoteRepository.getURL();
703                 String protocol = repoUrl.getProtocol();
704                 NetworkProxy networkProxy = null;
705                 if ( StringUtils.isNotBlank( connector.getProxyId() ) )
706                 {
707                     networkProxy = networkProxyAdmin.getNetworkProxy( connector.getProxyId() );
708                 }
709
710                 wagon = ( networkProxy != null && networkProxy.isUseNtlm() )
711                     ? wagonFactory.getWagon( new WagonFactoryRequest( "wagon#" + protocol + "-ntlm",
712                                                                       remoteRepository.getRepository().getExtraHeaders() ) )
713                     : wagonFactory.getWagon( new WagonFactoryRequest( "wagon#" + protocol,
714                                                                       remoteRepository.getRepository().getExtraHeaders() ) );
715                 if ( wagon == null )
716                 {
717                     throw new ProxyException( "Unsupported target repository protocol: " + protocol );
718                 }
719
720                 boolean connected = connectToRepository( connector, wagon, remoteRepository );
721                 if ( connected )
722                 {
723                     tmpResource = new File( workingDirectory, resource.getName() );
724                     transferSimpleFile( wagon, remoteRepository, remotePath, repository, resource, tmpResource );
725
726                     // TODO: these should be used to validate the download based on the policies, not always downloaded
727                     // to
728                     // save on connections since md5 is rarely used
729                     tmpSha1 =
730                         transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
731                                           ".sha1" );
732                     tmpMd5 =
733                         transferChecksum( wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
734                                           ".md5" );
735                 }
736             }
737             catch ( NotFoundException e )
738             {
739                 urlFailureCache.cacheFailure( url );
740                 throw e;
741             }
742             catch ( NotModifiedException e )
743             {
744                 // Do not cache url here.
745                 throw e;
746             }
747             catch ( ProxyException e )
748             {
749                 urlFailureCache.cacheFailure( url );
750                 throw e;
751             }
752             catch ( WagonFactoryException e )
753             {
754                 throw new ProxyException( e.getMessage(), e );
755             }
756             finally
757             {
758                 if ( wagon != null )
759                 {
760                     try
761                     {
762                         wagon.disconnect();
763                     }
764                     catch ( ConnectionException e )
765                     {
766                         log.warn( "Unable to disconnect wagon.", e );
767                     }
768                 }
769             }
770
771             // Handle post-download policies.
772             try
773             {
774                 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
775             }
776             catch ( PolicyViolationException e )
777             {
778                 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
779                 executeConsumers = false;
780                 if ( !fileExists( tmpResource ) )
781                 {
782                     resource = null;
783                 }
784             }
785
786             if ( resource != null )
787             {
788                 synchronized ( resource.getAbsolutePath().intern() )
789                 {
790                     File directory = resource.getParentFile();
791                     moveFileIfExists( tmpMd5, directory );
792                     moveFileIfExists( tmpSha1, directory );
793                     moveFileIfExists( tmpResource, directory );
794                 }
795             }
796         }
797         finally
798         {
799             FileUtils.deleteQuietly( workingDirectory );
800         }
801
802         if ( executeConsumers )
803         {
804             // Just-in-time update of the index and database by executing the consumers for this artifact
805             //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource );
806             queueRepositoryTask( connector.getSourceRepository().getRepository().getId(), resource );
807         }
808
809         return resource;
810     }
811
812     private void queueRepositoryTask( String repositoryId, File localFile )
813     {
814         RepositoryTask task = new RepositoryTask();
815         task.setRepositoryId( repositoryId );
816         task.setResourceFile( localFile );
817         task.setUpdateRelatedArtifacts( true );
818         task.setScanAll( true );
819
820         try
821         {
822             scheduler.queueTask( task );
823         }
824         catch ( TaskQueueException e )
825         {
826             log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
827                            + "']." );
828         }
829     }
830
831     /**
832      * Moves the file into repository location if it exists
833      *
834      * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
835      * @param directory  directory to write files to
836      */
837     private void moveFileIfExists( File fileToMove, File directory )
838         throws ProxyException
839     {
840         if ( fileToMove != null && fileToMove.exists() )
841         {
842             File newLocation = new File( directory, fileToMove.getName() );
843             moveTempToTarget( fileToMove, newLocation );
844         }
845     }
846
847     /**
848      * <p>
849      * Quietly transfer the checksum file from the remote repository to the local file.
850      * </p>
851      *
852      * @param wagon            the wagon instance (should already be connected) to use.
853      * @param remoteRepository the remote repository to transfer from.
854      * @param remotePath       the remote path to the resource to get.
855      * @param repository       the managed repository that will hold the file
856      * @param resource         the local file that should contain the downloaded contents
857      * @param tmpDirectory     the temporary directory to download to
858      * @param ext              the type of checksum to transfer (example: ".md5" or ".sha1")
859      * @throws ProxyException if copying the downloaded file into place did not succeed.
860      */
861     private File transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
862                                    ManagedRepositoryContent repository, File resource, File tmpDirectory, String ext )
863         throws ProxyException
864     {
865         String url = remoteRepository.getURL().getUrl() + remotePath + ext;
866
867         // Transfer checksum does not use the policy.
868         if ( urlFailureCache.hasFailedBefore( url ) )
869         {
870             return null;
871         }
872
873         File destFile = new File( tmpDirectory, resource.getName() + ext );
874
875         try
876         {
877             transferSimpleFile( wagon, remoteRepository, remotePath + ext, repository, resource, destFile );
878             log.debug( "Checksum {} Downloaded: {} to move to {}", Arrays.asList( url, destFile, resource ).toArray() );
879         }
880         catch ( NotFoundException e )
881         {
882             urlFailureCache.cacheFailure( url );
883             log.debug( "Transfer failed, checksum not found: {}", url );
884             // Consume it, do not pass this on.
885         }
886         catch ( NotModifiedException e )
887         {
888             log.debug( "Transfer skipped, checksum not modified: {}", url );
889             // Consume it, do not pass this on.
890         }
891         catch ( ProxyException e )
892         {
893             urlFailureCache.cacheFailure( url );
894             log.warn( "Transfer failed on checksum: " + url + " : " + e.getMessage(), e );
895             // Critical issue, pass it on.
896             throw e;
897         }
898         return destFile;
899     }
900
901     /**
902      * Perform the transfer of the remote file to the local file specified.
903      *
904      * @param wagon            the wagon instance to use.
905      * @param remoteRepository the remote repository to use
906      * @param remotePath       the remote path to attempt to get
907      * @param repository       the managed repository that will hold the file
908      * @param origFile         the local file to save to
909      * @return The local file that was transfered.
910      * @throws ProxyException if there was a problem moving the downloaded file into place.
911      * @throws WagonException if there was a problem tranfering the file.
912      */
913     private void transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
914                                      ManagedRepositoryContent repository, File origFile, File destFile )
915         throws ProxyException
916     {
917         assert ( remotePath != null );
918
919         // Transfer the file.
920         try
921         {
922             boolean success = false;
923
924             if ( !origFile.exists() )
925             {
926                 log.debug( "Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName() );
927                 wagon.get( addParameters( remotePath, remoteRepository.getRepository() ), destFile );
928                 success = true;
929
930                 // You wouldn't get here on failure, a WagonException would have been thrown.
931                 log.debug( "Downloaded successfully." );
932             }
933             else
934             {
935                 log.debug( "Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName() );
936                 success = wagon.getIfNewer( addParameters( remotePath, remoteRepository.getRepository() ), destFile,
937                                             origFile.lastModified() );
938                 if ( !success )
939                 {
940                     throw new NotModifiedException(
941                         "Not downloaded, as local file is newer than remote side: " + origFile.getAbsolutePath() );
942                 }
943
944                 if ( destFile.exists() )
945                 {
946                     log.debug( "Downloaded successfully." );
947                 }
948             }
949         }
950         catch ( ResourceDoesNotExistException e )
951         {
952             throw new NotFoundException(
953                 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
954                 e );
955         }
956         catch ( WagonException e )
957         {
958             // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
959
960             String msg =
961                 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
962             if ( e.getCause() != null )
963             {
964                 msg += " (cause: " + e.getCause() + ")";
965             }
966             throw new ProxyException( msg, e );
967         }
968     }
969
970     /**
971      * Apply the policies.
972      *
973      * @param policies  the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
974      * @param settings  the map of settings for the policies to execute. (Map of String policy keys, to String policy
975      *                  setting)
976      * @param request   the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)}
977      *                  )
978      * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String, Properties, File)})
979      */
980     private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
981                                    Properties request, File localFile )
982         throws PolicyViolationException
983     {
984         for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
985         {
986             // olamy with spring rolehint is now downloadPolicy#hint
987             // so substring after last # to get the hint as with plexus
988             String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
989             DownloadPolicy policy = entry.getValue();
990             String defaultSetting = policy.getDefaultOption();
991
992             String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
993
994             log.debug( "Applying [{}] policy with [{}]", key, setting );
995             try
996             {
997                 policy.applyPolicy( setting, request, localFile );
998             }
999             catch ( PolicyConfigurationException e )
1000             {
1001                 log.error( e.getMessage(), e );
1002             }
1003         }
1004     }
1005
1006     private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
1007                                    Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
1008                                    File localFile, Exception exception, Map<String, Exception> previousExceptions )
1009         throws ProxyDownloadException
1010     {
1011         boolean process = true;
1012         for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
1013         {
1014
1015             // olamy with spring rolehint is now downloadPolicy#hint
1016             // so substring after last # to get the hint as with plexus
1017             String key = StringUtils.substringAfterLast( entry.getKey(), "#" );
1018             DownloadErrorPolicy policy = entry.getValue();
1019             String defaultSetting = policy.getDefaultOption();
1020             String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
1021
1022             log.debug( "Applying [{}] policy with [{}]", key, setting );
1023             try
1024             {
1025                 // all policies must approve the exception, any can cancel
1026                 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
1027                 if ( !process )
1028                 {
1029                     break;
1030                 }
1031             }
1032             catch ( PolicyConfigurationException e )
1033             {
1034                 log.error( e.getMessage(), e );
1035             }
1036         }
1037
1038         if ( process )
1039         {
1040             // if the exception was queued, don't throw it
1041             if ( !previousExceptions.containsKey( content.getId() ) )
1042             {
1043                 throw new ProxyDownloadException(
1044                     "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
1045                     content.getId(), exception );
1046             }
1047         }
1048         else
1049         {
1050             // if the exception was queued, but cancelled, remove it
1051             previousExceptions.remove( content.getId() );
1052         }
1053
1054         log.warn(
1055             "Transfer error from repository \"" + content.getRepository().getId() + "\" for artifact " + Keys.toKey(
1056                 artifact ) + ", continuing to next repository. Error message: " + exception.getMessage() );
1057         log.debug( "Full stack trace", exception );
1058     }
1059
1060     /**
1061      * Creates a working directory
1062      *
1063      * @param repository
1064      * @return file location of working directory
1065      * @throws IOException
1066      */
1067     private File createWorkingDirectory( ManagedRepositoryContent repository )
1068     {
1069         return Files.createTempDir();
1070     }
1071
1072     /**
1073      * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
1074      * downloaded files.
1075      *
1076      * @param temp   The completed download file
1077      * @param target The final location of the downloaded file
1078      * @throws ProxyException when the temp file cannot replace the target file
1079      */
1080     private void moveTempToTarget( File temp, File target )
1081         throws ProxyException
1082     {
1083         if ( target.exists() && !target.delete() )
1084         {
1085             throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
1086         }
1087
1088         target.getParentFile().mkdirs();
1089         if ( !temp.renameTo( target ) )
1090         {
1091             log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
1092
1093             try
1094             {
1095                 FileUtils.copyFile( temp, target );
1096             }
1097             catch ( IOException e )
1098             {
1099                 if ( target.exists() )
1100                 {
1101                     log.debug( "Tried to copy file {} to {} but file with this name already exists.", temp.getName(),
1102                                target.getAbsolutePath() );
1103                 }
1104                 else
1105                 {
1106                     throw new ProxyException(
1107                         "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e );
1108                 }
1109             }
1110             finally
1111             {
1112                 FileUtils.deleteQuietly( temp );
1113             }
1114         }
1115     }
1116
1117     /**
1118      * Using wagon, connect to the remote repository.
1119      *
1120      * @param connector        the connector configuration to utilize (for obtaining network proxy configuration from)
1121      * @param wagon            the wagon instance to establish the connection on.
1122      * @param remoteRepository the remote repository to connect to.
1123      * @return true if the connection was successful. false if not connected.
1124      */
1125     private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
1126                                          RemoteRepositoryContent remoteRepository )
1127     {
1128         boolean connected = false;
1129
1130         final ProxyInfo networkProxy =
1131             connector.getProxyId() == null ? null : this.networkProxyMap.get( connector.getProxyId() );
1132
1133         if ( log.isDebugEnabled() )
1134         {
1135             if ( networkProxy != null )
1136             {
1137                 // TODO: move to proxyInfo.toString()
1138                 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
1139                     + " to connect to remote repository " + remoteRepository.getURL();
1140                 if ( networkProxy.getNonProxyHosts() != null )
1141                 {
1142                     msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
1143                 }
1144                 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
1145                 {
1146                     msg += "; as user: " + networkProxy.getUserName();
1147                 }
1148                 log.debug( msg );
1149             }
1150         }
1151
1152         AuthenticationInfo authInfo = null;
1153         String username = remoteRepository.getRepository().getUserName();
1154         String password = remoteRepository.getRepository().getPassword();
1155
1156         if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
1157         {
1158             log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getURL() );
1159             authInfo = new AuthenticationInfo();
1160             authInfo.setUserName( username );
1161             authInfo.setPassword( password );
1162         }
1163
1164         // Convert seconds to milliseconds
1165         int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
1166
1167         // Set timeout  read and connect
1168         // FIXME olamy having 2 config values
1169         wagon.setReadTimeout( timeoutInMilliseconds );
1170         wagon.setTimeout( timeoutInMilliseconds );
1171
1172         try
1173         {
1174             Repository wagonRepository =
1175                 new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
1176             wagon.connect( wagonRepository, authInfo, networkProxy );
1177             connected = true;
1178         }
1179         catch ( ConnectionException e )
1180         {
1181             log.warn( "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
1182             connected = false;
1183         }
1184         catch ( AuthenticationException e )
1185         {
1186             log.warn( "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
1187             connected = false;
1188         }
1189
1190         return connected;
1191     }
1192
1193     /**
1194      * Tests whitelist and blacklist patterns against path.
1195      *
1196      * @param path     the path to test.
1197      * @param patterns the list of patterns to check.
1198      * @return true if the path matches at least 1 pattern in the provided patterns list.
1199      */
1200     private boolean matchesPattern( String path, List<String> patterns )
1201     {
1202         if ( CollectionUtils.isEmpty( patterns ) )
1203         {
1204             return false;
1205         }
1206
1207         if ( !path.startsWith( "/" ) )
1208         {
1209             path = "/" + path;
1210         }
1211
1212         for ( String pattern : patterns )
1213         {
1214             if ( !pattern.startsWith( "/" ) )
1215             {
1216                 pattern = "/" + pattern;
1217             }
1218
1219             if ( SelectorUtils.matchPath( pattern, path, false ) )
1220             {
1221                 return true;
1222             }
1223         }
1224
1225         return false;
1226     }
1227
1228     /**
1229      * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1230      */
1231     public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1232     {
1233         synchronized ( this.proxyConnectorMap )
1234         {
1235             List<ProxyConnector> ret = this.proxyConnectorMap.get( repository.getId() );
1236             if ( ret == null )
1237             {
1238                 return Collections.emptyList();
1239             }
1240
1241             Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1242             return ret;
1243         }
1244     }
1245
1246     public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1247     {
1248         if ( ConfigurationNames.isNetworkProxy( propertyName ) || ConfigurationNames.isManagedRepositories(
1249             propertyName ) || ConfigurationNames.isRemoteRepositories( propertyName )
1250             || ConfigurationNames.isProxyConnector( propertyName ) )
1251         {
1252             initConnectorsAndNetworkProxies();
1253         }
1254     }
1255
1256     protected String addParameters( String path, RemoteRepository remoteRepository )
1257     {
1258         if ( remoteRepository.getExtraParameters().isEmpty() )
1259         {
1260             return path;
1261         }
1262
1263         boolean question = false;
1264
1265         StringBuilder res = new StringBuilder( path == null ? "" : path );
1266
1267         for ( Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
1268         {
1269             if ( !question )
1270             {
1271                 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
1272             }
1273         }
1274
1275         return res.toString();
1276     }
1277
1278
1279     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1280     {
1281         /* do nothing */
1282     }
1283
1284     public ArchivaConfiguration getArchivaConfiguration()
1285     {
1286         return archivaConfiguration;
1287     }
1288
1289     public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1290     {
1291         this.archivaConfiguration = archivaConfiguration;
1292     }
1293
1294     public RepositoryContentFactory getRepositoryFactory()
1295     {
1296         return repositoryFactory;
1297     }
1298
1299     public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1300     {
1301         this.repositoryFactory = repositoryFactory;
1302     }
1303
1304     public MetadataTools getMetadataTools()
1305     {
1306         return metadataTools;
1307     }
1308
1309     public void setMetadataTools( MetadataTools metadataTools )
1310     {
1311         this.metadataTools = metadataTools;
1312     }
1313
1314     public UrlFailureCache getUrlFailureCache()
1315     {
1316         return urlFailureCache;
1317     }
1318
1319     public void setUrlFailureCache( UrlFailureCache urlFailureCache )
1320     {
1321         this.urlFailureCache = urlFailureCache;
1322     }
1323
1324     public WagonFactory getWagonFactory()
1325     {
1326         return wagonFactory;
1327     }
1328
1329     public void setWagonFactory( WagonFactory wagonFactory )
1330     {
1331         this.wagonFactory = wagonFactory;
1332     }
1333
1334     public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
1335     {
1336         return preDownloadPolicies;
1337     }
1338
1339     public void setPreDownloadPolicies( Map<String, PreDownloadPolicy> preDownloadPolicies )
1340     {
1341         this.preDownloadPolicies = preDownloadPolicies;
1342     }
1343
1344     public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
1345     {
1346         return postDownloadPolicies;
1347     }
1348
1349     public void setPostDownloadPolicies( Map<String, PostDownloadPolicy> postDownloadPolicies )
1350     {
1351         this.postDownloadPolicies = postDownloadPolicies;
1352     }
1353
1354     public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
1355     {
1356         return downloadErrorPolicies;
1357     }
1358
1359     public void setDownloadErrorPolicies( Map<String, DownloadErrorPolicy> downloadErrorPolicies )
1360     {
1361         this.downloadErrorPolicies = downloadErrorPolicies;
1362     }
1363 }