1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.transport.wagon;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.nio.file.Files;
28 import java.nio.file.StandardCopyOption;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.Properties;
32 import java.util.Queue;
33 import java.util.concurrent.ConcurrentLinkedQueue;
34 import java.util.concurrent.atomic.AtomicBoolean;
35
36 import org.apache.maven.wagon.ConnectionException;
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.WagonException;
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.io.PathProcessor;
57 import org.eclipse.aether.transfer.NoTransporterException;
58 import org.eclipse.aether.util.ConfigUtils;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import static java.util.Objects.requireNonNull;
63 import static org.eclipse.aether.transport.wagon.WagonTransporterConfigurationKeys.CONFIG_PROP_CONFIG;
64 import static org.eclipse.aether.transport.wagon.WagonTransporterConfigurationKeys.CONFIG_PROP_DIR_MODE;
65 import static org.eclipse.aether.transport.wagon.WagonTransporterConfigurationKeys.CONFIG_PROP_FILE_MODE;
66 import static org.eclipse.aether.transport.wagon.WagonTransporterConfigurationKeys.CONFIG_PROP_GROUP;
67
68
69
70
71 final class WagonTransporter implements Transporter {
72 private static final Logger LOGGER = LoggerFactory.getLogger(WagonTransporter.class);
73
74 private final RemoteRepository repository;
75
76 private final RepositorySystemSession session;
77
78 private final PathProcessor pathProcessor;
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<>();
99
100 private final AtomicBoolean closed = new AtomicBoolean();
101
102 WagonTransporter(
103 WagonProvider wagonProvider,
104 WagonConfigurator wagonConfigurator,
105 RemoteRepository repository,
106 RepositorySystemSession session,
107 PathProcessor pathProcessor)
108 throws NoTransporterException {
109 this.wagonProvider = wagonProvider;
110 this.wagonConfigurator = wagonConfigurator;
111 this.repository = repository;
112 this.session = session;
113 this.pathProcessor = pathProcessor;
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.isEmpty()) {
120 throw new NoTransporterException(repository);
121 }
122
123 try {
124 wagons.add(lookupWagon());
125 } catch (Exception e) {
126 LOGGER.debug("No transport", e);
127 throw new NoTransporterException(repository, e);
128 }
129
130 repoAuthContext = AuthenticationContext.forRepository(session, repository);
131 proxyAuthContext = AuthenticationContext.forProxy(session, repository);
132
133 wagonAuth = getAuthenticationInfo(repoAuthContext);
134 wagonProxy = getProxy(repository, proxyAuthContext);
135
136 headers = new Properties();
137 headers.put(
138 "User-Agent",
139 ConfigUtils.getString(
140 session, ConfigurationProperties.DEFAULT_USER_AGENT, ConfigurationProperties.USER_AGENT));
141 Map<?, ?> headers = ConfigUtils.getMap(
142 session,
143 null,
144 ConfigurationProperties.HTTP_HEADERS + "." + repository.getId(),
145 ConfigurationProperties.HTTP_HEADERS);
146 if (headers != null) {
147 this.headers.putAll(headers);
148 }
149 }
150
151 private static RepositoryPermissions getPermissions(String repoId, RepositorySystemSession session) {
152 RepositoryPermissions result = null;
153
154 RepositoryPermissions perms = new RepositoryPermissions();
155
156 String suffix = '.' + repoId;
157
158 String fileMode = ConfigUtils.getString(session, null, CONFIG_PROP_FILE_MODE + suffix);
159 if (fileMode != null) {
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 perms.setDirectoryMode(dirMode);
167 result = perms;
168 }
169
170 String group = ConfigUtils.getString(session, null, CONFIG_PROP_GROUP + suffix);
171 if (group != null) {
172 perms.setGroup(group);
173 result = perms;
174 }
175
176 return result;
177 }
178
179 private AuthenticationInfo getAuthenticationInfo(final AuthenticationContext authContext) {
180 AuthenticationInfo auth = null;
181
182 if (authContext != null) {
183 auth = new AuthenticationInfo() {
184 @Override
185 public String getUserName() {
186 return authContext.get(AuthenticationContext.USERNAME);
187 }
188
189 @Override
190 public String getPassword() {
191 return authContext.get(AuthenticationContext.PASSWORD);
192 }
193
194 @Override
195 public String getPrivateKey() {
196 return authContext.get(AuthenticationContext.PRIVATE_KEY_PATH);
197 }
198
199 @Override
200 public String getPassphrase() {
201 return authContext.get(AuthenticationContext.PRIVATE_KEY_PASSPHRASE);
202 }
203 };
204 }
205
206 return auth;
207 }
208
209 private ProxyInfoProvider getProxy(RemoteRepository repository, final AuthenticationContext authContext) {
210 ProxyInfoProvider proxy = null;
211
212 Proxy p = repository.getProxy();
213 if (p != null) {
214 final ProxyInfo prox;
215 if (authContext != null) {
216 prox = new ProxyInfo() {
217 @Override
218 public String getUserName() {
219 return authContext.get(AuthenticationContext.USERNAME);
220 }
221
222 @Override
223 public String getPassword() {
224 return authContext.get(AuthenticationContext.PASSWORD);
225 }
226
227 @Override
228 public String getNtlmDomain() {
229 return authContext.get(AuthenticationContext.NTLM_DOMAIN);
230 }
231
232 @Override
233 public String getNtlmHost() {
234 return authContext.get(AuthenticationContext.NTLM_WORKSTATION);
235 }
236 };
237 } else {
238 prox = new ProxyInfo();
239 }
240 prox.setType(p.getType());
241 prox.setHost(p.getHost());
242 prox.setPort(p.getPort());
243
244 proxy = protocol -> prox;
245 }
246
247 return proxy;
248 }
249
250 private Wagon lookupWagon() throws Exception {
251 return wagonProvider.lookup(wagonHint);
252 }
253
254 private void releaseWagon(Wagon wagon) {
255 wagonProvider.release(wagon);
256 }
257
258 private void connectWagon(Wagon wagon) throws WagonException {
259 if (!headers.isEmpty()) {
260 try {
261 Method setHttpHeaders = wagon.getClass().getMethod("setHttpHeaders", Properties.class);
262 setHttpHeaders.invoke(wagon, headers);
263 } catch (NoSuchMethodException e) {
264
265 } catch (InvocationTargetException | IllegalAccessException | RuntimeException e) {
266 LOGGER.debug(
267 "Could not set user agent for Wagon {}",
268 wagon.getClass().getName(),
269 e);
270 }
271 }
272
273 int connectTimeout = ConfigUtils.getInteger(
274 session, ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT, ConfigurationProperties.CONNECT_TIMEOUT);
275 int requestTimeout = ConfigUtils.getInteger(
276 session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT, ConfigurationProperties.REQUEST_TIMEOUT);
277
278 wagon.setTimeout(Math.max(Math.max(connectTimeout, requestTimeout), 0));
279
280 wagon.setInteractive(ConfigUtils.getBoolean(
281 session, ConfigurationProperties.DEFAULT_INTERACTIVE, ConfigurationProperties.INTERACTIVE));
282
283 Object configuration = ConfigUtils.getObject(session, null, CONFIG_PROP_CONFIG + "." + repository.getId());
284 if (configuration != null && wagonConfigurator != null) {
285 try {
286 wagonConfigurator.configure(wagon, configuration);
287 } catch (Exception e) {
288 LOGGER.warn(
289 "Could not apply configuration for {} to Wagon {}",
290 repository.getId(),
291 wagon.getClass().getName(),
292 e);
293 }
294 }
295
296 wagon.connect(wagonRepo, wagonAuth, wagonProxy);
297 }
298
299 private void disconnectWagon(Wagon wagon) {
300 try {
301 if (wagon != null) {
302 wagon.disconnect();
303 }
304 } catch (ConnectionException e) {
305 LOGGER.debug("Could not disconnect Wagon {}", wagon, e);
306 }
307 }
308
309 private Wagon pollWagon() throws Exception {
310 Wagon wagon = wagons.poll();
311
312 if (wagon == null) {
313 try {
314 wagon = lookupWagon();
315 connectWagon(wagon);
316 } catch (Exception e) {
317 releaseWagon(wagon);
318 throw e;
319 }
320 } else if (wagon.getRepository() == null) {
321 try {
322 connectWagon(wagon);
323 } catch (Exception e) {
324 wagons.add(wagon);
325 throw e;
326 }
327 }
328
329 return wagon;
330 }
331
332 @Override
333 public int classify(Throwable error) {
334 if (error instanceof ResourceDoesNotExistException) {
335 return ERROR_NOT_FOUND;
336 }
337 return ERROR_OTHER;
338 }
339
340 @Override
341 public void peek(PeekTask task) throws Exception {
342 execute(task, new PeekTaskRunner(task));
343 }
344
345 @Override
346 public void get(GetTask task) throws Exception {
347 execute(task, new GetTaskRunner(pathProcessor, task));
348 }
349
350 @Override
351 public void put(PutTask task) throws Exception {
352 execute(task, new PutTaskRunner(pathProcessor, task));
353 }
354
355 private void execute(TransportTask task, TaskRunner runner) throws Exception {
356 requireNonNull(task, "task cannot be null");
357
358 if (closed.get()) {
359 throw new IllegalStateException("transporter closed, cannot execute task " + task);
360 }
361 try {
362 WagonTransferListener listener = new WagonTransferListener(task.getListener());
363 Wagon wagon = pollWagon();
364 try {
365 wagon.addTransferListener(listener);
366 runner.run(wagon);
367 } finally {
368 wagon.removeTransferListener(listener);
369 wagons.add(wagon);
370 }
371 } catch (RuntimeException e) {
372 throw WagonCancelledException.unwrap(e);
373 }
374 }
375
376 @Override
377 public void close() {
378 if (closed.compareAndSet(false, true)) {
379 AuthenticationContext.close(repoAuthContext);
380 AuthenticationContext.close(proxyAuthContext);
381
382 for (Wagon wagon = wagons.poll(); wagon != null; wagon = wagons.poll()) {
383 disconnectWagon(wagon);
384 releaseWagon(wagon);
385 }
386 }
387 }
388
389 private interface TaskRunner {
390
391 void run(Wagon wagon) throws IOException, WagonException;
392 }
393
394 private static class PeekTaskRunner implements TaskRunner {
395
396 private final PeekTask task;
397
398 PeekTaskRunner(PeekTask task) {
399 this.task = task;
400 }
401
402 @Override
403 public void run(Wagon wagon) throws WagonException {
404 String src = task.getLocation().toString();
405 if (!wagon.resourceExists(src)) {
406 throw new ResourceDoesNotExistException(
407 "Could not find " + src + " in " + wagon.getRepository().getUrl());
408 }
409 }
410 }
411
412 private static class GetTaskRunner implements TaskRunner {
413
414 private final PathProcessor pathProcessor;
415
416 private final GetTask task;
417
418 GetTaskRunner(PathProcessor pathProcessor, GetTask task) {
419 this.pathProcessor = pathProcessor;
420 this.task = task;
421 }
422
423 @Override
424 public void run(Wagon wagon) throws IOException, WagonException {
425 final String src = task.getLocation().toString();
426 final File file = task.getDataFile();
427 if (file == null && wagon instanceof StreamingWagon) {
428 try (OutputStream dst = task.newOutputStream()) {
429 ((StreamingWagon) wagon).getToStream(src, dst);
430 }
431 } else {
432
433 try (PathProcessor.TempFile tempFile =
434 file == null ? pathProcessor.newTempFile() : pathProcessor.newTempFile(file.toPath())) {
435 File dst = tempFile.getPath().toFile();
436 wagon.get(src, dst);
437
438
439
440
441
442 if (!dst.exists() && !dst.createNewFile()) {
443 throw new IOException(String.format("Failure creating file '%s'.", dst.getAbsolutePath()));
444 }
445
446 if (file != null) {
447 ((PathProcessor.CollocatedTempFile) tempFile).move();
448 } else {
449 try (OutputStream outputStream = task.newOutputStream()) {
450 Files.copy(dst.toPath(), outputStream);
451 }
452 }
453 }
454 }
455 }
456 }
457
458 private static class PutTaskRunner implements TaskRunner {
459
460 private final PathProcessor pathProcessor;
461
462 private final PutTask task;
463
464 PutTaskRunner(PathProcessor pathProcessor, PutTask task) {
465 this.pathProcessor = pathProcessor;
466 this.task = task;
467 }
468
469 @Override
470 public void run(Wagon wagon) throws WagonException, IOException {
471 final String dst = task.getLocation().toString();
472 final File file = task.getDataFile();
473 if (file == null && wagon instanceof StreamingWagon) {
474 try (InputStream src = task.newInputStream()) {
475
476 ((StreamingWagon) wagon).putFromStream(src, dst, task.getDataLength(), -1);
477 }
478 } else if (file == null) {
479 try (PathProcessor.TempFile tempFile = pathProcessor.newTempFile()) {
480 try (InputStream inputStream = task.newInputStream()) {
481 Files.copy(inputStream, tempFile.getPath(), StandardCopyOption.REPLACE_EXISTING);
482 }
483 wagon.put(tempFile.getPath().toFile(), dst);
484 }
485 } else {
486 wagon.put(file, dst);
487 }
488 }
489 }
490 }