1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.IOException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.concurrent.atomic.AtomicBoolean;
36
37 import org.eclipse.aether.ConfigurationProperties;
38 import org.eclipse.aether.RepositoryEvent;
39 import org.eclipse.aether.RepositoryEvent.EventType;
40 import org.eclipse.aether.RepositorySystemSession;
41 import org.eclipse.aether.RequestTrace;
42 import org.eclipse.aether.SyncContext;
43 import org.eclipse.aether.artifact.Artifact;
44 import org.eclipse.aether.impl.ArtifactResolver;
45 import org.eclipse.aether.impl.OfflineController;
46 import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
47 import org.eclipse.aether.impl.RemoteRepositoryManager;
48 import org.eclipse.aether.impl.RepositoryConnectorProvider;
49 import org.eclipse.aether.impl.RepositoryEventDispatcher;
50 import org.eclipse.aether.impl.UpdateCheck;
51 import org.eclipse.aether.impl.UpdateCheckManager;
52 import org.eclipse.aether.impl.VersionResolver;
53 import org.eclipse.aether.repository.*;
54 import org.eclipse.aether.resolution.ArtifactRequest;
55 import org.eclipse.aether.resolution.ArtifactResolutionException;
56 import org.eclipse.aether.resolution.ArtifactResult;
57 import org.eclipse.aether.resolution.ResolutionErrorPolicy;
58 import org.eclipse.aether.resolution.VersionRequest;
59 import org.eclipse.aether.resolution.VersionResolutionException;
60 import org.eclipse.aether.resolution.VersionResult;
61 import org.eclipse.aether.scope.SystemDependencyScope;
62 import org.eclipse.aether.spi.connector.ArtifactDownload;
63 import org.eclipse.aether.spi.connector.RepositoryConnector;
64 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
65 import org.eclipse.aether.spi.io.PathProcessor;
66 import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
67 import org.eclipse.aether.spi.synccontext.SyncContextFactory;
68 import org.eclipse.aether.transfer.ArtifactFilteredOutException;
69 import org.eclipse.aether.transfer.ArtifactNotFoundException;
70 import org.eclipse.aether.transfer.ArtifactTransferException;
71 import org.eclipse.aether.transfer.NoRepositoryConnectorException;
72 import org.eclipse.aether.transfer.RepositoryOfflineException;
73 import org.eclipse.aether.util.ConfigUtils;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77 import static java.util.Objects.requireNonNull;
78
79
80
81
82 @Singleton
83 @Named
84 public class DefaultArtifactResolver implements ArtifactResolver {
85
86 public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "artifactResolver.";
87
88
89
90
91
92
93
94
95
96
97
98 public static final String CONFIG_PROP_SNAPSHOT_NORMALIZATION = CONFIG_PROPS_PREFIX + "snapshotNormalization";
99
100 public static final boolean DEFAULT_SNAPSHOT_NORMALIZATION = true;
101
102
103
104
105
106
107
108
109
110 public static final String CONFIG_PROP_SIMPLE_LRM_INTEROP = CONFIG_PROPS_PREFIX + "simpleLrmInterop";
111
112 public static final boolean DEFAULT_SIMPLE_LRM_INTEROP = false;
113
114 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultArtifactResolver.class);
115
116 private final PathProcessor pathProcessor;
117
118 private final RepositoryEventDispatcher repositoryEventDispatcher;
119
120 private final VersionResolver versionResolver;
121
122 private final UpdateCheckManager updateCheckManager;
123
124 private final RepositoryConnectorProvider repositoryConnectorProvider;
125
126 private final RemoteRepositoryManager remoteRepositoryManager;
127
128 private final SyncContextFactory syncContextFactory;
129
130 private final OfflineController offlineController;
131
132 private final Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors;
133
134 private final RemoteRepositoryFilterManager remoteRepositoryFilterManager;
135
136 @SuppressWarnings("checkstyle:parameternumber")
137 @Inject
138 public DefaultArtifactResolver(
139 PathProcessor pathProcessor,
140 RepositoryEventDispatcher repositoryEventDispatcher,
141 VersionResolver versionResolver,
142 UpdateCheckManager updateCheckManager,
143 RepositoryConnectorProvider repositoryConnectorProvider,
144 RemoteRepositoryManager remoteRepositoryManager,
145 SyncContextFactory syncContextFactory,
146 OfflineController offlineController,
147 Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors,
148 RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
149 this.pathProcessor = requireNonNull(pathProcessor, "path processor cannot be null");
150 this.repositoryEventDispatcher =
151 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
152 this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null");
153 this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
154 this.repositoryConnectorProvider =
155 requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
156 this.remoteRepositoryManager =
157 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
158 this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
159 this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
160 this.artifactResolverPostProcessors =
161 requireNonNull(artifactResolverPostProcessors, "artifact resolver post-processors cannot be null");
162 this.remoteRepositoryFilterManager =
163 requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null");
164 }
165
166 @Override
167 public ArtifactResult resolveArtifact(RepositorySystemSession session, ArtifactRequest request)
168 throws ArtifactResolutionException {
169 requireNonNull(session, "session cannot be null");
170 requireNonNull(request, "request cannot be null");
171
172 return resolveArtifacts(session, Collections.singleton(request)).get(0);
173 }
174
175 @Override
176 public List<ArtifactResult> resolveArtifacts(
177 RepositorySystemSession session, Collection<? extends ArtifactRequest> requests)
178 throws ArtifactResolutionException {
179 requireNonNull(session, "session cannot be null");
180 requireNonNull(requests, "requests cannot be null");
181 try (SyncContext shared = syncContextFactory.newInstance(session, true);
182 SyncContext exclusive = syncContextFactory.newInstance(session, false)) {
183 Collection<Artifact> artifacts = new ArrayList<>(requests.size());
184 SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
185 for (ArtifactRequest request : requests) {
186 if (systemDependencyScope != null
187 && systemDependencyScope.getSystemPath(request.getArtifact()) != null) {
188 continue;
189 }
190 artifacts.add(request.getArtifact());
191 }
192
193 return resolve(shared, exclusive, artifacts, session, requests);
194 }
195 }
196
197 @SuppressWarnings("checkstyle:methodlength")
198 private List<ArtifactResult> resolve(
199 SyncContext shared,
200 SyncContext exclusive,
201 Collection<Artifact> subjects,
202 RepositorySystemSession session,
203 Collection<? extends ArtifactRequest> requests)
204 throws ArtifactResolutionException {
205 SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
206 SyncContext current = shared;
207 try {
208 while (true) {
209 current.acquire(subjects, null);
210
211 boolean failures = false;
212 final List<ArtifactResult> results = new ArrayList<>(requests.size());
213 final boolean simpleLrmInterop =
214 ConfigUtils.getBoolean(session, DEFAULT_SIMPLE_LRM_INTEROP, CONFIG_PROP_SIMPLE_LRM_INTEROP);
215 final LocalRepositoryManager lrm = session.getLocalRepositoryManager();
216 final WorkspaceReader workspace = session.getWorkspaceReader();
217 final List<ResolutionGroup> groups = new ArrayList<>();
218
219 final RemoteRepositoryFilter filter = remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
220
221 for (ArtifactRequest request : requests) {
222 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
223
224 ArtifactResult result = new ArtifactResult(request);
225 results.add(result);
226
227 Artifact artifact = request.getArtifact();
228
229 if (current == shared) {
230 artifactResolving(session, trace, artifact);
231 }
232
233 String localPath =
234 systemDependencyScope != null ? systemDependencyScope.getSystemPath(artifact) : null;
235 if (localPath != null) {
236
237 Path path = Paths.get(localPath);
238 if (!Files.isRegularFile(path)) {
239 failures = true;
240 result.addException(
241 ArtifactResult.NO_REPOSITORY, new ArtifactNotFoundException(artifact, localPath));
242 } else {
243 artifact = artifact.setPath(path);
244 result.setArtifact(artifact);
245 artifactResolved(session, trace, artifact, null, result.getExceptions());
246 }
247 continue;
248 }
249
250 List<RemoteRepository> remoteRepositories = request.getRepositories();
251 List<RemoteRepository> filteredRemoteRepositories = new ArrayList<>(remoteRepositories);
252 if (filter != null) {
253 for (RemoteRepository repository : remoteRepositories) {
254 RemoteRepositoryFilter.Result filterResult = filter.acceptArtifact(repository, artifact);
255 if (!filterResult.isAccepted()) {
256 result.addException(
257 repository,
258 new ArtifactFilteredOutException(
259 artifact, repository, filterResult.reasoning()));
260 filteredRemoteRepositories.remove(repository);
261 }
262 }
263 }
264
265 VersionResult versionResult;
266 try {
267 VersionRequest versionRequest =
268 new VersionRequest(artifact, filteredRemoteRepositories, request.getRequestContext());
269 versionRequest.setTrace(trace);
270 versionResult = versionResolver.resolveVersion(session, versionRequest);
271 } catch (VersionResolutionException e) {
272 if (filteredRemoteRepositories.isEmpty()) {
273 result.addException(lrm.getRepository(), e);
274 } else {
275 filteredRemoteRepositories.forEach(r -> result.addException(r, e));
276 }
277 continue;
278 }
279
280 artifact = artifact.setVersion(versionResult.getVersion());
281
282 if (versionResult.getRepository() != null) {
283 if (versionResult.getRepository() instanceof RemoteRepository) {
284 filteredRemoteRepositories =
285 Collections.singletonList((RemoteRepository) versionResult.getRepository());
286 } else {
287 filteredRemoteRepositories = Collections.emptyList();
288 }
289 }
290
291 if (workspace != null) {
292 Path path = workspace.findArtifactPath(artifact);
293 if (path != null) {
294 artifact = artifact.setPath(path);
295 result.setArtifact(artifact);
296 result.setRepository(workspace.getRepository());
297 artifactResolved(session, trace, artifact, result.getRepository(), null);
298 continue;
299 }
300 }
301
302 LocalArtifactResult local = lrm.find(
303 session,
304 new LocalArtifactRequest(
305 artifact, filteredRemoteRepositories, request.getRequestContext()));
306 result.setLocalArtifactResult(local);
307 boolean found = (filter != null && local.isAvailable()) || isLocallyInstalled(local, versionResult);
308
309
310
311 if (found) {
312 if (local.getRepository() != null) {
313 result.setRepository(local.getRepository());
314 } else {
315 result.setRepository(lrm.getRepository());
316 }
317
318 try {
319 artifact = artifact.setPath(getPath(session, artifact, local.getPath()));
320 result.setArtifact(artifact);
321 artifactResolved(session, trace, artifact, result.getRepository(), null);
322 } catch (ArtifactTransferException e) {
323 result.addException(lrm.getRepository(), e);
324 }
325 if (filter == null && simpleLrmInterop && !local.isAvailable()) {
326
327
328
329
330
331
332 lrm.add(session, new LocalArtifactRegistration(artifact));
333 }
334
335 continue;
336 }
337
338 if (local.getPath() != null) {
339 LOGGER.info(
340 "Artifact {} is present in the local repository, but cached from a remote repository ID that is unavailable in current build context, verifying that is downloadable from {}",
341 artifact,
342 remoteRepositories);
343 }
344
345 LOGGER.debug("Resolving artifact {} from {}", artifact, remoteRepositories);
346 AtomicBoolean resolved = new AtomicBoolean(false);
347 Iterator<ResolutionGroup> groupIt = groups.iterator();
348 for (RemoteRepository repo : filteredRemoteRepositories) {
349 if (!repo.getPolicy(artifact.isSnapshot()).isEnabled()) {
350 continue;
351 }
352
353 try {
354 Utils.checkOffline(session, offlineController, repo);
355 } catch (RepositoryOfflineException e) {
356 Exception exception = new ArtifactNotFoundException(
357 artifact,
358 repo,
359 "Cannot access " + repo.getId() + " ("
360 + repo.getUrl() + ") in offline mode and the artifact " + artifact
361 + " has not been downloaded from it before.",
362 e);
363 result.addException(repo, exception);
364 continue;
365 }
366
367 ResolutionGroup group = null;
368 while (groupIt.hasNext()) {
369 ResolutionGroup t = groupIt.next();
370 if (t.matches(repo)) {
371 group = t;
372 break;
373 }
374 }
375 if (group == null) {
376 group = new ResolutionGroup(repo);
377 groups.add(group);
378 groupIt = Collections.emptyIterator();
379 }
380 group.items.add(new ResolutionItem(trace, artifact, resolved, result, local, repo));
381 }
382 }
383
384 if (!groups.isEmpty() && current == shared) {
385 current.close();
386 current = exclusive;
387 continue;
388 }
389
390 for (ResolutionGroup group : groups) {
391 performDownloads(session, group);
392 }
393
394 for (ArtifactResolverPostProcessor artifactResolverPostProcessor :
395 artifactResolverPostProcessors.values()) {
396 artifactResolverPostProcessor.postProcess(session, results);
397 }
398
399 for (ArtifactResult result : results) {
400 ArtifactRequest request = result.getRequest();
401
402 Artifact artifact = result.getArtifact();
403 if (artifact == null || artifact.getPath() == null) {
404 failures = true;
405 if (result.getExceptions().isEmpty()) {
406 Exception exception =
407 new ArtifactNotFoundException(request.getArtifact(), (RemoteRepository) null);
408 result.addException(result.getRepository(), exception);
409 }
410 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
411 artifactResolved(session, trace, request.getArtifact(), null, result.getExceptions());
412 }
413 }
414
415 if (failures) {
416 throw new ArtifactResolutionException(results);
417 }
418
419 return results;
420 }
421 } finally {
422 current.close();
423 }
424 }
425
426 private boolean isLocallyInstalled(LocalArtifactResult lar, VersionResult vr) {
427 if (lar.isAvailable()) {
428 return true;
429 }
430 if (lar.getPath() != null) {
431
432 if (vr.getRepository() instanceof LocalRepository) {
433
434 return true;
435 } else {
436 return vr.getRepository() == null
437 && lar.getRequest().getRepositories().isEmpty();
438 }
439 }
440 return false;
441 }
442
443 private Path getPath(RepositorySystemSession session, Artifact artifact, Path path)
444 throws ArtifactTransferException {
445 if (artifact.isSnapshot()
446 && !artifact.getVersion().equals(artifact.getBaseVersion())
447 && ConfigUtils.getBoolean(
448 session, DEFAULT_SNAPSHOT_NORMALIZATION, CONFIG_PROP_SNAPSHOT_NORMALIZATION)) {
449 String name = path.getFileName().toString().replace(artifact.getVersion(), artifact.getBaseVersion());
450 Path dst = path.getParent().resolve(name);
451
452 try {
453 long pathLastModified = pathProcessor.lastModified(path, 0L);
454 boolean copy = pathProcessor.size(dst, 0L) != pathProcessor.size(path, 0L)
455 || pathProcessor.lastModified(dst, 0L) != pathLastModified;
456 if (copy) {
457 pathProcessor.copyWithTimestamp(path, dst);
458 }
459 } catch (IOException e) {
460 throw new ArtifactTransferException(artifact, null, e);
461 }
462
463 path = dst;
464 }
465
466 return path;
467 }
468
469 private void performDownloads(RepositorySystemSession session, ResolutionGroup group) {
470 List<ArtifactDownload> downloads = gatherDownloads(session, group);
471 if (downloads.isEmpty()) {
472 return;
473 }
474
475 for (ArtifactDownload download : downloads) {
476 artifactDownloading(session, download.getTrace(), download.getArtifact(), group.repository);
477 }
478
479 try {
480 try (RepositoryConnector connector =
481 repositoryConnectorProvider.newRepositoryConnector(session, group.repository)) {
482 connector.get(downloads, null);
483 }
484 } catch (NoRepositoryConnectorException e) {
485 for (ArtifactDownload download : downloads) {
486 download.setException(new ArtifactTransferException(download.getArtifact(), group.repository, e));
487 }
488 }
489
490 evaluateDownloads(session, group);
491 }
492
493 private List<ArtifactDownload> gatherDownloads(RepositorySystemSession session, ResolutionGroup group) {
494 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
495 List<ArtifactDownload> downloads = new ArrayList<>();
496
497 for (ResolutionItem item : group.items) {
498 Artifact artifact = item.artifact;
499
500 if (item.resolved.get()) {
501
502 continue;
503 }
504
505 ArtifactDownload download = new ArtifactDownload();
506 download.setArtifact(artifact);
507 download.setRequestContext(item.request.getRequestContext());
508 download.setListener(SafeTransferListener.wrap(session));
509 download.setTrace(item.trace);
510 if (item.local.getPath() != null) {
511 download.setPath(item.local.getPath());
512 download.setExistenceCheck(true);
513 } else {
514 String path =
515 lrm.getPathForRemoteArtifact(artifact, group.repository, item.request.getRequestContext());
516 download.setPath(lrm.getRepository().getBasePath().resolve(path));
517 }
518
519 boolean snapshot = artifact.isSnapshot();
520 RepositoryPolicy policy = remoteRepositoryManager.getPolicy(session, group.repository, !snapshot, snapshot);
521
522 int errorPolicy = Utils.getPolicy(session, artifact, group.repository);
523 if ((errorPolicy & ResolutionErrorPolicy.CACHE_ALL) != 0) {
524 UpdateCheck<Artifact, ArtifactTransferException> check = new UpdateCheck<>();
525 check.setItem(artifact);
526 check.setPath(download.getPath());
527 check.setFileValid(false);
528 check.setRepository(group.repository);
529 check.setArtifactPolicy(policy.getArtifactUpdatePolicy());
530 check.setMetadataPolicy(policy.getMetadataUpdatePolicy());
531 item.updateCheck = check;
532 updateCheckManager.checkArtifact(session, check);
533 if (!check.isRequired()) {
534 item.result.addException(group.repository, check.getException());
535 continue;
536 }
537 }
538
539 download.setChecksumPolicy(policy.getChecksumPolicy());
540 download.setRepositories(item.repository.getMirroredRepositories());
541 downloads.add(download);
542 item.download = download;
543 }
544
545 return downloads;
546 }
547
548 private void evaluateDownloads(RepositorySystemSession session, ResolutionGroup group) {
549 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
550
551 for (ResolutionItem item : group.items) {
552 ArtifactDownload download = item.download;
553 if (download == null) {
554 continue;
555 }
556
557 Artifact artifact = download.getArtifact();
558 if (download.getException() == null) {
559 item.resolved.set(true);
560 item.result.setRepository(group.repository);
561 try {
562 artifact = artifact.setPath(getPath(session, artifact, download.getPath()));
563 item.result.setArtifact(artifact);
564
565 lrm.add(
566 session,
567 new LocalArtifactRegistration(artifact, group.repository, download.getSupportedContexts()));
568 } catch (ArtifactTransferException e) {
569 download.setException(e);
570 item.result.addException(group.repository, e);
571 }
572 } else {
573 item.result.addException(group.repository, download.getException());
574 }
575
576
577
578
579
580 if (item.updateCheck != null) {
581 item.updateCheck.setException(download.getException());
582 updateCheckManager.touchArtifact(session, item.updateCheck);
583 }
584
585 artifactDownloaded(session, download.getTrace(), artifact, group.repository, download.getException());
586 if (download.getException() == null) {
587 artifactResolved(session, download.getTrace(), artifact, group.repository, null);
588 }
589 }
590 }
591
592 private void artifactResolving(RepositorySystemSession session, RequestTrace trace, Artifact artifact) {
593 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVING);
594 event.setTrace(trace);
595 event.setArtifact(artifact);
596
597 repositoryEventDispatcher.dispatch(event.build());
598 }
599
600 private void artifactResolved(
601 RepositorySystemSession session,
602 RequestTrace trace,
603 Artifact artifact,
604 ArtifactRepository repository,
605 Collection<Exception> exceptions) {
606 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVED);
607 event.setTrace(trace);
608 event.setArtifact(artifact);
609 event.setRepository(repository);
610 event.setExceptions(exceptions != null ? new ArrayList<>(exceptions) : null);
611 if (artifact != null) {
612 event.setPath(artifact.getPath());
613 }
614
615 repositoryEventDispatcher.dispatch(event.build());
616 }
617
618 private void artifactDownloading(
619 RepositorySystemSession session, RequestTrace trace, Artifact artifact, RemoteRepository repository) {
620 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADING);
621 event.setTrace(trace);
622 event.setArtifact(artifact);
623 event.setRepository(repository);
624
625 repositoryEventDispatcher.dispatch(event.build());
626 }
627
628 private void artifactDownloaded(
629 RepositorySystemSession session,
630 RequestTrace trace,
631 Artifact artifact,
632 RemoteRepository repository,
633 Exception exception) {
634 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADED);
635 event.setTrace(trace);
636 event.setArtifact(artifact);
637 event.setRepository(repository);
638 event.setException(exception);
639 if (artifact != null) {
640 event.setPath(artifact.getPath());
641 }
642
643 repositoryEventDispatcher.dispatch(event.build());
644 }
645
646 static class ResolutionGroup {
647
648 final RemoteRepository repository;
649
650 final List<ResolutionItem> items = new ArrayList<>();
651
652 ResolutionGroup(RemoteRepository repository) {
653 this.repository = repository;
654 }
655
656 boolean matches(RemoteRepository repo) {
657 return repository.getUrl().equals(repo.getUrl())
658 && repository.getContentType().equals(repo.getContentType())
659 && repository.isRepositoryManager() == repo.isRepositoryManager();
660 }
661 }
662
663 static class ResolutionItem {
664
665 final RequestTrace trace;
666
667 final ArtifactRequest request;
668
669 final ArtifactResult result;
670
671 final LocalArtifactResult local;
672
673 final RemoteRepository repository;
674
675 final Artifact artifact;
676
677 final AtomicBoolean resolved;
678
679 ArtifactDownload download;
680
681 UpdateCheck<Artifact, ArtifactTransferException> updateCheck;
682
683 ResolutionItem(
684 RequestTrace trace,
685 Artifact artifact,
686 AtomicBoolean resolved,
687 ArtifactResult result,
688 LocalArtifactResult local,
689 RemoteRepository repository) {
690 this.trace = trace;
691 this.artifact = artifact;
692 this.resolved = resolved;
693 this.result = result;
694 this.request = result.getRequest();
695 this.local = local;
696 this.repository = repository;
697 }
698 }
699 }