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