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