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