View Javadoc

1   package org.apache.maven.artifact.manager;
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.maven.artifact.Artifact;
23  import org.apache.maven.artifact.metadata.ArtifactMetadata;
24  import org.apache.maven.artifact.repository.ArtifactRepository;
25  import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
26  import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
27  import org.apache.maven.artifact.repository.DefaultArtifactRepository;
28  import org.apache.maven.wagon.ConnectionException;
29  import org.apache.maven.wagon.ResourceDoesNotExistException;
30  import org.apache.maven.wagon.TransferFailedException;
31  import org.apache.maven.wagon.UnsupportedProtocolException;
32  import org.apache.maven.wagon.Wagon;
33  import org.apache.maven.wagon.authentication.AuthenticationException;
34  import org.apache.maven.wagon.authentication.AuthenticationInfo;
35  import org.apache.maven.wagon.authorization.AuthorizationException;
36  import org.apache.maven.wagon.events.TransferListener;
37  import org.apache.maven.wagon.observers.ChecksumObserver;
38  import org.apache.maven.wagon.proxy.ProxyInfo;
39  import org.apache.maven.wagon.proxy.ProxyInfoProvider;
40  import org.apache.maven.wagon.repository.Repository;
41  import org.apache.maven.wagon.repository.RepositoryPermissions;
42  import org.codehaus.plexus.PlexusConstants;
43  import org.codehaus.plexus.PlexusContainer;
44  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
45  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
46  import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
47  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
48  import org.codehaus.plexus.configuration.PlexusConfiguration;
49  import org.codehaus.plexus.configuration.PlexusConfigurationException;
50  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
51  import org.codehaus.plexus.context.Context;
52  import org.codehaus.plexus.context.ContextException;
53  import org.codehaus.plexus.logging.AbstractLogEnabled;
54  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
55  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
56  import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
57  import org.codehaus.plexus.util.FileUtils;
58  import org.codehaus.plexus.util.IOUtil;
59  import org.codehaus.plexus.util.xml.Xpp3Dom;
60  
61  import java.io.File;
62  import java.io.IOException;
63  import java.io.InputStream;
64  import java.net.MalformedURLException;
65  import java.net.URL;
66  import java.security.NoSuchAlgorithmException;
67  import java.util.Collection;
68  import java.util.HashMap;
69  import java.util.Iterator;
70  import java.util.LinkedHashMap;
71  import java.util.List;
72  import java.util.Map;
73  import java.util.Properties;
74  import java.util.Set;
75  
76  public class DefaultWagonManager
77      extends AbstractLogEnabled
78      implements WagonManager, Contextualizable, Initializable
79  {
80      private static final String WILDCARD = "*";
81  
82      private static final String EXTERNAL_WILDCARD = "external:*";
83  
84      private static final String MAVEN_ARTIFACT_PROPERTIES = "META-INF/maven/org.apache.maven/maven-artifact/pom.properties";
85  
86      private static final String WAGON_PROVIDER_CONFIGURATION = "wagonProvider";
87  
88      private static int anonymousMirrorIdSeed = 0;
89      
90      private PlexusContainer container;
91  
92      // TODO: proxies, authentication and mirrors are via settings, and should come in via an alternate method - perhaps
93      // attached to ArtifactRepository before the method is called (so AR would be composed of WR, not inherit it)
94      private Map proxies = new HashMap();
95  
96      private Map authenticationInfoMap = new HashMap();
97  
98      private Map serverPermissionsMap = new HashMap();
99  
100     //used LinkedMap to preserve the order.
101     private Map mirrors = new LinkedHashMap();
102 
103     /** Map( String, XmlPlexusConfiguration ) with the repository id and the wagon configuration */
104     private Map<String, XmlPlexusConfiguration> serverConfigurationMap = new HashMap<String, XmlPlexusConfiguration>();
105 
106     private Map<String, String> serverWagonProviderMap = new HashMap<String, String>();
107 
108     private TransferListener downloadMonitor;
109 
110     private boolean online = true;
111 
112     private ArtifactRepositoryFactory repositoryFactory;
113 
114     private boolean interactive = true;
115 
116     private Map<String, PlexusContainer> availableWagons = new HashMap<String, PlexusContainer>();
117 
118     private RepositoryPermissions defaultRepositoryPermissions;
119 
120     private String httpUserAgent;
121     
122     private WagonProviderMapping providerMapping = new DefaultWagonProviderMapping();
123 
124     // TODO: this leaks the component in the public api - it is never released back to the container
125     public Wagon getWagon( Repository repository )
126         throws UnsupportedProtocolException, WagonConfigurationException
127     {
128         String protocol = repository.getProtocol();
129 
130         if ( protocol == null )
131         {
132             throw new UnsupportedProtocolException( "The repository " + repository + " does not specify a protocol" );
133         }
134 
135         Wagon wagon = getWagon( protocol, repository.getId() );
136 
137         configureWagon( wagon, repository.getId(), protocol );
138 
139         return wagon;
140     }
141     
142     public Wagon getWagon( String protocol )
143         throws UnsupportedProtocolException
144     {
145         return getWagon( protocol, null );
146     }
147     
148     private Wagon getWagon( String protocol, String repositoryId )
149         throws UnsupportedProtocolException
150     {
151         String hint = getWagonHint( protocol, repositoryId );
152         PlexusContainer container = getWagonContainer( hint );
153 
154         Wagon wagon;
155         try
156         {
157             wagon = (Wagon) container.lookup( Wagon.ROLE, hint );
158         }
159         catch ( ComponentLookupException e1 )
160         {
161             throw new UnsupportedProtocolException(
162                 "Cannot find wagon which supports the requested protocol: " + protocol, e1 );
163         }
164 
165         wagon.setInteractive( interactive );
166 
167         return wagon;
168     }
169     
170     private String getWagonHint( String protocol, String repositoryId )
171     {
172         // TODO: Implement a better way to get the hint, via settings.xml or something.
173         String impl = null;
174         
175         if ( repositoryId != null && serverWagonProviderMap.containsKey( repositoryId ) )
176         {
177             impl = serverWagonProviderMap.get( repositoryId );
178             getLogger().debug( "Using Wagon implementation " + impl + " from settings for server " + repositoryId );
179         }
180         else
181         {
182             impl = providerMapping.getWagonProvider( protocol );
183             if ( impl != null )
184             {
185                 getLogger().debug( "Using Wagon implementation " + impl + " from default mapping for protocol " + protocol );
186             }
187         }
188         
189         String hint;
190         if ( impl != null )
191         {
192             hint = protocol + "-" + impl;
193             PlexusContainer container = getWagonContainer( hint );
194             if ( container == null || !container.hasComponent( Wagon.ROLE, hint ) )
195             {
196                 getLogger().debug(
197                                    "Cannot find wagon for protocol-provider hint: '" + hint
198                                        + "', configured for repository: '" + repositoryId + "'. Using protocol hint: '"
199                                        + protocol + "' instead." );
200                 hint = protocol;
201             }
202         }
203         else
204         {
205             hint = protocol;
206         }
207         
208         return hint;
209     }
210 
211     private PlexusContainer getWagonContainer( String hint )
212     {
213         PlexusContainer container = this.container;
214         if ( availableWagons.containsKey( hint ) )
215         {
216             container = availableWagons.get( hint );
217         }
218         
219         return container;
220     }
221 
222     public void putArtifact( File source,
223                              Artifact artifact,
224                              ArtifactRepository deploymentRepository )
225         throws TransferFailedException
226     {
227         putRemoteFile( deploymentRepository, source, deploymentRepository.pathOf( artifact ), downloadMonitor );
228     }
229 
230     public void putArtifactMetadata( File source,
231                                      ArtifactMetadata artifactMetadata,
232                                      ArtifactRepository repository )
233         throws TransferFailedException
234     {
235         getLogger().info( "Uploading " + artifactMetadata );
236         putRemoteFile( repository, source, repository.pathOfRemoteRepositoryMetadata( artifactMetadata ), null );
237     }
238 
239     private void putRemoteFile( ArtifactRepository repository,
240                                 File source,
241                                 String remotePath,
242                                 TransferListener downloadMonitor )
243         throws TransferFailedException
244     {
245         failIfNotOnline();
246 
247         String protocol = repository.getProtocol();
248 
249         Wagon wagon;
250         try
251         {
252             wagon = getWagon( protocol, repository.getId() );
253 
254             configureWagon( wagon, repository );
255         }
256         catch ( UnsupportedProtocolException e )
257         {
258             throw new TransferFailedException( "Unsupported Protocol: '" + protocol + "': " + e.getMessage(), e );
259         }
260 
261         if ( downloadMonitor != null )
262         {
263             wagon.addTransferListener( downloadMonitor );
264         }
265 
266         Map checksums = new HashMap( 2 );
267         Map sums = new HashMap( 2 );
268 
269         // TODO: configure these on the repository
270         try
271         {
272             ChecksumObserver checksumObserver = new ChecksumObserver( "MD5" );
273             wagon.addTransferListener( checksumObserver );
274             checksums.put( "md5", checksumObserver );
275             checksumObserver = new ChecksumObserver( "SHA-1" );
276             wagon.addTransferListener( checksumObserver );
277             checksums.put( "sha1", checksumObserver );
278         }
279         catch ( NoSuchAlgorithmException e )
280         {
281             throw new TransferFailedException( "Unable to add checksum methods: " + e.getMessage(), e );
282         }
283 
284         try
285         {
286             Repository artifactRepository = new Repository( repository.getId(), repository.getUrl() );
287 
288             if ( serverPermissionsMap.containsKey( repository.getId() ) )
289             {
290                 RepositoryPermissions perms = (RepositoryPermissions) serverPermissionsMap.get( repository.getId() );
291 
292                 getLogger().debug(
293                     "adding permissions to wagon connection: " + perms.getFileMode() + " " + perms.getDirectoryMode() );
294 
295                 artifactRepository.setPermissions( perms );
296             }
297             else
298             {
299                 if ( defaultRepositoryPermissions != null )
300                 {
301                     artifactRepository.setPermissions( defaultRepositoryPermissions );
302                 }
303                 else
304                 {
305                     getLogger().debug( "not adding permissions to wagon connection" );
306                 }
307             }
308 
309             wagon.connect( artifactRepository, getAuthenticationInfo( repository.getId() ), new ProxyInfoProvider()
310             {
311                 public ProxyInfo getProxyInfo( String protocol )
312                 {
313                     return getProxy( protocol );
314                 }
315             });
316 
317             wagon.put( source, remotePath );
318 
319             wagon.removeTransferListener( downloadMonitor );
320 
321             // Pre-store the checksums as any future puts will overwrite them
322             for ( Iterator i = checksums.keySet().iterator(); i.hasNext(); )
323             {
324                 String extension = (String) i.next();
325                 ChecksumObserver observer = (ChecksumObserver) checksums.get( extension );
326                 sums.put( extension, observer.getActualChecksum() );
327             }
328 
329             // We do this in here so we can checksum the artifact metadata too, otherwise it could be metadata itself
330             for ( Iterator i = checksums.keySet().iterator(); i.hasNext(); )
331             {
332                 String extension = (String) i.next();
333 
334                 // TODO: shouldn't need a file intermediatary - improve wagon to take a stream
335                 File temp = File.createTempFile( "maven-artifact", null );
336                 temp.deleteOnExit();
337                 FileUtils.fileWrite( temp.getAbsolutePath(), "UTF-8", (String) sums.get( extension ) );
338 
339                 wagon.put( temp, remotePath + "." + extension );
340             }
341         }
342         catch ( ConnectionException e )
343         {
344             throw new TransferFailedException( "Connection failed: " + e.getMessage(), e );
345         }
346         catch ( AuthenticationException e )
347         {
348             throw new TransferFailedException( "Authentication failed: " + e.getMessage(), e );
349         }
350         catch ( AuthorizationException e )
351         {
352             throw new TransferFailedException( "Authorization failed: " + e.getMessage(), e );
353         }
354         catch ( ResourceDoesNotExistException e )
355         {
356             throw new TransferFailedException( "Resource to deploy not found: " + e.getMessage(), e );
357         }
358         catch ( IOException e )
359         {
360             throw new TransferFailedException( "Error creating temporary file for deployment: " + e.getMessage(), e );
361         }
362         finally
363         {
364             disconnectWagon( wagon );
365 
366             releaseWagon( protocol, wagon, repository.getId() );
367         }
368     }
369 
370     public void getArtifact( Artifact artifact,
371                              List remoteRepositories )
372         throws TransferFailedException, ResourceDoesNotExistException
373     {
374         // TODO [BP]: The exception handling here needs some work
375         boolean successful = false;
376         for ( Iterator iter = remoteRepositories.iterator(); iter.hasNext() && !successful; )
377         {
378             ArtifactRepository repository = (ArtifactRepository) iter.next();
379 
380             try
381             {
382                 getArtifact( artifact, repository );
383 
384                 successful = artifact.isResolved();
385             }
386             catch ( ResourceDoesNotExistException e )
387             {
388                 // This one we will eat when looking through remote repositories
389                 // because we want to cycle through them all before squawking.
390 
391                 getLogger().info( "Unable to find resource '" + artifact.getId() + "' in repository " +
392                     repository.getId() + " (" + repository.getUrl() + ")" );
393             }
394             catch ( TransferFailedException e )
395             {
396                 getLogger().warn( "Unable to get resource '" + artifact.getId() + "' from repository " +
397                     repository.getId() + " (" + repository.getUrl() + "): " + e.getMessage() );
398             }
399         }
400 
401         // if it already exists locally we were just trying to force it - ignore the update
402         if ( !successful && !artifact.getFile().exists() )
403         {
404             throw new ResourceDoesNotExistException( "Unable to download the artifact from any repository" );
405         }
406     }
407 
408     public void getArtifact( Artifact artifact,
409                              ArtifactRepository repository )
410         throws TransferFailedException, ResourceDoesNotExistException
411     {
412         String remotePath = repository.pathOf( artifact );
413 
414         ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
415 
416         if ( !policy.isEnabled() )
417         {
418             getLogger().debug( "Skipping disabled repository " + repository.getId() );
419         }
420         else if ( repository.isBlacklisted() )
421         {
422             getLogger().debug( "Skipping blacklisted repository " + repository.getId() );
423         }
424         else
425         {
426             getLogger().debug( "Trying repository " + repository.getId() );
427             getRemoteFile( getMirrorRepository( repository ), artifact.getFile(), remotePath, downloadMonitor,
428                                    policy.getChecksumPolicy(), false );
429             getLogger().debug( "  Artifact resolved" );
430 
431             artifact.setResolved( true );
432         }
433     }
434 
435     public void getArtifactMetadata( ArtifactMetadata metadata,
436                                      ArtifactRepository repository,
437                                      File destination,
438                                      String checksumPolicy )
439         throws TransferFailedException, ResourceDoesNotExistException
440     {
441         String remotePath = repository.pathOfRemoteRepositoryMetadata( metadata );
442 
443         getRemoteFile( getMirrorRepository( repository ), destination, remotePath, null, checksumPolicy, true );
444     }
445 
446     public void getArtifactMetadataFromDeploymentRepository( ArtifactMetadata metadata, ArtifactRepository repository,
447                                                              File destination, String checksumPolicy )
448         throws TransferFailedException, ResourceDoesNotExistException
449     {
450         String remotePath = repository.pathOfRemoteRepositoryMetadata( metadata );
451 
452         getRemoteFile( repository, destination, remotePath, null, checksumPolicy, true );
453     }
454 
455     private void getRemoteFile( ArtifactRepository repository,
456                                 File destination,
457                                 String remotePath,
458                                 TransferListener downloadMonitor,
459                                 String checksumPolicy,
460                                 boolean force )
461         throws TransferFailedException, ResourceDoesNotExistException
462     {
463         // TODO: better excetpions - transfer failed is not enough?
464 
465         failIfNotOnline();
466 
467         String protocol = repository.getProtocol();
468         Wagon wagon;
469         try
470         {
471             wagon = getWagon( protocol, repository.getId() );
472 
473             configureWagon( wagon, repository );
474         }
475         catch ( UnsupportedProtocolException e )
476         {
477             throw new TransferFailedException( "Unsupported Protocol: '" + protocol + "': " + e.getMessage(), e );
478         }
479 
480         if ( downloadMonitor != null )
481         {
482             wagon.addTransferListener( downloadMonitor );
483         }
484 
485         File temp = new File( destination + ".tmp" );
486         temp.deleteOnExit();
487 
488         boolean downloaded = false;
489 
490         try
491         {
492             getLogger().debug( "Connecting to repository: \'" + repository.getId() + "\' with url: \'" + repository.getUrl() + "\'." );
493             
494             wagon.connect( new Repository( repository.getId(), repository.getUrl() ),
495                            getAuthenticationInfo( repository.getId() ), new ProxyInfoProvider()
496             {
497                 public ProxyInfo getProxyInfo( String protocol )
498                 {
499                     return getProxy( protocol );
500                 }
501             });
502 
503             boolean firstRun = true;
504             boolean retry = true;
505 
506             // this will run at most twice. The first time, the firstRun flag is turned off, and if the retry flag
507             // is set on the first run, it will be turned off and not re-set on the second try. This is because the
508             // only way the retry flag can be set is if ( firstRun == true ).
509             while ( firstRun || retry )
510             {
511                 // reset the retry flag.
512                 retry = false;
513 
514                 // TODO: configure on repository
515                 ChecksumObserver md5ChecksumObserver = null;
516                 ChecksumObserver sha1ChecksumObserver = null;
517                 try
518                 {
519                     md5ChecksumObserver = new ChecksumObserver( "MD5" );
520                     wagon.addTransferListener( md5ChecksumObserver );
521 
522                     sha1ChecksumObserver = new ChecksumObserver( "SHA-1" );
523                     wagon.addTransferListener( sha1ChecksumObserver );
524 
525                     // This should take care of creating destination directory now on
526                     if ( destination.exists() && !force )
527                     {
528                         try
529                         {
530                             downloaded = wagon.getIfNewer( remotePath, temp, destination.lastModified() );
531                             if ( !downloaded )
532                             {
533                                 // prevent additional checks of this artifact until it expires again
534                                 destination.setLastModified( System.currentTimeMillis() );
535                             }
536                         }
537                         catch ( UnsupportedOperationException e )
538                         {
539                             // older wagons throw this. Just get() instead
540                             wagon.get( remotePath, temp );
541                             downloaded = true;
542                         }
543                     }
544                     else
545                     {
546                         wagon.get( remotePath, temp );
547                         downloaded = true;
548                     }
549                 }
550                 catch ( NoSuchAlgorithmException e )
551                 {
552                     throw new TransferFailedException( "Unable to add checksum methods: " + e.getMessage(), e );
553                 }
554                 finally
555                 {
556                     if ( md5ChecksumObserver != null )
557                     {
558                         wagon.removeTransferListener( md5ChecksumObserver );
559                     }
560                     if ( sha1ChecksumObserver != null )
561                     {
562                         wagon.removeTransferListener( sha1ChecksumObserver );
563                     }
564                 }
565 
566                 if ( downloaded )
567                 {
568                     // keep the checksum files from showing up on the download monitor...
569                     if ( downloadMonitor != null )
570                     {
571                         wagon.removeTransferListener( downloadMonitor );
572                     }
573 
574                     // try to verify the SHA-1 checksum for this file.
575                     try
576                     {
577                         verifyChecksum( sha1ChecksumObserver, destination, temp, remotePath, ".sha1", wagon );
578                     }
579                     catch ( ChecksumFailedException e )
580                     {
581                         // if we catch a ChecksumFailedException, it means the transfer/read succeeded, but the checksum
582                         // doesn't match. This could be a problem with the server (ibiblio HTTP-200 error page), so we'll
583                         // try this up to two times. On the second try, we'll handle it as a bona-fide error, based on the
584                         // repository's checksum checking policy.
585                         if ( firstRun )
586                         {
587                             getLogger().warn( "*** CHECKSUM FAILED - " + e.getMessage() + " - RETRYING" );
588                             retry = true;
589                         }
590                         else
591                         {
592                             handleChecksumFailure( checksumPolicy, e.getMessage(), e.getCause() );
593                         }
594                     }
595                     catch ( ResourceDoesNotExistException sha1TryException )
596                     {
597                         getLogger().debug( "SHA1 not found, trying MD5", sha1TryException );
598 
599                         // if this IS NOT a ChecksumFailedException, it was a problem with transfer/read of the checksum
600                         // file...we'll try again with the MD5 checksum.
601                         try
602                         {
603                             verifyChecksum( md5ChecksumObserver, destination, temp, remotePath, ".md5", wagon );
604                         }
605                         catch ( ChecksumFailedException e )
606                         {
607                             // if we also fail to verify based on the MD5 checksum, and the checksum transfer/read
608                             // succeeded, then we need to determine whether to retry or handle it as a failure.
609                             if ( firstRun )
610                             {
611                                 retry = true;
612                             }
613                             else
614                             {
615                                 handleChecksumFailure( checksumPolicy, e.getMessage(), e.getCause() );
616                             }
617                         }
618                         catch ( ResourceDoesNotExistException md5TryException )
619                         {
620                             // this was a failed transfer, and we don't want to retry.
621                             handleChecksumFailure( checksumPolicy, "Error retrieving checksum file for " + remotePath,
622                                 md5TryException );
623                         }
624                     }
625 
626                     // reinstate the download monitor...
627                     if ( downloadMonitor != null )
628                     {
629                         wagon.addTransferListener( downloadMonitor );
630                     }
631                 }
632 
633                 // unset the firstRun flag, so we don't get caught in an infinite loop...
634                 firstRun = false;
635             }
636         }
637         catch ( ConnectionException e )
638         {
639             throw new TransferFailedException( "Connection failed: " + e.getMessage(), e );
640         }
641         catch ( AuthenticationException e )
642         {
643             throw new TransferFailedException( "Authentication failed: " + e.getMessage(), e );
644         }
645         catch ( AuthorizationException e )
646         {
647             throw new TransferFailedException( "Authorization failed: " + e.getMessage(), e );
648         }
649         finally
650         {
651             disconnectWagon( wagon );
652 
653             releaseWagon( protocol, wagon, repository.getId() );
654         }
655 
656         if ( downloaded )
657         {
658             if ( !temp.exists() )
659             {
660                 throw new ResourceDoesNotExistException( "Downloaded file does not exist: " + temp );
661             }
662 
663             // The temporary file is named destination + ".tmp" and is done this way to ensure
664             // that the temporary file is in the same file system as the destination because the
665             // File.renameTo operation doesn't really work across file systems.
666             // So we will attempt to do a File.renameTo for efficiency and atomicity, if this fails
667             // then we will use a brute force copy and delete the temporary file.
668 
669             if ( !temp.renameTo( destination ) )
670             {
671                 try
672                 {
673                     FileUtils.copyFile( temp, destination );
674 
675                     temp.delete();
676                 }
677                 catch ( IOException e )
678                 {
679                     throw new TransferFailedException(
680                         "Error copying temporary file to the final destination: " + e.getMessage(), e );
681                 }
682             }
683         }
684     }
685 
686     public ArtifactRepository getMirrorRepository( ArtifactRepository repository )
687     {
688         ArtifactRepository mirror = getMirror( repository );
689         if ( mirror != null )
690         {
691             String id = mirror.getId();
692             if ( id == null )
693             {
694                 // TODO: this should be illegal in settings.xml
695                 id = repository.getId();
696             }
697             
698             getLogger().debug( "Using mirror: " + mirror.getUrl() + " (id: " + id + ")" );
699 
700             repository = repositoryFactory.createArtifactRepository( id, mirror.getUrl(),
701                                                                      repository.getLayout(), repository.getSnapshots(),
702                                                                      repository.getReleases() );
703         }
704         return repository;
705     }
706 
707     private void failIfNotOnline()
708         throws TransferFailedException
709     {
710         if ( !isOnline() )
711         {
712             throw new TransferFailedException( "System is offline." );
713         }
714     }
715 
716     private void handleChecksumFailure( String checksumPolicy,
717                                         String message,
718                                         Throwable cause )
719         throws ChecksumFailedException
720     {
721         if ( ArtifactRepositoryPolicy.CHECKSUM_POLICY_FAIL.equals( checksumPolicy ) )
722         {
723             throw new ChecksumFailedException( message, cause );
724         }
725         else if ( !ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE.equals( checksumPolicy ) )
726         {
727             // warn if it is set to anything other than ignore
728             getLogger().warn( "*** CHECKSUM FAILED - " + message + " - IGNORING" );
729         }
730         // otherwise it is ignore
731     }
732 
733     private void verifyChecksum( ChecksumObserver checksumObserver,
734                                  File destination,
735                                  File tempDestination,
736                                  String remotePath,
737                                  String checksumFileExtension,
738                                  Wagon wagon )
739         throws ResourceDoesNotExistException, TransferFailedException, AuthorizationException
740     {
741         try
742         {
743             // grab it first, because it's about to change...
744             String actualChecksum = checksumObserver.getActualChecksum();
745 
746             File tempChecksumFile = new File( tempDestination + checksumFileExtension + ".tmp" );
747             tempChecksumFile.deleteOnExit();
748             wagon.get( remotePath + checksumFileExtension, tempChecksumFile );
749 
750             String expectedChecksum = FileUtils.fileRead( tempChecksumFile, "UTF-8" );
751 
752             // remove whitespaces at the end
753             expectedChecksum = expectedChecksum.trim();
754 
755             // check for 'ALGO (name) = CHECKSUM' like used by openssl
756             if ( expectedChecksum.regionMatches( true, 0, "MD", 0, 2 )
757                 || expectedChecksum.regionMatches( true, 0, "SHA", 0, 3 ) )
758             {
759                 int lastSpacePos = expectedChecksum.lastIndexOf( ' ' );
760                 expectedChecksum = expectedChecksum.substring( lastSpacePos + 1 );
761             }
762             else
763             {
764                 // remove everything after the first space (if available)
765                 int spacePos = expectedChecksum.indexOf( ' ' );
766 
767                 if ( spacePos != -1 )
768                 {
769                     expectedChecksum = expectedChecksum.substring( 0, spacePos );
770                 }
771             }
772             if ( expectedChecksum.equalsIgnoreCase( actualChecksum ) )
773             {
774                 File checksumFile = new File( destination + checksumFileExtension );
775                 if ( checksumFile.exists() )
776                 {
777                     checksumFile.delete();
778                 }
779                 FileUtils.copyFile( tempChecksumFile, checksumFile );
780             }
781             else
782             {
783                 throw new ChecksumFailedException( "Checksum failed on download: local = '" + actualChecksum +
784                     "'; remote = '" + expectedChecksum + "'" );
785             }
786         }
787         catch ( IOException e )
788         {
789             throw new ChecksumFailedException( "Invalid checksum file", e );
790         }
791     }
792 
793 
794     private void disconnectWagon( Wagon wagon )
795     {
796         try
797         {
798             wagon.disconnect();
799         }
800         catch ( ConnectionException e )
801         {
802             getLogger().error( "Problem disconnecting from wagon - ignoring: " + e.getMessage() );
803         }
804     }
805 
806     private void releaseWagon( String protocol,
807                                Wagon wagon, String repositoryId )
808     {
809         String hint = getWagonHint( protocol, repositoryId );
810         
811         PlexusContainer container = getWagonContainer( hint );
812         try
813         {
814             container.release( wagon );
815         }
816         catch ( ComponentLifecycleException e )
817         {
818             getLogger().error( "Problem releasing wagon - ignoring: " + e.getMessage() );
819         }
820     }
821 
822     public ProxyInfo getProxy( String protocol )
823     {
824         ProxyInfo info = (ProxyInfo) proxies.get( protocol );
825         
826         if ( info != null )
827         {
828             getLogger().debug( "Using Proxy: " + info.getHost() );
829         }
830         
831         return info;
832     }
833 
834     public AuthenticationInfo getAuthenticationInfo( String id )
835     {
836         return (AuthenticationInfo) authenticationInfoMap.get( id );
837     }
838 
839     /**
840      * This method finds a matching mirror for the selected repository. If there is an exact match, this will be used.
841      * If there is no exact match, then the list of mirrors is examined to see if a pattern applies.
842      *
843      * @param originalRepository See if there is a mirror for this repository.
844      * @return the selected mirror or null if none are found.
845      */
846     public ArtifactRepository getMirror( ArtifactRepository originalRepository )
847     {
848         ArtifactRepository selectedMirror = (ArtifactRepository) mirrors.get( originalRepository.getId() );
849         if ( null == selectedMirror )
850         {
851             // Process the patterns in order. First one that matches wins.
852             Set keySet = mirrors.keySet();
853             if ( keySet != null )
854             {
855                 Iterator iter = keySet.iterator();
856                 while ( iter.hasNext() )
857                 {
858                     String pattern = (String) iter.next();
859                     if ( matchPattern( originalRepository, pattern ) )
860                     {
861                         selectedMirror = (ArtifactRepository) mirrors.get( pattern );
862                         break;
863                     }
864                 }
865             }
866 
867         }
868         return selectedMirror;
869     }
870 
871     /**
872      * This method checks if the pattern matches the originalRepository.
873      * Valid patterns:
874      * * = everything
875      * external:* = everything not on the localhost and not file based.
876      * repo,repo1 = repo or repo1
877      * *,!repo1 = everything except repo1
878      *
879      * @param originalRepository to compare for a match.
880      * @param pattern used for match. Currently only '*' is supported.
881      * @return true if the repository is a match to this pattern.
882      */
883     public boolean matchPattern( ArtifactRepository originalRepository, String pattern )
884     {
885         boolean result = false;
886         String originalId = originalRepository.getId();
887 
888         // simple checks first to short circuit processing below.
889         if ( WILDCARD.equals( pattern ) || pattern.equals( originalId ) )
890         {
891             result = true;
892         }
893         else
894         {
895             // process the list
896             String[] repos = pattern.split( "," );
897             for ( int i = 0; i < repos.length; i++ )
898             {
899                 String repo = repos[i];
900 
901                 // see if this is a negative match
902                 if ( repo.length() > 1 && repo.startsWith( "!" ) )
903                 {
904                     if ( originalId.equals( repo.substring( 1 ) ) )
905                     {
906                         // explicitly exclude. Set result and stop processing.
907                         result = false;
908                         break;
909                     }
910                 }
911                 // check for exact match
912                 else if ( originalId.equals( repo ) )
913                 {
914                     result = true;
915                     break;
916                 }
917                 // check for external:*
918                 else if ( EXTERNAL_WILDCARD.equals( repo ) && isExternalRepo( originalRepository ) )
919                 {
920                     result = true;
921                     // don't stop processing in case a future segment explicitly excludes this repo
922                 }
923                 else if ( WILDCARD.equals( repo ) )
924                 {
925                     result = true;
926                     // don't stop processing in case a future segment explicitly excludes this repo
927                 }
928             }
929         }
930         return result;
931     }
932 
933     /**
934      * Checks the URL to see if this repository refers to an external repository
935      *
936      * @param originalRepository
937      * @return true if external.
938      */
939     public boolean isExternalRepo( ArtifactRepository originalRepository )
940     {
941         try
942         {
943             URL url = new URL( originalRepository.getUrl() );
944             return !( url.getHost().equals( "localhost" ) || url.getHost().equals( "127.0.0.1" ) || url.getProtocol().equals(
945                                                                                                                               "file" ) );
946         }
947         catch ( MalformedURLException e )
948         {
949             // bad url just skip it here. It should have been validated already, but the wagon lookup will deal with it
950             return false;
951         }
952     }
953 
954     /**
955      * Set the proxy used for a particular protocol.
956      *
957      * @param protocol the protocol (required)
958      * @param host the proxy host name (required)
959      * @param port the proxy port (required)
960      * @param username the username for the proxy, or null if there is none
961      * @param password the password for the proxy, or null if there is none
962      * @param nonProxyHosts the set of hosts not to use the proxy for. Follows Java system property format:
963      *            <code>*.foo.com|localhost</code>.
964      * @todo [BP] would be nice to configure this via plexus in some way
965      */
966     public void addProxy( String protocol,
967                           String host,
968                           int port,
969                           String username,
970                           String password,
971                           String nonProxyHosts )
972     {
973         ProxyInfo proxyInfo = new ProxyInfo();
974         proxyInfo.setHost( host );
975         proxyInfo.setType( protocol );
976         proxyInfo.setPort( port );
977         proxyInfo.setNonProxyHosts( nonProxyHosts );
978         proxyInfo.setUserName( username );
979         proxyInfo.setPassword( password );
980 
981         proxies.put( protocol, proxyInfo );
982     }
983 
984     public void contextualize( Context context )
985         throws ContextException
986     {
987         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
988     }
989 
990     /** @todo I'd rather not be setting this explicitly. */
991     public void setDownloadMonitor( TransferListener downloadMonitor )
992     {
993         this.downloadMonitor = downloadMonitor;
994     }
995 
996     public void addAuthenticationInfo( String repositoryId,
997                                        String username,
998                                        String password,
999                                        String privateKey,
1000                                        String passphrase )
1001     {
1002         AuthenticationInfo authInfo = new AuthenticationInfo();
1003 
1004         authInfo.setUserName( username );
1005 
1006         authInfo.setPassword( password );
1007 
1008         authInfo.setPrivateKey( privateKey );
1009 
1010         authInfo.setPassphrase( passphrase );
1011 
1012         authenticationInfoMap.put( repositoryId, authInfo );
1013     }
1014 
1015     public void addPermissionInfo( String repositoryId,
1016                                    String filePermissions,
1017                                    String directoryPermissions )
1018     {
1019 
1020         RepositoryPermissions permissions = new RepositoryPermissions();
1021         boolean addPermissions = false;
1022 
1023         if ( filePermissions != null )
1024         {
1025             permissions.setFileMode( filePermissions );
1026             addPermissions = true;
1027         }
1028 
1029         if ( directoryPermissions != null )
1030         {
1031             permissions.setDirectoryMode( directoryPermissions );
1032             addPermissions = true;
1033         }
1034 
1035         if ( addPermissions )
1036         {
1037             serverPermissionsMap.put( repositoryId, permissions );
1038         }
1039     }
1040 
1041     public void addMirror( String id,
1042                            String mirrorOf,
1043                            String url )
1044     {
1045         if ( id == null )
1046         {
1047             id = "mirror-" + anonymousMirrorIdSeed++;
1048             getLogger().warn( "You are using a mirror that doesn't declare an <id/> element. Using \'" + id + "\' instead:\nId: " + id + "\nmirrorOf: " + mirrorOf + "\nurl: " + url + "\n" );
1049         }
1050         
1051         ArtifactRepository mirror = new DefaultArtifactRepository( id, url, null );
1052 
1053         //to preserve first wins, don't add repeated mirrors.
1054         if (!mirrors.containsKey( mirrorOf ))
1055         {
1056             mirrors.put( mirrorOf, mirror );
1057         }
1058     }
1059 
1060     public void setOnline( boolean online )
1061     {
1062         this.online = online;
1063     }
1064 
1065     public boolean isOnline()
1066     {
1067         return online;
1068     }
1069 
1070     public void setInteractive( boolean interactive )
1071     {
1072         this.interactive = interactive;
1073     }
1074 
1075     @SuppressWarnings( "unchecked" )
1076     public void registerWagons( Collection wagons,
1077                                 PlexusContainer extensionContainer )
1078     {
1079         for ( Iterator<String> i = wagons.iterator(); i.hasNext(); )
1080         {
1081             availableWagons.put( i.next(), extensionContainer );
1082         }
1083     }
1084 
1085     /**
1086      * Applies the server configuration to the wagon
1087      *
1088      * @param wagon      the wagon to configure
1089      * @param repository the repository that has the configuration
1090      * @throws WagonConfigurationException wraps any error given during configuration of the wagon instance
1091      */
1092     private void configureWagon( Wagon wagon,
1093                                  ArtifactRepository repository )
1094         throws WagonConfigurationException
1095     {
1096         configureWagon( wagon, repository.getId(), repository.getProtocol() );
1097     }
1098 
1099     private void configureWagon( Wagon wagon, String repositoryId, String protocol )
1100         throws WagonConfigurationException
1101     {
1102         PlexusConfiguration config = (PlexusConfiguration) serverConfigurationMap.get( repositoryId ); 
1103         if ( protocol.startsWith( "http" ) || protocol.startsWith( "dav" ) )
1104         {
1105             config = updateUserAgentForHttp( wagon, config );
1106         }
1107         
1108         if ( config != null )
1109         {
1110             ComponentConfigurator componentConfigurator = null;
1111             try
1112             {
1113                 componentConfigurator = (ComponentConfigurator) container.lookup( ComponentConfigurator.ROLE, "wagon" );
1114                 componentConfigurator.configureComponent( wagon, config, container.getContainerRealm() );
1115             }
1116             catch ( final ComponentLookupException e )
1117             {
1118                 throw new WagonConfigurationException( repositoryId,
1119                                                        "Unable to lookup wagon configurator. Wagon configuration cannot be applied.",
1120                                                        e );
1121             }
1122             catch ( ComponentConfigurationException e )
1123             {
1124                 throw new WagonConfigurationException( repositoryId, "Unable to apply wagon configuration.", e );
1125             }
1126             finally
1127             {
1128                 if ( componentConfigurator != null )
1129                 {
1130                     try
1131                     {
1132                         container.release( componentConfigurator );
1133                     }
1134                     catch ( ComponentLifecycleException e )
1135                     {
1136                         getLogger().error( "Problem releasing configurator - ignoring: " + e.getMessage() );
1137                     }
1138                 }
1139 
1140             }
1141         }
1142     }
1143 
1144     // TODO: Remove this, once the maven-shade-plugin 1.2 release is out, allowing configuration of httpHeaders in the components.xml
1145     private PlexusConfiguration updateUserAgentForHttp( Wagon wagon, PlexusConfiguration config )
1146     {
1147         if ( config == null )
1148         {
1149             config = new XmlPlexusConfiguration( "configuration" );
1150         }
1151         
1152         if ( httpUserAgent != null )
1153         {
1154             try
1155             {
1156                 wagon.getClass().getMethod( "setHttpHeaders", new Class[]{ Properties.class } );
1157                 
1158                 PlexusConfiguration headerConfig = config.getChild( "httpHeaders", true );
1159                 PlexusConfiguration[] children = headerConfig.getChildren( "property" );
1160                 boolean found = false;
1161                 
1162                 getLogger().debug( "Checking for pre-existing User-Agent configuration." );
1163                 for ( int i = 0; i < children.length; i++ )
1164                 {
1165                     PlexusConfiguration c = children[i].getChild( "name", false );
1166                     if ( c != null && "User-Agent".equals( c.getValue( null ) ) )
1167                     {
1168                         found = true;
1169                         break;
1170                     }
1171                 }
1172                 
1173                 if ( !found )
1174                 {
1175                     getLogger().debug( "Adding User-Agent configuration." );
1176                     XmlPlexusConfiguration propertyConfig = new XmlPlexusConfiguration( "property" );
1177                     headerConfig.addChild( propertyConfig );
1178                     
1179                     XmlPlexusConfiguration nameConfig = new XmlPlexusConfiguration( "name" );
1180                     nameConfig.setValue( "User-Agent" );
1181                     propertyConfig.addChild( nameConfig );
1182                     
1183                     XmlPlexusConfiguration versionConfig = new XmlPlexusConfiguration( "value" );
1184                     versionConfig.setValue( httpUserAgent );
1185                     propertyConfig.addChild( versionConfig );
1186                 }
1187                 else
1188                 {
1189                     getLogger().debug( "User-Agent configuration found." );
1190                 }
1191             }
1192             catch ( SecurityException e )
1193             {
1194                 getLogger().debug( "setHttpHeaders method not accessible on wagon: " + wagon + "; skipping User-Agent configuration." );
1195                 // forget it. this method is public, if it exists.
1196             }
1197             catch ( NoSuchMethodException e )
1198             {
1199                 getLogger().debug( "setHttpHeaders method not found on wagon: " + wagon + "; skipping User-Agent configuration." );
1200                 // forget it.
1201             }
1202         }
1203         
1204         return config;
1205     }
1206 
1207     public void addConfiguration( String repositoryId,
1208                                   Xpp3Dom configuration )
1209     {
1210         if ( repositoryId == null || configuration == null )
1211         {
1212             throw new IllegalArgumentException( "arguments can't be null" );
1213         }
1214 
1215         final XmlPlexusConfiguration xmlConf = new XmlPlexusConfiguration( configuration );
1216 
1217         for ( int i = 0; i < configuration.getChildCount(); i++ )
1218         {
1219             Xpp3Dom domChild = configuration.getChild( i );
1220             if ( WAGON_PROVIDER_CONFIGURATION.equals( domChild.getName() ) )
1221             {
1222                 serverWagonProviderMap.put( repositoryId, domChild.getValue() );
1223                 configuration.removeChild( i );
1224                 break;
1225             }
1226             
1227             i++;
1228         }
1229 
1230         serverConfigurationMap.put( repositoryId, xmlConf );
1231     }
1232     
1233     public void setDefaultRepositoryPermissions( RepositoryPermissions defaultRepositoryPermissions )
1234     {
1235         this.defaultRepositoryPermissions = defaultRepositoryPermissions;
1236     }
1237 
1238     // TODO: Remove this, once the maven-shade-plugin 1.2 release is out, allowing configuration of httpHeaders in the components.xml
1239     public void initialize()
1240         throws InitializationException
1241     {
1242         if ( httpUserAgent == null )
1243         {
1244             InputStream resourceAsStream = null;
1245             try
1246             {
1247                 Properties properties = new Properties();
1248                 resourceAsStream = getClass().getClassLoader().getResourceAsStream( MAVEN_ARTIFACT_PROPERTIES );
1249 
1250                 if ( resourceAsStream != null )
1251                 {
1252                     try
1253                     {
1254                         properties.load( resourceAsStream );
1255 
1256                         httpUserAgent =
1257                             "maven-artifact/" + properties.getProperty( "version" ) + " (Java "
1258                                 + System.getProperty( "java.version" ) + "; " + System.getProperty( "os.name" ) + " "
1259                                 + System.getProperty( "os.version" ) + ")";
1260                     }
1261                     catch ( IOException e )
1262                     {
1263                         getLogger().warn(
1264                                           "Failed to load Maven artifact properties from:\n" + MAVEN_ARTIFACT_PROPERTIES
1265                                               + "\n\nUser-Agent HTTP header may be incorrect for artifact resolution." );
1266                     }
1267                 }
1268             }
1269             finally
1270             {
1271                 IOUtil.close( resourceAsStream );
1272             }
1273         }
1274     }
1275     
1276     /**
1277      * {@inheritDoc}
1278      */
1279     public void setHttpUserAgent( String userAgent )
1280     {
1281         this.httpUserAgent = userAgent;
1282     }
1283     
1284     /**
1285      * {@inheritDoc}
1286      */
1287     public String getHttpUserAgent()
1288     {
1289         return httpUserAgent;
1290     }
1291 }