1 package org.eclipse.aether.transport.wagon;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.spi.log.Logger;
56 import org.eclipse.aether.transfer.NoTransporterException;
57 import org.eclipse.aether.util.ConfigUtils;
58
59
60
61
62 final class WagonTransporter
63 implements Transporter
64 {
65
66 private static final String CONFIG_PROP_CONFIG = "aether.connector.wagon.config";
67
68 private static final String CONFIG_PROP_FILE_MODE = "aether.connector.perms.fileMode";
69
70 private static final String CONFIG_PROP_DIR_MODE = "aether.connector.perms.dirMode";
71
72 private static final String CONFIG_PROP_GROUP = "aether.connector.perms.group";
73
74 private final Logger logger;
75
76 private final RemoteRepository repository;
77
78 private final RepositorySystemSession session;
79
80 private final AuthenticationContext repoAuthContext;
81
82 private final AuthenticationContext proxyAuthContext;
83
84 private final WagonProvider wagonProvider;
85
86 private final WagonConfigurator wagonConfigurator;
87
88 private final String wagonHint;
89
90 private final Repository wagonRepo;
91
92 private final AuthenticationInfo wagonAuth;
93
94 private final ProxyInfoProvider wagonProxy;
95
96 private final Properties headers;
97
98 private final Queue<Wagon> wagons = new ConcurrentLinkedQueue<Wagon>();
99
100 private final AtomicBoolean closed = new AtomicBoolean();
101
102 WagonTransporter( WagonProvider wagonProvider, WagonConfigurator wagonConfigurator,
103 RemoteRepository repository, RepositorySystemSession session, Logger logger )
104 throws NoTransporterException
105 {
106 this.logger = logger;
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( 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, (String) 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, (String) 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, (String) 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
300 }
301 catch ( Exception e )
302 {
303 logger.debug( "Could not set user agent for wagon " + wagon.getClass().getName() + ": " + e );
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 temorary 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
559 }
560 }
561 }
562 else
563 {
564 File dst = ( file != null ) ? file : newTempFile();
565 try
566 {
567 wagon.get( src, dst );
568
569
570
571
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
619 }
620 finally
621 {
622 try
623 {
624 if ( in != null )
625 {
626 in.close();
627 }
628 }
629 catch ( final IOException e )
630 {
631
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
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
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
731 }
732 finally
733 {
734 try
735 {
736 if ( in != null )
737 {
738 in.close();
739 }
740 }
741 catch ( final IOException e )
742 {
743
744 }
745 }
746 }
747
748 return tmp;
749 }
750
751 }
752
753 }