View Javadoc

1   package org.sonatype.aether.connector.wagon;
2   
3   /*******************************************************************************
4    * Copyright (c) 2010-2011 Sonatype, Inc.
5    * All rights reserved. This program and the accompanying materials
6    * are made available under the terms of the Eclipse Public License v1.0
7    * which accompanies this distribution, and is available at
8    *   http://www.eclipse.org/legal/epl-v10.html
9    *******************************************************************************/
10  
11  import java.io.ByteArrayInputStream;
12  import java.io.File;
13  import java.io.IOException;
14  import java.lang.reflect.Method;
15  import java.util.ArrayList;
16  import java.util.Collection;
17  import java.util.Collections;
18  import java.util.LinkedHashMap;
19  import java.util.Locale;
20  import java.util.Map;
21  import java.util.Properties;
22  import java.util.Queue;
23  import java.util.UUID;
24  import java.util.concurrent.ConcurrentLinkedQueue;
25  import java.util.concurrent.Executor;
26  import java.util.concurrent.ExecutorService;
27  import java.util.concurrent.LinkedBlockingQueue;
28  import java.util.concurrent.ThreadPoolExecutor;
29  import java.util.concurrent.TimeUnit;
30  
31  import org.apache.maven.wagon.ResourceDoesNotExistException;
32  import org.apache.maven.wagon.StreamingWagon;
33  import org.apache.maven.wagon.Wagon;
34  import org.apache.maven.wagon.WagonException;
35  import org.apache.maven.wagon.authentication.AuthenticationInfo;
36  import org.apache.maven.wagon.observers.ChecksumObserver;
37  import org.apache.maven.wagon.proxy.ProxyInfo;
38  import org.apache.maven.wagon.proxy.ProxyInfoProvider;
39  import org.apache.maven.wagon.repository.Repository;
40  import org.apache.maven.wagon.repository.RepositoryPermissions;
41  import org.sonatype.aether.ConfigurationProperties;
42  import org.sonatype.aether.RepositorySystemSession;
43  import org.sonatype.aether.repository.Authentication;
44  import org.sonatype.aether.repository.Proxy;
45  import org.sonatype.aether.repository.RemoteRepository;
46  import org.sonatype.aether.repository.RepositoryPolicy;
47  import org.sonatype.aether.spi.connector.ArtifactDownload;
48  import org.sonatype.aether.spi.connector.ArtifactTransfer;
49  import org.sonatype.aether.spi.connector.ArtifactUpload;
50  import org.sonatype.aether.spi.connector.MetadataDownload;
51  import org.sonatype.aether.spi.connector.MetadataTransfer;
52  import org.sonatype.aether.spi.connector.MetadataUpload;
53  import org.sonatype.aether.spi.connector.RepositoryConnector;
54  import org.sonatype.aether.spi.connector.Transfer;
55  import org.sonatype.aether.spi.io.FileProcessor;
56  import org.sonatype.aether.spi.log.Logger;
57  import org.sonatype.aether.transfer.ArtifactNotFoundException;
58  import org.sonatype.aether.transfer.ArtifactTransferException;
59  import org.sonatype.aether.transfer.ChecksumFailureException;
60  import org.sonatype.aether.transfer.MetadataNotFoundException;
61  import org.sonatype.aether.transfer.MetadataTransferException;
62  import org.sonatype.aether.transfer.NoRepositoryConnectorException;
63  import org.sonatype.aether.transfer.TransferEvent;
64  import org.sonatype.aether.transfer.TransferListener;
65  import org.sonatype.aether.util.ChecksumUtils;
66  import org.sonatype.aether.util.ConfigUtils;
67  import org.sonatype.aether.util.concurrency.RunnableErrorForwarder;
68  import org.sonatype.aether.util.layout.MavenDefaultLayout;
69  import org.sonatype.aether.util.layout.RepositoryLayout;
70  import org.sonatype.aether.util.listener.DefaultTransferEvent;
71  
72  /**
73   * A repository connector that uses Maven Wagon for the transfer.
74   * 
75   * @author Benjamin Bentmann
76   */
77  class WagonRepositoryConnector
78      implements RepositoryConnector
79  {
80  
81      private static final String PROP_THREADS = "aether.connector.wagon.threads";
82  
83      private static final String PROP_CONFIG = "aether.connector.wagon.config";
84  
85      private static final String PROP_FILE_MODE = "aether.connector.perms.fileMode";
86  
87      private static final String PROP_DIR_MODE = "aether.connector.perms.dirMode";
88  
89      private static final String PROP_GROUP = "aether.connector.perms.group";
90  
91      private final Logger logger;
92  
93      private final FileProcessor fileProcessor;
94  
95      private final RemoteRepository repository;
96  
97      private final RepositorySystemSession session;
98  
99      private final WagonProvider wagonProvider;
100 
101     private final WagonConfigurator wagonConfigurator;
102 
103     private final String wagonHint;
104 
105     private final Repository wagonRepo;
106 
107     private final AuthenticationInfo wagonAuth;
108 
109     private final ProxyInfoProvider wagonProxy;
110 
111     private final RepositoryLayout layout = new MavenDefaultLayout();
112 
113     private final TransferListener listener;
114 
115     private final Queue<Wagon> wagons = new ConcurrentLinkedQueue<Wagon>();
116 
117     private final Executor executor;
118 
119     private boolean closed;
120 
121     private final Map<String, String> checksumAlgos;
122 
123     private final Properties headers;
124 
125     public WagonRepositoryConnector( WagonProvider wagonProvider, WagonConfigurator wagonConfigurator,
126                                      RemoteRepository repository, RepositorySystemSession session,
127                                      FileProcessor fileProcessor, Logger logger )
128         throws NoRepositoryConnectorException
129     {
130         this.logger = logger;
131         this.fileProcessor = fileProcessor;
132         this.wagonProvider = wagonProvider;
133         this.wagonConfigurator = wagonConfigurator;
134         this.repository = repository;
135         this.session = session;
136         this.listener = session.getTransferListener();
137 
138         if ( !"default".equals( repository.getContentType() ) )
139         {
140             throw new NoRepositoryConnectorException( repository );
141         }
142 
143         wagonRepo = new Repository( repository.getId(), repository.getUrl() );
144         wagonRepo.setPermissions( getPermissions( repository.getId(), session ) );
145 
146         wagonHint = wagonRepo.getProtocol().toLowerCase( Locale.ENGLISH );
147         if ( wagonHint == null || wagonHint.length() <= 0 )
148         {
149             throw new NoRepositoryConnectorException( repository );
150         }
151 
152         try
153         {
154             wagons.add( lookupWagon() );
155         }
156         catch ( Exception e )
157         {
158             logger.debug( e.getMessage(), e );
159             throw new NoRepositoryConnectorException( repository );
160         }
161 
162         wagonAuth = getAuthenticationInfo( repository );
163         wagonProxy = getProxy( repository );
164 
165         int threads = ConfigUtils.getInteger( session, 5, PROP_THREADS, "maven.artifact.threads" );
166         executor = getExecutor( threads );
167 
168         checksumAlgos = new LinkedHashMap<String, String>();
169         checksumAlgos.put( "SHA-1", ".sha1" );
170         checksumAlgos.put( "MD5", ".md5" );
171 
172         headers = new Properties();
173         headers.put( "User-Agent", ConfigUtils.getString( session, ConfigurationProperties.DEFAULT_USER_AGENT,
174                                                           ConfigurationProperties.USER_AGENT ) );
175         Map<?, ?> headers =
176             ConfigUtils.getMap( session, null, ConfigurationProperties.HTTP_HEADERS + "." + repository.getId(),
177                                 ConfigurationProperties.HTTP_HEADERS );
178         if ( headers != null )
179         {
180             this.headers.putAll( headers );
181         }
182     }
183 
184     private Executor getExecutor( int threads )
185     {
186         if ( threads <= 1 )
187         {
188             return new Executor()
189             {
190                 public void execute( Runnable command )
191                 {
192                     command.run();
193                 }
194             };
195         }
196         else
197         {
198             return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() );
199         }
200     }
201 
202     private static RepositoryPermissions getPermissions( String repoId, RepositorySystemSession session )
203     {
204         RepositoryPermissions result = null;
205 
206         RepositoryPermissions perms = new RepositoryPermissions();
207 
208         String suffix = '.' + repoId;
209 
210         String fileMode = ConfigUtils.getString( session, (String) null, PROP_FILE_MODE + suffix );
211         if ( fileMode != null )
212         {
213             perms.setFileMode( fileMode );
214             result = perms;
215         }
216 
217         String dirMode = ConfigUtils.getString( session, (String) null, PROP_DIR_MODE + suffix );
218         if ( dirMode != null )
219         {
220             perms.setDirectoryMode( dirMode );
221             result = perms;
222         }
223 
224         String group = ConfigUtils.getString( session, (String) null, PROP_GROUP + suffix );
225         if ( group != null )
226         {
227             perms.setGroup( group );
228             result = perms;
229         }
230 
231         return result;
232     }
233 
234     private AuthenticationInfo getAuthenticationInfo( RemoteRepository repository )
235     {
236         AuthenticationInfo auth = null;
237 
238         Authentication a = repository.getAuthentication();
239         if ( a != null )
240         {
241             auth = new AuthenticationInfo();
242             auth.setUserName( a.getUsername() );
243             auth.setPassword( a.getPassword() );
244             auth.setPrivateKey( a.getPrivateKeyFile() );
245             auth.setPassphrase( a.getPassphrase() );
246         }
247 
248         return auth;
249     }
250 
251     private ProxyInfoProvider getProxy( RemoteRepository repository )
252     {
253         ProxyInfoProvider proxy = null;
254 
255         Proxy p = repository.getProxy();
256         if ( p != null )
257         {
258             final ProxyInfo prox = new ProxyInfo();
259             prox.setType( p.getType() );
260             prox.setHost( p.getHost() );
261             prox.setPort( p.getPort() );
262             if ( p.getAuthentication() != null )
263             {
264                 prox.setUserName( p.getAuthentication().getUsername() );
265                 prox.setPassword( p.getAuthentication().getPassword() );
266             }
267             proxy = new ProxyInfoProvider()
268             {
269                 public ProxyInfo getProxyInfo( String protocol )
270                 {
271                     return prox;
272                 }
273             };
274         }
275 
276         return proxy;
277     }
278 
279     private Wagon lookupWagon()
280         throws Exception
281     {
282         return wagonProvider.lookup( wagonHint );
283     }
284 
285     private void releaseWagon( Wagon wagon )
286     {
287         wagonProvider.release( wagon );
288     }
289 
290     private void connectWagon( Wagon wagon )
291         throws Exception
292     {
293         if ( !headers.isEmpty() )
294         {
295             try
296             {
297                 Method setHttpHeaders = wagon.getClass().getMethod( "setHttpHeaders", Properties.class );
298                 setHttpHeaders.invoke( wagon, headers );
299             }
300             catch ( NoSuchMethodException e )
301             {
302                 // normal for non-http wagons
303             }
304             catch ( Exception e )
305             {
306                 logger.debug( "Could not set user agent for wagon " + wagon.getClass().getName() + ": " + e );
307             }
308         }
309 
310         int connectTimeout =
311             ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
312                                     ConfigurationProperties.CONNECT_TIMEOUT );
313         int requestTimeout =
314             ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
315                                     ConfigurationProperties.REQUEST_TIMEOUT );
316 
317         wagon.setTimeout( Math.max( Math.max( connectTimeout, requestTimeout ), 0 ) );
318 
319         wagon.setInteractive( ConfigUtils.getBoolean( session, ConfigurationProperties.DEFAULT_INTERACTIVE,
320                                                       ConfigurationProperties.INTERACTIVE ) );
321 
322         Object configuration = ConfigUtils.getObject( session, null, PROP_CONFIG + "." + repository.getId() );
323         if ( configuration != null && wagonConfigurator != null )
324         {
325             try
326             {
327                 wagonConfigurator.configure( wagon, configuration );
328             }
329             catch ( Exception e )
330             {
331                 String msg =
332                     "Could not apply configuration for " + repository.getId() + " to wagon "
333                         + wagon.getClass().getName() + ":" + e.getMessage();
334                 if ( logger.isDebugEnabled() )
335                 {
336                     logger.warn( msg, e );
337                 }
338                 else
339                 {
340                     logger.warn( msg );
341                 }
342             }
343         }
344 
345         wagon.connect( wagonRepo, wagonAuth, wagonProxy );
346     }
347 
348     private void disconnectWagon( Wagon wagon )
349     {
350         try
351         {
352             if ( wagon != null )
353             {
354                 wagon.disconnect();
355             }
356         }
357         catch ( Exception e )
358         {
359             // too bad
360         }
361     }
362 
363     Wagon pollWagon()
364         throws Exception
365     {
366         Wagon wagon = wagons.poll();
367 
368         if ( wagon == null )
369         {
370             try
371             {
372                 wagon = lookupWagon();
373                 connectWagon( wagon );
374             }
375             catch ( Exception e )
376             {
377                 releaseWagon( wagon );
378                 throw e;
379             }
380         }
381         else if ( wagon.getRepository() == null )
382         {
383             try
384             {
385                 connectWagon( wagon );
386             }
387             catch ( Exception e )
388             {
389                 wagons.add( wagon );
390                 throw e;
391             }
392         }
393 
394         return wagon;
395     }
396 
397     private <T> Collection<T> safe( Collection<T> items )
398     {
399         return ( items != null ) ? items : Collections.<T> emptyList();
400     }
401 
402     private File getTmpFile( String path )
403     {
404         File file;
405         do
406         {
407             file = new File( path + ".tmp" + UUID.randomUUID().toString().replace( "-", "" ).substring( 0, 16 ) );
408         }
409         while ( file.exists() );
410         return file;
411     }
412 
413     public void get( Collection<? extends ArtifactDownload> artifactDownloads,
414                      Collection<? extends MetadataDownload> metadataDownloads )
415     {
416         if ( closed )
417         {
418             throw new IllegalStateException( "connector closed" );
419         }
420 
421         artifactDownloads = safe( artifactDownloads );
422         metadataDownloads = safe( metadataDownloads );
423 
424         Collection<GetTask<?>> tasks = new ArrayList<GetTask<?>>();
425 
426         RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
427 
428         for ( MetadataDownload download : metadataDownloads )
429         {
430             String resource = layout.getPath( download.getMetadata() ).getPath();
431             GetTask<?> task =
432                 new GetTask<MetadataTransfer>( resource, download.getFile(), download.getChecksumPolicy(), download,
433                                                METADATA );
434             tasks.add( task );
435             executor.execute( errorForwarder.wrap( task ) );
436         }
437 
438         for ( ArtifactDownload download : artifactDownloads )
439         {
440             String resource = layout.getPath( download.getArtifact() ).getPath();
441             GetTask<?> task =
442                 new GetTask<ArtifactTransfer>( resource, download.isExistenceCheck() ? null : download.getFile(),
443                                                download.getChecksumPolicy(), download, ARTIFACT );
444             tasks.add( task );
445             executor.execute( errorForwarder.wrap( task ) );
446         }
447 
448         errorForwarder.await();
449     }
450 
451     public void put( Collection<? extends ArtifactUpload> artifactUploads,
452                      Collection<? extends MetadataUpload> metadataUploads )
453     {
454         if ( closed )
455         {
456             throw new IllegalStateException( "connector closed" );
457         }
458 
459         artifactUploads = safe( artifactUploads );
460         metadataUploads = safe( metadataUploads );
461 
462         for ( ArtifactUpload upload : artifactUploads )
463         {
464             String path = layout.getPath( upload.getArtifact() ).getPath();
465 
466             PutTask<?> task = new PutTask<ArtifactTransfer>( path, upload.getFile(), upload, ARTIFACT );
467             task.run();
468         }
469 
470         for ( MetadataUpload upload : metadataUploads )
471         {
472             String path = layout.getPath( upload.getMetadata() ).getPath();
473 
474             PutTask<?> task = new PutTask<MetadataTransfer>( path, upload.getFile(), upload, METADATA );
475             task.run();
476         }
477     }
478 
479     public void close()
480     {
481         closed = true;
482 
483         for ( Wagon wagon = wagons.poll(); wagon != null; wagon = wagons.poll() )
484         {
485             disconnectWagon( wagon );
486             releaseWagon( wagon );
487         }
488 
489         shutdown( executor );
490     }
491 
492     private void shutdown( Executor executor )
493     {
494         if ( executor instanceof ExecutorService )
495         {
496             ( (ExecutorService) executor ).shutdown();
497         }
498     }
499 
500     @Override
501     protected void finalize()
502         throws Throwable
503     {
504         try
505         {
506             close();
507         }
508         finally
509         {
510             super.finalize();
511         }
512     }
513 
514     @Override
515     public String toString()
516     {
517         return String.valueOf( repository );
518     }
519 
520     class GetTask<T extends Transfer>
521         implements Runnable
522     {
523 
524         private final T download;
525 
526         private final String path;
527 
528         private final File file;
529 
530         private final String checksumPolicy;
531 
532         private final ExceptionWrapper<T> wrapper;
533 
534         public GetTask( String path, File file, String checksumPolicy, T download, ExceptionWrapper<T> wrapper )
535         {
536             this.path = path;
537             this.file = file;
538             this.checksumPolicy = checksumPolicy;
539             this.download = download;
540             this.wrapper = wrapper;
541         }
542 
543         public T getDownload()
544         {
545             return download;
546         }
547 
548         public void run()
549         {
550             download.setState( Transfer.State.ACTIVE );
551 
552             WagonTransferListenerAdapter wagonListener = null;
553             if ( listener != null )
554             {
555                 wagonListener =
556                     new WagonTransferListenerAdapter( listener, wagonRepo.getUrl(), path, file, download.getTrace() );
557             }
558 
559             try
560             {
561                 if ( listener != null )
562                 {
563                     DefaultTransferEvent event = wagonListener.newEvent();
564                     event.setRequestType( TransferEvent.RequestType.GET );
565                     event.setType( TransferEvent.EventType.INITIATED );
566                     listener.transferInitiated( event );
567                 }
568 
569                 File tmp = ( file != null ) ? getTmpFile( file.getPath() ) : null;
570 
571                 Wagon wagon = pollWagon();
572 
573                 try
574                 {
575                     if ( file == null )
576                     {
577                         if ( !wagon.resourceExists( path ) )
578                         {
579                             throw new ResourceDoesNotExistException( "Could not find " + path + " in "
580                                 + wagonRepo.getUrl() );
581                         }
582                     }
583                     else
584                     {
585                         for ( int trial = 1; trial >= 0; trial-- )
586                         {
587                             ChecksumObserver sha1 = new ChecksumObserver( "SHA-1" );
588                             ChecksumObserver md5 = new ChecksumObserver( "MD5" );
589                             try
590                             {
591                                 wagon.addTransferListener( wagonListener );
592                                 wagon.addTransferListener( md5 );
593                                 wagon.addTransferListener( sha1 );
594 
595                                 /*
596                                  * NOTE: AbstractWagon.createParentDirectories() uses File.mkdirs() which is not
597                                  * thread-safe in all JREs.
598                                  */
599                                 fileProcessor.mkdirs( tmp.getParentFile() );
600 
601                                 wagon.get( path, tmp );
602                             }
603                             finally
604                             {
605                                 wagon.removeTransferListener( wagonListener );
606                                 wagon.removeTransferListener( md5 );
607                                 wagon.removeTransferListener( sha1 );
608                             }
609 
610                             if ( RepositoryPolicy.CHECKSUM_POLICY_IGNORE.equals( checksumPolicy ) )
611                             {
612                                 break;
613                             }
614                             else
615                             {
616                                 try
617                                 {
618                                     if ( !verifyChecksum( wagon, sha1.getActualChecksum(), ".sha1" )
619                                         && !verifyChecksum( wagon, md5.getActualChecksum(), ".md5" ) )
620                                     {
621                                         trial = 0;
622                                         throw new ChecksumFailureException( "Checksum validation failed"
623                                             + ", no checksums available from the repository" );
624                                     }
625                                     break;
626                                 }
627                                 catch ( ChecksumFailureException e )
628                                 {
629                                     if ( trial <= 0 && RepositoryPolicy.CHECKSUM_POLICY_FAIL.equals( checksumPolicy ) )
630                                     {
631                                         throw e;
632                                     }
633                                     if ( listener != null )
634                                     {
635                                         DefaultTransferEvent event = wagonListener.newEvent();
636                                         event.setRequestType( TransferEvent.RequestType.GET );
637                                         event.setType( TransferEvent.EventType.CORRUPTED );
638                                         event.setException( e );
639                                         listener.transferCorrupted( event );
640                                     }
641                                 }
642                             }
643                         }
644 
645                         rename( tmp, file );
646                     }
647 
648                     wrapper.wrap( download, null, repository );
649 
650                     if ( listener != null )
651                     {
652                         DefaultTransferEvent event = wagonListener.newEvent();
653                         event.setRequestType( TransferEvent.RequestType.GET );
654                         event.setType( TransferEvent.EventType.SUCCEEDED );
655                         listener.transferSucceeded( event );
656                     }
657                 }
658                 finally
659                 {
660                     if ( tmp != null )
661                     {
662                         tmp.delete();
663                     }
664                     wagons.add( wagon );
665                 }
666             }
667             catch ( Exception e )
668             {
669                 e = wrapper.wrap( download, e, repository );
670 
671                 if ( listener != null )
672                 {
673                     DefaultTransferEvent event = wagonListener.newEvent();
674                     event.setRequestType( TransferEvent.RequestType.GET );
675                     event.setType( TransferEvent.EventType.FAILED );
676                     event.setException( e );
677                     listener.transferFailed( event );
678                 }
679             }
680             finally
681             {
682                 download.setState( Transfer.State.DONE );
683             }
684         }
685 
686         private boolean verifyChecksum( Wagon wagon, String actual, String ext )
687             throws ChecksumFailureException
688         {
689             File tmp = getTmpFile( file.getPath() + ext );
690 
691             try
692             {
693                 try
694                 {
695                     wagon.get( path + ext, tmp );
696                 }
697                 catch ( ResourceDoesNotExistException e )
698                 {
699                     return false;
700                 }
701                 catch ( WagonException e )
702                 {
703                     throw new ChecksumFailureException( e );
704                 }
705 
706                 String expected;
707 
708                 try
709                 {
710                     expected = ChecksumUtils.read( tmp );
711                 }
712                 catch ( IOException e )
713                 {
714                     throw new ChecksumFailureException( e );
715                 }
716 
717                 if ( expected.equalsIgnoreCase( actual ) )
718                 {
719                     try
720                     {
721                         rename( tmp, new File( file.getPath() + ext ) );
722                     }
723                     catch ( IOException e )
724                     {
725                         logger.debug( "Failed to write checksum file " + file.getPath() + ext + ": " + e.getMessage(),
726                                       e );
727                     }
728                 }
729                 else
730                 {
731                     throw new ChecksumFailureException( expected, actual );
732                 }
733             }
734             finally
735             {
736                 tmp.delete();
737             }
738 
739             return true;
740         }
741 
742         private void rename( File from, File to )
743             throws IOException
744         {
745             if ( !from.exists() )
746             {
747                 /*
748                  * NOTE: Wagon (1.0-beta-6) doesn't create the destination file when transferring a 0-byte resource. So
749                  * if the resource we asked for didn't cause any exception but doesn't show up in the tmp file either,
750                  * Wagon tells us in its weird way the file is empty.
751                  */
752                 fileProcessor.write( to, "" );
753             }
754             else
755             {
756                 fileProcessor.move( from, to );
757             }
758         }
759 
760     }
761 
762     class PutTask<T extends Transfer>
763         implements Runnable
764     {
765 
766         private final T upload;
767 
768         private final ExceptionWrapper<T> wrapper;
769 
770         private final String path;
771 
772         private final File file;
773 
774         public PutTask( String path, File file, T upload, ExceptionWrapper<T> wrapper )
775         {
776             this.path = path;
777             this.file = file;
778             this.upload = upload;
779             this.wrapper = wrapper;
780         }
781 
782         public void run()
783         {
784             upload.setState( Transfer.State.ACTIVE );
785 
786             WagonTransferListenerAdapter wagonListener = null;
787             if ( listener != null )
788             {
789                 wagonListener =
790                     new WagonTransferListenerAdapter( listener, wagonRepo.getUrl(), path, file, upload.getTrace() );
791             }
792 
793             try
794             {
795                 if ( listener != null )
796                 {
797                     DefaultTransferEvent event = wagonListener.newEvent();
798                     event.setRequestType( TransferEvent.RequestType.PUT );
799                     event.setType( TransferEvent.EventType.INITIATED );
800                     listener.transferInitiated( event );
801                 }
802 
803                 Wagon wagon = pollWagon();
804 
805                 try
806                 {
807                     try
808                     {
809                         wagon.addTransferListener( wagonListener );
810 
811                         wagon.put( file, path );
812                     }
813                     finally
814                     {
815                         wagon.removeTransferListener( wagonListener );
816                     }
817 
818                     uploadChecksums( wagon, file, path );
819 
820                     wrapper.wrap( upload, null, repository );
821 
822                     if ( listener != null )
823                     {
824                         DefaultTransferEvent event = wagonListener.newEvent();
825                         event.setRequestType( TransferEvent.RequestType.PUT );
826                         event.setType( TransferEvent.EventType.SUCCEEDED );
827                         listener.transferSucceeded( event );
828                     }
829                 }
830                 finally
831                 {
832                     wagons.add( wagon );
833                 }
834             }
835             catch ( Exception e )
836             {
837                 e = wrapper.wrap( upload, e, repository );
838 
839                 if ( listener != null )
840                 {
841                     DefaultTransferEvent event = wagonListener.newEvent();
842                     event.setRequestType( TransferEvent.RequestType.PUT );
843                     event.setType( TransferEvent.EventType.FAILED );
844                     event.setException( e );
845                     listener.transferFailed( event );
846                 }
847             }
848             finally
849             {
850                 upload.setState( Transfer.State.DONE );
851             }
852         }
853 
854         private void uploadChecksums( Wagon wagon, File file, String path )
855         {
856             try
857             {
858                 Map<String, Object> checksums = ChecksumUtils.calc( file, checksumAlgos.keySet() );
859                 for ( Map.Entry<String, Object> entry : checksums.entrySet() )
860                 {
861                     uploadChecksum( wagon, file, path, entry.getKey(), entry.getValue() );
862                 }
863             }
864             catch ( IOException e )
865             {
866                 logger.debug( "Failed to upload checksums for " + file + ": " + e.getMessage(), e );
867             }
868         }
869 
870         private void uploadChecksum( Wagon wagon, File file, String path, String algo, Object checksum )
871         {
872             try
873             {
874                 if ( checksum instanceof Exception )
875                 {
876                     throw (Exception) checksum;
877                 }
878 
879                 String ext = checksumAlgos.get( algo );
880                 String dst = path + ext;
881                 String sum = String.valueOf( checksum );
882 
883                 if ( wagon instanceof StreamingWagon )
884                 {
885                     byte[] data = sum.getBytes( "UTF-8" );
886                     ( (StreamingWagon) wagon ).putFromStream( new ByteArrayInputStream( data ), dst, data.length, -1 );
887                 }
888                 else
889                 {
890                     File tmpFile = File.createTempFile( "wagon" + UUID.randomUUID().toString().replace( "-", "" ), ext );
891                     try
892                     {
893                         fileProcessor.write( tmpFile, sum );
894                         wagon.put( tmpFile, dst );
895                     }
896                     finally
897                     {
898                         tmpFile.delete();
899                     }
900                 }
901             }
902             catch ( Exception e )
903             {
904                 logger.warn( "Failed to upload " + algo + " checksum for " + file + ": " + e.getMessage(), e );
905             }
906         }
907 
908     }
909 
910     static interface ExceptionWrapper<T>
911     {
912 
913         Exception wrap( T transfer, Exception e, RemoteRepository repository );
914 
915     }
916 
917     private static final ExceptionWrapper<MetadataTransfer> METADATA = new ExceptionWrapper<MetadataTransfer>()
918     {
919 
920         public Exception wrap( MetadataTransfer transfer, Exception e, RemoteRepository repository )
921         {
922             MetadataTransferException ex = null;
923             e = WagonCancelledException.unwrap( e );
924             if ( e instanceof ResourceDoesNotExistException )
925             {
926                 ex = new MetadataNotFoundException( transfer.getMetadata(), repository );
927             }
928             else if ( e != null )
929             {
930                 ex = new MetadataTransferException( transfer.getMetadata(), repository, e );
931             }
932             transfer.setException( ex );
933             return ex;
934         }
935 
936     };
937 
938     private static final ExceptionWrapper<ArtifactTransfer> ARTIFACT = new ExceptionWrapper<ArtifactTransfer>()
939     {
940 
941         public Exception wrap( ArtifactTransfer transfer, Exception e, RemoteRepository repository )
942         {
943             ArtifactTransferException ex = null;
944             e = WagonCancelledException.unwrap( e );
945             if ( e instanceof ResourceDoesNotExistException )
946             {
947                 ex = new ArtifactNotFoundException( transfer.getArtifact(), repository );
948             }
949             else if ( e != null )
950             {
951                 ex = new ArtifactTransferException( transfer.getArtifact(), repository, e );
952             }
953             transfer.setException( ex );
954             return ex;
955         }
956 
957     };
958 
959 }