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