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