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.maven.archiva.configuration.ArchivaConfiguration;
24 import org.apache.maven.archiva.configuration.NetworkProxyConfiguration;
25 import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
26 import org.apache.maven.archiva.configuration.RepositoryConfiguration;
27 import org.apache.maven.archiva.model.ArchivaRepository;
28 import org.apache.maven.archiva.model.ArtifactReference;
29 import org.apache.maven.archiva.model.ProjectReference;
30 import org.apache.maven.archiva.policies.DownloadPolicy;
31 import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
32 import org.apache.maven.archiva.repository.layout.BidirectionalRepositoryLayout;
33 import org.apache.maven.archiva.repository.layout.BidirectionalRepositoryLayoutFactory;
34 import org.apache.maven.archiva.repository.layout.LayoutException;
35 import org.apache.maven.wagon.ConnectionException;
36 import org.apache.maven.wagon.ResourceDoesNotExistException;
37 import org.apache.maven.wagon.Wagon;
38 import org.apache.maven.wagon.WagonException;
39 import org.apache.maven.wagon.authentication.AuthenticationException;
40 import org.apache.maven.wagon.proxy.ProxyInfo;
41 import org.apache.maven.wagon.repository.Repository;
42 import org.codehaus.plexus.logging.AbstractLogEnabled;
43 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
44 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
45 import org.codehaus.plexus.registry.Registry;
46 import org.codehaus.plexus.registry.RegistryListener;
47 import org.codehaus.plexus.util.SelectorUtils;
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.Collections;
54 import java.util.HashMap;
55 import java.util.Iterator;
56 import java.util.List;
58 import java.util.Properties;
59 import java.util.Map.Entry;
62 * DefaultRepositoryProxyConnectors
64 * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
67 * @plexus.component role-hint="default"
69 public class DefaultRepositoryProxyConnectors
70 extends AbstractLogEnabled
71 implements RepositoryProxyConnectors, RegistryListener, Initializable
73 private static final String FILENAME_MAVEN_METADATA = "maven-metadata.xml";
78 private ArchivaConfiguration archivaConfiguration;
81 * @plexus.requirement role="org.apache.maven.wagon.Wagon"
83 private Map/*<String,Wagon>*/wagons;
88 private BidirectionalRepositoryLayoutFactory layoutFactory;
91 * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
93 private Map preDownloadPolicies;
96 * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
98 private Map postDownloadPolicies;
101 * @plexus.requirement role-hint="default"
103 private UrlFailureCache urlFailureCache;
105 private Map proxyConnectorMap = new HashMap();
107 private Map networkProxyMap = new HashMap();
109 private List propertyNameTriggers = new ArrayList();
111 public File fetchFromProxies( ArchivaRepository repository, ArtifactReference artifact )
112 throws ProxyException
114 if ( !repository.isManaged() )
116 throw new ProxyException( "Can only proxy managed repositories." );
122 BidirectionalRepositoryLayout sourceLayout = layoutFactory.getLayout( repository.getLayoutType() );
123 String sourcePath = sourceLayout.toPath( artifact );
124 localFile = new File( repository.getUrl().getPath(), sourcePath );
126 catch ( LayoutException e )
128 throw new ProxyException( "Unable to proxy due to bad source repository layout definition: "
129 + e.getMessage(), e );
132 Properties requestProperties = new Properties();
133 requestProperties.setProperty( "version", artifact.getVersion() );
135 List connectors = getProxyConnectors( repository );
136 Iterator it = connectors.iterator();
137 while ( it.hasNext() )
139 ProxyConnector connector = (ProxyConnector) it.next();
140 getLogger().debug( "Attempting connector: " + connector );
141 ArchivaRepository targetRepository = connector.getTargetRepository();
144 BidirectionalRepositoryLayout targetLayout = layoutFactory.getLayout( targetRepository.getLayoutType() );
145 String targetPath = targetLayout.toPath( artifact );
148 "Using target repository: " + targetRepository.getId() + " - layout: "
149 + targetRepository.getLayoutType() + " - targetPath: " + targetPath );
151 File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
154 if ( fileExists( downloadedFile ) )
156 getLogger().info( "Successfully transfered: " + downloadedFile.getAbsolutePath() );
157 return downloadedFile;
160 catch ( LayoutException e )
162 getLogger().error( "Unable to proxy due to bad layout definition: " + e.getMessage(), e );
170 public File fetchFromProxies( ArchivaRepository repository, ProjectReference metadata )
171 throws ProxyException
173 if ( !repository.isManaged() )
175 throw new ProxyException( "Can only proxy managed repositories." );
181 BidirectionalRepositoryLayout sourceLayout = layoutFactory.getLayout( repository.getLayoutType() );
182 String sourcePath = sourceLayout.toPath( metadata ) + FILENAME_MAVEN_METADATA;
183 localFile = new File( repository.getUrl().getPath(), sourcePath );
185 catch ( LayoutException e )
187 throw new ProxyException( "Unable to proxy due to bad source repository layout definition: "
188 + e.getMessage(), e );
191 Properties requestProperties = new Properties();
193 List connectors = getProxyConnectors( repository );
194 Iterator it = connectors.iterator();
195 while ( it.hasNext() )
197 ProxyConnector connector = (ProxyConnector) it.next();
198 ArchivaRepository targetRepository = connector.getTargetRepository();
201 BidirectionalRepositoryLayout targetLayout = layoutFactory.getLayout( targetRepository.getLayoutType() );
202 String targetPath = targetLayout.toPath( metadata ) + FILENAME_MAVEN_METADATA;
204 File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
207 if ( fileExists( downloadedFile ) )
209 getLogger().info( "Successfully transfered: " + downloadedFile.getAbsolutePath() );
210 return downloadedFile;
213 catch ( LayoutException e )
215 getLogger().error( "Unable to proxy due to bad layout definition: " + e.getMessage(), e );
223 private boolean fileExists( File file )
230 if ( !file.exists() )
235 if ( !file.isFile() )
244 * Perform the transfer of the file.
247 * @param targetRepository
250 * @param requestProperties
252 * @throws ProxyException
254 private File transferFile( ProxyConnector connector, ArchivaRepository targetRepository, String targetPath,
255 File localFile, Properties requestProperties )
256 throws ProxyException
258 String url = targetRepository.getUrl().toString() + targetPath;
259 requestProperties.setProperty( "url", url );
261 // Handle pre-download policy
262 if ( !applyPolicies( connector.getPolicies(), this.preDownloadPolicies, requestProperties, localFile ) )
264 getLogger().info( "Failed pre-download policies - " + localFile.getAbsolutePath() );
266 if ( fileExists( localFile ) )
274 // Is a whitelist defined?
275 if ( !isEmpty( connector.getWhitelist() ) )
277 // Path must belong to whitelist.
278 if ( !matchesPattern( targetPath, connector.getWhitelist() ) )
280 getLogger().debug( "Path [" + targetPath + "] is not part of defined whitelist (skipping transfer)." );
285 // Is target path part of blacklist?
286 if ( matchesPattern( targetPath, connector.getBlacklist() ) )
288 getLogger().debug( "Path [" + targetPath + "] is part of blacklist (skipping transfer)." );
295 String protocol = targetRepository.getUrl().getProtocol();
296 wagon = (Wagon) wagons.get( protocol );
299 throw new ProxyException( "Unsupported target repository protocol: " + protocol );
302 boolean connected = connectToRepository( connector, wagon, targetRepository );
305 localFile = transferSimpleFile( wagon, targetRepository, targetPath, localFile );
307 transferChecksum( wagon, targetRepository, targetPath, localFile, ".sha1" );
308 transferChecksum( wagon, targetRepository, targetPath, localFile, ".md5" );
311 catch ( ResourceDoesNotExistException e )
313 // Do not cache url here.
316 catch ( WagonException e )
318 urlFailureCache.cacheFailure( url );
329 catch ( ConnectionException e )
331 getLogger().warn( "Unable to disconnect wagon.", e );
336 // Handle post-download policies.
337 if ( !applyPolicies( connector.getPolicies(), this.postDownloadPolicies, requestProperties, localFile ) )
339 getLogger().info( "Failed post-download policies - " + localFile.getAbsolutePath() );
341 if ( fileExists( localFile ) )
349 // Everything passes.
353 private void transferChecksum( Wagon wagon, ArchivaRepository targetRepository, String targetPath, File localFile,
355 throws ProxyException
357 String url = targetRepository.getUrl().toString() + targetPath;
359 // Transfer checksum does not use the policy.
360 if ( urlFailureCache.hasFailedBefore( url + type ) )
367 File hashFile = new File( localFile.getAbsolutePath() + type );
368 transferSimpleFile( wagon, targetRepository, targetPath + type, hashFile );
369 getLogger().debug( "Checksum" + type + " Downloaded: " + hashFile );
371 catch ( ResourceDoesNotExistException e )
373 getLogger().debug( "Checksum" + type + " Not Download: " + e.getMessage() );
375 catch ( WagonException e )
377 urlFailureCache.cacheFailure( url + type );
378 getLogger().warn( "Transfer failed on checksum: " + url + " : " + e.getMessage(), e );
382 private File transferSimpleFile( Wagon wagon, ArchivaRepository targetRepository, String targetPath, File localFile )
383 throws ProxyException, WagonException
385 // Transfer the file.
390 temp = new File( localFile.getAbsolutePath() + ".tmp" );
392 boolean success = false;
394 if ( localFile.exists() )
396 getLogger().debug( "Retrieving " + targetPath + " from " + targetRepository.getName() );
397 wagon.get( targetPath, temp );
402 moveTempToTarget( temp, localFile );
405 // You wouldn't get here on failure, a WagonException would have been thrown.
406 getLogger().debug( "Downloaded successfully." );
410 getLogger().debug( "Retrieving " + targetPath + " from " + targetRepository.getName() + " if updated" );
411 success = wagon.getIfNewer( targetPath, temp, localFile.lastModified() );
415 "Not downloaded, as local file is newer than remote side: "
416 + localFile.getAbsolutePath() );
418 else if ( temp.exists() )
420 getLogger().debug( "Downloaded successfully." );
421 moveTempToTarget( temp, localFile );
427 catch ( ResourceDoesNotExistException e )
429 getLogger().warn( "Resource does not exist: " + e.getMessage() );
432 catch ( WagonException e )
434 getLogger().warn( "Download failure:" + e.getMessage(), e );
446 private boolean applyPolicies( Properties policySettings, Map downloadPolicies, Properties request, File localFile )
448 Iterator it = downloadPolicies.entrySet().iterator();
449 while ( it.hasNext() )
451 Map.Entry entry = (Entry) it.next();
452 String key = (String) entry.getKey();
453 DownloadPolicy policy = (DownloadPolicy) entry.getValue();
454 String defaultSetting = policy.getDefaultPolicySetting();
455 String setting = policySettings.getProperty( key, defaultSetting );
457 getLogger().debug( "Applying [" + key + "] policy with [" + setting + "]" );
458 if ( !policy.applyPolicy( setting, request, localFile ) )
460 getLogger().debug( "Didn't pass the [" + key + "] policy." );
468 * Used to move the temporary file to its real destination. This is patterned from the way WagonManager handles
469 * its downloaded files.
471 * @param temp The completed download file
472 * @param target The final location of the downloaded file
473 * @throws ProxyException when the temp file cannot replace the target file
475 private void moveTempToTarget( File temp, File target )
476 throws ProxyException
478 if ( target.exists() && !target.delete() )
480 throw new ProxyException( "Unable to overwrite existing target file: " + target.getAbsolutePath() );
483 if ( !temp.renameTo( target ) )
485 getLogger().warn( "Unable to rename tmp file to its final name... resorting to copy command." );
489 FileUtils.copyFile( temp, target );
491 catch ( IOException e )
493 throw new ProxyException( "Cannot copy tmp file to its final location", e );
502 private boolean connectToRepository( ProxyConnector connector, Wagon wagon, ArchivaRepository targetRepository )
504 boolean connected = false;
506 ProxyInfo networkProxy = null;
507 synchronized ( this.networkProxyMap )
509 networkProxy = (ProxyInfo) this.networkProxyMap.get( connector.getProxyId() );
514 Repository wagonRepository = new Repository( targetRepository.getId(), targetRepository.getUrl().toString() );
515 if ( networkProxy != null )
517 wagon.connect( wagonRepository, networkProxy );
521 wagon.connect( wagonRepository );
525 catch ( ConnectionException e )
527 getLogger().info( "Could not connect to " + targetRepository.getName() + ": " + e.getMessage() );
529 catch ( AuthenticationException e )
531 getLogger().info( "Could not connect to " + targetRepository.getName() + ": " + e.getMessage() );
537 private boolean matchesPattern( String path, List patterns )
539 if ( isEmpty( patterns ) )
544 Iterator it = patterns.iterator();
545 while ( it.hasNext() )
547 String pattern = (String) it.next();
548 if ( SelectorUtils.matchPath( pattern, path, false ) )
557 public List getProxyConnectors( ArchivaRepository repository )
559 synchronized ( this.proxyConnectorMap )
561 List ret = (List) this.proxyConnectorMap.get( repository.getId() );
564 return Collections.EMPTY_LIST;
570 public boolean hasProxies( ArchivaRepository repository )
572 synchronized ( this.proxyConnectorMap )
574 return this.proxyConnectorMap.containsKey( repository.getId() );
578 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
580 if ( propertyNameTriggers.contains( propertyName ) )
582 initConnectorsAndNetworkProxies();
586 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
591 private void initConnectorsAndNetworkProxies()
595 synchronized ( this.proxyConnectorMap )
597 this.proxyConnectorMap.clear();
599 List proxyConfigs = archivaConfiguration.getConfiguration().getProxyConnectors();
600 it = proxyConfigs.iterator();
601 while ( it.hasNext() )
603 ProxyConnectorConfiguration proxyConfig = (ProxyConnectorConfiguration) it.next();
604 String key = proxyConfig.getSourceRepoId();
606 // Create connector object.
607 ProxyConnector connector = new ProxyConnector();
608 connector.setSourceRepository( getRepository( proxyConfig.getSourceRepoId() ) );
609 connector.setTargetRepository( getRepository( proxyConfig.getTargetRepoId() ) );
610 connector.setPolicies( proxyConfig.getPolicies() );
612 // Copy any blacklist patterns.
613 List blacklist = new ArrayList();
614 if ( !isEmpty( proxyConfig.getBlackListPatterns() ) )
616 blacklist.addAll( proxyConfig.getBlackListPatterns() );
618 connector.setBlacklist( blacklist );
620 // Copy any whitelist patterns.
621 List whitelist = new ArrayList();
622 if ( !isEmpty( proxyConfig.getWhiteListPatterns() ) )
624 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
626 connector.setWhitelist( whitelist );
628 // Get other connectors
629 List connectors = (List) this.proxyConnectorMap.get( key );
630 if ( connectors == null )
632 // Create if we are the first.
633 connectors = new ArrayList();
636 // Add the connector.
637 connectors.add( connector );
639 // Set the key to the list of connectors.
640 this.proxyConnectorMap.put( key, connectors );
644 synchronized ( this.networkProxyMap )
646 this.networkProxyMap.clear();
648 List networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
649 it = networkProxies.iterator();
650 while ( it.hasNext() )
652 NetworkProxyConfiguration networkProxyConfig = (NetworkProxyConfiguration) it.next();
653 String key = networkProxyConfig.getId();
655 ProxyInfo proxy = new ProxyInfo();
657 proxy.setType( networkProxyConfig.getProtocol() );
658 proxy.setHost( networkProxyConfig.getHost() );
659 proxy.setPort( networkProxyConfig.getPort() );
660 proxy.setUserName( networkProxyConfig.getUsername() );
661 proxy.setPassword( networkProxyConfig.getPassword() );
663 this.networkProxyMap.put( key, proxy );
668 private boolean isEmpty( Collection collection )
670 if ( collection == null )
675 return collection.isEmpty();
678 private ArchivaRepository getRepository( String repoId )
680 RepositoryConfiguration repoConfig = archivaConfiguration.getConfiguration().findRepositoryById( repoId );
681 if ( repoConfig == null )
686 ArchivaRepository repo = new ArchivaRepository( repoConfig.getId(), repoConfig.getName(), repoConfig.getUrl() );
687 repo.getModel().setLayoutName( repoConfig.getLayout() );
691 public void initialize()
692 throws InitializationException
694 propertyNameTriggers.add( "repositories" );
695 propertyNameTriggers.add( "repository" );
696 propertyNameTriggers.add( "id" );
697 propertyNameTriggers.add( "name" );
698 propertyNameTriggers.add( "url" );
699 propertyNameTriggers.add( "layout" );
700 propertyNameTriggers.add( "releases" );
701 propertyNameTriggers.add( "snapshots" );
702 propertyNameTriggers.add( "indexed" );
704 propertyNameTriggers.add( "proxyConnectors" );
705 propertyNameTriggers.add( "proxyConnector" );
706 propertyNameTriggers.add( "sourceRepoId" );
707 propertyNameTriggers.add( "targetRepoId" );
708 propertyNameTriggers.add( "proxyId" );
709 propertyNameTriggers.add( "snapshotsPolicy" );
710 propertyNameTriggers.add( "releasePolicy" );
711 propertyNameTriggers.add( "checksumPolicy" );
712 propertyNameTriggers.add( "whiteListPatterns" );
713 propertyNameTriggers.add( "whiteListPattern" );
714 propertyNameTriggers.add( "blackListPatterns" );
715 propertyNameTriggers.add( "blackListPattern" );
717 propertyNameTriggers.add( "networkProxies" );
718 propertyNameTriggers.add( "networkProxy" );
719 propertyNameTriggers.add( "protocol" );
720 propertyNameTriggers.add( "host" );
721 propertyNameTriggers.add( "port" );
722 propertyNameTriggers.add( "username" );
723 propertyNameTriggers.add( "password" );
725 archivaConfiguration.addChangeListener( this );
726 initConnectorsAndNetworkProxies();