View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.repository.legacy;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.io.IOException;
27  import java.lang.reflect.Method;
28  import java.security.NoSuchAlgorithmException;
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Properties;
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.ArtifactRepositoryPolicy;
39  import org.apache.maven.plugin.LegacySupport;
40  import org.apache.maven.wagon.ConnectionException;
41  import org.apache.maven.wagon.ResourceDoesNotExistException;
42  import org.apache.maven.wagon.TransferFailedException;
43  import org.apache.maven.wagon.UnsupportedProtocolException;
44  import org.apache.maven.wagon.Wagon;
45  import org.apache.maven.wagon.authentication.AuthenticationException;
46  import org.apache.maven.wagon.authentication.AuthenticationInfo;
47  import org.apache.maven.wagon.authorization.AuthorizationException;
48  import org.apache.maven.wagon.events.TransferListener;
49  import org.apache.maven.wagon.observers.ChecksumObserver;
50  import org.apache.maven.wagon.proxy.ProxyInfo;
51  import org.apache.maven.wagon.repository.Repository;
52  import org.codehaus.plexus.PlexusContainer;
53  import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
54  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
55  import org.codehaus.plexus.logging.Logger;
56  import org.codehaus.plexus.util.FileUtils;
57  import org.eclipse.aether.ConfigurationProperties;
58  import org.eclipse.aether.util.ConfigUtils;
59  
60  // TODO remove the update check manager
61  // TODO separate into retriever and publisher
62  // TODO remove hardcoding of checksum logic
63  
64  /**
65   * Manages <a href="https://maven.apache.org/wagon">Wagon</a> related operations in Maven.
66   */
67  @Named
68  @Singleton
69  public class DefaultWagonManager implements WagonManager {
70  
71      private static final String[] CHECKSUM_IDS = {"md5", "sha1"};
72  
73      /**
74       * have to match the CHECKSUM_IDS
75       */
76      private static final String[] CHECKSUM_ALGORITHMS = {"MD5", "SHA-1"};
77  
78      @Inject
79      private Logger logger;
80  
81      @Inject
82      private PlexusContainer container;
83  
84      @Inject
85      private UpdateCheckManager updateCheckManager;
86  
87      @Inject
88      private LegacySupport legacySupport;
89  
90      //
91      // Retriever
92      //
93      @Override
94      public void getArtifact(
95              Artifact artifact, ArtifactRepository repository, TransferListener downloadMonitor, boolean force)
96              throws TransferFailedException, ResourceDoesNotExistException {
97          String remotePath = repository.pathOf(artifact);
98  
99          ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
100 
101         if (!policy.isEnabled()) {
102             logger.debug(
103                     "Skipping disabled repository " + repository.getId() + " for resolution of " + artifact.getId());
104 
105         } else if (artifact.isSnapshot() || !artifact.getFile().exists()) {
106             if (force || updateCheckManager.isUpdateRequired(artifact, repository)) {
107                 logger.debug("Trying repository " + repository.getId() + " for resolution of " + artifact.getId()
108                         + " from " + remotePath);
109 
110                 try {
111                     getRemoteFile(
112                             repository,
113                             artifact.getFile(),
114                             remotePath,
115                             downloadMonitor,
116                             policy.getChecksumPolicy(),
117                             false);
118 
119                     updateCheckManager.touch(artifact, repository, null);
120                 } catch (ResourceDoesNotExistException e) {
121                     updateCheckManager.touch(artifact, repository, null);
122                     throw e;
123                 } catch (TransferFailedException e) {
124                     String error = (e.getMessage() != null)
125                             ? e.getMessage()
126                             : e.getClass().getSimpleName();
127                     updateCheckManager.touch(artifact, repository, error);
128                     throw e;
129                 }
130 
131                 logger.debug("  Artifact " + artifact.getId() + " resolved to " + artifact.getFile());
132 
133                 artifact.setResolved(true);
134             } else if (!artifact.getFile().exists()) {
135                 String error = updateCheckManager.getError(artifact, repository);
136                 if (error != null) {
137                     throw new TransferFailedException("Failure to resolve " + remotePath + " from "
138                             + repository.getUrl()
139                             + " was cached in the local repository. "
140                             + "Resolution will not be reattempted until the update interval of "
141                             + repository.getId() + " has elapsed or updates are forced. Original error: " + error);
142 
143                 } else {
144                     throw new ResourceDoesNotExistException(
145                             "Failure to resolve " + remotePath + " from " + repository.getUrl()
146                                     + " was cached in the local repository. "
147                                     + "Resolution will not be reattempted until the update interval of "
148                                     + repository.getId() + " has elapsed or updates are forced.");
149                 }
150             }
151         }
152     }
153 
154     @Override
155     public void getArtifact(
156             Artifact artifact,
157             List<ArtifactRepository> remoteRepositories,
158             TransferListener downloadMonitor,
159             boolean force)
160             throws TransferFailedException, ResourceDoesNotExistException {
161         TransferFailedException tfe = null;
162 
163         for (ArtifactRepository repository : remoteRepositories) {
164             try {
165                 getArtifact(artifact, repository, downloadMonitor, force);
166 
167                 if (artifact.isResolved()) {
168                     artifact.setRepository(repository);
169                     break;
170                 }
171             } catch (ResourceDoesNotExistException e) {
172                 // This one we will eat when looking through remote repositories
173                 // because we want to cycle through them all before squawking.
174 
175                 logger.debug(
176                         "Unable to find artifact " + artifact.getId() + " in repository " + repository.getId() + " ("
177                                 + repository.getUrl() + ")",
178                         e);
179 
180             } catch (TransferFailedException e) {
181                 tfe = e;
182 
183                 String msg = "Unable to get artifact " + artifact.getId() + " from repository " + repository.getId()
184                         + " (" + repository.getUrl() + "): " + e.getMessage();
185 
186                 if (logger.isDebugEnabled()) {
187                     logger.warn(msg, e);
188                 } else {
189                     logger.warn(msg);
190                 }
191             }
192         }
193 
194         // if it already exists locally we were just trying to force it - ignore the update
195         if (!artifact.getFile().exists()) {
196             if (tfe != null) {
197                 throw tfe;
198             } else {
199                 throw new ResourceDoesNotExistException("Unable to download the artifact from any repository");
200             }
201         }
202     }
203 
204     @Override
205     public void getArtifactMetadata(
206             ArtifactMetadata metadata, ArtifactRepository repository, File destination, String checksumPolicy)
207             throws TransferFailedException, ResourceDoesNotExistException {
208         String remotePath = repository.pathOfRemoteRepositoryMetadata(metadata);
209 
210         getRemoteFile(repository, destination, remotePath, null, checksumPolicy, true);
211     }
212 
213     @Override
214     public void getArtifactMetadataFromDeploymentRepository(
215             ArtifactMetadata metadata, ArtifactRepository repository, File destination, String checksumPolicy)
216             throws TransferFailedException, ResourceDoesNotExistException {
217         String remotePath = repository.pathOfRemoteRepositoryMetadata(metadata);
218 
219         getRemoteFile(repository, destination, remotePath, null, checksumPolicy, true);
220     }
221 
222     /**
223      * Deal with connecting to a wagon repository taking into account authentication and proxies.
224      *
225      * @param wagon
226      * @param repository
227      *
228      * @throws ConnectionException
229      * @throws AuthenticationException
230      */
231     private void connectWagon(Wagon wagon, ArtifactRepository repository)
232             throws ConnectionException, AuthenticationException {
233         // MNG-5509
234         // See org.eclipse.aether.connector.wagon.WagonRepositoryConnector.connectWagon(Wagon)
235         if (legacySupport.getRepositorySession() != null) {
236             String userAgent = ConfigUtils.getString(
237                     legacySupport.getRepositorySession(), null, ConfigurationProperties.USER_AGENT);
238 
239             if (userAgent == null) {
240                 Properties headers = new Properties();
241 
242                 headers.put(
243                         "User-Agent",
244                         ConfigUtils.getString(
245                                 legacySupport.getRepositorySession(), "Maven", ConfigurationProperties.USER_AGENT));
246                 try {
247                     Method setHttpHeaders = wagon.getClass().getMethod("setHttpHeaders", Properties.class);
248                     setHttpHeaders.invoke(wagon, headers);
249                 } catch (NoSuchMethodException e) {
250                     // normal for non-http wagons
251                 } catch (Exception e) {
252                     logger.debug("Could not set user agent for wagon "
253                             + wagon.getClass().getName() + ": " + e);
254                 }
255             }
256         }
257 
258         if (repository.getProxy() != null && logger.isDebugEnabled()) {
259             logger.debug("Using proxy " + repository.getProxy().getHost() + ":"
260                     + repository.getProxy().getPort() + " for " + repository.getUrl());
261         }
262 
263         if (repository.getAuthentication() != null && repository.getProxy() != null) {
264             wagon.connect(
265                     new Repository(repository.getId(), repository.getUrl()),
266                     authenticationInfo(repository),
267                     proxyInfo(repository));
268 
269         } else if (repository.getAuthentication() != null) {
270             wagon.connect(new Repository(repository.getId(), repository.getUrl()), authenticationInfo(repository));
271 
272         } else if (repository.getProxy() != null) {
273             wagon.connect(new Repository(repository.getId(), repository.getUrl()), proxyInfo(repository));
274         } else {
275             wagon.connect(new Repository(repository.getId(), repository.getUrl()));
276         }
277     }
278 
279     private AuthenticationInfo authenticationInfo(ArtifactRepository repository) {
280         AuthenticationInfo ai = new AuthenticationInfo();
281         ai.setUserName(repository.getAuthentication().getUsername());
282         ai.setPassword(repository.getAuthentication().getPassword());
283         return ai;
284     }
285 
286     private ProxyInfo proxyInfo(ArtifactRepository repository) {
287         ProxyInfo proxyInfo = new ProxyInfo();
288         proxyInfo.setHost(repository.getProxy().getHost());
289         proxyInfo.setType(repository.getProxy().getProtocol());
290         proxyInfo.setPort(repository.getProxy().getPort());
291         proxyInfo.setNonProxyHosts(repository.getProxy().getNonProxyHosts());
292         proxyInfo.setUserName(repository.getProxy().getUserName());
293         proxyInfo.setPassword(repository.getProxy().getPassword());
294         return proxyInfo;
295     }
296 
297     @SuppressWarnings("checkstyle:methodlength")
298     @Override
299     public void getRemoteFile(
300             ArtifactRepository repository,
301             File destination,
302             String remotePath,
303             TransferListener downloadMonitor,
304             String checksumPolicy,
305             boolean force)
306             throws TransferFailedException, ResourceDoesNotExistException {
307         String protocol = repository.getProtocol();
308 
309         Wagon wagon;
310 
311         try {
312             wagon = getWagon(protocol);
313         } catch (UnsupportedProtocolException e) {
314             throw new TransferFailedException("Unsupported Protocol: '" + protocol + "': " + e.getMessage(), e);
315         }
316 
317         if (downloadMonitor != null) {
318             wagon.addTransferListener(downloadMonitor);
319         }
320 
321         File temp = new File(destination + ".tmp");
322 
323         temp.deleteOnExit();
324 
325         boolean downloaded = false;
326 
327         try {
328             connectWagon(wagon, repository);
329 
330             boolean firstRun = true;
331             boolean retry = true;
332 
333             // this will run at most twice. The first time, the firstRun flag is turned off, and if the retry flag
334             // is set on the first run, it will be turned off and not re-set on the second try. This is because the
335             // only way the retry flag can be set is if ( firstRun == true ).
336             while (firstRun || retry) {
337                 ChecksumObserver md5ChecksumObserver = null;
338                 ChecksumObserver sha1ChecksumObserver = null;
339                 try {
340                     // TODO configure on repository
341                     int i = 0;
342 
343                     md5ChecksumObserver = addChecksumObserver(wagon, CHECKSUM_ALGORITHMS[i++]);
344                     sha1ChecksumObserver = addChecksumObserver(wagon, CHECKSUM_ALGORITHMS[i++]);
345 
346                     // reset the retry flag.
347                     retry = false;
348 
349                     // This should take care of creating destination directory now on
350                     if (destination.exists() && !force) {
351                         try {
352                             downloaded = wagon.getIfNewer(remotePath, temp, destination.lastModified());
353 
354                             if (!downloaded) {
355                                 // prevent additional checks of this artifact until it expires again
356                                 destination.setLastModified(System.currentTimeMillis());
357                             }
358                         } catch (UnsupportedOperationException e) {
359                             // older wagons throw this. Just get() instead
360                             wagon.get(remotePath, temp);
361 
362                             downloaded = true;
363                         }
364                     } else {
365                         wagon.get(remotePath, temp);
366                         downloaded = true;
367                     }
368                 } finally {
369                     wagon.removeTransferListener(md5ChecksumObserver);
370                     wagon.removeTransferListener(sha1ChecksumObserver);
371                 }
372 
373                 if (downloaded) {
374                     // keep the checksum files from showing up on the download monitor...
375                     if (downloadMonitor != null) {
376                         wagon.removeTransferListener(downloadMonitor);
377                     }
378 
379                     // try to verify the SHA-1 checksum for this file.
380                     try {
381                         verifyChecksum(sha1ChecksumObserver, destination, temp, remotePath, ".sha1", wagon);
382                     } catch (ChecksumFailedException e) {
383                         // if we catch a ChecksumFailedException, it means the transfer/read succeeded, but the
384                         // checksum doesn't match. This could be a problem with the server (ibiblio HTTP-200 error
385                         // page), so we'll try this up to two times. On the second try, we'll handle it as a bona-fide
386                         // error, based on the repository's checksum checking policy.
387                         if (firstRun) {
388                             logger.warn("*** CHECKSUM FAILED - " + e.getMessage() + " - RETRYING");
389                             retry = true;
390                         } else {
391                             handleChecksumFailure(checksumPolicy, e.getMessage(), e.getCause());
392                         }
393                     } catch (ResourceDoesNotExistException sha1TryException) {
394                         logger.debug("SHA1 not found, trying MD5: " + sha1TryException.getMessage());
395 
396                         // if this IS NOT a ChecksumFailedException, it was a problem with transfer/read of the checksum
397                         // file...we'll try again with the MD5 checksum.
398                         try {
399                             verifyChecksum(md5ChecksumObserver, destination, temp, remotePath, ".md5", wagon);
400                         } catch (ChecksumFailedException e) {
401                             // if we also fail to verify based on the MD5 checksum, and the checksum transfer/read
402                             // succeeded, then we need to determine whether to retry or handle it as a failure.
403                             if (firstRun) {
404                                 retry = true;
405                             } else {
406                                 handleChecksumFailure(checksumPolicy, e.getMessage(), e.getCause());
407                             }
408                         } catch (ResourceDoesNotExistException md5TryException) {
409                             // this was a failed transfer, and we don't want to retry.
410                             handleChecksumFailure(
411                                     checksumPolicy,
412                                     "Error retrieving checksum file for " + remotePath,
413                                     md5TryException);
414                         }
415                     }
416 
417                     // reinstate the download monitor...
418                     if (downloadMonitor != null) {
419                         wagon.addTransferListener(downloadMonitor);
420                     }
421                 }
422 
423                 // unset the firstRun flag, so we don't get caught in an infinite loop...
424                 firstRun = false;
425             }
426         } catch (ConnectionException e) {
427             throw new TransferFailedException("Connection failed: " + e.getMessage(), e);
428         } catch (AuthenticationException e) {
429             throw new TransferFailedException("Authentication failed: " + e.getMessage(), e);
430         } catch (AuthorizationException e) {
431             throw new TransferFailedException("Authorization failed: " + e.getMessage(), e);
432         } finally {
433             // Remove remaining TransferListener instances (checksum handlers removed in above finally clause)
434             if (downloadMonitor != null) {
435                 wagon.removeTransferListener(downloadMonitor);
436             }
437 
438             disconnectWagon(wagon);
439 
440             releaseWagon(protocol, wagon);
441         }
442 
443         if (downloaded) {
444             if (!temp.exists()) {
445                 throw new ResourceDoesNotExistException("Downloaded file does not exist: " + temp);
446             }
447 
448             // The temporary file is named destination + ".tmp" and is done this way to ensure
449             // that the temporary file is in the same file system as the destination because the
450             // File.renameTo operation doesn't really work across file systems.
451             // So we will attempt to do a File.renameTo for efficiency and atomicity, if this fails
452             // then we will use a brute force copy and delete the temporary file.
453             if (!temp.renameTo(destination)) {
454                 try {
455                     FileUtils.copyFile(temp, destination);
456 
457                     if (!temp.delete()) {
458                         temp.deleteOnExit();
459                     }
460                 } catch (IOException e) {
461                     throw new TransferFailedException(
462                             "Error copying temporary file to the final destination: " + e.getMessage(), e);
463                 }
464             }
465         }
466     }
467 
468     //
469     // Publisher
470     //
471     @Override
472     public void putArtifact(
473             File source, Artifact artifact, ArtifactRepository deploymentRepository, TransferListener downloadMonitor)
474             throws TransferFailedException {
475         putRemoteFile(deploymentRepository, source, deploymentRepository.pathOf(artifact), downloadMonitor);
476     }
477 
478     @Override
479     public void putArtifactMetadata(File source, ArtifactMetadata artifactMetadata, ArtifactRepository repository)
480             throws TransferFailedException {
481         logger.info("Uploading " + artifactMetadata);
482         putRemoteFile(repository, source, repository.pathOfRemoteRepositoryMetadata(artifactMetadata), null);
483     }
484 
485     @Override
486     public void putRemoteFile(
487             ArtifactRepository repository, File source, String remotePath, TransferListener downloadMonitor)
488             throws TransferFailedException {
489         String protocol = repository.getProtocol();
490 
491         Wagon wagon;
492         try {
493             wagon = getWagon(protocol);
494         } catch (UnsupportedProtocolException e) {
495             throw new TransferFailedException("Unsupported Protocol: '" + protocol + "': " + e.getMessage(), e);
496         }
497 
498         if (downloadMonitor != null) {
499             wagon.addTransferListener(downloadMonitor);
500         }
501 
502         Map<String, ChecksumObserver> checksums = new HashMap<>(2);
503 
504         Map<String, String> sums = new HashMap<>(2);
505 
506         // TODO configure these on the repository
507         for (int i = 0; i < CHECKSUM_IDS.length; i++) {
508             checksums.put(CHECKSUM_IDS[i], addChecksumObserver(wagon, CHECKSUM_ALGORITHMS[i]));
509         }
510 
511         List<File> temporaryFiles = new ArrayList<>();
512 
513         try {
514             try {
515                 connectWagon(wagon, repository);
516 
517                 wagon.put(source, remotePath);
518             } finally {
519                 if (downloadMonitor != null) {
520                     wagon.removeTransferListener(downloadMonitor);
521                 }
522             }
523 
524             // Pre-store the checksums as any future puts will overwrite them
525             for (String extension : checksums.keySet()) {
526                 ChecksumObserver observer = checksums.get(extension);
527                 sums.put(extension, observer.getActualChecksum());
528             }
529 
530             // We do this in here so we can checksum the artifact metadata too, otherwise it could be metadata itself
531             for (String extension : checksums.keySet()) {
532                 // TODO shouldn't need a file intermediary - improve wagon to take a stream
533                 File temp = File.createTempFile("maven-artifact", null);
534                 temp.deleteOnExit();
535                 FileUtils.fileWrite(temp.getAbsolutePath(), "UTF-8", sums.get(extension));
536 
537                 temporaryFiles.add(temp);
538                 wagon.put(temp, remotePath + "." + extension);
539             }
540         } catch (ConnectionException e) {
541             throw new TransferFailedException("Connection failed: " + e.getMessage(), e);
542         } catch (AuthenticationException e) {
543             throw new TransferFailedException("Authentication failed: " + e.getMessage(), e);
544         } catch (AuthorizationException e) {
545             throw new TransferFailedException("Authorization failed: " + e.getMessage(), e);
546         } catch (ResourceDoesNotExistException e) {
547             throw new TransferFailedException("Resource to deploy not found: " + e.getMessage(), e);
548         } catch (IOException e) {
549             throw new TransferFailedException("Error creating temporary file for deployment: " + e.getMessage(), e);
550         } finally {
551             // MNG-4543
552             cleanupTemporaryFiles(temporaryFiles);
553 
554             // Remove every checksum listener
555             for (String id : CHECKSUM_IDS) {
556                 TransferListener checksumListener = checksums.get(id);
557                 if (checksumListener != null) {
558                     wagon.removeTransferListener(checksumListener);
559                 }
560             }
561 
562             disconnectWagon(wagon);
563 
564             releaseWagon(protocol, wagon);
565         }
566     }
567 
568     private void cleanupTemporaryFiles(List<File> files) {
569         for (File file : files) {
570             // really don't care if it failed here only log warning
571             if (!file.delete()) {
572                 logger.warn("skip failed to delete temporary file : " + file.getAbsolutePath());
573                 file.deleteOnExit();
574             }
575         }
576     }
577 
578     private ChecksumObserver addChecksumObserver(Wagon wagon, String algorithm) throws TransferFailedException {
579         try {
580             ChecksumObserver checksumObserver = new ChecksumObserver(algorithm);
581             wagon.addTransferListener(checksumObserver);
582             return checksumObserver;
583         } catch (NoSuchAlgorithmException e) {
584             throw new TransferFailedException("Unable to add checksum for unsupported algorithm " + algorithm, e);
585         }
586     }
587 
588     private void handleChecksumFailure(String checksumPolicy, String message, Throwable cause)
589             throws ChecksumFailedException {
590         if (ArtifactRepositoryPolicy.CHECKSUM_POLICY_FAIL.equals(checksumPolicy)) {
591             throw new ChecksumFailedException(message, cause);
592         } else if (!ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE.equals(checksumPolicy)) {
593             // warn if it is set to anything other than ignore
594             logger.warn("*** CHECKSUM FAILED - " + message + " - IGNORING");
595         }
596         // otherwise it is ignore
597     }
598 
599     private void verifyChecksum(
600             ChecksumObserver checksumObserver,
601             File destination,
602             File tempDestination,
603             String remotePath,
604             String checksumFileExtension,
605             Wagon wagon)
606             throws ResourceDoesNotExistException, TransferFailedException, AuthorizationException {
607         try {
608             // grab it first, because it's about to change...
609             String actualChecksum = checksumObserver.getActualChecksum();
610 
611             File tempChecksumFile = new File(tempDestination + checksumFileExtension + ".tmp");
612             tempChecksumFile.deleteOnExit();
613             wagon.get(remotePath + checksumFileExtension, tempChecksumFile);
614 
615             String expectedChecksum = FileUtils.fileRead(tempChecksumFile, "UTF-8");
616 
617             // remove whitespaces at the end
618             expectedChecksum = expectedChecksum.trim();
619 
620             // check for 'ALGO (name) = CHECKSUM' like used by openssl
621             if (expectedChecksum.regionMatches(true, 0, "MD", 0, 2)
622                     || expectedChecksum.regionMatches(true, 0, "SHA", 0, 3)) {
623                 int lastSpacePos = expectedChecksum.lastIndexOf(' ');
624                 expectedChecksum = expectedChecksum.substring(lastSpacePos + 1);
625             } else {
626                 // remove everything after the first space (if available)
627                 int spacePos = expectedChecksum.indexOf(' ');
628 
629                 if (spacePos != -1) {
630                     expectedChecksum = expectedChecksum.substring(0, spacePos);
631                 }
632             }
633             if (expectedChecksum.equalsIgnoreCase(actualChecksum)) {
634                 File checksumFile = new File(destination + checksumFileExtension);
635                 if (checksumFile.exists()) {
636                     checksumFile.delete(); // ignore if failed as we will overwrite
637                 }
638                 FileUtils.copyFile(tempChecksumFile, checksumFile);
639                 if (!tempChecksumFile.delete()) {
640                     tempChecksumFile.deleteOnExit();
641                 }
642             } else {
643                 throw new ChecksumFailedException("Checksum failed on download: local = '" + actualChecksum
644                         + "'; remote = '" + expectedChecksum + "'");
645             }
646         } catch (IOException e) {
647             throw new ChecksumFailedException("Invalid checksum file", e);
648         }
649     }
650 
651     private void disconnectWagon(Wagon wagon) {
652         try {
653             wagon.disconnect();
654         } catch (ConnectionException e) {
655             logger.error("Problem disconnecting from wagon - ignoring: " + e.getMessage());
656         }
657     }
658 
659     private void releaseWagon(String protocol, Wagon wagon) {
660         try {
661             container.release(wagon);
662         } catch (ComponentLifecycleException e) {
663             logger.error("Problem releasing wagon - ignoring: " + e.getMessage());
664             logger.debug("", e);
665         }
666     }
667 
668     @Override
669     @Deprecated
670     public Wagon getWagon(Repository repository) throws UnsupportedProtocolException {
671         return getWagon(repository.getProtocol());
672     }
673 
674     @Override
675     @Deprecated
676     public Wagon getWagon(String protocol) throws UnsupportedProtocolException {
677         if (protocol == null) {
678             throw new UnsupportedProtocolException("Unspecified protocol");
679         }
680 
681         String hint = protocol.toLowerCase(java.util.Locale.ENGLISH);
682 
683         Wagon wagon;
684         try {
685             wagon = container.lookup(Wagon.class, hint);
686         } catch (ComponentLookupException e) {
687             throw new UnsupportedProtocolException(
688                     "Cannot find wagon which supports the requested protocol: " + protocol, e);
689         }
690 
691         return wagon;
692     }
693 }