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