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