]> source.dussan.org Git - archiva.git/blob
3604bc20b9a1db17eb94fe64faad53b92da286ae
[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 org.apache.commons.collections.CollectionUtils;
23 import org.apache.commons.io.FileUtils;
24 import org.apache.commons.lang.StringUtils;
25 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
26 import org.apache.maven.archiva.configuration.ConfigurationNames;
27 import org.apache.maven.archiva.configuration.NetworkProxyConfiguration;
28 import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
29 import org.apache.maven.archiva.model.ArtifactReference;
30 import org.apache.maven.archiva.model.Keys;
31 import org.apache.maven.archiva.model.ProjectReference;
32 import org.apache.maven.archiva.model.RepositoryURL;
33 import org.apache.maven.archiva.model.VersionedReference;
34 import org.apache.maven.archiva.policies.DownloadErrorPolicy;
35 import org.apache.maven.archiva.policies.DownloadPolicy;
36 import org.apache.maven.archiva.policies.PolicyConfigurationException;
37 import org.apache.maven.archiva.policies.PolicyViolationException;
38 import org.apache.maven.archiva.policies.PostDownloadPolicy;
39 import org.apache.maven.archiva.policies.PreDownloadPolicy;
40 import org.apache.maven.archiva.policies.ProxyDownloadException;
41 import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
42 import org.apache.maven.archiva.repository.ContentNotFoundException;
43 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
44 import org.apache.maven.archiva.repository.RemoteRepositoryContent;
45 import org.apache.maven.archiva.repository.RepositoryContentFactory;
46 import org.apache.maven.archiva.repository.RepositoryException;
47 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
48 import org.apache.maven.archiva.repository.layout.LayoutException;
49 import org.apache.maven.archiva.repository.metadata.MetadataTools;
50 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
51 import org.apache.maven.archiva.repository.scanner.RepositoryContentConsumers;
52 import org.apache.maven.wagon.ConnectionException;
53 import org.apache.maven.wagon.ResourceDoesNotExistException;
54 import org.apache.maven.wagon.Wagon;
55 import org.apache.maven.wagon.WagonException;
56 import org.apache.maven.wagon.authentication.AuthenticationException;
57 import org.apache.maven.wagon.authentication.AuthenticationInfo;
58 import org.apache.maven.wagon.proxy.ProxyInfo;
59 import org.apache.maven.wagon.repository.Repository;
60 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
61 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
62 import org.codehaus.plexus.registry.Registry;
63 import org.codehaus.plexus.registry.RegistryListener;
64 import org.codehaus.plexus.util.SelectorUtils;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68 import java.io.File;
69 import java.io.IOException;
70 import java.util.ArrayList;
71 import java.util.Collections;
72 import java.util.HashMap;
73 import java.util.LinkedHashMap;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Map.Entry;
77 import java.util.Properties;
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 role="org.apache.maven.wagon.Wagon"
99      */
100     private Map<String, Wagon> wagons;
101
102     /**
103      * @plexus.requirement
104      */
105     private RepositoryContentFactory repositoryFactory;
106
107     /**
108      * @plexus.requirement
109      */
110     private MetadataTools metadataTools;
111
112     /**
113      * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
114      */
115     private Map<String, PreDownloadPolicy> preDownloadPolicies;
116
117     /**
118      * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
119      */
120     private Map<String, PostDownloadPolicy> postDownloadPolicies;
121
122     /**
123      * @plexus.requirement role="org.apache.maven.archiva.policies.DownloadErrorPolicy"
124      */
125     private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
126
127     /**
128      * @plexus.requirement role-hint="default"
129      */
130     private UrlFailureCache urlFailureCache;
131
132     private Map<String, List<ProxyConnector>> proxyConnectorMap = new HashMap<String, List<ProxyConnector>>();
133
134     private Map<String, ProxyInfo> networkProxyMap = new HashMap<String, ProxyInfo>();
135
136     /**
137      * @plexus.requirement
138      */
139     private RepositoryContentConsumers consumers;
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         Wagon wagon = null;
593         try
594         {
595             RepositoryURL repoUrl = remoteRepository.getURL();
596             String protocol = repoUrl.getProtocol();
597             wagon = (Wagon) wagons.get( protocol );
598             if ( wagon == null )
599             {
600                 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
601             }
602
603             boolean connected = connectToRepository( connector, wagon, remoteRepository );
604             if ( connected )
605             {
606                 localFile = transferSimpleFile( wagon, remoteRepository, remotePath, repository, localFile );
607
608                 // TODO: these should be used to validate the download based on the policies, not always downloaded to
609                 //   save on connections since md5 is rarely used
610                 transferChecksum( wagon, remoteRepository, remotePath, repository, localFile, ".sha1" );
611                 transferChecksum( wagon, remoteRepository, remotePath, repository, localFile, ".md5" );
612             }
613         }
614         catch ( NotFoundException e )
615         {
616             urlFailureCache.cacheFailure( url );
617             throw e;
618         }
619         catch ( NotModifiedException e )
620         {
621             // Do not cache url here.
622             throw e;
623         }
624         catch ( ProxyException e )
625         {
626             urlFailureCache.cacheFailure( url );
627             throw e;
628         }
629         finally
630         {
631             if ( wagon != null )
632             {
633                 try
634                 {
635                     wagon.disconnect();
636                 }
637                 catch ( ConnectionException e )
638                 {
639                     log.warn( "Unable to disconnect wagon.", e );
640                 }
641             }
642         }
643
644         // Handle post-download policies.
645         try
646         {
647             validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, localFile );
648         }
649         catch ( PolicyViolationException e )
650         {
651             log.info( "Transfer invalidated from " + url + " : " + e.getMessage() );
652             if ( fileExists( localFile ) )
653             {
654                 return localFile;
655             }
656
657             return null;
658         }
659
660         if ( executeConsumers )
661         {
662             // Just-in-time update of the index and database by executing the consumers for this artifact
663             consumers.executeConsumers( connector.getSourceRepository().getRepository(), localFile );
664         }
665
666         // Everything passes.
667         return localFile;
668     }
669
670     /**
671      * <p>
672      * Quietly transfer the checksum file from the remote repository to the local file.
673      * </p>
674      *
675      * @param wagon            the wagon instance (should already be connected) to use.
676      * @param remoteRepository the remote repository to transfer from.
677      * @param remotePath       the remote path to the resource to get.
678      * @param repository       the managed repository that will hold the file
679      * @param localFile        the local file that should contain the downloaded contents
680      * @param type             the type of checksum to transfer (example: ".md5" or ".sha1")
681      * @throws ProxyException if copying the downloaded file into place did not succeed.
682      */
683     private void transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
684                                    ManagedRepositoryContent repository, File localFile, String type )
685         throws ProxyException
686     {
687         String url = remoteRepository.getURL().getUrl() + remotePath;
688
689         // Transfer checksum does not use the policy.
690         if ( urlFailureCache.hasFailedBefore( url + type ) )
691         {
692             return;
693         }
694
695         try
696         {
697             File hashFile = new File( localFile.getAbsolutePath() + type );
698             transferSimpleFile( wagon, remoteRepository, remotePath + type, repository, hashFile );
699             log.debug( "Checksum" + type + " Downloaded: " + hashFile );
700         }
701         catch ( NotFoundException e )
702         {
703             urlFailureCache.cacheFailure( url + type );
704             log.debug( "Transfer failed, checksum not found: " + url );
705             // Consume it, do not pass this on.
706         }
707         catch ( NotModifiedException e )
708         {
709             log.debug( "Transfer skipped, checksum not modified: " + url );
710             // Consume it, do not pass this on.
711         }
712         catch ( ProxyException e )
713         {
714             urlFailureCache.cacheFailure( url + type );
715             log.warn( "Transfer failed on checksum: " + url + " : " + e.getMessage(), e );
716             // Critical issue, pass it on.
717             throw e;
718         }
719     }
720
721     /**
722      * Perform the transfer of the remote file to the local file specified.
723      *
724      * @param wagon            the wagon instance to use.
725      * @param remoteRepository the remote repository to use
726      * @param remotePath       the remote path to attempt to get
727      * @param repository       the managed repository that will hold the file
728      * @param localFile        the local file to save to
729      * @return The local file that was transfered.
730      * @throws ProxyException if there was a problem moving the downloaded file into place.
731      * @throws WagonException if there was a problem tranfering the file.
732      */
733     private File transferSimpleFile( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
734                                      ManagedRepositoryContent repository, File localFile )
735         throws ProxyException
736     {
737         assert ( remotePath != null );
738
739         // Transfer the file.
740         File temp = null;
741
742         try
743         {
744             temp = File.createTempFile(localFile.getName() + ".", null, new File( repository.getRepoRoot() ));
745
746             boolean success = false;
747
748             if ( !localFile.exists() )
749             {
750                 log.debug( "Retrieving " + remotePath + " from " + remoteRepository.getRepository().getName() );
751                 wagon.get( remotePath, temp );
752                 success = true;
753
754                 if ( temp.exists() )
755                 {
756                     moveTempToTarget( temp, localFile );
757                 }
758
759                 // You wouldn't get here on failure, a WagonException would have been thrown.
760                 log.debug( "Downloaded successfully." );
761             }
762             else
763             {
764                 log.debug( "Retrieving " + remotePath + " from " + remoteRepository.getRepository().getName()
765                                        + " if updated" );
766                 success = wagon.getIfNewer( remotePath, temp, localFile.lastModified() );
767                 if ( !success )
768                 {
769                     throw new NotModifiedException(
770                         "Not downloaded, as local file is newer than remote side: " + localFile.getAbsolutePath() );
771                 }
772
773                 if ( temp.exists() )
774                 {
775                     log.debug( "Downloaded successfully." );
776                     moveTempToTarget( temp, localFile );
777                 }
778             }
779
780             return localFile;
781         }
782         catch (IOException e)
783         {
784             throw new ProxyException("Could not create temporary file at " + localFile.getAbsolutePath(), e);
785         }
786         catch ( ResourceDoesNotExistException e )
787         {
788             throw new NotFoundException(
789                 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
790                 e );
791         }
792         catch ( WagonException e )
793         {
794             throw new ProxyException(
795                 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage(),
796                 e );
797         }
798         finally
799         {
800             FileUtils.deleteQuietly(temp);
801         }
802     }
803
804     /**
805      * Apply the policies.
806      *
807      * @param policies  the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
808      * @param settings  the map of settings for the policies to execute. (Map of String policy keys, to String policy setting)
809      * @param request   the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String,Properties,File)})
810      * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String,Properties,File)})
811      */
812     private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
813                                    Properties request, File localFile )
814         throws PolicyViolationException
815     {
816         for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
817         {
818             String key = entry.getKey();
819             DownloadPolicy policy = entry.getValue();
820             String defaultSetting = policy.getDefaultOption();
821             String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
822
823             log.debug( "Applying [" + key + "] policy with [" + setting + "]" );
824             try
825             {
826                 policy.applyPolicy( setting, request, localFile );
827             }
828             catch ( PolicyConfigurationException e )
829             {
830                 log.error( e.getMessage(), e );
831             }
832         }
833     }
834
835     private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
836                                    Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
837                                    File localFile, ProxyException exception, Map<String, Exception> previousExceptions )
838         throws ProxyDownloadException
839     {
840         boolean process = true;
841         for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
842         {
843             String key = entry.getKey();
844             DownloadErrorPolicy policy = entry.getValue();
845             String defaultSetting = policy.getDefaultOption();
846             String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
847
848             log.debug( "Applying [" + key + "] policy with [" + setting + "]" );
849             try
850             {
851                 // all policies must approve the exception, any can cancel
852                 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
853                 if ( !process )
854                 {
855                     break;
856                 }
857             }
858             catch ( PolicyConfigurationException e )
859             {
860                 log.error( e.getMessage(), e );
861             }
862         }
863
864         if ( process )
865         {
866             // if the exception was queued, don't throw it
867             if ( !previousExceptions.containsKey( content.getId() ) )
868             {
869                 throw new ProxyDownloadException(
870                     "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
871                     content.getId(), exception );
872             }
873         }
874         else
875         {
876             // if the exception was queued, but cancelled, remove it
877             previousExceptions.remove( content.getId() );
878         }
879
880         log.warn( "Transfer error from repository \"" + content.getRepository().getId() + "\" for artifact " +
881             Keys.toKey( artifact ) + ", continuing to next repository. Error message: " + exception.getMessage() );
882         log.debug( "Full stack trace", exception );
883     }
884
885     /**
886      * Used to move the temporary file to its real destination.  This is patterned from the way WagonManager handles
887      * its downloaded files.
888      *
889      * @param temp   The completed download file
890      * @param target The final location of the downloaded file
891      * @throws ProxyException when the temp file cannot replace the target file
892      */
893     private void moveTempToTarget( File temp, File target )
894         throws ProxyException
895     {
896         if ( target.exists() && !target.delete() )
897         {
898             throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
899         }
900
901         target.getParentFile().mkdirs();
902         if ( !temp.renameTo( target ) )
903         {
904             log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
905
906             try
907             {
908                 FileUtils.copyFile( temp, target );
909             }
910             catch ( IOException e )
911             {
912                 if (target.exists())
913                 {
914                     log.debug("Tried to copy file " + temp.getName() + " to " + target.getAbsolutePath() + " but file with this name already exists.");
915                 }
916                 else
917                 {
918                     throw new ProxyException( "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e );
919                 }
920             }
921             finally
922             {
923                 FileUtils.deleteQuietly(temp);
924             }
925         }
926     }
927
928     /**
929      * Using wagon, connect to the remote repository.
930      *
931      * @param connector        the connector configuration to utilize (for obtaining network proxy configuration from)
932      * @param wagon            the wagon instance to establish the connection on.
933      * @param remoteRepository the remote repository to connect to.
934      * @return true if the connection was successful. false if not connected.
935      */
936     private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
937                                          RemoteRepositoryContent remoteRepository )
938     {
939         boolean connected = false;
940
941         ProxyInfo networkProxy = null;
942         synchronized ( this.networkProxyMap )
943         {
944             networkProxy = (ProxyInfo) this.networkProxyMap.get( connector.getProxyId() );
945         }
946
947         try
948         {
949             AuthenticationInfo authInfo = null;
950             String username = remoteRepository.getRepository().getUsername();
951             String password = remoteRepository.getRepository().getPassword();
952
953             if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
954             {
955                 log.debug( "Using username " + username + " to connect to remote repository "
956                                        + remoteRepository.getURL() );
957                 authInfo = new AuthenticationInfo();
958                 authInfo.setUserName( username );
959                 authInfo.setPassword( password );
960             }
961             else
962             {
963                 log.debug( "No authentication for remote repository needed" );
964             }
965
966             //Convert seconds to milliseconds
967             int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
968
969             //Set timeout
970             wagon.setTimeout(timeoutInMilliseconds);
971
972             Repository wagonRepository = new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
973             if ( networkProxy != null )
974             {
975                 wagon.connect( wagonRepository, authInfo, networkProxy );
976             }
977             else
978             {
979                 wagon.connect( wagonRepository, authInfo );
980             }
981             connected = true;
982         }
983         catch ( ConnectionException e )
984         {
985             log.warn(
986                 "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
987             connected = false;
988         }
989         catch ( AuthenticationException e )
990         {
991             log.warn(
992                 "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
993             connected = false;
994         }
995
996         return connected;
997     }
998
999     /**
1000      * Tests whitelist and blacklist patterns against path.
1001      *
1002      * @param path     the path to test.
1003      * @param patterns the list of patterns to check.
1004      * @return true if the path matches at least 1 pattern in the provided patterns list.
1005      */
1006     private boolean matchesPattern( String path, List<String> patterns )
1007     {
1008         if ( CollectionUtils.isEmpty( patterns ) )
1009         {
1010             return false;
1011         }
1012
1013         for ( String pattern : patterns )
1014         {
1015             if ( SelectorUtils.matchPath( pattern, path, false ) )
1016             {
1017                 return true;
1018             }
1019         }
1020
1021         return false;
1022     }
1023
1024     /**
1025      * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
1026      */
1027     public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
1028     {
1029         synchronized ( this.proxyConnectorMap )
1030         {
1031             List<ProxyConnector> ret = (List<ProxyConnector>) this.proxyConnectorMap.get( repository.getId() );
1032             if ( ret == null )
1033             {
1034                 return Collections.emptyList();
1035             }
1036
1037             Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
1038             return ret;
1039         }
1040     }
1041
1042     public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1043     {
1044         if ( ConfigurationNames.isNetworkProxy( propertyName ) ||
1045             ConfigurationNames.isManagedRepositories( propertyName ) ||
1046             ConfigurationNames.isRemoteRepositories( propertyName ) ||
1047             ConfigurationNames.isProxyConnector( propertyName ) )
1048         {
1049             initConnectorsAndNetworkProxies();
1050         }
1051     }
1052
1053     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1054     {
1055         /* do nothing */
1056     }
1057
1058     @SuppressWarnings("unchecked")
1059     private void initConnectorsAndNetworkProxies()
1060     {
1061         synchronized ( this.proxyConnectorMap )
1062         {
1063             ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
1064             this.proxyConnectorMap.clear();
1065
1066             List<ProxyConnectorConfiguration> proxyConfigs = archivaConfiguration.getConfiguration()
1067                 .getProxyConnectors();
1068             for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
1069             {
1070                 String key = proxyConfig.getSourceRepoId();
1071
1072                 try
1073                 {
1074                     // Create connector object.
1075                     ProxyConnector connector = new ProxyConnector();
1076
1077                     connector.setSourceRepository( repositoryFactory.getManagedRepositoryContent( proxyConfig
1078                         .getSourceRepoId() ) );
1079                     connector.setTargetRepository( repositoryFactory.getRemoteRepositoryContent( proxyConfig
1080                         .getTargetRepoId() ) );
1081
1082                     connector.setProxyId( proxyConfig.getProxyId() );
1083                     connector.setPolicies( proxyConfig.getPolicies() );
1084                     connector.setOrder( proxyConfig.getOrder() );
1085
1086                     // Copy any blacklist patterns.
1087                     List<String> blacklist = new ArrayList<String>();
1088                     if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
1089                     {
1090                         blacklist.addAll( proxyConfig.getBlackListPatterns() );
1091                     }
1092                     connector.setBlacklist( blacklist );
1093
1094                     // Copy any whitelist patterns.
1095                     List<String> whitelist = new ArrayList<String>();
1096                     if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
1097                     {
1098                         whitelist.addAll( proxyConfig.getWhiteListPatterns() );
1099                     }
1100                     connector.setWhitelist( whitelist );
1101
1102                     // Get other connectors
1103                     List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
1104                     if ( connectors == null )
1105                     {
1106                         // Create if we are the first.
1107                         connectors = new ArrayList<ProxyConnector>();
1108                     }
1109
1110                     // Add the connector.
1111                     connectors.add( connector );
1112
1113                     // Ensure the list is sorted.
1114                     Collections.sort( connectors, proxyOrderSorter );
1115
1116                     // Set the key to the list of connectors.
1117                     this.proxyConnectorMap.put( key, connectors );
1118                 }
1119                 catch ( RepositoryNotFoundException e )
1120                 {
1121                     log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
1122                 }
1123                 catch ( RepositoryException e )
1124                 {
1125                     log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
1126                 }
1127             }
1128
1129         }
1130
1131         synchronized ( this.networkProxyMap )
1132         {
1133             this.networkProxyMap.clear();
1134
1135             List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
1136             for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
1137             {
1138                 String key = networkProxyConfig.getId();
1139
1140                 ProxyInfo proxy = new ProxyInfo();
1141
1142                 proxy.setType( networkProxyConfig.getProtocol() );
1143                 proxy.setHost( networkProxyConfig.getHost() );
1144                 proxy.setPort( networkProxyConfig.getPort() );
1145                 proxy.setUserName( networkProxyConfig.getUsername() );
1146                 proxy.setPassword( networkProxyConfig.getPassword() );
1147
1148                 this.networkProxyMap.put( key, proxy );
1149             }
1150         }
1151     }
1152
1153     public void initialize()
1154         throws InitializationException
1155     {
1156         initConnectorsAndNetworkProxies();
1157         archivaConfiguration.addChangeListener( this );
1158     }
1159 }