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