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