1 package org.eclipse.aether.internal.impl;
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.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import static java.util.Objects.requireNonNull;
31 import java.util.concurrent.Executor;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.LinkedBlockingQueue;
34 import java.util.concurrent.ThreadPoolExecutor;
35 import java.util.concurrent.TimeUnit;
36
37 import javax.inject.Inject;
38 import javax.inject.Named;
39 import javax.inject.Singleton;
40
41 import org.eclipse.aether.RepositoryEvent;
42 import org.eclipse.aether.RepositoryEvent.EventType;
43 import org.eclipse.aether.RepositorySystemSession;
44 import org.eclipse.aether.RequestTrace;
45 import org.eclipse.aether.SyncContext;
46 import org.eclipse.aether.impl.MetadataResolver;
47 import org.eclipse.aether.impl.OfflineController;
48 import org.eclipse.aether.impl.RemoteRepositoryManager;
49 import org.eclipse.aether.impl.RepositoryConnectorProvider;
50 import org.eclipse.aether.impl.RepositoryEventDispatcher;
51 import org.eclipse.aether.spi.synccontext.SyncContextFactory;
52 import org.eclipse.aether.impl.UpdateCheck;
53 import org.eclipse.aether.impl.UpdateCheckManager;
54 import org.eclipse.aether.metadata.Metadata;
55 import org.eclipse.aether.repository.ArtifactRepository;
56 import org.eclipse.aether.repository.LocalMetadataRegistration;
57 import org.eclipse.aether.repository.LocalMetadataRequest;
58 import org.eclipse.aether.repository.LocalMetadataResult;
59 import org.eclipse.aether.repository.LocalRepository;
60 import org.eclipse.aether.repository.LocalRepositoryManager;
61 import org.eclipse.aether.repository.RemoteRepository;
62 import org.eclipse.aether.repository.RepositoryPolicy;
63 import org.eclipse.aether.resolution.MetadataRequest;
64 import org.eclipse.aether.resolution.MetadataResult;
65 import org.eclipse.aether.spi.connector.MetadataDownload;
66 import org.eclipse.aether.spi.connector.RepositoryConnector;
67 import org.eclipse.aether.spi.locator.Service;
68 import org.eclipse.aether.spi.locator.ServiceLocator;
69 import org.eclipse.aether.transfer.MetadataNotFoundException;
70 import org.eclipse.aether.transfer.MetadataTransferException;
71 import org.eclipse.aether.transfer.NoRepositoryConnectorException;
72 import org.eclipse.aether.transfer.RepositoryOfflineException;
73 import org.eclipse.aether.util.ConfigUtils;
74 import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
75 import org.eclipse.aether.util.concurrency.WorkerThreadFactory;
76
77
78
79 @Singleton
80 @Named
81 public class DefaultMetadataResolver
82 implements MetadataResolver, Service
83 {
84
85 private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
86
87 private RepositoryEventDispatcher repositoryEventDispatcher;
88
89 private UpdateCheckManager updateCheckManager;
90
91 private RepositoryConnectorProvider repositoryConnectorProvider;
92
93 private RemoteRepositoryManager remoteRepositoryManager;
94
95 private SyncContextFactory syncContextFactory;
96
97 private OfflineController offlineController;
98
99 public DefaultMetadataResolver()
100 {
101
102 }
103
104 @Inject
105 DefaultMetadataResolver( RepositoryEventDispatcher repositoryEventDispatcher,
106 UpdateCheckManager updateCheckManager,
107 RepositoryConnectorProvider repositoryConnectorProvider,
108 RemoteRepositoryManager remoteRepositoryManager, SyncContextFactory syncContextFactory,
109 OfflineController offlineController )
110 {
111 setRepositoryEventDispatcher( repositoryEventDispatcher );
112 setUpdateCheckManager( updateCheckManager );
113 setRepositoryConnectorProvider( repositoryConnectorProvider );
114 setRemoteRepositoryManager( remoteRepositoryManager );
115 setSyncContextFactory( syncContextFactory );
116 setOfflineController( offlineController );
117 }
118
119 public void initService( ServiceLocator locator )
120 {
121 setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) );
122 setUpdateCheckManager( locator.getService( UpdateCheckManager.class ) );
123 setRepositoryConnectorProvider( locator.getService( RepositoryConnectorProvider.class ) );
124 setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) );
125 setSyncContextFactory( locator.getService( SyncContextFactory.class ) );
126 setOfflineController( locator.getService( OfflineController.class ) );
127 }
128
129 public DefaultMetadataResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher )
130 {
131 this.repositoryEventDispatcher = requireNonNull(
132 repositoryEventDispatcher, "repository event dispatcher cannot be null" );
133 return this;
134 }
135
136 public DefaultMetadataResolver setUpdateCheckManager( UpdateCheckManager updateCheckManager )
137 {
138 this.updateCheckManager = requireNonNull( updateCheckManager, "update check manager cannot be null" );
139 return this;
140 }
141
142 public DefaultMetadataResolver setRepositoryConnectorProvider(
143 RepositoryConnectorProvider repositoryConnectorProvider )
144 {
145 this.repositoryConnectorProvider = requireNonNull(
146 repositoryConnectorProvider, "repository connector provider cannot be null" );
147 return this;
148 }
149
150 public DefaultMetadataResolver setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager )
151 {
152 this.remoteRepositoryManager = requireNonNull(
153 remoteRepositoryManager, "remote repository provider cannot be null" );
154 return this;
155 }
156
157 public DefaultMetadataResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
158 {
159 this.syncContextFactory = requireNonNull( syncContextFactory, "sync context factory cannot be null" );
160 return this;
161 }
162
163 public DefaultMetadataResolver setOfflineController( OfflineController offlineController )
164 {
165 this.offlineController = requireNonNull( offlineController, "offline controller cannot be null" );
166 return this;
167 }
168
169 public List<MetadataResult> resolveMetadata( RepositorySystemSession session,
170 Collection<? extends MetadataRequest> requests )
171 {
172 requireNonNull( session, "session cannot be null" );
173 requireNonNull( requests, "requests cannot be null" );
174 try ( SyncContext syncContext = syncContextFactory.newInstance( session, false ) )
175 {
176 Collection<Metadata> metadata = new ArrayList<>( requests.size() );
177 for ( MetadataRequest request : requests )
178 {
179 metadata.add( request.getMetadata() );
180 }
181
182 syncContext.acquire( null, metadata );
183
184 return resolve( session, requests );
185 }
186 }
187
188 @SuppressWarnings( "checkstyle:methodlength" )
189 private List<MetadataResult> resolve( RepositorySystemSession session,
190 Collection<? extends MetadataRequest> requests )
191 {
192 List<MetadataResult> results = new ArrayList<>( requests.size() );
193
194 List<ResolveTask> tasks = new ArrayList<>( requests.size() );
195
196 Map<File, Long> localLastUpdates = new HashMap<>();
197
198 for ( MetadataRequest request : requests )
199 {
200 RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
201
202 MetadataResult result = new MetadataResult( request );
203 results.add( result );
204
205 Metadata metadata = request.getMetadata();
206 RemoteRepository repository = request.getRepository();
207
208 if ( repository == null )
209 {
210 LocalRepository localRepo = session.getLocalRepositoryManager().getRepository();
211
212 metadataResolving( session, trace, metadata, localRepo );
213
214 File localFile = getLocalFile( session, metadata );
215
216 if ( localFile != null )
217 {
218 metadata = metadata.setFile( localFile );
219 result.setMetadata( metadata );
220 }
221 else
222 {
223 result.setException( new MetadataNotFoundException( metadata, localRepo ) );
224 }
225
226 metadataResolved( session, trace, metadata, localRepo, result.getException() );
227 continue;
228 }
229
230 List<RemoteRepository> repositories = getEnabledSourceRepositories( repository, metadata.getNature() );
231
232 if ( repositories.isEmpty() )
233 {
234 continue;
235 }
236
237 metadataResolving( session, trace, metadata, repository );
238 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
239 LocalMetadataRequest localRequest =
240 new LocalMetadataRequest( metadata, repository, request.getRequestContext() );
241 LocalMetadataResult lrmResult = lrm.find( session, localRequest );
242
243 File metadataFile = lrmResult.getFile();
244
245 try
246 {
247 Utils.checkOffline( session, offlineController, repository );
248 }
249 catch ( RepositoryOfflineException e )
250 {
251 if ( metadataFile != null )
252 {
253 metadata = metadata.setFile( metadataFile );
254 result.setMetadata( metadata );
255 }
256 else
257 {
258 String msg =
259 "Cannot access " + repository.getId() + " (" + repository.getUrl()
260 + ") in offline mode and the metadata " + metadata
261 + " has not been downloaded from it before";
262 result.setException( new MetadataNotFoundException( metadata, repository, msg, e ) );
263 }
264
265 metadataResolved( session, trace, metadata, repository, result.getException() );
266 continue;
267 }
268
269 Long localLastUpdate = null;
270 if ( request.isFavorLocalRepository() )
271 {
272 File localFile = getLocalFile( session, metadata );
273 localLastUpdate = localLastUpdates.get( localFile );
274 if ( localLastUpdate == null )
275 {
276 localLastUpdate = localFile != null ? localFile.lastModified() : 0;
277 localLastUpdates.put( localFile, localLastUpdate );
278 }
279 }
280
281 List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>();
282 Exception exception = null;
283 for ( RemoteRepository repo : repositories )
284 {
285 UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
286 check.setLocalLastUpdated( ( localLastUpdate != null ) ? localLastUpdate : 0 );
287 check.setItem( metadata );
288
289
290 File checkFile = new File(
291 session.getLocalRepository().getBasedir(),
292 session.getLocalRepositoryManager()
293 .getPathForRemoteMetadata( metadata, repository, request.getRequestContext() ) );
294 check.setFile( checkFile );
295 check.setRepository( repository );
296 check.setAuthoritativeRepository( repo );
297 check.setPolicy( getPolicy( session, repo, metadata.getNature() ).getUpdatePolicy() );
298
299 if ( lrmResult.isStale() )
300 {
301 checks.add( check );
302 }
303 else
304 {
305 updateCheckManager.checkMetadata( session, check );
306 if ( check.isRequired() )
307 {
308 checks.add( check );
309 }
310 else if ( exception == null )
311 {
312 exception = check.getException();
313 }
314 }
315 }
316
317 if ( !checks.isEmpty() )
318 {
319 RepositoryPolicy policy = getPolicy( session, repository, metadata.getNature() );
320
321
322 File installFile = new File(
323 session.getLocalRepository().getBasedir(),
324 session.getLocalRepositoryManager().getPathForRemoteMetadata(
325 metadata, request.getRepository(), request.getRequestContext() ) );
326
327 metadataDownloading(
328 session, trace, result.getRequest().getMetadata(), result.getRequest().getRepository() );
329
330 ResolveTask task =
331 new ResolveTask( session, trace, result, installFile, checks, policy.getChecksumPolicy() );
332 tasks.add( task );
333 }
334 else
335 {
336 result.setException( exception );
337 if ( metadataFile != null )
338 {
339 metadata = metadata.setFile( metadataFile );
340 result.setMetadata( metadata );
341 }
342 metadataResolved( session, trace, metadata, repository, result.getException() );
343 }
344 }
345
346 if ( !tasks.isEmpty() )
347 {
348 int threads = ConfigUtils.getInteger( session, 4, CONFIG_PROP_THREADS );
349 Executor executor = getExecutor( Math.min( tasks.size(), threads ) );
350 try
351 {
352 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
353
354 for ( ResolveTask task : tasks )
355 {
356 executor.execute( errorForwarder.wrap( task ) );
357 }
358
359 errorForwarder.await();
360
361 for ( ResolveTask task : tasks )
362 {
363
364
365
366
367
368 for ( UpdateCheck<Metadata, MetadataTransferException> check : task.checks )
369 {
370 updateCheckManager.touchMetadata( task.session, check.setException( task.exception ) );
371 }
372
373 metadataDownloaded( session, task.trace, task.request.getMetadata(), task.request.getRepository(),
374 task.metadataFile, task.exception );
375
376 task.result.setException( task.exception );
377 }
378 }
379 finally
380 {
381 shutdown( executor );
382 }
383 for ( ResolveTask task : tasks )
384 {
385 Metadata metadata = task.request.getMetadata();
386
387 LocalMetadataRequest localRequest = new LocalMetadataRequest(
388 metadata, task.request.getRepository(), task.request.getRequestContext() );
389 File metadataFile = session.getLocalRepositoryManager().find( session, localRequest ).getFile();
390 if ( metadataFile != null )
391 {
392 metadata = metadata.setFile( metadataFile );
393 task.result.setMetadata( metadata );
394 }
395 if ( task.result.getException() == null )
396 {
397 task.result.setUpdated( true );
398 }
399 metadataResolved( session, task.trace, metadata, task.request.getRepository(),
400 task.result.getException() );
401 }
402 }
403
404 return results;
405 }
406
407 private File getLocalFile( RepositorySystemSession session, Metadata metadata )
408 {
409 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
410 LocalMetadataResult localResult = lrm.find( session, new LocalMetadataRequest( metadata, null, null ) );
411 return localResult.getFile();
412 }
413
414 private List<RemoteRepository> getEnabledSourceRepositories( RemoteRepository repository, Metadata.Nature nature )
415 {
416 List<RemoteRepository> repositories = new ArrayList<>();
417
418 if ( repository.isRepositoryManager() )
419 {
420 for ( RemoteRepository repo : repository.getMirroredRepositories() )
421 {
422 if ( isEnabled( repo, nature ) )
423 {
424 repositories.add( repo );
425 }
426 }
427 }
428 else if ( isEnabled( repository, nature ) )
429 {
430 repositories.add( repository );
431 }
432
433 return repositories;
434 }
435
436 private boolean isEnabled( RemoteRepository repository, Metadata.Nature nature )
437 {
438 if ( !Metadata.Nature.SNAPSHOT.equals( nature ) && repository.getPolicy( false ).isEnabled() )
439 {
440 return true;
441 }
442 if ( !Metadata.Nature.RELEASE.equals( nature ) && repository.getPolicy( true ).isEnabled() )
443 {
444 return true;
445 }
446 return false;
447 }
448
449 private RepositoryPolicy getPolicy( RepositorySystemSession session, RemoteRepository repository,
450 Metadata.Nature nature )
451 {
452 boolean releases = !Metadata.Nature.SNAPSHOT.equals( nature );
453 boolean snapshots = !Metadata.Nature.RELEASE.equals( nature );
454 return remoteRepositoryManager.getPolicy( session, repository, releases, snapshots );
455 }
456
457 private void metadataResolving( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
458 ArtifactRepository repository )
459 {
460 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVING );
461 event.setTrace( trace );
462 event.setMetadata( metadata );
463 event.setRepository( repository );
464
465 repositoryEventDispatcher.dispatch( event.build() );
466 }
467
468 private void metadataResolved( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
469 ArtifactRepository repository, Exception exception )
470 {
471 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVED );
472 event.setTrace( trace );
473 event.setMetadata( metadata );
474 event.setRepository( repository );
475 event.setException( exception );
476 event.setFile( metadata.getFile() );
477
478 repositoryEventDispatcher.dispatch( event.build() );
479 }
480
481 private void metadataDownloading( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
482 ArtifactRepository repository )
483 {
484 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADING );
485 event.setTrace( trace );
486 event.setMetadata( metadata );
487 event.setRepository( repository );
488
489 repositoryEventDispatcher.dispatch( event.build() );
490 }
491
492 private void metadataDownloaded( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
493 ArtifactRepository repository, File file, Exception exception )
494 {
495 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADED );
496 event.setTrace( trace );
497 event.setMetadata( metadata );
498 event.setRepository( repository );
499 event.setException( exception );
500 event.setFile( file );
501
502 repositoryEventDispatcher.dispatch( event.build() );
503 }
504
505 private Executor getExecutor( int threads )
506 {
507 if ( threads <= 1 )
508 {
509 return command -> command.run();
510 }
511 else
512 {
513 return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
514 new WorkerThreadFactory( null ) );
515 }
516 }
517
518 private void shutdown( Executor executor )
519 {
520 if ( executor instanceof ExecutorService )
521 {
522 ( (ExecutorService) executor ).shutdown();
523 }
524 }
525
526 class ResolveTask
527 implements Runnable
528 {
529 final RepositorySystemSession session;
530
531 final RequestTrace trace;
532
533 final MetadataResult result;
534
535 final MetadataRequest request;
536
537 final File metadataFile;
538
539 final String policy;
540
541 final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
542
543 volatile MetadataTransferException exception;
544
545 ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result,
546 File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks,
547 String policy )
548 {
549 this.session = session;
550 this.trace = trace;
551 this.result = result;
552 this.request = result.getRequest();
553 this.metadataFile = metadataFile;
554 this.policy = policy;
555 this.checks = checks;
556 }
557
558 public void run()
559 {
560 Metadata metadata = request.getMetadata();
561 RemoteRepository requestRepository = request.getRepository();
562
563 try
564 {
565 List<RemoteRepository> repositories = new ArrayList<>();
566 for ( UpdateCheck<Metadata, MetadataTransferException> check : checks )
567 {
568 repositories.add( check.getAuthoritativeRepository() );
569 }
570
571 MetadataDownload download = new MetadataDownload();
572 download.setMetadata( metadata );
573 download.setRequestContext( request.getRequestContext() );
574 download.setFile( metadataFile );
575 download.setChecksumPolicy( policy );
576 download.setRepositories( repositories );
577 download.setListener( SafeTransferListener.wrap( session ) );
578 download.setTrace( trace );
579
580 try ( RepositoryConnector connector =
581 repositoryConnectorProvider.newRepositoryConnector( session, requestRepository ) )
582 {
583 connector.get( null, Arrays.asList( download ) );
584 }
585
586 exception = download.getException();
587
588 if ( exception == null )
589 {
590
591 List<String> contexts = Collections.singletonList( request.getRequestContext() );
592 LocalMetadataRegistration registration =
593 new LocalMetadataRegistration( metadata, requestRepository, contexts );
594
595 session.getLocalRepositoryManager().add( session, registration );
596 }
597 else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException )
598 {
599 download.getFile().delete();
600 }
601 }
602 catch ( NoRepositoryConnectorException e )
603 {
604 exception = new MetadataTransferException( metadata, requestRepository, e );
605 }
606 }
607 }
608 }