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