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