View Javadoc
1   package org.eclipse.aether.transport.wagon;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   * 
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.Closeable;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.OutputStream;
29  import java.lang.reflect.Method;
30  import java.util.Locale;
31  import java.util.Map;
32  import java.util.Properties;
33  import java.util.Queue;
34  import java.util.UUID;
35  import java.util.concurrent.ConcurrentLinkedQueue;
36  import java.util.concurrent.atomic.AtomicBoolean;
37  
38  import org.apache.maven.wagon.ResourceDoesNotExistException;
39  import org.apache.maven.wagon.StreamingWagon;
40  import org.apache.maven.wagon.Wagon;
41  import org.apache.maven.wagon.authentication.AuthenticationInfo;
42  import org.apache.maven.wagon.proxy.ProxyInfo;
43  import org.apache.maven.wagon.proxy.ProxyInfoProvider;
44  import org.apache.maven.wagon.repository.Repository;
45  import org.apache.maven.wagon.repository.RepositoryPermissions;
46  import org.eclipse.aether.ConfigurationProperties;
47  import org.eclipse.aether.RepositorySystemSession;
48  import org.eclipse.aether.repository.AuthenticationContext;
49  import org.eclipse.aether.repository.Proxy;
50  import org.eclipse.aether.repository.RemoteRepository;
51  import org.eclipse.aether.spi.connector.transport.GetTask;
52  import org.eclipse.aether.spi.connector.transport.PeekTask;
53  import org.eclipse.aether.spi.connector.transport.PutTask;
54  import org.eclipse.aether.spi.connector.transport.TransportTask;
55  import org.eclipse.aether.spi.connector.transport.Transporter;
56  import org.eclipse.aether.spi.log.Logger;
57  import org.eclipse.aether.transfer.NoTransporterException;
58  import org.eclipse.aether.util.ConfigUtils;
59  
60  /**
61   * A transporter using Maven Wagon.
62   */
63  final class WagonTransporter
64      implements Transporter
65  {
66  
67      private static final String CONFIG_PROP_CONFIG = "aether.connector.wagon.config";
68  
69      private static final String CONFIG_PROP_FILE_MODE = "aether.connector.perms.fileMode";
70  
71      private static final String CONFIG_PROP_DIR_MODE = "aether.connector.perms.dirMode";
72  
73      private static final String CONFIG_PROP_GROUP = "aether.connector.perms.group";
74  
75      private final Logger logger;
76  
77      private final RemoteRepository repository;
78  
79      private final RepositorySystemSession session;
80  
81      private final AuthenticationContext repoAuthContext;
82  
83      private final AuthenticationContext proxyAuthContext;
84  
85      private final WagonProvider wagonProvider;
86  
87      private final WagonConfigurator wagonConfigurator;
88  
89      private final String wagonHint;
90  
91      private final Repository wagonRepo;
92  
93      private final AuthenticationInfo wagonAuth;
94  
95      private final ProxyInfoProvider wagonProxy;
96  
97      private final Properties headers;
98  
99      private final Queue<Wagon> wagons = new ConcurrentLinkedQueue<Wagon>();
100 
101     private final AtomicBoolean closed = new AtomicBoolean();
102 
103     public WagonTransporter( WagonProvider wagonProvider, WagonConfigurator wagonConfigurator,
104                              RemoteRepository repository, RepositorySystemSession session, Logger logger )
105         throws NoTransporterException
106     {
107         this.logger = logger;
108         this.wagonProvider = wagonProvider;
109         this.wagonConfigurator = wagonConfigurator;
110         this.repository = repository;
111         this.session = session;
112 
113         wagonRepo = new Repository( repository.getId(), repository.getUrl() );
114         wagonRepo.setPermissions( getPermissions( repository.getId(), session ) );
115 
116         wagonHint = wagonRepo.getProtocol().toLowerCase( Locale.ENGLISH );
117         if ( wagonHint == null || wagonHint.length() <= 0 )
118         {
119             throw new NoTransporterException( repository );
120         }
121 
122         try
123         {
124             wagons.add( lookupWagon() );
125         }
126         catch ( Exception e )
127         {
128             logger.debug( e.getMessage(), e );
129             throw new NoTransporterException( repository, e.getMessage(), e );
130         }
131 
132         repoAuthContext = AuthenticationContext.forRepository( session, repository );
133         proxyAuthContext = AuthenticationContext.forProxy( session, repository );
134 
135         wagonAuth = getAuthenticationInfo( repository, repoAuthContext );
136         wagonProxy = getProxy( repository, proxyAuthContext );
137 
138         headers = new Properties();
139         headers.put( "User-Agent", ConfigUtils.getString( session, ConfigurationProperties.DEFAULT_USER_AGENT,
140                                                           ConfigurationProperties.USER_AGENT ) );
141         Map<?, ?> headers =
142             ConfigUtils.getMap( session, null, ConfigurationProperties.HTTP_HEADERS + "." + repository.getId(),
143                                 ConfigurationProperties.HTTP_HEADERS );
144         if ( headers != null )
145         {
146             this.headers.putAll( headers );
147         }
148     }
149 
150     private static RepositoryPermissions getPermissions( String repoId, RepositorySystemSession session )
151     {
152         RepositoryPermissions result = null;
153 
154         RepositoryPermissions perms = new RepositoryPermissions();
155 
156         String suffix = '.' + repoId;
157 
158         String fileMode = ConfigUtils.getString( session, (String) null, CONFIG_PROP_FILE_MODE + suffix );
159         if ( fileMode != null )
160         {
161             perms.setFileMode( fileMode );
162             result = perms;
163         }
164 
165         String dirMode = ConfigUtils.getString( session, (String) null, CONFIG_PROP_DIR_MODE + suffix );
166         if ( dirMode != null )
167         {
168             perms.setDirectoryMode( dirMode );
169             result = perms;
170         }
171 
172         String group = ConfigUtils.getString( session, (String) null, CONFIG_PROP_GROUP + suffix );
173         if ( group != null )
174         {
175             perms.setGroup( group );
176             result = perms;
177         }
178 
179         return result;
180     }
181 
182     private AuthenticationInfo getAuthenticationInfo( RemoteRepository repository,
183                                                       final AuthenticationContext authContext )
184     {
185         AuthenticationInfo auth = null;
186 
187         if ( authContext != null )
188         {
189             auth = new AuthenticationInfo()
190             {
191                 @Override
192                 public String getUserName()
193                 {
194                     return authContext.get( AuthenticationContext.USERNAME );
195                 }
196 
197                 @Override
198                 public String getPassword()
199                 {
200                     return authContext.get( AuthenticationContext.PASSWORD );
201                 }
202 
203                 @Override
204                 public String getPrivateKey()
205                 {
206                     return authContext.get( AuthenticationContext.PRIVATE_KEY_PATH );
207                 }
208 
209                 @Override
210                 public String getPassphrase()
211                 {
212                     return authContext.get( AuthenticationContext.PRIVATE_KEY_PASSPHRASE );
213                 }
214             };
215         }
216 
217         return auth;
218     }
219 
220     private ProxyInfoProvider getProxy( RemoteRepository repository, final AuthenticationContext authContext )
221     {
222         ProxyInfoProvider proxy = null;
223 
224         Proxy p = repository.getProxy();
225         if ( p != null )
226         {
227             final ProxyInfo prox;
228             if ( authContext != null )
229             {
230                 prox = new ProxyInfo()
231                 {
232                     @Override
233                     public String getUserName()
234                     {
235                         return authContext.get( AuthenticationContext.USERNAME );
236                     }
237 
238                     @Override
239                     public String getPassword()
240                     {
241                         return authContext.get( AuthenticationContext.PASSWORD );
242                     }
243 
244                     @Override
245                     public String getNtlmDomain()
246                     {
247                         return authContext.get( AuthenticationContext.NTLM_DOMAIN );
248                     }
249 
250                     @Override
251                     public String getNtlmHost()
252                     {
253                         return authContext.get( AuthenticationContext.NTLM_WORKSTATION );
254                     }
255                 };
256             }
257             else
258             {
259                 prox = new ProxyInfo();
260             }
261             prox.setType( p.getType() );
262             prox.setHost( p.getHost() );
263             prox.setPort( p.getPort() );
264 
265             proxy = new ProxyInfoProvider()
266             {
267                 public ProxyInfo getProxyInfo( String protocol )
268                 {
269                     return prox;
270                 }
271             };
272         }
273 
274         return proxy;
275     }
276 
277     private Wagon lookupWagon()
278         throws Exception
279     {
280         return wagonProvider.lookup( wagonHint );
281     }
282 
283     private void releaseWagon( Wagon wagon )
284     {
285         wagonProvider.release( wagon );
286     }
287 
288     private void connectWagon( Wagon wagon )
289         throws Exception
290     {
291         if ( !headers.isEmpty() )
292         {
293             try
294             {
295                 Method setHttpHeaders = wagon.getClass().getMethod( "setHttpHeaders", Properties.class );
296                 setHttpHeaders.invoke( wagon, headers );
297             }
298             catch ( NoSuchMethodException e )
299             {
300                 // normal for non-http wagons
301             }
302             catch ( Exception e )
303             {
304                 logger.debug( "Could not set user agent for wagon " + wagon.getClass().getName() + ": " + e );
305             }
306         }
307 
308         int connectTimeout =
309             ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
310                                     ConfigurationProperties.CONNECT_TIMEOUT );
311         int requestTimeout =
312             ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
313                                     ConfigurationProperties.REQUEST_TIMEOUT );
314 
315         wagon.setTimeout( Math.max( Math.max( connectTimeout, requestTimeout ), 0 ) );
316 
317         wagon.setInteractive( ConfigUtils.getBoolean( session, ConfigurationProperties.DEFAULT_INTERACTIVE,
318                                                       ConfigurationProperties.INTERACTIVE ) );
319 
320         Object configuration = ConfigUtils.getObject( session, null, CONFIG_PROP_CONFIG + "." + repository.getId() );
321         if ( configuration != null && wagonConfigurator != null )
322         {
323             try
324             {
325                 wagonConfigurator.configure( wagon, configuration );
326             }
327             catch ( Exception e )
328             {
329                 String msg =
330                     "Could not apply configuration for " + repository.getId() + " to wagon "
331                         + wagon.getClass().getName() + ":" + e.getMessage();
332                 if ( logger.isDebugEnabled() )
333                 {
334                     logger.warn( msg, e );
335                 }
336                 else
337                 {
338                     logger.warn( msg );
339                 }
340             }
341         }
342 
343         wagon.connect( wagonRepo, wagonAuth, wagonProxy );
344     }
345 
346     private void disconnectWagon( Wagon wagon )
347     {
348         try
349         {
350             if ( wagon != null )
351             {
352                 wagon.disconnect();
353             }
354         }
355         catch ( Exception e )
356         {
357             logger.debug( "Could not disconnect wagon " + wagon, e );
358         }
359     }
360 
361     private Wagon pollWagon()
362         throws Exception
363     {
364         Wagon wagon = wagons.poll();
365 
366         if ( wagon == null )
367         {
368             try
369             {
370                 wagon = lookupWagon();
371                 connectWagon( wagon );
372             }
373             catch ( Exception e )
374             {
375                 releaseWagon( wagon );
376                 throw e;
377             }
378         }
379         else if ( wagon.getRepository() == null )
380         {
381             try
382             {
383                 connectWagon( wagon );
384             }
385             catch ( Exception e )
386             {
387                 wagons.add( wagon );
388                 throw e;
389             }
390         }
391 
392         return wagon;
393     }
394 
395     public int classify( Throwable error )
396     {
397         if ( error instanceof ResourceDoesNotExistException )
398         {
399             return ERROR_NOT_FOUND;
400         }
401         return ERROR_OTHER;
402     }
403 
404     public void peek( PeekTask task )
405         throws Exception
406     {
407         execute( task, new PeekTaskRunner( task ) );
408     }
409 
410     public void get( GetTask task )
411         throws Exception
412     {
413         execute( task, new GetTaskRunner( task ) );
414     }
415 
416     public void put( PutTask task )
417         throws Exception
418     {
419         execute( task, new PutTaskRunner( task ) );
420     }
421 
422     private void execute( TransportTask task, TaskRunner runner )
423         throws Exception
424     {
425         if ( closed.get() )
426         {
427             throw new IllegalStateException( "transporter closed, cannot execute task " + task );
428         }
429         try
430         {
431             WagonTransferListener listener = new WagonTransferListener( task.getListener() );
432             Wagon wagon = pollWagon();
433             try
434             {
435                 wagon.addTransferListener( listener );
436                 runner.run( wagon );
437             }
438             finally
439             {
440                 wagon.removeTransferListener( listener );
441                 wagons.add( wagon );
442             }
443         }
444         catch ( Exception e )
445         {
446             throw WagonCancelledException.unwrap( e );
447         }
448     }
449 
450     private static File newTempFile()
451         throws IOException
452     {
453         return File.createTempFile( "wagon-" + UUID.randomUUID().toString().replace( "-", "" ), ".tmp" );
454     }
455 
456     private void delTempFile( File path )
457     {
458         if ( path != null && !path.delete() && path.exists() )
459         {
460             logger.debug( "Could not delete temorary file " + path );
461         }
462     }
463 
464     private static void copy( OutputStream os, InputStream is )
465         throws IOException
466     {
467         byte[] buffer = new byte[1024 * 32];
468         for ( int read = is.read( buffer ); read >= 0; read = is.read( buffer ) )
469         {
470             os.write( buffer, 0, read );
471         }
472     }
473 
474     private static void close( Closeable file )
475     {
476         if ( file != null )
477         {
478             try
479             {
480                 file.close();
481             }
482             catch ( IOException e )
483             {
484                 // too bad
485             }
486         }
487     }
488 
489     public void close()
490     {
491         if ( closed.compareAndSet( false, true ) )
492         {
493             AuthenticationContext.close( repoAuthContext );
494             AuthenticationContext.close( proxyAuthContext );
495 
496             for ( Wagon wagon = wagons.poll(); wagon != null; wagon = wagons.poll() )
497             {
498                 disconnectWagon( wagon );
499                 releaseWagon( wagon );
500             }
501         }
502     }
503 
504     private interface TaskRunner
505     {
506 
507         void run( Wagon wagon )
508             throws Exception;
509 
510     }
511 
512     private static class PeekTaskRunner
513         implements TaskRunner
514     {
515 
516         private final PeekTask task;
517 
518         public PeekTaskRunner( PeekTask task )
519         {
520             this.task = task;
521         }
522 
523         public void run( Wagon wagon )
524             throws Exception
525         {
526             String src = task.getLocation().toString();
527             if ( !wagon.resourceExists( src ) )
528             {
529                 throw new ResourceDoesNotExistException( "Could not find " + src + " in "
530                     + wagon.getRepository().getUrl() );
531             }
532         }
533 
534     }
535 
536     private class GetTaskRunner
537         implements TaskRunner
538     {
539 
540         private final GetTask task;
541 
542         public GetTaskRunner( GetTask task )
543         {
544             this.task = task;
545         }
546 
547         public void run( Wagon wagon )
548             throws Exception
549         {
550             String src = task.getLocation().toString();
551             File file = task.getDataFile();
552             if ( file == null && wagon instanceof StreamingWagon )
553             {
554                 OutputStream dst = task.newOutputStream();
555                 try
556                 {
557                     ( (StreamingWagon) wagon ).getToStream( src, dst );
558                 }
559                 finally
560                 {
561                     dst.close();
562                 }
563             }
564             else
565             {
566                 File dst = ( file != null ) ? file : newTempFile();
567                 try
568                 {
569                     wagon.get( src, dst );
570                     if ( !dst.exists() )
571                     {
572                         /*
573                          * NOTE: Wagon (1.0-beta-6) doesn't create the destination file when transferring a 0-byte
574                          * resource. So if the resource we asked for didn't cause any exception but doesn't show up in
575                          * the dst file either, Wagon tells us in its weird way the file is empty.
576                          */
577                         new FileOutputStream( dst ).close();
578                     }
579                     if ( file == null )
580                     {
581                         readTempFile( dst );
582                     }
583                 }
584                 finally
585                 {
586                     if ( file == null )
587                     {
588                         delTempFile( dst );
589                     }
590                 }
591             }
592         }
593 
594         private void readTempFile( File dst )
595             throws IOException
596         {
597             FileInputStream fis = new FileInputStream( dst );
598             try
599             {
600                 OutputStream os = task.newOutputStream();
601                 try
602                 {
603                     copy( os, fis );
604                     os.close();
605                 }
606                 finally
607                 {
608                     close( os );
609                 }
610             }
611             finally
612             {
613                 close( fis );
614             }
615         }
616 
617     }
618 
619     private class PutTaskRunner
620         implements TaskRunner
621     {
622 
623         private final PutTask task;
624 
625         public PutTaskRunner( PutTask task )
626         {
627             this.task = task;
628         }
629 
630         public void run( Wagon wagon )
631             throws Exception
632         {
633             String dst = task.getLocation().toString();
634             File file = task.getDataFile();
635             if ( file == null && wagon instanceof StreamingWagon )
636             {
637                 InputStream src = task.newInputStream();
638                 try
639                 {
640                     // StreamingWagon uses an internal buffer on src input stream.
641                     ( (StreamingWagon) wagon ).putFromStream( src, dst, task.getDataLength(), -1 );
642                 }
643                 finally
644                 {
645                     close( src );
646                 }
647             }
648             else
649             {
650                 File src = ( file != null ) ? file : createTempFile();
651                 try
652                 {
653                     wagon.put( src, dst );
654                 }
655                 finally
656                 {
657                     if ( file == null )
658                     {
659                         delTempFile( src );
660                     }
661                 }
662             }
663         }
664 
665         private File createTempFile()
666             throws IOException
667         {
668             File tmp = newTempFile();
669             try
670             {
671                 FileOutputStream fos = new FileOutputStream( tmp );
672                 try
673                 {
674                     InputStream is = task.newInputStream();
675                     try
676                     {
677                         copy( fos, is );
678                         fos.close();
679                     }
680                     finally
681                     {
682                         close( is );
683                     }
684                 }
685                 finally
686                 {
687                     close( fos );
688                 }
689             }
690             catch ( IOException e )
691             {
692                 delTempFile( tmp );
693                 throw e;
694             }
695             return tmp;
696         }
697 
698     }
699 
700 }