]> source.dussan.org Git - archiva.git/blob
e5c0b4b0dfe495289e95df3a037c2ae7cecb63ce
[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 org.apache.archiva.checksum.ChecksumAlgorithm;
23 import org.apache.archiva.checksum.ChecksumUtil;
24 import org.apache.archiva.common.filelock.FileLockManager;
25 import org.apache.archiva.common.utils.PathUtil;
26 import org.apache.archiva.configuration.ArchivaConfiguration;
27 import org.apache.archiva.configuration.ProxyConnectorConfiguration;
28 import org.apache.archiva.configuration.ProxyConnectorRuleConfiguration;
29 import org.apache.archiva.model.ArtifactReference;
30 import org.apache.archiva.model.Keys;
31 import org.apache.archiva.policies.DownloadErrorPolicy;
32 import org.apache.archiva.policies.DownloadPolicy;
33 import org.apache.archiva.policies.Policy;
34 import org.apache.archiva.policies.PolicyConfigurationException;
35 import org.apache.archiva.policies.PolicyOption;
36 import org.apache.archiva.policies.PolicyViolationException;
37 import org.apache.archiva.policies.PostDownloadPolicy;
38 import org.apache.archiva.policies.PreDownloadPolicy;
39 import org.apache.archiva.policies.ProxyDownloadException;
40 import org.apache.archiva.policies.urlcache.UrlFailureCache;
41 import org.apache.archiva.proxy.model.NetworkProxy;
42 import org.apache.archiva.proxy.model.ProxyConnector;
43 import org.apache.archiva.proxy.model.ProxyFetchResult;
44 import org.apache.archiva.proxy.model.RepositoryProxyHandler;
45 import org.apache.archiva.components.taskqueue.TaskQueueException;
46 import org.apache.archiva.repository.BaseRepositoryContentLayout;
47 import org.apache.archiva.repository.LayoutException;
48 import org.apache.archiva.repository.ManagedRepository;
49 import org.apache.archiva.repository.RemoteRepository;
50 import org.apache.archiva.repository.RemoteRepositoryContent;
51 import org.apache.archiva.repository.RepositoryType;
52 import org.apache.archiva.repository.metadata.base.MetadataTools;
53 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
54 import org.apache.archiva.repository.storage.fs.FilesystemStorage;
55 import org.apache.archiva.repository.storage.fs.FsStorageUtil;
56 import org.apache.archiva.repository.storage.StorageAsset;
57 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
58 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
59 import org.apache.commons.collections4.CollectionUtils;
60 import org.apache.commons.lang3.StringUtils;
61 import org.apache.commons.lang3.SystemUtils;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64 import org.slf4j.MarkerFactory;
65
66 import javax.annotation.PostConstruct;
67 import javax.inject.Inject;
68 import javax.inject.Named;
69 import java.io.IOException;
70 import java.net.MalformedURLException;
71 import java.nio.file.Files;
72 import java.nio.file.Path;
73 import java.nio.file.StandardCopyOption;
74 import java.util.ArrayList;
75 import java.util.Collections;
76 import java.util.HashMap;
77 import java.util.LinkedHashMap;
78 import java.util.List;
79 import java.util.Map;
80 import java.util.Properties;
81 import java.util.concurrent.ConcurrentHashMap;
82 import java.util.concurrent.ConcurrentMap;
83
84 public abstract class DefaultRepositoryProxyHandler implements RepositoryProxyHandler {
85
86     protected Logger log = LoggerFactory.getLogger( DefaultRepositoryProxyHandler.class );
87     @Inject
88     protected UrlFailureCache urlFailureCache;
89
90     @Inject
91     @Named(value = "metadataTools#default")
92     private MetadataTools metadataTools;
93
94     private Map<String, PreDownloadPolicy> preDownloadPolicies = new HashMap<>(  );
95     private Map<String, PostDownloadPolicy> postDownloadPolicies = new HashMap<>(  );
96     private Map<String, DownloadErrorPolicy> downloadErrorPolicies = new HashMap<>(  );
97     private ConcurrentMap<String, List<ProxyConnector>> proxyConnectorMap = new ConcurrentHashMap<>();
98
99     @Inject
100     @Named(value = "archivaTaskScheduler#repository")
101     private ArchivaTaskScheduler<RepositoryTask> scheduler;
102
103     @Inject
104     private ArchivaConfiguration archivaConfiguration;
105
106     @Inject
107     @Named(value = "fileLockManager#default")
108     private FileLockManager fileLockManager;
109
110     private Map<String, NetworkProxy> networkProxyMap = new ConcurrentHashMap<>();
111     private List<ChecksumAlgorithm> checksumAlgorithms;
112
113     @PostConstruct
114     public void initialize()
115     {
116         checksumAlgorithms = ChecksumUtil.getAlgorithms(archivaConfiguration.getConfiguration().getArchivaRuntimeConfiguration().getChecksumTypes());
117     }
118
119     private List<ProxyConnectorRuleConfiguration> findProxyConnectorRules(String sourceRepository,
120                                                                           String targetRepository,
121                                                                           List<ProxyConnectorRuleConfiguration> all )
122     {
123         List<ProxyConnectorRuleConfiguration> proxyConnectorRuleConfigurations = new ArrayList<>();
124
125         for ( ProxyConnectorRuleConfiguration proxyConnectorRuleConfiguration : all )
126         {
127             for ( ProxyConnectorConfiguration proxyConnector : proxyConnectorRuleConfiguration.getProxyConnectors() )
128             {
129                 if ( StringUtils.equals( sourceRepository, proxyConnector.getSourceRepoId() ) && StringUtils.equals(
130                     targetRepository, proxyConnector.getTargetRepoId() ) )
131                 {
132                     proxyConnectorRuleConfigurations.add( proxyConnectorRuleConfiguration );
133                 }
134             }
135         }
136
137         return proxyConnectorRuleConfigurations;
138     }
139
140
141
142     @Override
143     public StorageAsset fetchFromProxies( ManagedRepository repository, ArtifactReference artifact )
144         throws ProxyDownloadException
145     {
146         StorageAsset localFile = null;
147         Map<String, Exception> previousExceptions = new LinkedHashMap<>();
148         try
149         {
150             localFile = toLocalFile( repository, artifact );
151         }
152         catch ( LayoutException e )
153         {
154             previousExceptions.put( "LayoutException", e );
155             throw new ProxyDownloadException( "Could not convert to BasicRepositoryContentLayout " + e.getMessage( ), previousExceptions);
156         }
157
158         Properties requestProperties = new Properties();
159         requestProperties.setProperty( "filetype", "artifact" );
160         requestProperties.setProperty( "version", artifact.getVersion() );
161         requestProperties.setProperty( "managedRepositoryId", repository.getId() );
162
163         List<ProxyConnector> connectors = getProxyConnectors( repository );
164         for ( ProxyConnector connector : connectors )
165         {
166             if ( !connector.isEnabled() )
167             {
168                 continue;
169             }
170
171             RemoteRepository targetRepository = connector.getTargetRepository();
172             requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
173
174             String targetPath = targetRepository.getContent().toPath( artifact );
175
176             if ( SystemUtils.IS_OS_WINDOWS )
177             {
178                 // toPath use system PATH_SEPARATOR so on windows url are \ which doesn't work very well :-)
179                 targetPath = PathUtil.separatorsToUnix( targetPath );
180             }
181
182             try
183             {
184                 StorageAsset downloadedFile =
185                     transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
186                                   true );
187
188                 if ( fileExists(downloadedFile) )
189                 {
190                     log.debug( "Successfully transferred: {}", downloadedFile.getPath() );
191                     return downloadedFile;
192                 }
193             }
194             catch ( NotFoundException e )
195             {
196                 log.debug( "Artifact {} not found on repository \"{}\".", Keys.toKey( artifact ),
197                            targetRepository.getId() );
198             }
199             catch ( NotModifiedException e )
200             {
201                 log.debug( "Artifact {} not updated on repository \"{}\".", Keys.toKey( artifact ),
202                            targetRepository.getId() );
203             }
204             catch ( ProxyException e )
205             {
206                 validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
207                                   targetRepository.getContent(), localFile, e, previousExceptions );
208             }
209         }
210
211         if ( !previousExceptions.isEmpty() )
212         {
213             throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
214                                               previousExceptions );
215         }
216
217         log.debug( "Exhausted all target repositories, artifact {} not found.", Keys.toKey( artifact ) );
218
219         return null;
220     }
221
222     @Override
223     public StorageAsset fetchFromProxies( ManagedRepository repository, String path )
224     {
225         StorageAsset localFile = repository.getAsset( path );
226
227         // no update policies for these paths
228         if ( localFile.exists() )
229         {
230             return null;
231         }
232
233         Properties requestProperties = new Properties();
234         requestProperties.setProperty( "filetype", "resource" );
235         requestProperties.setProperty( "managedRepositoryId", repository.getId() );
236
237         List<ProxyConnector> connectors = getProxyConnectors( repository );
238         for ( ProxyConnector connector : connectors )
239         {
240             if ( !connector.isEnabled() )
241             {
242                 continue;
243             }
244
245             RemoteRepository targetRepository = connector.getTargetRepository();
246             requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
247
248             String targetPath = path;
249
250             try
251             {
252                 StorageAsset downloadedFile =
253                     transferFile( connector, targetRepository, targetPath, repository, localFile, requestProperties,
254                                   false );
255
256                 if ( fileExists( downloadedFile ) )
257                 {
258                     log.debug( "Successfully transferred: {}", downloadedFile.getPath() );
259                     return downloadedFile;
260                 }
261             }
262             catch ( NotFoundException e )
263             {
264                 log.debug( "Resource {} not found on repository \"{}\".", path,
265                            targetRepository.getId() );
266             }
267             catch ( NotModifiedException e )
268             {
269                 log.debug( "Resource {} not updated on repository \"{}\".", path,
270                            targetRepository.getId() );
271             }
272             catch ( ProxyException e )
273             {
274                 log.warn(
275                     "Transfer error from repository {} for resource {}, continuing to next repository. Error message: {}",
276                     targetRepository.getId(), path, e.getMessage() );
277                 log.debug( MarkerFactory.getDetachedMarker( "transfer.error" ),
278                            "Transfer error from repository \"{}"
279                                + "\" for resource {}, continuing to next repository. Error message: {}",
280                            targetRepository.getId(), path, e.getMessage(), e );
281             }
282
283         }
284
285         log.debug( "Exhausted all target repositories, resource {} not found.", path );
286
287         return null;
288     }
289
290     @Override
291     public ProxyFetchResult fetchMetadataFromProxies( ManagedRepository repository, String rawLogicalPath )
292     {
293         String logicalPath;
294         if (rawLogicalPath.startsWith( "/" )){
295             logicalPath = rawLogicalPath.substring( 1 );
296         } else {
297             logicalPath = rawLogicalPath;
298         }
299         StorageAsset localFile = repository.getAsset( logicalPath );
300
301         Properties requestProperties = new Properties();
302         requestProperties.setProperty( "filetype", "metadata" );
303         boolean metadataNeedsUpdating = false;
304         long originalTimestamp = getLastModified( localFile );
305
306         List<ProxyConnector> connectors = new ArrayList<>( getProxyConnectors( repository ) );
307         for ( ProxyConnector connector : connectors )
308         {
309             if ( !connector.isEnabled() )
310             {
311                 continue;
312             }
313
314             RemoteRepository targetRepository = connector.getTargetRepository();
315
316             StorageAsset localRepoFile = toLocalRepoFile( repository, targetRepository.getContent(), logicalPath );
317             long originalMetadataTimestamp = getLastModified( localRepoFile );
318
319             try
320             {
321                 transferFile( connector, targetRepository, logicalPath, repository, localRepoFile, requestProperties,
322                               true );
323
324                 if ( hasBeenUpdated( localRepoFile, originalMetadataTimestamp ) )
325                 {
326                     metadataNeedsUpdating = true;
327                 }
328             }
329             catch ( NotFoundException e )
330             {
331
332                 log.debug( "Metadata {} not found on remote repository '{}'.", logicalPath,
333                            targetRepository.getId(), e );
334
335             }
336             catch ( NotModifiedException e )
337             {
338
339                 log.debug( "Metadata {} not updated on remote repository '{}'.", logicalPath,
340                            targetRepository.getId(), e );
341
342             }
343             catch ( ProxyException e )
344             {
345                 log.warn(
346                     "Transfer error from repository {} for versioned Metadata {}, continuing to next repository. Error message: {}",
347                     targetRepository.getId(), logicalPath, e.getMessage() );
348                 log.debug( "Full stack trace", e );
349             }
350         }
351
352         if ( hasBeenUpdated( localFile, originalTimestamp ) )
353         {
354             metadataNeedsUpdating = true;
355         }
356
357         if ( metadataNeedsUpdating || !localFile.exists())
358         {
359             try
360             {
361                 metadataTools.updateMetadata( repository.getContent(), logicalPath );
362             }
363             catch ( RepositoryMetadataException e )
364             {
365                 log.warn( "Unable to update metadata {}:{}", localFile.getPath(), e.getMessage(), e );
366             }
367
368         }
369
370         if ( fileExists( localFile ) )
371         {
372             return new ProxyFetchResult( localFile, metadataNeedsUpdating );
373         }
374
375         return new ProxyFetchResult( null, false );
376     }
377
378     private long getLastModified(StorageAsset file )
379     {
380         if ( !file.exists() || file.isContainer() )
381         {
382             return 0;
383         }
384
385         return file.getModificationTime().toEpochMilli();
386     }
387
388     private boolean hasBeenUpdated(StorageAsset file, long originalLastModified )
389     {
390         if ( !file.exists() || file.isContainer() )
391         {
392             return false;
393         }
394
395         long currentLastModified = getLastModified( file );
396         return ( currentLastModified > originalLastModified );
397     }
398
399     private StorageAsset toLocalRepoFile( ManagedRepository repository, RemoteRepositoryContent targetRepository,
400                                           String targetPath )
401     {
402         String repoPath = metadataTools.getRepositorySpecificName( targetRepository, targetPath );
403         return repository.getAsset( repoPath );
404     }
405
406     /**
407      * Test if the provided ManagedRepositoryContent has any proxies configured for it.
408      * @param repository
409      */
410     @Override
411     public boolean hasProxies( ManagedRepository repository )
412     {
413         synchronized ( this.proxyConnectorMap )
414         {
415             return this.proxyConnectorMap.containsKey( repository.getId() );
416         }
417     }
418
419     private StorageAsset toLocalFile(ManagedRepository repository, ArtifactReference artifact ) throws LayoutException
420     {
421         return repository.getContent().getLayout( BaseRepositoryContentLayout.class ).toFile( artifact );
422     }
423
424     /**
425      * Simple method to test if the file exists on the local disk.
426      *
427      * @param file the file to test. (may be null)
428      * @return true if file exists. false if the file param is null, doesn't exist, or is not of type File.
429      */
430     private boolean fileExists( StorageAsset file )
431     {
432         if ( file == null )
433         {
434             return false;
435         }
436
437         if ( !file.exists())
438         {
439             return false;
440         }
441
442         return !file.isContainer();
443     }
444
445     /**
446      * Perform the transfer of the file.
447      *
448      * @param connector         the connector configuration to use.
449      * @param remoteRepository  the remote repository get the resource from.
450      * @param remotePath        the path in the remote repository to the resource to get.
451      * @param repository        the managed repository that will hold the file
452      * @param resource          the path relative to the repository storage where the file should be downloaded to
453      * @param requestProperties the request properties to utilize for policy handling.
454      * @param executeConsumers  whether to execute the consumers after proxying
455      * @return the local file that was downloaded, or null if not downloaded.
456      * @throws NotFoundException    if the file was not found on the remote repository.
457      * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository, but
458      *                              the remote resource is not newer than the local File.
459      * @throws ProxyException       if transfer was unsuccessful.
460      */
461     protected StorageAsset transferFile( ProxyConnector connector, RemoteRepository remoteRepository, String remotePath,
462                                          ManagedRepository repository, StorageAsset resource, Properties requestProperties,
463                                          boolean executeConsumers )
464         throws ProxyException, NotModifiedException
465     {
466         String url = null;
467         try
468         {
469             url = remoteRepository.getLocation().toURL().toString();
470         }
471         catch ( MalformedURLException e )
472         {
473             throw new ProxyException( e.getMessage(), e );
474         }
475         if ( !url.endsWith( "/" ) )
476         {
477             url = url + "/";
478         }
479         if (remotePath.startsWith( "/" )) {
480             url = url + remotePath.substring( 1 );
481         } else {
482             url = url + remotePath;
483         }
484         requestProperties.setProperty( "url", url );
485
486         // Is a whitelist defined?
487         if ( CollectionUtils.isNotEmpty( connector.getWhitelist() ) )
488         {
489             // Path must belong to whitelist.
490             if ( !matchesPattern( remotePath, connector.getWhitelist() ) )
491             {
492                 log.debug( "Path [{}] is not part of defined whitelist (skipping transfer from repository [{}]).",
493                            remotePath, remoteRepository.getId() );
494                 return null;
495             }
496         }
497
498         // Is target path part of blacklist?
499         if ( matchesPattern( remotePath, connector.getBlacklist() ) )
500         {
501             log.debug( "Path [{}] is part of blacklist (skipping transfer from repository [{}]).", remotePath,
502                        remoteRepository.getId() );
503             return null;
504         }
505
506         // Handle pre-download policy
507         try
508         {
509             validatePolicies( this.preDownloadPolicies, connector.getPolicies(), requestProperties, resource );
510         }
511         catch ( PolicyViolationException e )
512         {
513             String emsg = "Transfer not attempted on " + url + " : " + e.getMessage();
514             if ( resource.exists() )
515             {
516                 log.debug( "{} : using already present local file.", emsg );
517                 return resource;
518             }
519
520             log.debug( emsg );
521             return null;
522         }
523
524         Path workingDirectory = createWorkingDirectory( repository );
525         FilesystemStorage tmpStorage = null;
526         try
527         {
528             tmpStorage = new FilesystemStorage( workingDirectory, fileLockManager );
529         }
530         catch ( IOException e )
531         {
532             throw new ProxyException( "Could not create tmp storage" );
533         }
534         StorageAsset tmpResource = tmpStorage.getAsset( resource.getName( ) );
535         StorageAsset[] tmpChecksumFiles = new StorageAsset[checksumAlgorithms.size()];
536         for(int i=0; i<checksumAlgorithms.size(); i++) {
537             ChecksumAlgorithm alg = checksumAlgorithms.get( i );
538             tmpChecksumFiles[i] = tmpStorage.getAsset( resource.getName() + "." + alg.getDefaultExtension() );
539         }
540
541         try
542         {
543
544             transferResources( connector, remoteRepository, tmpResource,tmpChecksumFiles , url, remotePath,
545                 resource, workingDirectory, repository );
546
547             // Handle post-download policies.
548             try
549             {
550                 validatePolicies( this.postDownloadPolicies, connector.getPolicies(), requestProperties, tmpResource );
551             }
552             catch ( PolicyViolationException e )
553             {
554                 log.warn( "Transfer invalidated from {} : {}", url, e.getMessage() );
555                 executeConsumers = false;
556                 if ( !fileExists( tmpResource ) )
557                 {
558                     resource = null;
559                 }
560             }
561
562             if ( resource != null )
563             {
564                 synchronized ( resource.getPath().intern() )
565                 {
566                     StorageAsset directory = resource.getParent();
567                     for (int i=0; i<tmpChecksumFiles.length; i++) {
568                         moveFileIfExists( tmpChecksumFiles[i], directory );
569                     }
570                     moveFileIfExists( tmpResource, directory );
571                 }
572             }
573         }
574         finally
575         {
576             org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
577         }
578
579         if ( executeConsumers )
580         {
581             // Just-in-time update of the index and database by executing the consumers for this artifact
582             //consumers.executeConsumers( connector.getSourceRepository().getRepository(), resource );
583             queueRepositoryTask( connector.getSourceRepository().getId(), resource );
584         }
585
586         return resource;
587     }
588
589     protected abstract void transferResources( ProxyConnector connector, RemoteRepository remoteRepository,
590                                                StorageAsset tmpResource, StorageAsset[] checksumFiles, String url, String remotePath, StorageAsset resource, Path workingDirectory,
591                                                ManagedRepository repository ) throws ProxyException;
592
593     private void queueRepositoryTask(String repositoryId, StorageAsset localFile )
594     {
595         RepositoryTask task = new RepositoryTask();
596         task.setRepositoryId( repositoryId );
597         task.setResourceFile( localFile );
598         task.setUpdateRelatedArtifacts( true );
599         task.setScanAll( true );
600
601         try
602         {
603             scheduler.queueTask( task );
604         }
605         catch ( TaskQueueException e )
606         {
607             log.error( "Unable to queue repository task to execute consumers on resource file ['{}"
608                            + "'].", localFile.getName() );
609         }
610     }
611
612     /**
613      * Moves the file into repository location if it exists
614      *
615      * @param fileToMove this could be either the main artifact, sha1 or md5 checksum file.
616      * @param directory  directory to write files to
617      */
618     private void moveFileIfExists( StorageAsset fileToMove, StorageAsset directory )
619         throws ProxyException
620     {
621         if ( fileToMove != null && fileToMove.exists() )
622         {
623             StorageAsset newLocation = directory.getStorage().getAsset( directory.getPath()+ "/" + fileToMove.getName());
624             moveTempToTarget( fileToMove, newLocation );
625         }
626     }
627
628     /**
629      * Apply the policies.
630      *
631      * @param policies  the map of policies to execute. (Map of String policy keys, to {@link DownloadPolicy} objects)
632      * @param settings  the map of settings for the policies to execute. (Map of String policy keys, to String policy
633      *                  setting)
634      * @param request   the request properties (utilized by the {@link DownloadPolicy#applyPolicy(PolicyOption, Properties, StorageAsset)}
635      *                  )
636      * @param localFile the local file (utilized by the {@link DownloadPolicy#applyPolicy(PolicyOption, Properties, StorageAsset)})
637      * @throws PolicyViolationException
638      */
639     private void validatePolicies( Map<String, ? extends DownloadPolicy> policies, Map<Policy, PolicyOption> settings,
640                                    Properties request, StorageAsset localFile )
641         throws PolicyViolationException
642     {
643         for ( Map.Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
644         {
645             // olamy with spring rolehint is now downloadPolicy#hint
646             // so substring after last # to get the hint as with plexus
647             String key = entry.getValue( ).getId( );
648             DownloadPolicy policy = entry.getValue();
649             PolicyOption option = settings.containsKey(policy ) ? settings.get(policy) : policy.getDefaultOption();
650
651             log.debug( "Applying [{}] policy with [{}]", key, option );
652             try
653             {
654                 policy.applyPolicy( option, request, localFile );
655             }
656             catch ( PolicyConfigurationException e )
657             {
658                 log.error( e.getMessage(), e );
659             }
660         }
661     }
662
663     private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<Policy, PolicyOption> settings,
664                                    Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
665                                    StorageAsset localFile, Exception exception, Map<String, Exception> previousExceptions )
666         throws ProxyDownloadException
667     {
668         boolean process = true;
669         for ( Map.Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
670         {
671
672             // olamy with spring rolehint is now downloadPolicy#hint
673             // so substring after last # to get the hint as with plexus
674             String key = entry.getValue( ).getId( );
675             DownloadErrorPolicy policy = entry.getValue();
676             PolicyOption option = settings.containsKey( policy ) ? settings.get(policy) : policy.getDefaultOption();
677
678             log.debug( "Applying [{}] policy with [{}]", key, option );
679             try
680             {
681                 // all policies must approve the exception, any can cancel
682                 process = policy.applyPolicy( option, request, localFile, exception, previousExceptions );
683                 if ( !process )
684                 {
685                     break;
686                 }
687             }
688             catch ( PolicyConfigurationException e )
689             {
690                 log.error( e.getMessage(), e );
691             }
692         }
693
694         if ( process )
695         {
696             // if the exception was queued, don't throw it
697             if ( !previousExceptions.containsKey( content.getId() ) )
698             {
699                 throw new ProxyDownloadException(
700                     "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
701                     content.getId(), exception );
702             }
703         }
704         else
705         {
706             // if the exception was queued, but cancelled, remove it
707             previousExceptions.remove( content.getId() );
708         }
709
710         log.warn(
711             "Transfer error from repository {} for artifact {} , continuing to next repository. Error message: {}",
712             content.getRepository().getId(), Keys.toKey( artifact ), exception.getMessage() );
713         log.debug( "Full stack trace", exception );
714     }
715
716     /**
717      * Creates a working directory
718      *
719      * @param repository
720      * @return file location of working directory
721      */
722     private Path createWorkingDirectory( ManagedRepository repository )
723     {
724         try
725         {
726             return Files.createTempDirectory( "temp" );
727         }
728         catch ( IOException e )
729         {
730             throw new RuntimeException( e.getMessage(), e );
731         }
732
733     }
734
735     /**
736      * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles its
737      * downloaded files.
738      *
739      * @param temp   The completed download file
740      * @param target The final location of the downloaded file
741      * @throws ProxyException when the temp file cannot replace the target file
742      */
743     private void moveTempToTarget( StorageAsset temp, StorageAsset target )
744         throws ProxyException
745     {
746
747         try
748         {
749             org.apache.archiva.repository.storage.util.StorageUtil.moveAsset( temp, target, true , StandardCopyOption.REPLACE_EXISTING);
750         }
751         catch ( IOException e )
752         {
753             log.error( "Move failed from {} to {}, trying copy.", temp, target );
754             try
755             {
756                 FsStorageUtil.copyAsset( temp, target, true );
757                 if (temp.exists()) {
758                     temp.getStorage( ).removeAsset( temp );
759                 }
760             }
761             catch ( IOException ex )
762             {
763                 log.error("Copy failed from {} to {}: ({}) {}", temp, target, e.getClass(), e.getMessage());
764                 throw new ProxyException("Could not move temp file "+temp.getPath()+" to target "+target.getPath()+": ("+e.getClass()+") "+e.getMessage(), e);
765             }
766         }
767     }
768
769     /**
770      * Tests whitelist and blacklist patterns against path.
771      *
772      * @param path     the path to test.
773      * @param patterns the list of patterns to check.
774      * @return true if the path matches at least 1 pattern in the provided patterns list.
775      */
776     private boolean matchesPattern( String path, List<String> patterns )
777     {
778         if ( CollectionUtils.isEmpty( patterns ) )
779         {
780             return false;
781         }
782
783         if ( !path.startsWith( "/" ) )
784         {
785             path = "/" + path;
786         }
787
788         for ( String pattern : patterns )
789         {
790             if ( !pattern.startsWith( "/" ) )
791             {
792                 pattern = "/" + pattern;
793             }
794
795             if ( PathUtil.matchPath( pattern, path, false ) )
796             {
797                 return true;
798             }
799         }
800
801         return false;
802     }
803
804     /**
805      * TODO: Ensure that list is correctly ordered based on configuration. See MRM-477
806      * @param repository
807      */
808     @Override
809     public List<ProxyConnector> getProxyConnectors( ManagedRepository repository )
810     {
811
812         if ( !this.proxyConnectorMap.containsKey( repository.getId() ) )
813         {
814             return Collections.emptyList();
815         }
816         List<ProxyConnector> ret = new ArrayList<>( this.proxyConnectorMap.get( repository.getId() ) );
817
818         Collections.sort( ret, ProxyConnectorOrderComparator.getInstance() );
819         return ret;
820
821     }
822
823
824     protected String addParameters(String path, RemoteRepository remoteRepository )
825     {
826         if ( remoteRepository.getExtraParameters().isEmpty() )
827         {
828             return path;
829         }
830
831         boolean question = false;
832
833         StringBuilder res = new StringBuilder( path == null ? "" : path );
834
835         for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
836         {
837             if ( !question )
838             {
839                 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
840             }
841         }
842
843         return res.toString();
844     }
845
846     public void setArchivaConfiguration(ArchivaConfiguration archivaConfiguration )
847     {
848         this.archivaConfiguration = archivaConfiguration;
849     }
850
851     public MetadataTools getMetadataTools()
852     {
853         return metadataTools;
854     }
855
856     public void setMetadataTools(MetadataTools metadataTools )
857     {
858         this.metadataTools = metadataTools;
859     }
860
861     public UrlFailureCache getUrlFailureCache()
862     {
863         return urlFailureCache;
864     }
865
866     public void setUrlFailureCache(UrlFailureCache urlFailureCache )
867     {
868         this.urlFailureCache = urlFailureCache;
869     }
870
871     public Map<String, PreDownloadPolicy> getPreDownloadPolicies()
872     {
873         return preDownloadPolicies;
874     }
875
876     public void setPreDownloadPolicies(Map<String, PreDownloadPolicy> preDownloadPolicies )
877     {
878         this.preDownloadPolicies = preDownloadPolicies;
879     }
880
881     public Map<String, PostDownloadPolicy> getPostDownloadPolicies()
882     {
883         return postDownloadPolicies;
884     }
885
886     public void setPostDownloadPolicies(Map<String, PostDownloadPolicy> postDownloadPolicies )
887     {
888         this.postDownloadPolicies = postDownloadPolicies;
889     }
890
891     public Map<String, DownloadErrorPolicy> getDownloadErrorPolicies()
892     {
893         return downloadErrorPolicies;
894     }
895
896     public void setDownloadErrorPolicies(Map<String, DownloadErrorPolicy> downloadErrorPolicies )
897     {
898         this.downloadErrorPolicies = downloadErrorPolicies;
899     }
900
901     @Override
902     public void setNetworkProxies(Map<String, NetworkProxy> networkProxies ) {
903         this.networkProxyMap.clear();
904         this.networkProxyMap.putAll( networkProxies );
905     }
906
907     @Override
908     public NetworkProxy getNetworkProxy(String id) {
909         return this.networkProxyMap.get(id);
910     }
911
912     @Override
913     public Map<String, NetworkProxy> getNetworkProxies() {
914         return this.networkProxyMap;
915     }
916
917     @Override
918     public abstract List<RepositoryType> supports();
919
920     @Override
921     public void setPolicies( List<Policy> policyList )
922     {
923         preDownloadPolicies.clear();
924         postDownloadPolicies.clear();
925         downloadErrorPolicies.clear();
926         for (Policy policy : policyList) {
927             addPolicy( policy );
928         }
929     }
930
931     void addPolicy(PreDownloadPolicy policy) {
932         preDownloadPolicies.put( policy.getId( ), policy );
933     }
934
935     void addPolicy(PostDownloadPolicy policy) {
936         postDownloadPolicies.put( policy.getId( ), policy );
937     }
938     void addPolicy(DownloadErrorPolicy policy) {
939         downloadErrorPolicies.put( policy.getId( ), policy );
940     }
941
942     @Override
943     public void addPolicy( Policy policy )
944     {
945         if (policy instanceof PreDownloadPolicy) {
946             addPolicy( (PreDownloadPolicy)policy );
947         } else if (policy instanceof PostDownloadPolicy) {
948             addPolicy( (PostDownloadPolicy) policy );
949         } else if (policy instanceof DownloadErrorPolicy) {
950             addPolicy( (DownloadErrorPolicy) policy );
951         } else {
952             log.warn( "Policy not known: {}, {}", policy.getId( ), policy.getClass( ).getName( ) );
953         }
954     }
955
956     @Override
957     public void removePolicy( Policy policy )
958     {
959         final String id = policy.getId();
960         if (preDownloadPolicies.containsKey( id )) {
961             preDownloadPolicies.remove( id );
962         } else if (postDownloadPolicies.containsKey( id )) {
963             postDownloadPolicies.remove( id );
964         } else if (downloadErrorPolicies.containsKey( id )) {
965             downloadErrorPolicies.remove( id );
966         }
967     }
968
969     @Override
970     public void addProxyConnector( ProxyConnector connector )
971     {
972         final String sourceId = connector.getSourceRepository( ).getId( );
973         List<ProxyConnector> connectors;
974         if (proxyConnectorMap.containsKey( sourceId )) {
975             connectors = proxyConnectorMap.get( sourceId );
976         } else {
977             connectors = new ArrayList<>( );
978             proxyConnectorMap.put( sourceId, connectors );
979         }
980         connectors.add( connector );
981     }
982
983     @Override
984     public void setProxyConnectors( List<ProxyConnector> proxyConnectors )
985     {
986         proxyConnectorMap.clear();
987         for ( ProxyConnector connector : proxyConnectors )
988         {
989             addProxyConnector( connector );
990         }
991     }
992 }