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