]> source.dussan.org Git - archiva.git/blob
c23f80233366fbb620986fcd951105916940b683
[archiva.git] /
1 package org.apache.maven.archiva.proxy;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *  http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.commons.io.FileUtils;
23 import org.apache.commons.lang.StringUtils;
24 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
25 import org.apache.maven.archiva.configuration.ConfigurationNames;
26 import org.apache.maven.archiva.configuration.NetworkProxyConfiguration;
27 import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
28 import org.apache.maven.archiva.configuration.RepositoryConfiguration;
29 import org.apache.maven.archiva.model.ArchivaRepository;
30 import org.apache.maven.archiva.model.ArtifactReference;
31 import org.apache.maven.archiva.model.ProjectReference;
32 import org.apache.maven.archiva.model.VersionedReference;
33 import org.apache.maven.archiva.policies.DownloadPolicy;
34 import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
35 import org.apache.maven.archiva.repository.layout.BidirectionalRepositoryLayout;
36 import org.apache.maven.archiva.repository.layout.BidirectionalRepositoryLayoutFactory;
37 import org.apache.maven.archiva.repository.layout.LayoutException;
38 import org.apache.maven.wagon.ConnectionException;
39 import org.apache.maven.wagon.ResourceDoesNotExistException;
40 import org.apache.maven.wagon.Wagon;
41 import org.apache.maven.wagon.WagonException;
42 import org.apache.maven.wagon.authentication.AuthenticationException;
43 import org.apache.maven.wagon.proxy.ProxyInfo;
44 import org.apache.maven.wagon.repository.Repository;
45 import org.codehaus.plexus.logging.AbstractLogEnabled;
46 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
47 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
48 import org.codehaus.plexus.registry.Registry;
49 import org.codehaus.plexus.registry.RegistryListener;
50 import org.codehaus.plexus.util.SelectorUtils;
51
52 import java.io.File;
53 import java.io.IOException;
54 import java.util.ArrayList;
55 import java.util.Collection;
56 import java.util.Collections;
57 import java.util.HashMap;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Properties;
62 import java.util.Map.Entry;
63
64 /**
65  * DefaultRepositoryProxyConnectors 
66  *
67  * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
68  * @version $Id$
69  * 
70  * @plexus.component role-hint="default"
71  */
72 public class DefaultRepositoryProxyConnectors
73     extends AbstractLogEnabled
74     implements RepositoryProxyConnectors, RegistryListener, Initializable
75 {
76     /**
77      * @plexus.requirement
78      */
79     private ArchivaConfiguration archivaConfiguration;
80
81     /**
82      * @plexus.requirement role="org.apache.maven.wagon.Wagon"
83      */
84     private Map/*<String,Wagon>*/wagons;
85
86     /**
87      * @plexus.requirement
88      */
89     private BidirectionalRepositoryLayoutFactory layoutFactory;
90
91     /**
92      * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
93      */
94     private Map preDownloadPolicies;
95
96     /**
97      * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
98      */
99     private Map postDownloadPolicies;
100
101     /**
102      * @plexus.requirement role-hint="default"
103      */
104     private UrlFailureCache urlFailureCache;
105
106     private Map proxyConnectorMap = new HashMap();
107
108     private Map networkProxyMap = new HashMap();
109
110     public File fetchFromProxies( ArchivaRepository repository, ArtifactReference artifact )
111         throws ProxyException
112     {
113         if ( !repository.isManaged() )
114         {
115             throw new ProxyException( "Can only proxy managed repositories." );
116         }
117
118         File localFile;
119         try
120         {
121             BidirectionalRepositoryLayout sourceLayout = layoutFactory.getLayout( repository.getLayoutType() );
122             String sourcePath = sourceLayout.toPath( artifact );
123             localFile = new File( repository.getUrl().getPath(), sourcePath );
124         }
125         catch ( LayoutException e )
126         {
127             throw new ProxyException( "Unable to proxy due to bad source repository layout definition: "
128                 + e.getMessage(), e );
129         }
130
131         Properties requestProperties = new Properties();
132         requestProperties.setProperty( "version", artifact.getVersion() );
133
134         List connectors = getProxyConnectors( repository );
135         Iterator it = connectors.iterator();
136         while ( it.hasNext() )
137         {
138             ProxyConnector connector = (ProxyConnector) it.next();
139             getLogger().debug( "Attempting connector: " + connector );
140             ArchivaRepository targetRepository = connector.getTargetRepository();
141             try
142             {
143                 BidirectionalRepositoryLayout targetLayout = layoutFactory.getLayout( targetRepository.getLayoutType() );
144                 String targetPath = targetLayout.toPath( artifact );
145
146                 getLogger().debug(
147                                    "Using target repository: " + targetRepository.getId() + " - layout: "
148                                        + targetRepository.getLayoutType() + " - targetPath: " + targetPath );
149
150                 File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
151                                                     requestProperties );
152
153                 if ( fileExists( downloadedFile ) )
154                 {
155                     getLogger().info( "Successfully transfered: " + downloadedFile.getAbsolutePath() );
156                     return downloadedFile;
157                 }
158             }
159             catch ( LayoutException e )
160             {
161                 getLogger().error( "Unable to proxy due to bad layout definition: " + e.getMessage(), e );
162                 return null;
163             }
164         }
165
166         return null;
167     }
168
169     public File fetchFromProxies( ArchivaRepository repository, VersionedReference metadata )
170         throws ProxyException
171     {
172         if ( !repository.isManaged() )
173         {
174             throw new ProxyException( "Can only proxy managed repositories." );
175         }
176
177         File localFile;
178         try
179         {
180             BidirectionalRepositoryLayout sourceLayout = layoutFactory.getLayout( repository.getLayoutType() );
181             String sourcePath = sourceLayout.toPath( metadata );
182             localFile = new File( repository.getUrl().getPath(), sourcePath );
183         }
184         catch ( LayoutException e )
185         {
186             throw new ProxyException( "Unable to proxy due to bad source repository layout definition: "
187                 + e.getMessage(), e );
188         }
189
190         Properties requestProperties = new Properties();
191
192         List connectors = getProxyConnectors( repository );
193         Iterator it = connectors.iterator();
194         while ( it.hasNext() )
195         {
196             ProxyConnector connector = (ProxyConnector) it.next();
197             ArchivaRepository targetRepository = connector.getTargetRepository();
198             try
199             {
200                 BidirectionalRepositoryLayout targetLayout = layoutFactory.getLayout( targetRepository.getLayoutType() );
201                 String targetPath = targetLayout.toPath( metadata );
202
203                 File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
204                                                     requestProperties );
205
206                 if ( fileExists( downloadedFile ) )
207                 {
208                     getLogger().info( "Successfully transfered: " + downloadedFile.getAbsolutePath() );
209                     return downloadedFile;
210                 }
211             }
212             catch ( LayoutException e )
213             {
214                 getLogger().error( "Unable to proxy due to bad layout definition: " + e.getMessage(), e );
215                 return null;
216             }
217         }
218
219         return null;
220     }
221
222     public File fetchFromProxies( ArchivaRepository repository, ProjectReference metadata )
223         throws ProxyException
224     {
225         if ( !repository.isManaged() )
226         {
227             throw new ProxyException( "Can only proxy managed repositories." );
228         }
229
230         File localFile;
231         try
232         {
233             BidirectionalRepositoryLayout sourceLayout = layoutFactory.getLayout( repository.getLayoutType() );
234             String sourcePath = sourceLayout.toPath( metadata );
235             localFile = new File( repository.getUrl().getPath(), sourcePath );
236         }
237         catch ( LayoutException e )
238         {
239             throw new ProxyException( "Unable to proxy due to bad source repository layout definition: "
240                 + e.getMessage(), e );
241         }
242
243         Properties requestProperties = new Properties();
244
245         List connectors = getProxyConnectors( repository );
246         Iterator it = connectors.iterator();
247         while ( it.hasNext() )
248         {
249             ProxyConnector connector = (ProxyConnector) it.next();
250             ArchivaRepository targetRepository = connector.getTargetRepository();
251             try
252             {
253                 BidirectionalRepositoryLayout targetLayout = layoutFactory.getLayout( targetRepository.getLayoutType() );
254                 String targetPath = targetLayout.toPath( metadata );
255
256                 File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
257                                                     requestProperties );
258
259                 if ( fileExists( downloadedFile ) )
260                 {
261                     getLogger().info( "Successfully transfered: " + downloadedFile.getAbsolutePath() );
262                     return downloadedFile;
263                 }
264             }
265             catch ( LayoutException e )
266             {
267                 getLogger().error( "Unable to proxy due to bad layout definition: " + e.getMessage(), e );
268                 return null;
269             }
270         }
271
272         return null;
273     }
274
275     private boolean fileExists( File file )
276     {
277         if ( file == null )
278         {
279             return false;
280         }
281
282         if ( !file.exists() )
283         {
284             return false;
285         }
286
287         if ( !file.isFile() )
288         {
289             return false;
290         }
291
292         return true;
293     }
294
295     /**
296      * Perform the transfer of the file.
297      * 
298      * @param connector
299      * @param targetRepository
300      * @param targetPath
301      * @param localFile
302      * @param requestProperties
303      * @return
304      * @throws ProxyException 
305      */
306     private File transferFile( ProxyConnector connector, ArchivaRepository targetRepository, String targetPath,
307                                File localFile, Properties requestProperties )
308         throws ProxyException
309     {
310         String url = targetRepository.getUrl().toString() + targetPath;
311         requestProperties.setProperty( "url", url );
312
313         // Handle pre-download policy
314         if ( !applyPolicies( connector.getPolicies(), this.preDownloadPolicies, requestProperties, localFile ) )
315         {
316             getLogger().info( "Failed pre-download policies - " + localFile.getAbsolutePath() );
317
318             if ( fileExists( localFile ) )
319             {
320                 return localFile;
321             }
322
323             return null;
324         }
325
326         // Is a whitelist defined?
327         if ( !isEmpty( connector.getWhitelist() ) )
328         {
329             // Path must belong to whitelist.
330             if ( !matchesPattern( targetPath, connector.getWhitelist() ) )
331             {
332                 getLogger().debug( "Path [" + targetPath + "] is not part of defined whitelist (skipping transfer)." );
333                 return null;
334             }
335         }
336
337         // Is target path part of blacklist?
338         if ( matchesPattern( targetPath, connector.getBlacklist() ) )
339         {
340             getLogger().debug( "Path [" + targetPath + "] is part of blacklist (skipping transfer)." );
341             return null;
342         }
343
344         Wagon wagon = null;
345         try
346         {
347             String protocol = targetRepository.getUrl().getProtocol();
348             wagon = (Wagon) wagons.get( protocol );
349             if ( wagon == null )
350             {
351                 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
352             }
353
354             boolean connected = connectToRepository( connector, wagon, targetRepository );
355             if ( connected )
356             {
357                 localFile = transferSimpleFile( wagon, targetRepository, targetPath, localFile );
358
359                 transferChecksum( wagon, targetRepository, targetPath, localFile, ".sha1" );
360                 transferChecksum( wagon, targetRepository, targetPath, localFile, ".md5" );
361             }
362         }
363         catch ( ResourceDoesNotExistException e )
364         {
365             // Do not cache url here.
366             return null;
367         }
368         catch ( WagonException e )
369         {
370             urlFailureCache.cacheFailure( url );
371             return null;
372         }
373         finally
374         {
375             if ( wagon != null )
376             {
377                 try
378                 {
379                     wagon.disconnect();
380                 }
381                 catch ( ConnectionException e )
382                 {
383                     getLogger().warn( "Unable to disconnect wagon.", e );
384                 }
385             }
386         }
387
388         // Handle post-download policies.
389         if ( !applyPolicies( connector.getPolicies(), this.postDownloadPolicies, requestProperties, localFile ) )
390         {
391             getLogger().info( "Failed post-download policies - " + localFile.getAbsolutePath() );
392
393             if ( fileExists( localFile ) )
394             {
395                 return localFile;
396             }
397
398             return null;
399         }
400
401         // Everything passes.
402         return localFile;
403     }
404
405     private void transferChecksum( Wagon wagon, ArchivaRepository targetRepository, String targetPath, File localFile,
406                                    String type )
407         throws ProxyException
408     {
409         String url = targetRepository.getUrl().toString() + targetPath;
410
411         // Transfer checksum does not use the policy. 
412         if ( urlFailureCache.hasFailedBefore( url + type ) )
413         {
414             return;
415         }
416
417         try
418         {
419             File hashFile = new File( localFile.getAbsolutePath() + type );
420             transferSimpleFile( wagon, targetRepository, targetPath + type, hashFile );
421             getLogger().debug( "Checksum" + type + " Downloaded: " + hashFile );
422         }
423         catch ( ResourceDoesNotExistException e )
424         {
425             getLogger().debug( "Checksum" + type + " Not Download: " + e.getMessage() );
426         }
427         catch ( WagonException e )
428         {
429             urlFailureCache.cacheFailure( url + type );
430             getLogger().warn( "Transfer failed on checksum: " + url + " : " + e.getMessage(), e );
431         }
432     }
433
434     private File transferSimpleFile( Wagon wagon, ArchivaRepository targetRepository, String targetPath, File localFile )
435         throws ProxyException, WagonException
436     {
437         // Transfer the file.
438         File temp = null;
439
440         try
441         {
442             temp = new File( localFile.getAbsolutePath() + ".tmp" );
443
444             boolean success = false;
445
446             if ( localFile.exists() )
447             {
448                 getLogger().debug( "Retrieving " + targetPath + " from " + targetRepository.getName() );
449                 wagon.get( targetPath, temp );
450                 success = true;
451
452                 if ( temp.exists() )
453                 {
454                     moveTempToTarget( temp, localFile );
455                 }
456
457                 // You wouldn't get here on failure, a WagonException would have been thrown.
458                 getLogger().debug( "Downloaded successfully." );
459             }
460             else
461             {
462                 getLogger().debug( "Retrieving " + targetPath + " from " + targetRepository.getName() + " if updated" );
463                 success = wagon.getIfNewer( targetPath, temp, localFile.lastModified() );
464                 if ( !success )
465                 {
466                     getLogger().debug(
467                                        "Not downloaded, as local file is newer than remote side: "
468                                            + localFile.getAbsolutePath() );
469                 }
470                 else if ( temp.exists() )
471                 {
472                     getLogger().debug( "Downloaded successfully." );
473                     moveTempToTarget( temp, localFile );
474                 }
475             }
476
477             return localFile;
478         }
479         catch ( ResourceDoesNotExistException e )
480         {
481             getLogger().warn( "Resource does not exist: " + e.getMessage() );
482             throw e;
483         }
484         catch ( WagonException e )
485         {
486             getLogger().warn( "Download failure:" + e.getMessage(), e );
487             throw e;
488         }
489         finally
490         {
491             if ( temp != null )
492             {
493                 temp.delete();
494             }
495         }
496     }
497
498     private boolean applyPolicies( Map policySettings, Map downloadPolicies, Properties request, File localFile )
499     {
500         Iterator it = downloadPolicies.entrySet().iterator();
501         while ( it.hasNext() )
502         {
503             Map.Entry entry = (Entry) it.next();
504             String key = (String) entry.getKey();
505             DownloadPolicy policy = (DownloadPolicy) entry.getValue();
506             String defaultSetting = policy.getDefaultOption();
507             String setting = StringUtils.defaultString( (String) policySettings.get( key ), defaultSetting );
508
509             getLogger().debug( "Applying [" + key + "] policy with [" + setting + "]" );
510             if ( !policy.applyPolicy( setting, request, localFile ) )
511             {
512                 getLogger().debug( "Didn't pass the [" + key + "] policy." );
513                 return false;
514             }
515         }
516         return true;
517     }
518
519     /**
520      * Used to move the temporary file to its real destination.  This is patterned from the way WagonManager handles
521      * its downloaded files.
522      *
523      * @param temp   The completed download file
524      * @param target The final location of the downloaded file
525      * @throws ProxyException when the temp file cannot replace the target file
526      */
527     private void moveTempToTarget( File temp, File target )
528         throws ProxyException
529     {
530         if ( target.exists() && !target.delete() )
531         {
532             throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
533         }
534
535         if ( !temp.renameTo( target ) )
536         {
537             getLogger().warn( "Unable to rename tmp file to its final name... resorting to copy command." );
538
539             try
540             {
541                 FileUtils.copyFile( temp, target );
542             }
543             catch ( IOException e )
544             {
545                 throw new ProxyException( "Cannot copy tmp file to its final location", e );
546             }
547             finally
548             {
549                 temp.delete();
550             }
551         }
552     }
553
554     private boolean connectToRepository( ProxyConnector connector, Wagon wagon, ArchivaRepository targetRepository )
555     {
556         boolean connected = false;
557
558         ProxyInfo networkProxy = null;
559         synchronized ( this.networkProxyMap )
560         {
561             networkProxy = (ProxyInfo) this.networkProxyMap.get( connector.getProxyId() );
562         }
563
564         try
565         {
566             Repository wagonRepository = new Repository( targetRepository.getId(), targetRepository.getUrl().toString() );
567             if ( networkProxy != null )
568             {
569                 wagon.connect( wagonRepository, networkProxy );
570             }
571             else
572             {
573                 wagon.connect( wagonRepository );
574             }
575             connected = true;
576         }
577         catch ( ConnectionException e )
578         {
579             getLogger().info( "Could not connect to " + targetRepository.getName() + ": " + e.getMessage() );
580         }
581         catch ( AuthenticationException e )
582         {
583             getLogger().info( "Could not connect to " + targetRepository.getName() + ": " + e.getMessage() );
584         }
585
586         return connected;
587     }
588
589     private boolean matchesPattern( String path, List patterns )
590     {
591         if ( isEmpty( patterns ) )
592         {
593             return false;
594         }
595
596         Iterator it = patterns.iterator();
597         while ( it.hasNext() )
598         {
599             String pattern = (String) it.next();
600             if ( SelectorUtils.matchPath( pattern, path, false ) )
601             {
602                 return true;
603             }
604         }
605
606         return false;
607     }
608
609     public List getProxyConnectors( ArchivaRepository repository )
610     {
611         synchronized ( this.proxyConnectorMap )
612         {
613             List ret = (List) this.proxyConnectorMap.get( repository.getId() );
614             if ( ret == null )
615             {
616                 return Collections.EMPTY_LIST;
617             }
618             return ret;
619         }
620     }
621
622     public boolean hasProxies( ArchivaRepository repository )
623     {
624         synchronized ( this.proxyConnectorMap )
625         {
626             return this.proxyConnectorMap.containsKey( repository.getId() );
627         }
628     }
629
630     public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
631     {
632         if ( ConfigurationNames.isNetworkProxy( propertyName ) || ConfigurationNames.isRepositories( propertyName )
633             || ConfigurationNames.isProxyConnector( propertyName ) )
634         {
635             initConnectorsAndNetworkProxies();
636         }
637     }
638
639     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
640     {
641         /* do nothing */
642     }
643
644     private void initConnectorsAndNetworkProxies()
645     {
646         Iterator it;
647
648         synchronized ( this.proxyConnectorMap )
649         {
650             this.proxyConnectorMap.clear();
651
652             List proxyConfigs = archivaConfiguration.getConfiguration().getProxyConnectors();
653             it = proxyConfigs.iterator();
654             while ( it.hasNext() )
655             {
656                 ProxyConnectorConfiguration proxyConfig = (ProxyConnectorConfiguration) it.next();
657                 String key = proxyConfig.getSourceRepoId();
658
659                 // Create connector object.
660                 ProxyConnector connector = new ProxyConnector();
661                 connector.setSourceRepository( getRepository( proxyConfig.getSourceRepoId() ) );
662                 connector.setTargetRepository( getRepository( proxyConfig.getTargetRepoId() ) );
663                 connector.setPolicies( proxyConfig.getPolicies() );
664
665                 // Copy any blacklist patterns.
666                 List blacklist = new ArrayList();
667                 if ( !isEmpty( proxyConfig.getBlackListPatterns() ) )
668                 {
669                     blacklist.addAll( proxyConfig.getBlackListPatterns() );
670                 }
671                 connector.setBlacklist( blacklist );
672
673                 // Copy any whitelist patterns.
674                 List whitelist = new ArrayList();
675                 if ( !isEmpty( proxyConfig.getWhiteListPatterns() ) )
676                 {
677                     whitelist.addAll( proxyConfig.getWhiteListPatterns() );
678                 }
679                 connector.setWhitelist( whitelist );
680
681                 // Get other connectors
682                 List connectors = (List) this.proxyConnectorMap.get( key );
683                 if ( connectors == null )
684                 {
685                     // Create if we are the first.
686                     connectors = new ArrayList();
687                 }
688
689                 // Add the connector.
690                 connectors.add( connector );
691
692                 // Set the key to the list of connectors.
693                 this.proxyConnectorMap.put( key, connectors );
694             }
695         }
696
697         synchronized ( this.networkProxyMap )
698         {
699             this.networkProxyMap.clear();
700
701             List networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
702             it = networkProxies.iterator();
703             while ( it.hasNext() )
704             {
705                 NetworkProxyConfiguration networkProxyConfig = (NetworkProxyConfiguration) it.next();
706                 String key = networkProxyConfig.getId();
707
708                 ProxyInfo proxy = new ProxyInfo();
709
710                 proxy.setType( networkProxyConfig.getProtocol() );
711                 proxy.setHost( networkProxyConfig.getHost() );
712                 proxy.setPort( networkProxyConfig.getPort() );
713                 proxy.setUserName( networkProxyConfig.getUsername() );
714                 proxy.setPassword( networkProxyConfig.getPassword() );
715
716                 this.networkProxyMap.put( key, proxy );
717             }
718         }
719     }
720
721     private boolean isEmpty( Collection collection )
722     {
723         if ( collection == null )
724         {
725             return true;
726         }
727
728         return collection.isEmpty();
729     }
730
731     private ArchivaRepository getRepository( String repoId )
732     {
733         RepositoryConfiguration repoConfig = archivaConfiguration.getConfiguration().findRepositoryById( repoId );
734         if ( repoConfig == null )
735         {
736             return null;
737         }
738
739         ArchivaRepository repo = new ArchivaRepository( repoConfig.getId(), repoConfig.getName(), repoConfig.getUrl() );
740         repo.getModel().setLayoutName( repoConfig.getLayout() );
741         return repo;
742     }
743
744     public void initialize()
745         throws InitializationException
746     {
747         initConnectorsAndNetworkProxies();
748         archivaConfiguration.addChangeListener( this );
749     }
750 }