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