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.InvocationTargetException;
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.ConnectionException;
39 import org.apache.maven.wagon.ResourceDoesNotExistException;
40 import org.apache.maven.wagon.StreamingWagon;
41 import org.apache.maven.wagon.Wagon;
42 import org.apache.maven.wagon.WagonException;
43 import org.apache.maven.wagon.authentication.AuthenticationInfo;
44 import org.apache.maven.wagon.proxy.ProxyInfo;
45 import org.apache.maven.wagon.proxy.ProxyInfoProvider;
46 import org.apache.maven.wagon.repository.Repository;
47 import org.apache.maven.wagon.repository.RepositoryPermissions;
48 import org.eclipse.aether.ConfigurationProperties;
49 import org.eclipse.aether.RepositorySystemSession;
50 import org.eclipse.aether.repository.AuthenticationContext;
51 import org.eclipse.aether.repository.Proxy;
52 import org.eclipse.aether.repository.RemoteRepository;
53 import org.eclipse.aether.spi.connector.transport.GetTask;
54 import org.eclipse.aether.spi.connector.transport.PeekTask;
55 import org.eclipse.aether.spi.connector.transport.PutTask;
56 import org.eclipse.aether.spi.connector.transport.TransportTask;
57 import org.eclipse.aether.spi.connector.transport.Transporter;
58 import org.eclipse.aether.transfer.NoTransporterException;
59 import org.eclipse.aether.util.ConfigUtils;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63
64
65
66 final class WagonTransporter
67 implements Transporter
68 {
69
70 private static final String CONFIG_PROP_CONFIG = "aether.connector.wagon.config";
71
72 private static final String CONFIG_PROP_FILE_MODE = "aether.connector.perms.fileMode";
73
74 private static final String CONFIG_PROP_DIR_MODE = "aether.connector.perms.dirMode";
75
76 private static final String CONFIG_PROP_GROUP = "aether.connector.perms.group";
77
78 private static final Logger LOGGER = LoggerFactory.getLogger( WagonTransporter.class );
79
80 private final RemoteRepository repository;
81
82 private final RepositorySystemSession session;
83
84 private final AuthenticationContext repoAuthContext;
85
86 private final AuthenticationContext proxyAuthContext;
87
88 private final WagonProvider wagonProvider;
89
90 private final WagonConfigurator wagonConfigurator;
91
92 private final String wagonHint;
93
94 private final Repository wagonRepo;
95
96 private final AuthenticationInfo wagonAuth;
97
98 private final ProxyInfoProvider wagonProxy;
99
100 private final Properties headers;
101
102 private final Queue<Wagon> wagons = new ConcurrentLinkedQueue<>();
103
104 private final AtomicBoolean closed = new AtomicBoolean();
105
106 WagonTransporter( WagonProvider wagonProvider, WagonConfigurator wagonConfigurator,
107 RemoteRepository repository, RepositorySystemSession session )
108 throws NoTransporterException
109 {
110 this.wagonProvider = wagonProvider;
111 this.wagonConfigurator = wagonConfigurator;
112 this.repository = repository;
113 this.session = session;
114
115 wagonRepo = new Repository( repository.getId(), repository.getUrl() );
116 wagonRepo.setPermissions( getPermissions( repository.getId(), session ) );
117
118 wagonHint = wagonRepo.getProtocol().toLowerCase( Locale.ENGLISH );
119 if ( wagonHint == null || wagonHint.length() <= 0 )
120 {
121 throw new NoTransporterException( repository );
122 }
123
124 try
125 {
126 wagons.add( lookupWagon() );
127 }
128 catch ( Exception e )
129 {
130 LOGGER.debug( "No transport {}", e );
131 throw new NoTransporterException( repository, e );
132 }
133
134 repoAuthContext = AuthenticationContext.forRepository( session, repository );
135 proxyAuthContext = AuthenticationContext.forProxy( session, repository );
136
137 wagonAuth = getAuthenticationInfo( repository, repoAuthContext );
138 wagonProxy = getProxy( repository, proxyAuthContext );
139
140 headers = new Properties();
141 headers.put( "User-Agent", ConfigUtils.getString( session, ConfigurationProperties.DEFAULT_USER_AGENT,
142 ConfigurationProperties.USER_AGENT ) );
143 Map<?, ?> headers =
144 ConfigUtils.getMap( session, null, ConfigurationProperties.HTTP_HEADERS + "." + repository.getId(),
145 ConfigurationProperties.HTTP_HEADERS );
146 if ( headers != null )
147 {
148 this.headers.putAll( headers );
149 }
150 }
151
152 private static RepositoryPermissions getPermissions( String repoId, RepositorySystemSession session )
153 {
154 RepositoryPermissions result = null;
155
156 RepositoryPermissions perms = new RepositoryPermissions();
157
158 String suffix = '.' + repoId;
159
160 String fileMode = ConfigUtils.getString( session, null, CONFIG_PROP_FILE_MODE + suffix );
161 if ( fileMode != null )
162 {
163 perms.setFileMode( fileMode );
164 result = perms;
165 }
166
167 String dirMode = ConfigUtils.getString( session, null, CONFIG_PROP_DIR_MODE + suffix );
168 if ( dirMode != null )
169 {
170 perms.setDirectoryMode( dirMode );
171 result = perms;
172 }
173
174 String group = ConfigUtils.getString( session, null, CONFIG_PROP_GROUP + suffix );
175 if ( group != null )
176 {
177 perms.setGroup( group );
178 result = perms;
179 }
180
181 return result;
182 }
183
184 private AuthenticationInfo getAuthenticationInfo( RemoteRepository repository,
185 final AuthenticationContext authContext )
186 {
187 AuthenticationInfo auth = null;
188
189 if ( authContext != null )
190 {
191 auth = new AuthenticationInfo()
192 {
193 @Override
194 public String getUserName()
195 {
196 return authContext.get( AuthenticationContext.USERNAME );
197 }
198
199 @Override
200 public String getPassword()
201 {
202 return authContext.get( AuthenticationContext.PASSWORD );
203 }
204
205 @Override
206 public String getPrivateKey()
207 {
208 return authContext.get( AuthenticationContext.PRIVATE_KEY_PATH );
209 }
210
211 @Override
212 public String getPassphrase()
213 {
214 return authContext.get( AuthenticationContext.PRIVATE_KEY_PASSPHRASE );
215 }
216 };
217 }
218
219 return auth;
220 }
221
222 private ProxyInfoProvider getProxy( RemoteRepository repository, final AuthenticationContext authContext )
223 {
224 ProxyInfoProvider proxy = null;
225
226 Proxy p = repository.getProxy();
227 if ( p != null )
228 {
229 final ProxyInfo prox;
230 if ( authContext != null )
231 {
232 prox = new ProxyInfo()
233 {
234 @Override
235 public String getUserName()
236 {
237 return authContext.get( AuthenticationContext.USERNAME );
238 }
239
240 @Override
241 public String getPassword()
242 {
243 return authContext.get( AuthenticationContext.PASSWORD );
244 }
245
246 @Override
247 public String getNtlmDomain()
248 {
249 return authContext.get( AuthenticationContext.NTLM_DOMAIN );
250 }
251
252 @Override
253 public String getNtlmHost()
254 {
255 return authContext.get( AuthenticationContext.NTLM_WORKSTATION );
256 }
257 };
258 }
259 else
260 {
261 prox = new ProxyInfo();
262 }
263 prox.setType( p.getType() );
264 prox.setHost( p.getHost() );
265 prox.setPort( p.getPort() );
266
267 proxy = new ProxyInfoProvider()
268 {
269 @Override
270 public ProxyInfo getProxyInfo( String protocol )
271 {
272 return prox;
273 }
274 };
275 }
276
277 return proxy;
278 }
279
280 private Wagon lookupWagon()
281 throws Exception
282 {
283 return wagonProvider.lookup( wagonHint );
284 }
285
286 private void releaseWagon( Wagon wagon )
287 {
288 wagonProvider.release( wagon );
289 }
290
291 private void connectWagon( Wagon wagon )
292 throws WagonException
293 {
294 if ( !headers.isEmpty() )
295 {
296 try
297 {
298 Method setHttpHeaders = wagon.getClass().getMethod( "setHttpHeaders", Properties.class );
299 setHttpHeaders.invoke( wagon, headers );
300 }
301 catch ( NoSuchMethodException e )
302 {
303
304 }
305 catch ( InvocationTargetException | IllegalAccessException | RuntimeException e )
306 {
307 LOGGER.debug( "Could not set user agent for Wagon {}", wagon.getClass().getName(), e );
308 }
309 }
310
311 int connectTimeout =
312 ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
313 ConfigurationProperties.CONNECT_TIMEOUT );
314 int requestTimeout =
315 ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
316 ConfigurationProperties.REQUEST_TIMEOUT );
317
318 wagon.setTimeout( Math.max( Math.max( connectTimeout, requestTimeout ), 0 ) );
319
320 wagon.setInteractive( ConfigUtils.getBoolean( session, ConfigurationProperties.DEFAULT_INTERACTIVE,
321 ConfigurationProperties.INTERACTIVE ) );
322
323 Object configuration = ConfigUtils.getObject( session, null, CONFIG_PROP_CONFIG + "." + repository.getId() );
324 if ( configuration != null && wagonConfigurator != null )
325 {
326 try
327 {
328 wagonConfigurator.configure( wagon, configuration );
329 }
330 catch ( Exception e )
331 {
332 LOGGER.warn( "Could not apply configuration for {} to Wagon {}",
333 repository.getId(), wagon.getClass().getName(), e );
334 }
335 }
336
337 wagon.connect( wagonRepo, wagonAuth, wagonProxy );
338 }
339
340 private void disconnectWagon( Wagon wagon )
341 {
342 try
343 {
344 if ( wagon != null )
345 {
346 wagon.disconnect();
347 }
348 }
349 catch ( ConnectionException e )
350 {
351 LOGGER.debug( "Could not disconnect Wagon {}", wagon, e );
352 }
353 }
354
355 private Wagon pollWagon()
356 throws Exception
357 {
358 Wagon wagon = wagons.poll();
359
360 if ( wagon == null )
361 {
362 try
363 {
364 wagon = lookupWagon();
365 connectWagon( wagon );
366 }
367 catch ( Exception e )
368 {
369 releaseWagon( wagon );
370 throw e;
371 }
372 }
373 else if ( wagon.getRepository() == null )
374 {
375 try
376 {
377 connectWagon( wagon );
378 }
379 catch ( Exception e )
380 {
381 wagons.add( wagon );
382 throw e;
383 }
384 }
385
386 return wagon;
387 }
388
389 public int classify( Throwable error )
390 {
391 if ( error instanceof ResourceDoesNotExistException )
392 {
393 return ERROR_NOT_FOUND;
394 }
395 return ERROR_OTHER;
396 }
397
398 public void peek( PeekTask task )
399 throws Exception
400 {
401 execute( task, new PeekTaskRunner( task ) );
402 }
403
404 public void get( GetTask task )
405 throws Exception
406 {
407 execute( task, new GetTaskRunner( task ) );
408 }
409
410 public void put( PutTask task )
411 throws Exception
412 {
413 execute( task, new PutTaskRunner( task ) );
414 }
415
416 private void execute( TransportTask task, TaskRunner runner )
417 throws Exception
418 {
419 if ( closed.get() )
420 {
421 throw new IllegalStateException( "transporter closed, cannot execute task " + task );
422 }
423 try
424 {
425 WagonTransferListener listener = new WagonTransferListener( task.getListener() );
426 Wagon wagon = pollWagon();
427 try
428 {
429 wagon.addTransferListener( listener );
430 runner.run( wagon );
431 }
432 finally
433 {
434 wagon.removeTransferListener( listener );
435 wagons.add( wagon );
436 }
437 }
438 catch ( RuntimeException e )
439 {
440 throw WagonCancelledException.unwrap( e );
441 }
442 }
443
444 private static File newTempFile()
445 throws IOException
446 {
447 return File.createTempFile( "wagon-" + UUID.randomUUID().toString().replace( "-", "" ), ".tmp" );
448 }
449
450 private void delTempFile( File path )
451 {
452 if ( path != null && !path.delete() && path.exists() )
453 {
454 LOGGER.debug( "Could not delete temporary file {}", path );
455 path.deleteOnExit();
456 }
457 }
458
459 private static void copy( OutputStream os, InputStream is )
460 throws IOException
461 {
462 byte[] buffer = new byte[1024 * 32];
463 for ( int read = is.read( buffer ); read >= 0; read = is.read( buffer ) )
464 {
465 os.write( buffer, 0, read );
466 }
467 }
468
469 public void close()
470 {
471 if ( closed.compareAndSet( false, true ) )
472 {
473 AuthenticationContext.close( repoAuthContext );
474 AuthenticationContext.close( proxyAuthContext );
475
476 for ( Wagon wagon = wagons.poll(); wagon != null; wagon = wagons.poll() )
477 {
478 disconnectWagon( wagon );
479 releaseWagon( wagon );
480 }
481 }
482 }
483
484 private interface TaskRunner
485 {
486
487 void run( Wagon wagon )
488 throws IOException, WagonException;
489
490 }
491
492 private static class PeekTaskRunner
493 implements TaskRunner
494 {
495
496 private final PeekTask task;
497
498 PeekTaskRunner( PeekTask task )
499 {
500 this.task = task;
501 }
502
503 @Override
504 public void run( Wagon wagon )
505 throws WagonException
506 {
507 String src = task.getLocation().toString();
508 if ( !wagon.resourceExists( src ) )
509 {
510 throw new ResourceDoesNotExistException( "Could not find " + src + " in "
511 + wagon.getRepository().getUrl() );
512 }
513 }
514
515 }
516
517 private class GetTaskRunner
518 implements TaskRunner
519 {
520
521 private final GetTask task;
522
523 GetTaskRunner( GetTask task )
524 {
525 this.task = task;
526 }
527
528 @Override
529 public void run( Wagon wagon )
530 throws IOException, WagonException
531 {
532 String src = task.getLocation().toString();
533 File file = task.getDataFile();
534 if ( file == null && wagon instanceof StreamingWagon )
535 {
536 try ( OutputStream dst = task.newOutputStream() )
537 {
538 ( (StreamingWagon) wagon ).getToStream( src, dst );
539 }
540 }
541 else
542 {
543 File dst = ( file != null ) ? file : newTempFile();
544 try
545 {
546 wagon.get( src, dst );
547
548
549
550
551
552 if ( !dst.exists() && !dst.createNewFile() )
553 {
554 throw new IOException( String.format( "Failure creating file '%s'.", dst.getAbsolutePath() ) );
555 }
556 if ( file == null )
557 {
558 readTempFile( dst );
559 }
560 }
561 finally
562 {
563 if ( file == null )
564 {
565 delTempFile( dst );
566 }
567 }
568 }
569 }
570
571 private void readTempFile( File dst )
572 throws IOException
573 {
574 try ( FileInputStream in = new FileInputStream( dst );
575 OutputStream out = task.newOutputStream() )
576 {
577 copy( out, in );
578 }
579 }
580
581 }
582
583 private class PutTaskRunner
584 implements TaskRunner
585 {
586
587 private final PutTask task;
588
589 PutTaskRunner( PutTask task )
590 {
591 this.task = task;
592 }
593
594 @Override
595 public void run( Wagon wagon )
596 throws WagonException, IOException
597 {
598 String dst = task.getLocation().toString();
599 File file = task.getDataFile();
600 if ( file == null && wagon instanceof StreamingWagon )
601 {
602 try ( InputStream src = task.newInputStream() )
603 {
604
605 ( (StreamingWagon) wagon ).putFromStream( src, dst, task.getDataLength(), -1 );
606 }
607 }
608 else
609 {
610 File src = ( file != null ) ? file : createTempFile();
611 try
612 {
613 wagon.put( src, dst );
614 }
615 finally
616 {
617 if ( file == null )
618 {
619 delTempFile( src );
620 }
621 }
622 }
623 }
624
625 private File createTempFile()
626 throws IOException
627 {
628 File tmp = newTempFile();
629
630 try ( InputStream in = task.newInputStream();
631 OutputStream out = new FileOutputStream( tmp ) )
632 {
633 copy( out, in );
634 }
635 catch ( IOException e )
636 {
637 delTempFile( tmp );
638 throw e;
639 }
640
641 return tmp;
642 }
643
644 }
645
646 }