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