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