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