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