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