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.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
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
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
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
574
575
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
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 }