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