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