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( 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( final AuthenticationContext authContext )
185 {
186 AuthenticationInfo auth = null;
187
188 if ( authContext != null )
189 {
190 auth = new AuthenticationInfo()
191 {
192 @Override
193 public String getUserName()
194 {
195 return authContext.get( AuthenticationContext.USERNAME );
196 }
197
198 @Override
199 public String getPassword()
200 {
201 return authContext.get( AuthenticationContext.PASSWORD );
202 }
203
204 @Override
205 public String getPrivateKey()
206 {
207 return authContext.get( AuthenticationContext.PRIVATE_KEY_PATH );
208 }
209
210 @Override
211 public String getPassphrase()
212 {
213 return authContext.get( AuthenticationContext.PRIVATE_KEY_PASSPHRASE );
214 }
215 };
216 }
217
218 return auth;
219 }
220
221 private ProxyInfoProvider getProxy( RemoteRepository repository, final AuthenticationContext authContext )
222 {
223 ProxyInfoProvider proxy = null;
224
225 Proxy p = repository.getProxy();
226 if ( p != null )
227 {
228 final ProxyInfo prox;
229 if ( authContext != null )
230 {
231 prox = new ProxyInfo()
232 {
233 @Override
234 public String getUserName()
235 {
236 return authContext.get( AuthenticationContext.USERNAME );
237 }
238
239 @Override
240 public String getPassword()
241 {
242 return authContext.get( AuthenticationContext.PASSWORD );
243 }
244
245 @Override
246 public String getNtlmDomain()
247 {
248 return authContext.get( AuthenticationContext.NTLM_DOMAIN );
249 }
250
251 @Override
252 public String getNtlmHost()
253 {
254 return authContext.get( AuthenticationContext.NTLM_WORKSTATION );
255 }
256 };
257 }
258 else
259 {
260 prox = new ProxyInfo();
261 }
262 prox.setType( p.getType() );
263 prox.setHost( p.getHost() );
264 prox.setPort( p.getPort() );
265
266 proxy = new ProxyInfoProvider()
267 {
268 @Override
269 public ProxyInfo getProxyInfo( String protocol )
270 {
271 return prox;
272 }
273 };
274 }
275
276 return proxy;
277 }
278
279 private Wagon lookupWagon()
280 throws Exception
281 {
282 return wagonProvider.lookup( wagonHint );
283 }
284
285 private void releaseWagon( Wagon wagon )
286 {
287 wagonProvider.release( wagon );
288 }
289
290 private void connectWagon( Wagon wagon )
291 throws WagonException
292 {
293 if ( !headers.isEmpty() )
294 {
295 try
296 {
297 Method setHttpHeaders = wagon.getClass().getMethod( "setHttpHeaders", Properties.class );
298 setHttpHeaders.invoke( wagon, headers );
299 }
300 catch ( NoSuchMethodException e )
301 {
302
303 }
304 catch ( InvocationTargetException | IllegalAccessException | RuntimeException e )
305 {
306 LOGGER.debug( "Could not set user agent for Wagon {}", wagon.getClass().getName(), e );
307 }
308 }
309
310 int connectTimeout =
311 ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
312 ConfigurationProperties.CONNECT_TIMEOUT );
313 int requestTimeout =
314 ConfigUtils.getInteger( session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
315 ConfigurationProperties.REQUEST_TIMEOUT );
316
317 wagon.setTimeout( Math.max( Math.max( connectTimeout, requestTimeout ), 0 ) );
318
319 wagon.setInteractive( ConfigUtils.getBoolean( session, ConfigurationProperties.DEFAULT_INTERACTIVE,
320 ConfigurationProperties.INTERACTIVE ) );
321
322 Object configuration = ConfigUtils.getObject( session, null, CONFIG_PROP_CONFIG + "." + repository.getId() );
323 if ( configuration != null && wagonConfigurator != null )
324 {
325 try
326 {
327 wagonConfigurator.configure( wagon, configuration );
328 }
329 catch ( Exception e )
330 {
331 LOGGER.warn( "Could not apply configuration for {} to Wagon {}",
332 repository.getId(), wagon.getClass().getName(), e );
333 }
334 }
335
336 wagon.connect( wagonRepo, wagonAuth, wagonProxy );
337 }
338
339 private void disconnectWagon( Wagon wagon )
340 {
341 try
342 {
343 if ( wagon != null )
344 {
345 wagon.disconnect();
346 }
347 }
348 catch ( ConnectionException e )
349 {
350 LOGGER.debug( "Could not disconnect Wagon {}", wagon, e );
351 }
352 }
353
354 private Wagon pollWagon()
355 throws Exception
356 {
357 Wagon wagon = wagons.poll();
358
359 if ( wagon == null )
360 {
361 try
362 {
363 wagon = lookupWagon();
364 connectWagon( wagon );
365 }
366 catch ( Exception e )
367 {
368 releaseWagon( wagon );
369 throw e;
370 }
371 }
372 else if ( wagon.getRepository() == null )
373 {
374 try
375 {
376 connectWagon( wagon );
377 }
378 catch ( Exception e )
379 {
380 wagons.add( wagon );
381 throw e;
382 }
383 }
384
385 return wagon;
386 }
387
388 public int classify( Throwable error )
389 {
390 if ( error instanceof ResourceDoesNotExistException )
391 {
392 return ERROR_NOT_FOUND;
393 }
394 return ERROR_OTHER;
395 }
396
397 public void peek( PeekTask task )
398 throws Exception
399 {
400 execute( task, new PeekTaskRunner( task ) );
401 }
402
403 public void get( GetTask task )
404 throws Exception
405 {
406 execute( task, new GetTaskRunner( task ) );
407 }
408
409 public void put( PutTask task )
410 throws Exception
411 {
412 execute( task, new PutTaskRunner( task ) );
413 }
414
415 private void execute( TransportTask task, TaskRunner runner )
416 throws Exception
417 {
418 if ( closed.get() )
419 {
420 throw new IllegalStateException( "transporter closed, cannot execute task " + task );
421 }
422 try
423 {
424 WagonTransferListener listener = new WagonTransferListener( task.getListener() );
425 Wagon wagon = pollWagon();
426 try
427 {
428 wagon.addTransferListener( listener );
429 runner.run( wagon );
430 }
431 finally
432 {
433 wagon.removeTransferListener( listener );
434 wagons.add( wagon );
435 }
436 }
437 catch ( RuntimeException e )
438 {
439 throw WagonCancelledException.unwrap( e );
440 }
441 }
442
443 private static File newTempFile()
444 throws IOException
445 {
446 return File.createTempFile( "wagon-" + UUID.randomUUID().toString().replace( "-", "" ), ".tmp" );
447 }
448
449 private void delTempFile( File path )
450 {
451 if ( path != null && !path.delete() && path.exists() )
452 {
453 LOGGER.debug( "Could not delete temporary file {}", path );
454 path.deleteOnExit();
455 }
456 }
457
458 private static void copy( OutputStream os, InputStream is )
459 throws IOException
460 {
461 byte[] buffer = new byte[1024 * 32];
462 for ( int read = is.read( buffer ); read >= 0; read = is.read( buffer ) )
463 {
464 os.write( buffer, 0, read );
465 }
466 }
467
468 public void close()
469 {
470 if ( closed.compareAndSet( false, true ) )
471 {
472 AuthenticationContext.close( repoAuthContext );
473 AuthenticationContext.close( proxyAuthContext );
474
475 for ( Wagon wagon = wagons.poll(); wagon != null; wagon = wagons.poll() )
476 {
477 disconnectWagon( wagon );
478 releaseWagon( wagon );
479 }
480 }
481 }
482
483 private interface TaskRunner
484 {
485
486 void run( Wagon wagon )
487 throws IOException, WagonException;
488
489 }
490
491 private static class PeekTaskRunner
492 implements TaskRunner
493 {
494
495 private final PeekTask task;
496
497 PeekTaskRunner( PeekTask task )
498 {
499 this.task = task;
500 }
501
502 @Override
503 public void run( Wagon wagon )
504 throws WagonException
505 {
506 String src = task.getLocation().toString();
507 if ( !wagon.resourceExists( src ) )
508 {
509 throw new ResourceDoesNotExistException( "Could not find " + src + " in "
510 + wagon.getRepository().getUrl() );
511 }
512 }
513
514 }
515
516 private class GetTaskRunner
517 implements TaskRunner
518 {
519
520 private final GetTask task;
521
522 GetTaskRunner( GetTask task )
523 {
524 this.task = task;
525 }
526
527 @Override
528 public void run( Wagon wagon )
529 throws IOException, WagonException
530 {
531 String src = task.getLocation().toString();
532 File file = task.getDataFile();
533 if ( file == null && wagon instanceof StreamingWagon )
534 {
535 try ( OutputStream dst = task.newOutputStream() )
536 {
537 ( (StreamingWagon) wagon ).getToStream( src, dst );
538 }
539 }
540 else
541 {
542 File dst = ( file != null ) ? file : newTempFile();
543 try
544 {
545 wagon.get( src, dst );
546
547
548
549
550
551 if ( !dst.exists() && !dst.createNewFile() )
552 {
553 throw new IOException( String.format( "Failure creating file '%s'.", dst.getAbsolutePath() ) );
554 }
555 if ( file == null )
556 {
557 readTempFile( dst );
558 }
559 }
560 finally
561 {
562 if ( file == null )
563 {
564 delTempFile( dst );
565 }
566 }
567 }
568 }
569
570 private void readTempFile( File dst )
571 throws IOException
572 {
573 try ( FileInputStream in = new FileInputStream( dst );
574 OutputStream out = task.newOutputStream() )
575 {
576 copy( out, in );
577 }
578 }
579
580 }
581
582 private class PutTaskRunner
583 implements TaskRunner
584 {
585
586 private final PutTask task;
587
588 PutTaskRunner( PutTask task )
589 {
590 this.task = task;
591 }
592
593 @Override
594 public void run( Wagon wagon )
595 throws WagonException, IOException
596 {
597 String dst = task.getLocation().toString();
598 File file = task.getDataFile();
599 if ( file == null && wagon instanceof StreamingWagon )
600 {
601 try ( InputStream src = task.newInputStream() )
602 {
603
604 ( (StreamingWagon) wagon ).putFromStream( src, dst, task.getDataLength(), -1 );
605 }
606 }
607 else
608 {
609 File src = ( file != null ) ? file : createTempFile();
610 try
611 {
612 wagon.put( src, dst );
613 }
614 finally
615 {
616 if ( file == null )
617 {
618 delTempFile( src );
619 }
620 }
621 }
622 }
623
624 private File createTempFile()
625 throws IOException
626 {
627 File tmp = newTempFile();
628
629 try ( InputStream in = task.newInputStream();
630 OutputStream out = new FileOutputStream( tmp ) )
631 {
632 copy( out, in );
633 }
634 catch ( IOException e )
635 {
636 delTempFile( tmp );
637 throw e;
638 }
639
640 return tmp;
641 }
642
643 }
644
645 }