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