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.model.VersionedReference;
31 import org.apache.maven.archiva.policies.DownloadPolicy;
32 import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
33 import org.apache.maven.archiva.repository.layout.BidirectionalRepositoryLayout;
34 import org.apache.maven.archiva.repository.layout.BidirectionalRepositoryLayoutFactory;
35 import org.apache.maven.archiva.repository.layout.LayoutException;
36 import org.apache.maven.wagon.ConnectionException;
37 import org.apache.maven.wagon.ResourceDoesNotExistException;
38 import org.apache.maven.wagon.Wagon;
39 import org.apache.maven.wagon.WagonException;
40 import org.apache.maven.wagon.authentication.AuthenticationException;
41 import org.apache.maven.wagon.proxy.ProxyInfo;
42 import org.apache.maven.wagon.repository.Repository;
43 import org.codehaus.plexus.logging.AbstractLogEnabled;
44 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
45 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
46 import org.codehaus.plexus.registry.Registry;
47 import org.codehaus.plexus.registry.RegistryListener;
48 import org.codehaus.plexus.util.SelectorUtils;
51 import java.io.IOException;
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.HashMap;
56 import java.util.Iterator;
57 import java.util.List;
59 import java.util.Properties;
60 import java.util.Map.Entry;
63 * DefaultRepositoryProxyConnectors
65 * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
68 * @plexus.component role-hint="default"
70 public class DefaultRepositoryProxyConnectors
71 extends AbstractLogEnabled
72 implements RepositoryProxyConnectors, RegistryListener, Initializable
77 private ArchivaConfiguration archivaConfiguration;
80 * @plexus.requirement role="org.apache.maven.wagon.Wagon"
82 private Map/*<String,Wagon>*/wagons;
87 private BidirectionalRepositoryLayoutFactory layoutFactory;
90 * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
92 private Map preDownloadPolicies;
95 * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
97 private Map postDownloadPolicies;
100 * @plexus.requirement role-hint="default"
102 private UrlFailureCache urlFailureCache;
104 private Map proxyConnectorMap = new HashMap();
106 private Map networkProxyMap = new HashMap();
108 private List propertyNameTriggers = new ArrayList();
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( Properties 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.getDefaultPolicySetting();
507 String setting = policySettings.getProperty( 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 ( propertyNameTriggers.contains( propertyName ) )
634 initConnectorsAndNetworkProxies();
638 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
643 private void initConnectorsAndNetworkProxies()
647 synchronized ( this.proxyConnectorMap )
649 this.proxyConnectorMap.clear();
651 List proxyConfigs = archivaConfiguration.getConfiguration().getProxyConnectors();
652 it = proxyConfigs.iterator();
653 while ( it.hasNext() )
655 ProxyConnectorConfiguration proxyConfig = (ProxyConnectorConfiguration) it.next();
656 String key = proxyConfig.getSourceRepoId();
658 // Create connector object.
659 ProxyConnector connector = new ProxyConnector();
660 connector.setSourceRepository( getRepository( proxyConfig.getSourceRepoId() ) );
661 connector.setTargetRepository( getRepository( proxyConfig.getTargetRepoId() ) );
662 connector.setPolicies( proxyConfig.getPolicies() );
664 // Copy any blacklist patterns.
665 List blacklist = new ArrayList();
666 if ( !isEmpty( proxyConfig.getBlackListPatterns() ) )
668 blacklist.addAll( proxyConfig.getBlackListPatterns() );
670 connector.setBlacklist( blacklist );
672 // Copy any whitelist patterns.
673 List whitelist = new ArrayList();
674 if ( !isEmpty( proxyConfig.getWhiteListPatterns() ) )
676 whitelist.addAll( proxyConfig.getWhiteListPatterns() );
678 connector.setWhitelist( whitelist );
680 // Get other connectors
681 List connectors = (List) this.proxyConnectorMap.get( key );
682 if ( connectors == null )
684 // Create if we are the first.
685 connectors = new ArrayList();
688 // Add the connector.
689 connectors.add( connector );
691 // Set the key to the list of connectors.
692 this.proxyConnectorMap.put( key, connectors );
696 synchronized ( this.networkProxyMap )
698 this.networkProxyMap.clear();
700 List networkProxies = archivaConfiguration.getConfiguration().getNetworkProxies();
701 it = networkProxies.iterator();
702 while ( it.hasNext() )
704 NetworkProxyConfiguration networkProxyConfig = (NetworkProxyConfiguration) it.next();
705 String key = networkProxyConfig.getId();
707 ProxyInfo proxy = new ProxyInfo();
709 proxy.setType( networkProxyConfig.getProtocol() );
710 proxy.setHost( networkProxyConfig.getHost() );
711 proxy.setPort( networkProxyConfig.getPort() );
712 proxy.setUserName( networkProxyConfig.getUsername() );
713 proxy.setPassword( networkProxyConfig.getPassword() );
715 this.networkProxyMap.put( key, proxy );
720 private boolean isEmpty( Collection collection )
722 if ( collection == null )
727 return collection.isEmpty();
730 private ArchivaRepository getRepository( String repoId )
732 RepositoryConfiguration repoConfig = archivaConfiguration.getConfiguration().findRepositoryById( repoId );
733 if ( repoConfig == null )
738 ArchivaRepository repo = new ArchivaRepository( repoConfig.getId(), repoConfig.getName(), repoConfig.getUrl() );
739 repo.getModel().setLayoutName( repoConfig.getLayout() );
743 public void initialize()
744 throws InitializationException
746 propertyNameTriggers.add( "repositories" );
747 propertyNameTriggers.add( "repository" );
748 propertyNameTriggers.add( "id" );
749 propertyNameTriggers.add( "name" );
750 propertyNameTriggers.add( "url" );
751 propertyNameTriggers.add( "layout" );
752 propertyNameTriggers.add( "releases" );
753 propertyNameTriggers.add( "snapshots" );
754 propertyNameTriggers.add( "indexed" );
756 propertyNameTriggers.add( "proxyConnectors" );
757 propertyNameTriggers.add( "proxyConnector" );
758 propertyNameTriggers.add( "sourceRepoId" );
759 propertyNameTriggers.add( "targetRepoId" );
760 propertyNameTriggers.add( "proxyId" );
761 propertyNameTriggers.add( "snapshotsPolicy" );
762 propertyNameTriggers.add( "releasePolicy" );
763 propertyNameTriggers.add( "checksumPolicy" );
764 propertyNameTriggers.add( "whiteListPatterns" );
765 propertyNameTriggers.add( "whiteListPattern" );
766 propertyNameTriggers.add( "blackListPatterns" );
767 propertyNameTriggers.add( "blackListPattern" );
769 propertyNameTriggers.add( "networkProxies" );
770 propertyNameTriggers.add( "networkProxy" );
771 propertyNameTriggers.add( "protocol" );
772 propertyNameTriggers.add( "host" );
773 propertyNameTriggers.add( "port" );
774 propertyNameTriggers.add( "username" );
775 propertyNameTriggers.add( "password" );
777 archivaConfiguration.addChangeListener( this );
778 initConnectorsAndNetworkProxies();