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