]> source.dussan.org Git - archiva.git/blob
398734973a0407fc029428f810d5eef46cf71fc2
[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             localFile.getParentFile().mkdirs();
695             temp = File.createTempFile(localFile.getName() + ".", null, localFile.getParentFile());
696             
697             boolean success = false;
698
699             if ( !localFile.exists() )
700             {
701                 log.debug( "Retrieving " + remotePath + " from " + remoteRepository.getRepository().getName() );
702                 wagon.get( remotePath, temp );
703                 success = true;
704
705                 if ( temp.exists() )
706                 {
707                     moveTempToTarget( temp, localFile );
708                 }
709
710                 // You wouldn't get here on failure, a WagonException would have been thrown.
711                 log.debug( "Downloaded successfully." );
712             }
713             else
714             {
715                 log.debug( "Retrieving " + remotePath + " from " + remoteRepository.getRepository().getName()
716                                        + " if updated" );
717                 success = wagon.getIfNewer( remotePath, temp, localFile.lastModified() );
718                 if ( !success )
719                 {
720                     throw new NotModifiedException(
721                         "Not downloaded, as local file is newer than remote side: " + localFile.getAbsolutePath() );
722                 }
723
724                 if ( temp.exists() )
725                 {
726                     log.debug( "Downloaded successfully." );
727                     moveTempToTarget( temp, localFile );
728                 }
729             }
730
731             return localFile;
732         }
733         catch (IOException e)
734         {
735             throw new ProxyException("Could not create temporary file at " + localFile.getAbsolutePath(), e);
736         }
737         catch ( ResourceDoesNotExistException e )
738         {
739             throw new NotFoundException(
740                 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
741                 e );
742         }
743         catch ( WagonException e )
744         {
745             throw new ProxyException(
746                 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage(),
747                 e );
748         }
749         finally
750         {
751             FileUtils.deleteQuietly(temp);
752         }
753     }
754
755     /**
756      * Apply the policies.
757      *
758      * @param policies  the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
759      * @param settings  the map of settings for the policies to execute. (Map of String policy keys, to String policy setting)
760      * @param request   the request properties (utilized by the {@link DownloadPolicy#applyPolicy(String,Properties,File)})
761      * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(String,Properties,File)})
762      */
763     private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<String, String> settings,
764                                    Properties request, File localFile )
765         throws PolicyViolationException
766     {
767         for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
768         {
769             String key = entry.getKey();
770             DownloadPolicy policy = entry.getValue();
771             String defaultSetting = policy.getDefaultOption();
772             String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
773
774             log.debug( "Applying [" + key + "] policy with [" + setting + "]" );
775             try
776             {
777                 policy.applyPolicy( setting, request, localFile );
778             }
779             catch ( PolicyConfigurationException e )
780             {
781                 log.error( e.getMessage(), e );
782             }
783         }
784     }
785
786     private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
787                                    Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
788                                    File localFile, ProxyException exception, Map<String, Exception> previousExceptions )
789         throws ProxyDownloadException
790     {
791         boolean process = true;
792         for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
793         {
794             String key = entry.getKey();
795             DownloadErrorPolicy policy = entry.getValue();
796             String defaultSetting = policy.getDefaultOption();
797             String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
798
799             log.debug( "Applying [" + key + "] policy with [" + setting + "]" );
800             try
801             {
802                 // all policies must approve the exception, any can cancel
803                 process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
804                 if ( !process )
805                 {
806                     break;
807                 }
808             }
809             catch ( PolicyConfigurationException e )
810             {
811                 log.error( e.getMessage(), e );
812             }
813         }
814
815         if ( process )
816         {
817             // if the exception was queued, don't throw it
818             if ( !previousExceptions.containsKey( content.getId() ) )
819             {
820                 throw new ProxyDownloadException(
821                     "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
822                     content.getId(), exception );
823             }
824         }
825         else
826         {
827             // if the exception was queued, but cancelled, remove it
828             previousExceptions.remove( content.getId() );
829         }
830
831         log.warn( "Transfer error from repository \"" + content.getRepository().getId() + "\" for artifact " +
832             Keys.toKey( artifact ) + ", continuing to next repository. Error message: " + exception.getMessage() );
833         log.debug( "Full stack trace", exception );
834     }
835
836     /**
837      * Used to move the temporary file to its real destination.  This is patterned from the way WagonManager handles
838      * its downloaded files.
839      *
840      * @param temp   The completed download file
841      * @param target The final location of the downloaded file
842      * @throws ProxyException when the temp file cannot replace the target file
843      */
844     private void moveTempToTarget( File temp, File target )
845         throws ProxyException
846     {
847         if ( target.exists() && !target.delete() )
848         {
849             throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
850         }
851
852         if ( !temp.renameTo( target ) )
853         {
854             log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );
855
856             try
857             {
858                 FileUtils.copyFile( temp, target );
859             }
860             catch ( IOException e )
861             {
862                 if (target.exists())
863                 {
864                     log.debug("Tried to copy file " + temp.getName() + " to " + target.getAbsolutePath() + " but file with this name already exists.");
865                 }
866                 else
867                 {
868                     throw new ProxyException( "Cannot copy tmp file " + temp.getAbsolutePath() + " to its final location", e );
869                 }
870             }
871             finally
872             {
873                 FileUtils.deleteQuietly(temp);
874             }
875         }
876     }
877
878     /**
879      * Using wagon, connect to the remote repository.
880      *
881      * @param connector        the connector configuration to utilize (for obtaining network proxy configuration from)
882      * @param wagon            the wagon instance to establish the connection on.
883      * @param remoteRepository the remote repository to connect to.
884      * @return true if the connection was successful. false if not connected.
885      */
886     private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
887                                          RemoteRepositoryContent remoteRepository )
888     {
889         boolean connected = false;
890
891         ProxyInfo networkProxy = null;
892         synchronized ( this.networkProxyMap )
893         {
894             networkProxy = (ProxyInfo) this.networkProxyMap.get( connector.getProxyId() );
895         }
896
897         try
898         {
899             AuthenticationInfo authInfo = null;
900             String username = remoteRepository.getRepository().getUsername();
901             String password = remoteRepository.getRepository().getPassword();
902
903             if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
904             {
905                 log.debug( "Using username " + username + " to connect to remote repository "
906                                        + remoteRepository.getURL() );
907                 authInfo = new AuthenticationInfo();
908                 authInfo.setUserName( username );
909                 authInfo.setPassword( password );
910             }
911             else
912             {
913                 log.debug( "No authentication for remote repository needed" );
914             }
915
916             //Convert seconds to milliseconds
917             int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
918
919             //Set timeout
920             wagon.setTimeout(timeoutInMilliseconds);
921
922             Repository wagonRepository = new Repository( remoteRepository.getId(), remoteRepository.getURL().toString() );
923             if ( networkProxy != null )
924             {
925                 wagon.connect( wagonRepository, authInfo, networkProxy );
926             }
927             else
928             {
929                 wagon.connect( wagonRepository, authInfo );
930             }
931             connected = true;
932         }
933         catch ( ConnectionException e )
934         {
935             log.warn(
936                 "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
937             connected = false;
938         }
939         catch ( AuthenticationException e )
940         {
941             log.warn(
942                 "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
943             connected = false;
944         }
945
946         return connected;
947     }
948
949     /**
950      * Tests whitelist and blacklist patterns against path.
951      *
952      * @param path     the path to test.
953      * @param patterns the list of patterns to check.
954      * @return true if the path matches at least 1 pattern in the provided patterns list.
955      */
956     private boolean matchesPattern( String path, List<String> patterns )
957     {
958         if ( CollectionUtils.isEmpty( patterns ) )
959         {
960             return false;
961         }
962
963         for ( String pattern : patterns )
964         {
965             if ( SelectorUtils.matchPath( pattern, path, false ) )
966             {
967                 return true;
968             }
969         }
970
971         return false;
972     }
973
974     /**
975      * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
976      */
977     public List<ProxyConnector> getProxyConnectors( ManagedRepositoryContent repository )
978     {
979         synchronized ( this.proxyConnectorMap )
980         {
981             List<ProxyConnector> ret = (List<ProxyConnector>) this.proxyConnectorMap.get( repository.getId() );
982             if ( ret == null )
983             {
984                 return Collections.EMPTY_LIST;
985             }
986
987             Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
988             return ret;
989         }
990     }
991
992     public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
993     {
994         if ( ConfigurationNames.isNetworkProxy( propertyName ) ||
995             ConfigurationNames.isManagedRepositories( propertyName ) ||
996             ConfigurationNames.isRemoteRepositories( propertyName ) ||
997             ConfigurationNames.isProxyConnector( propertyName ) )
998         {
999             initConnectorsAndNetworkProxies();
1000         }
1001     }
1002
1003     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
1004     {
1005         /* do nothing */
1006     }
1007     
1008     private void logProcess( String managedRepoId, String resource, String event )
1009     {
1010         
1011     }
1012     
1013     private void logRejection( String managedRepoId, String remoteRepoId, String resource, String reason )
1014     {
1015         
1016     }
1017
1018     private void initConnectorsAndNetworkProxies()
1019     {
1020         synchronized ( this.proxyConnectorMap )
1021         {
1022             ProxyConnectorOrderComparator proxyOrderSorter = new ProxyConnectorOrderComparator();
1023             this.proxyConnectorMap.clear();
1024
1025             List<ProxyConnectorConfiguration> proxyConfigs = archivaConfiguration.getConfiguration()
1026                 .getProxyConnectors();
1027             for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
1028             {
1029                 String key = proxyConfig.getSourceRepoId();
1030
1031                 try
1032                 {
1033                     // Create connector object.
1034                     ProxyConnector connector = new ProxyConnector();
1035
1036                     connector.setSourceRepository( repositoryFactory.getManagedRepositoryContent( proxyConfig
1037                         .getSourceRepoId() ) );
1038                     connector.setTargetRepository( repositoryFactory.getRemoteRepositoryContent( proxyConfig
1039                         .getTargetRepoId() ) );
1040
1041                     connector.setProxyId( proxyConfig.getProxyId() );
1042                     connector.setPolicies( proxyConfig.getPolicies() );
1043                     connector.setOrder( proxyConfig.getOrder() );
1044
1045                     // Copy any blacklist patterns.
1046                     List<String> blacklist = new ArrayList<String>();
1047                     if ( CollectionUtils.isNotEmpty( proxyConfig.getBlackListPatterns() ) )
1048                     {
1049                         blacklist.addAll( proxyConfig.getBlackListPatterns() );
1050                     }
1051                     connector.setBlacklist( blacklist );
1052
1053                     // Copy any whitelist patterns.
1054                     List<String> whitelist = new ArrayList<String>();
1055                     if ( CollectionUtils.isNotEmpty( proxyConfig.getWhiteListPatterns() ) )
1056                     {
1057                         whitelist.addAll( proxyConfig.getWhiteListPatterns() );
1058                     }
1059                     connector.setWhitelist( whitelist );
1060
1061                     // Get other connectors
1062                     List<ProxyConnector> connectors = this.proxyConnectorMap.get( key );
1063                     if ( connectors == null )
1064                     {
1065                         // Create if we are the first.
1066                         connectors = new ArrayList<ProxyConnector>();
1067                     }
1068
1069                     // Add the connector.
1070                     connectors.add( connector );
1071
1072                     // Ensure the list is sorted.
1073                     Collections.sort( connectors, proxyOrderSorter );
1074
1075                     // Set the key to the list of connectors.
1076                     this.proxyConnectorMap.put( key, connectors );
1077                 }
1078                 catch ( RepositoryNotFoundException e )
1079                 {
1080                     log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
1081                 }
1082                 catch ( RepositoryException e )
1083                 {
1084                     log.warn( "Unable to use proxy connector: " + e.getMessage(), e );
1085                 }
1086             }
1087
1088         }
1089
1090         synchronized ( this.networkProxyMap )
1091         {
1092             this.networkProxyMap.clear();
1093
1094             List<NetworkProxyConfiguration> networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
1095             for ( NetworkProxyConfiguration networkProxyConfig : networkProxies )
1096             {
1097                 String key = networkProxyConfig.getId();
1098
1099                 ProxyInfo proxy = new ProxyInfo();
1100
1101                 proxy.setType( networkProxyConfig.getProtocol() );
1102                 proxy.setHost( networkProxyConfig.getHost() );
1103                 proxy.setPort( networkProxyConfig.getPort() );
1104                 proxy.setUserName( networkProxyConfig.getUsername() );
1105                 proxy.setPassword( networkProxyConfig.getPassword() );
1106
1107                 this.networkProxyMap.put( key, proxy );
1108             }
1109         }
1110     }
1111
1112     public void initialize()
1113         throws InitializationException
1114     {
1115         initConnectorsAndNetworkProxies();
1116         archivaConfiguration.addChangeListener( this );
1117     }
1118 }