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