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