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