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