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