1 package org.apache.maven.archiva.proxy;
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
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;
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;
61 import java.util.Properties;
62 import java.util.Map.Entry;
65 * DefaultRepositoryProxyConnectors
67 * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
70 * @plexus.component role-hint="default"
72 public class DefaultRepositoryProxyConnectors
73 extends AbstractLogEnabled
74 implements RepositoryProxyConnectors, RegistryListener, Initializable
79 private ArchivaConfiguration archivaConfiguration;
82 * @plexus.requirement role="org.apache.maven.wagon.Wagon"
84 private Map/*<String,Wagon>*/wagons;
89 private BidirectionalRepositoryLayoutFactory layoutFactory;
92 * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
94 private Map preDownloadPolicies;
97 * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
99 private Map postDownloadPolicies;
102 * @plexus.requirement role-hint="default"
104 private UrlFailureCache urlFailureCache;
106 private Map proxyConnectorMap = new HashMap();
108 private Map networkProxyMap = new HashMap();
110 public File fetchFromProxies( ArchivaRepository repository, ArtifactReference artifact )
111 throws ProxyException
113 if ( !repository.isManaged() )
115 throw new ProxyException( "Can only proxy managed repositories." );
121 BidirectionalRepositoryLayout sourceLayout = layoutFactory.getLayout( repository.getLayoutType() );
122 String sourcePath = sourceLayout.toPath( artifact );
123 localFile = new File( repository.getUrl().getPath(), sourcePath );
125 catch ( LayoutException e )
127 throw new ProxyException( "Unable to proxy due to bad source repository layout definition: "
128 + e.getMessage(), e );
131 Properties requestProperties = new Properties();
132 requestProperties.setProperty( "version", artifact.getVersion() );
134 List connectors = getProxyConnectors( repository );
135 Iterator it = connectors.iterator();
136 while ( it.hasNext() )
138 ProxyConnector connector = (ProxyConnector) it.next();
139 getLogger().debug( "Attempting connector: " + connector );
140 ArchivaRepository targetRepository = connector.getTargetRepository();
143 BidirectionalRepositoryLayout targetLayout = layoutFactory.getLayout( targetRepository.getLayoutType() );
144 String targetPath = targetLayout.toPath( artifact );
147 "Using target repository: " + targetRepository.getId() + " - layout: "
148 + targetRepository.getLayoutType() + " - targetPath: " + targetPath );
150 File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
153 if ( fileExists( downloadedFile ) )
155 getLogger().info( "Successfully transfered: " + downloadedFile.getAbsolutePath() );
156 return downloadedFile;
159 catch ( LayoutException e )
161 getLogger().error( "Unable to proxy due to bad layout definition: " + e.getMessage(), e );
169 public File fetchFromProxies( ArchivaRepository repository, VersionedReference metadata )
170 throws ProxyException
172 if ( !repository.isManaged() )
174 throw new ProxyException( "Can only proxy managed repositories." );
180 BidirectionalRepositoryLayout sourceLayout = layoutFactory.getLayout( repository.getLayoutType() );
181 String sourcePath = sourceLayout.toPath( metadata );
182 localFile = new File( repository.getUrl().getPath(), sourcePath );
184 catch ( LayoutException e )
186 throw new ProxyException( "Unable to proxy due to bad source repository layout definition: "
187 + e.getMessage(), e );
190 Properties requestProperties = new Properties();
192 List connectors = getProxyConnectors( repository );
193 Iterator it = connectors.iterator();
194 while ( it.hasNext() )
196 ProxyConnector connector = (ProxyConnector) it.next();
197 ArchivaRepository targetRepository = connector.getTargetRepository();
200 BidirectionalRepositoryLayout targetLayout = layoutFactory.getLayout( targetRepository.getLayoutType() );
201 String targetPath = targetLayout.toPath( metadata );
203 File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
206 if ( fileExists( downloadedFile ) )
208 getLogger().info( "Successfully transfered: " + downloadedFile.getAbsolutePath() );
209 return downloadedFile;
212 catch ( LayoutException e )
214 getLogger().error( "Unable to proxy due to bad layout definition: " + e.getMessage(), e );
222 public File fetchFromProxies( ArchivaRepository repository, ProjectReference metadata )
223 throws ProxyException
225 if ( !repository.isManaged() )
227 throw new ProxyException( "Can only proxy managed repositories." );
233 BidirectionalRepositoryLayout sourceLayout = layoutFactory.getLayout( repository.getLayoutType() );
234 String sourcePath = sourceLayout.toPath( metadata );
235 localFile = new File( repository.getUrl().getPath(), sourcePath );
237 catch ( LayoutException e )
239 throw new ProxyException( "Unable to proxy due to bad source repository layout definition: "
240 + e.getMessage(), e );
243 Properties requestProperties = new Properties();
245 List connectors = getProxyConnectors( repository );
246 Iterator it = connectors.iterator();
247 while ( it.hasNext() )
249 ProxyConnector connector = (ProxyConnector) it.next();
250 ArchivaRepository targetRepository = connector.getTargetRepository();
253 BidirectionalRepositoryLayout targetLayout = layoutFactory.getLayout( targetRepository.getLayoutType() );
254 String targetPath = targetLayout.toPath( metadata );
256 File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
259 if ( fileExists( downloadedFile ) )
261 getLogger().info( "Successfully transfered: " + downloadedFile.getAbsolutePath() );
262 return downloadedFile;
265 catch ( LayoutException e )
267 getLogger().error( "Unable to proxy due to bad layout definition: " + e.getMessage(), e );
275 private boolean fileExists( File file )
282 if ( !file.exists() )
287 if ( !file.isFile() )
296 * Perform the transfer of the file.
299 * @param targetRepository
302 * @param requestProperties
304 * @throws ProxyException
306 private File transferFile( ProxyConnector connector, ArchivaRepository targetRepository, String targetPath,
307 File localFile, Properties requestProperties )
308 throws ProxyException
310 String url = targetRepository.getUrl().toString() + targetPath;
311 requestProperties.setProperty( "url", url );
313 // Handle pre-download policy
314 if ( !applyPolicies( connector.getPolicies(), this.preDownloadPolicies, requestProperties, localFile ) )
316 getLogger().info( "Failed pre-download policies - " + localFile.getAbsolutePath() );
318 if ( fileExists( localFile ) )
326 // Is a whitelist defined?
327 if ( !isEmpty( connector.getWhitelist() ) )
329 // Path must belong to whitelist.
330 if ( !matchesPattern( targetPath, connector.getWhitelist() ) )
332 getLogger().debug( "Path [" + targetPath + "] is not part of defined whitelist (skipping transfer)." );
337 // Is target path part of blacklist?
338 if ( matchesPattern( targetPath, connector.getBlacklist() ) )
340 getLogger().debug( "Path [" + targetPath + "] is part of blacklist (skipping transfer)." );
347 String protocol = targetRepository.getUrl().getProtocol();
348 wagon = (Wagon) wagons.get( protocol );
351 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
354 boolean connected = connectToRepository( connector, wagon, targetRepository );
357 localFile = transferSimpleFile( wagon, targetRepository, targetPath, localFile );
359 transferChecksum( wagon, targetRepository, targetPath, localFile, ".sha1" );
360 transferChecksum( wagon, targetRepository, targetPath, localFile, ".md5" );
363 catch ( ResourceDoesNotExistException e )
365 // Do not cache url here.
368 catch ( WagonException e )
370 urlFailureCache.cacheFailure( url );
381 catch ( ConnectionException e )
383 getLogger().warn( "Unable to disconnect wagon.", e );
388 // Handle post-download policies.
389 if ( !applyPolicies( connector.getPolicies(), this.postDownloadPolicies, requestProperties, localFile ) )
391 getLogger().info( "Failed post-download policies - " + localFile.getAbsolutePath() );
393 if ( fileExists( localFile ) )
401 // Everything passes.
405 private void transferChecksum( Wagon wagon, ArchivaRepository targetRepository, String targetPath, File localFile,
407 throws ProxyException
409 String url = targetRepository.getUrl().toString() + targetPath;
411 // Transfer checksum does not use the policy.
412 if ( urlFailureCache.hasFailedBefore( url + type ) )
419 File hashFile = new File( localFile.getAbsolutePath() + type );
420 transferSimpleFile( wagon, targetRepository, targetPath + type, hashFile );
421 getLogger().debug( "Checksum" + type + " Downloaded: " + hashFile );
423 catch ( ResourceDoesNotExistException e )
425 getLogger().debug( "Checksum" + type + " Not Download: " + e.getMessage() );
427 catch ( WagonException e )
429 urlFailureCache.cacheFailure( url + type );
430 getLogger().warn( "Transfer failed on checksum: " + url + " : " + e.getMessage(), e );
434 private File transferSimpleFile( Wagon wagon, ArchivaRepository targetRepository, String targetPath, File localFile )
435 throws ProxyException, WagonException
437 // Transfer the file.
442 temp = new File( localFile.getAbsolutePath() + ".tmp" );
444 boolean success = false;
446 if ( localFile.exists() )
448 getLogger().debug( "Retrieving " + targetPath + " from " + targetRepository.getName() );
449 wagon.get( targetPath, temp );
454 moveTempToTarget( temp, localFile );
457 // You wouldn't get here on failure, a WagonException would have been thrown.
458 getLogger().debug( "Downloaded successfully." );
462 getLogger().debug( "Retrieving " + targetPath + " from " + targetRepository.getName() + " if updated" );
463 success = wagon.getIfNewer( targetPath, temp, localFile.lastModified() );
467 "Not downloaded, as local file is newer than remote side: "
468 + localFile.getAbsolutePath() );
470 else if ( temp.exists() )
472 getLogger().debug( "Downloaded successfully." );
473 moveTempToTarget( temp, localFile );
479 catch ( ResourceDoesNotExistException e )
481 getLogger().warn( "Resource does not exist: " + e.getMessage() );
484 catch ( WagonException e )
486 getLogger().warn( "Download failure:" + e.getMessage(), e );
498 private boolean applyPolicies( Map policySettings, Map downloadPolicies, Properties request, File localFile )
500 Iterator it = downloadPolicies.entrySet().iterator();
501 while ( it.hasNext() )
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 );
509 getLogger().debug( "Applying [" + key + "] policy with [" + setting + "]" );
510 if ( !policy.applyPolicy( setting, request, localFile ) )
512 getLogger().debug( "Didn't pass the [" + key + "] policy." );
520 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles
521 * its downloaded files.
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
527 private void moveTempToTarget( File temp, File target )
528 throws ProxyException
530 if ( target.exists() && !target.delete() )
532 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
535 if ( !temp.renameTo( target ) )
537 getLogger().warn( "Unable to rename tmp file to its final name... resorting to copy command." );
541 FileUtils.copyFile( temp, target );
543 catch ( IOException e )
545 throw new ProxyException( "Cannot copy tmp file to its final location", e );
554 private boolean connectToRepository( ProxyConnector connector, Wagon wagon, ArchivaRepository targetRepository )
556 boolean connected = false;
558 ProxyInfo networkProxy = null;
559 synchronized ( this.networkProxyMap )
561 networkProxy = (ProxyInfo) this.networkProxyMap.get( connector.getProxyId() );
566 Repository wagonRepository = new Repository( targetRepository.getId(), targetRepository.getUrl().toString() );
567 if ( networkProxy != null )
569 wagon.connect( wagonRepository, networkProxy );
573 wagon.connect( wagonRepository );
577 catch ( ConnectionException e )
579 getLogger().info( "Could not connect to " + targetRepository.getName() + ": " + e.getMessage() );
581 catch ( AuthenticationException e )
583 getLogger().info( "Could not connect to " + targetRepository.getName() + ": " + e.getMessage() );
589 private boolean matchesPattern( String path, List patterns )
591 if ( isEmpty( patterns ) )
596 Iterator it = patterns.iterator();
597 while ( it.hasNext() )
599 String pattern = (String) it.next();
600 if ( SelectorUtils.matchPath( pattern, path, false ) )
609 public List getProxyConnectors( ArchivaRepository repository )
611 synchronized ( this.proxyConnectorMap )
613 List ret = (List) this.proxyConnectorMap.get( repository.getId() );
616 return Collections.EMPTY_LIST;
622 public boolean hasProxies( ArchivaRepository repository )
624 synchronized ( this.proxyConnectorMap )
626 return this.proxyConnectorMap.containsKey( repository.getId() );
630 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
632 if ( ConfigurationNames.isNetworkProxy( propertyName ) || ConfigurationNames.isRepositories( propertyName )
633 || ConfigurationNames.isProxyConnector( propertyName ) )
635 initConnectorsAndNetworkProxies();
639 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
644 private void initConnectorsAndNetworkProxies()
648 synchronized ( this.proxyConnectorMap )
650 this.proxyConnectorMap.clear();
652 List proxyConfigs = archivaConfiguration.getConfiguration().getProxyConnectors();
653 it = proxyConfigs.iterator();
654 while ( it.hasNext() )
656 ProxyConnectorConfiguration proxyConfig = (ProxyConnectorConfiguration) it.next();
657 String key = proxyConfig.getSourceRepoId();
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() );
665 // Copy any blacklist patterns.
666 List blacklist = new ArrayList();
667 if ( !isEmpty( proxyConfig.getBlackListPatterns() ) )
669 blacklist.addAll( proxyConfig.getBlackListPatterns() );
671 connector.setBlacklist( blacklist );
673 // Copy any whitelist patterns.
674 List whitelist = new ArrayList();
675 if ( !isEmpty( proxyConfig.getWhiteListPatterns() ) )
677 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
679 connector.setWhitelist( whitelist );
681 // Get other connectors
682 List connectors = (List) this.proxyConnectorMap.get( key );
683 if ( connectors == null )
685 // Create if we are the first.
686 connectors = new ArrayList();
689 // Add the connector.
690 connectors.add( connector );
692 // Set the key to the list of connectors.
693 this.proxyConnectorMap.put( key, connectors );
697 synchronized ( this.networkProxyMap )
699 this.networkProxyMap.clear();
701 List networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
702 it = networkProxies.iterator();
703 while ( it.hasNext() )
705 NetworkProxyConfiguration networkProxyConfig = (NetworkProxyConfiguration) it.next();
706 String key = networkProxyConfig.getId();
708 ProxyInfo proxy = new ProxyInfo();
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() );
716 this.networkProxyMap.put( key, proxy );
721 private boolean isEmpty( Collection collection )
723 if ( collection == null )
728 return collection.isEmpty();
731 private ArchivaRepository getRepository( String repoId )
733 RepositoryConfiguration repoConfig = archivaConfiguration.getConfiguration().findRepositoryById( repoId );
734 if ( repoConfig == null )
739 ArchivaRepository repo = new ArchivaRepository( repoConfig.getId(), repoConfig.getName(), repoConfig.getUrl() );
740 repo.getModel().setLayoutName( repoConfig.getLayout() );
744 public void initialize()
745 throws InitializationException
747 initConnectorsAndNetworkProxies();
748 archivaConfiguration.addChangeListener( this );