You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DefaultBrowseService.java 50KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256
  1. package org.apache.archiva.rest.services;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. */
  20. import org.apache.archiva.admin.model.beans.ManagedRepository;
  21. import org.apache.archiva.common.utils.VersionComparator;
  22. import org.apache.archiva.common.utils.VersionUtil;
  23. import org.apache.archiva.dependency.tree.maven2.DependencyTreeBuilder;
  24. import org.apache.archiva.maven2.metadata.MavenMetadataReader;
  25. import org.apache.archiva.maven2.model.Artifact;
  26. import org.apache.archiva.maven2.model.TreeEntry;
  27. import org.apache.archiva.metadata.generic.GenericMetadataFacet;
  28. import org.apache.archiva.metadata.model.ArtifactMetadata;
  29. import org.apache.archiva.metadata.model.MetadataFacet;
  30. import org.apache.archiva.metadata.model.ProjectVersionMetadata;
  31. import org.apache.archiva.metadata.model.ProjectVersionReference;
  32. import org.apache.archiva.metadata.repository.*;
  33. import org.apache.archiva.metadata.repository.storage.maven2.ArtifactMetadataVersionComparator;
  34. import org.apache.archiva.metadata.repository.storage.maven2.MavenProjectFacet;
  35. import org.apache.archiva.model.ArchivaArtifact;
  36. import org.apache.archiva.model.ArchivaRepositoryMetadata;
  37. import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
  38. import org.apache.archiva.redback.components.cache.Cache;
  39. import org.apache.archiva.repository.ManagedRepositoryContent;
  40. import org.apache.archiva.repository.ReleaseScheme;
  41. import org.apache.archiva.repository.RepositoryException;
  42. import org.apache.archiva.repository.RepositoryNotFoundException;
  43. import org.apache.archiva.repository.metadata.MetadataTools;
  44. import org.apache.archiva.rest.api.model.*;
  45. import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
  46. import org.apache.archiva.rest.api.services.BrowseService;
  47. import org.apache.archiva.rest.services.utils.ArtifactContentEntryComparator;
  48. import org.apache.archiva.security.ArchivaSecurityException;
  49. import org.apache.archiva.xml.XMLException;
  50. import org.apache.commons.collections4.CollectionUtils;
  51. import org.apache.commons.io.IOUtils;
  52. import org.apache.commons.lang.StringUtils;
  53. import org.springframework.stereotype.Service;
  54. import javax.inject.Inject;
  55. import javax.inject.Named;
  56. import javax.ws.rs.core.Response;
  57. import java.io.IOException;
  58. import java.io.InputStream;
  59. import java.nio.charset.Charset;
  60. import java.nio.file.Files;
  61. import java.nio.file.Path;
  62. import java.util.*;
  63. import java.util.jar.JarEntry;
  64. import java.util.jar.JarFile;
  65. import java.util.zip.ZipEntry;
  66. /**
  67. * @author Olivier Lamy
  68. * @since 1.4-M3
  69. */
  70. @Service( "browseService#rest" )
  71. public class DefaultBrowseService
  72. extends AbstractRestService
  73. implements BrowseService
  74. {
  75. private final Charset ARTIFACT_CONTENT_ENCODING=Charset.forName( "UTF-8" );
  76. @Inject
  77. private DependencyTreeBuilder dependencyTreeBuilder;
  78. @Inject
  79. @Named( value = "repositoryProxyConnectors#default" )
  80. private RepositoryProxyConnectors connectors;
  81. @Inject
  82. @Named( value = "browse#versionMetadata" )
  83. private Cache<String, ProjectVersionMetadata> versionMetadataCache;
  84. private ManagedRepositoryContent getManagedRepositoryContent(String id) throws RepositoryException
  85. {
  86. org.apache.archiva.repository.ManagedRepository repo = repositoryRegistry.getManagedRepository( id );
  87. if (repo==null) {
  88. throw new RepositoryException( "Could not find repository "+id );
  89. }
  90. return repo.getContent();
  91. }
  92. @Override
  93. public BrowseResult getRootGroups( String repositoryId )
  94. throws ArchivaRestServiceException
  95. {
  96. List<String> selectedRepos = getSelectedRepos( repositoryId );
  97. Set<String> namespaces = new LinkedHashSet<String>();
  98. // TODO: this logic should be optional, particularly remembering we want to keep this code simple
  99. // it is located here to avoid the content repository implementation needing to do too much for what
  100. // is essentially presentation code
  101. Set<String> namespacesToCollapse = new LinkedHashSet<String>();
  102. RepositorySession repositorySession = repositorySessionFactory.createSession();
  103. try
  104. {
  105. MetadataResolver metadataResolver = repositorySession.getResolver();
  106. for ( String repoId : selectedRepos )
  107. {
  108. namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
  109. }
  110. for ( String n : namespacesToCollapse )
  111. {
  112. // TODO: check performance of this
  113. namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
  114. }
  115. }
  116. catch ( MetadataResolutionException e )
  117. {
  118. throw new ArchivaRestServiceException( e.getMessage(),
  119. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  120. }
  121. finally
  122. {
  123. repositorySession.close();
  124. }
  125. List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<>( namespaces.size() );
  126. for ( String namespace : namespaces )
  127. {
  128. browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
  129. }
  130. Collections.sort( browseGroupResultEntries );
  131. return new BrowseResult( browseGroupResultEntries );
  132. }
  133. @Override
  134. public BrowseResult browseGroupId( String groupId, String repositoryId )
  135. throws ArchivaRestServiceException
  136. {
  137. List<String> selectedRepos = getSelectedRepos( repositoryId );
  138. Set<String> projects = new LinkedHashSet<>();
  139. RepositorySession repositorySession = repositorySessionFactory.createSession();
  140. Set<String> namespaces;
  141. try
  142. {
  143. MetadataResolver metadataResolver = repositorySession.getResolver();
  144. Set<String> namespacesToCollapse = new LinkedHashSet<>();
  145. for ( String repoId : selectedRepos )
  146. {
  147. namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
  148. projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
  149. }
  150. // TODO: this logic should be optional, particularly remembering we want to keep this code simple
  151. // it is located here to avoid the content repository implementation needing to do too much for what
  152. // is essentially presentation code
  153. namespaces = new LinkedHashSet<>();
  154. for ( String n : namespacesToCollapse )
  155. {
  156. // TODO: check performance of this
  157. namespaces.add(
  158. collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
  159. }
  160. }
  161. catch ( MetadataResolutionException e )
  162. {
  163. throw new ArchivaRestServiceException( e.getMessage(),
  164. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  165. }
  166. finally
  167. {
  168. repositorySession.close();
  169. }
  170. List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<>( namespaces.size() + projects.size() );
  171. for ( String namespace : namespaces )
  172. {
  173. browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ).groupId( namespace ) );
  174. }
  175. for ( String project : projects )
  176. {
  177. browseGroupResultEntries.add(
  178. new BrowseResultEntry( groupId + '.' + project, true ).groupId( groupId ).artifactId( project ) );
  179. }
  180. Collections.sort( browseGroupResultEntries );
  181. return new BrowseResult( browseGroupResultEntries );
  182. }
  183. @Override
  184. public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
  185. throws ArchivaRestServiceException
  186. {
  187. List<String> selectedRepos = getSelectedRepos( repositoryId );
  188. try
  189. {
  190. Collection<String> versions = getVersions( selectedRepos, groupId, artifactId );
  191. return new VersionsList( new ArrayList<>( versions ) );
  192. }
  193. catch ( MetadataResolutionException e )
  194. {
  195. throw new ArchivaRestServiceException( e.getMessage(),
  196. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  197. }
  198. }
  199. private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
  200. throws MetadataResolutionException
  201. {
  202. RepositorySession repositorySession = repositorySessionFactory.createSession();
  203. try
  204. {
  205. MetadataResolver metadataResolver = repositorySession.getResolver();
  206. Set<String> versions = new LinkedHashSet<String>();
  207. for ( String repoId : selectedRepos )
  208. {
  209. Collection<String> projectVersions =
  210. metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId );
  211. versions.addAll( projectVersions );
  212. }
  213. List<String> sortedVersions = new ArrayList<>( versions );
  214. Collections.sort( sortedVersions, VersionComparator.getInstance() );
  215. return sortedVersions;
  216. }
  217. finally
  218. {
  219. repositorySession.close();
  220. }
  221. }
  222. @Override
  223. public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
  224. String repositoryId )
  225. throws ArchivaRestServiceException
  226. {
  227. List<String> selectedRepos = getSelectedRepos( repositoryId );
  228. RepositorySession repositorySession = null;
  229. try
  230. {
  231. repositorySession = repositorySessionFactory.createSession();
  232. MetadataResolver metadataResolver = repositorySession.getResolver();
  233. ProjectVersionMetadata versionMetadata = null;
  234. for ( String repoId : selectedRepos )
  235. {
  236. if ( versionMetadata == null || versionMetadata.isIncomplete() )
  237. {
  238. try
  239. {
  240. ProjectVersionMetadata versionMetadataTmp =
  241. metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
  242. version );
  243. if ( versionMetadata == null && versionMetadataTmp != null )
  244. {
  245. versionMetadata = versionMetadataTmp;
  246. }
  247. }
  248. catch ( MetadataResolutionException e )
  249. {
  250. log.warn( "Skipping invalid metadata while compiling shared model for {}:{} in repo {}: {}",
  251. groupId, artifactId, repoId, e.getMessage() );
  252. }
  253. }
  254. }
  255. return versionMetadata;
  256. }
  257. finally
  258. {
  259. if ( repositorySession != null )
  260. {
  261. repositorySession.close();
  262. }
  263. }
  264. }
  265. @Override
  266. public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
  267. throws ArchivaRestServiceException
  268. {
  269. List<String> selectedRepos = getSelectedRepos( repositoryId );
  270. RepositorySession repositorySession = null;
  271. try
  272. {
  273. Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
  274. repositorySession = repositorySessionFactory.createSession();
  275. MetadataResolver metadataResolver = repositorySession.getResolver();
  276. ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
  277. MavenProjectFacet mavenFacet = new MavenProjectFacet();
  278. mavenFacet.setGroupId( groupId );
  279. mavenFacet.setArtifactId( artifactId );
  280. sharedModel.addFacet( mavenFacet );
  281. boolean isFirstVersion = true;
  282. for ( String version : projectVersions )
  283. {
  284. ProjectVersionMetadata versionMetadata = null;
  285. for ( String repoId : selectedRepos )
  286. {
  287. if ( versionMetadata == null || versionMetadata.isIncomplete() )
  288. {
  289. try
  290. {
  291. ProjectVersionMetadata projectVersionMetadataResolved = null;
  292. boolean useCache = !StringUtils.endsWith( version, VersionUtil.SNAPSHOT );
  293. String cacheKey = null;
  294. boolean cacheToUpdate = false;
  295. // FIXME a bit maven centric!!!
  296. // not a snapshot so get it from cache
  297. if ( useCache )
  298. {
  299. cacheKey = repoId + groupId + artifactId + version;
  300. projectVersionMetadataResolved = versionMetadataCache.get( cacheKey );
  301. }
  302. if ( useCache && projectVersionMetadataResolved != null )
  303. {
  304. versionMetadata = projectVersionMetadataResolved;
  305. }
  306. else
  307. {
  308. projectVersionMetadataResolved =
  309. metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId,
  310. artifactId, version );
  311. versionMetadata = projectVersionMetadataResolved;
  312. cacheToUpdate = true;
  313. }
  314. if ( useCache && cacheToUpdate )
  315. {
  316. versionMetadataCache.put( cacheKey, projectVersionMetadataResolved );
  317. }
  318. }
  319. catch ( MetadataResolutionException e )
  320. {
  321. log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
  322. + artifactId + " in repo " + repoId + ": " + e.getMessage() );
  323. }
  324. }
  325. }
  326. if ( versionMetadata == null )
  327. {
  328. continue;
  329. }
  330. if ( isFirstVersion )
  331. {
  332. sharedModel = versionMetadata;
  333. sharedModel.setId( null );
  334. }
  335. else
  336. {
  337. MavenProjectFacet versionMetadataMavenFacet =
  338. (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
  339. if ( versionMetadataMavenFacet != null )
  340. {
  341. if ( mavenFacet.getPackaging() != null //
  342. && !StringUtils.equalsIgnoreCase( mavenFacet.getPackaging(),
  343. versionMetadataMavenFacet.getPackaging() ) )
  344. {
  345. mavenFacet.setPackaging( null );
  346. }
  347. }
  348. if ( StringUtils.isEmpty( sharedModel.getName() ) //
  349. && !StringUtils.isEmpty( versionMetadata.getName() ) )
  350. {
  351. sharedModel.setName( versionMetadata.getName() );
  352. }
  353. if ( sharedModel.getDescription() != null //
  354. && !StringUtils.equalsIgnoreCase( sharedModel.getDescription(),
  355. versionMetadata.getDescription() ) )
  356. {
  357. sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
  358. ? versionMetadata.getDescription()
  359. : "" );
  360. }
  361. if ( sharedModel.getIssueManagement() != null //
  362. && versionMetadata.getIssueManagement() != null //
  363. && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
  364. versionMetadata.getIssueManagement().getUrl() ) )
  365. {
  366. sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
  367. }
  368. if ( sharedModel.getCiManagement() != null //
  369. && versionMetadata.getCiManagement() != null //
  370. && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
  371. versionMetadata.getCiManagement().getUrl() ) )
  372. {
  373. sharedModel.setCiManagement( versionMetadata.getCiManagement() );
  374. }
  375. if ( sharedModel.getOrganization() != null //
  376. && versionMetadata.getOrganization() != null //
  377. && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
  378. versionMetadata.getOrganization().getName() ) )
  379. {
  380. sharedModel.setOrganization( versionMetadata.getOrganization() );
  381. }
  382. if ( sharedModel.getUrl() != null //
  383. && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(), versionMetadata.getUrl() ) )
  384. {
  385. sharedModel.setUrl( versionMetadata.getUrl() );
  386. }
  387. }
  388. isFirstVersion = false;
  389. }
  390. return sharedModel;
  391. }
  392. catch ( MetadataResolutionException e )
  393. {
  394. throw new ArchivaRestServiceException( e.getMessage(),
  395. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  396. }
  397. finally
  398. {
  399. if ( repositorySession != null )
  400. {
  401. repositorySession.close();
  402. }
  403. }
  404. }
  405. @Override
  406. public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
  407. throws ArchivaRestServiceException
  408. {
  409. List<String> selectedRepos = getSelectedRepos( repositoryId );
  410. try
  411. {
  412. return dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version );
  413. }
  414. catch ( Exception e )
  415. {
  416. log.error( e.getMessage(), e );
  417. }
  418. return Collections.emptyList();
  419. }
  420. @Override
  421. public List<ManagedRepository> getUserRepositories()
  422. throws ArchivaRestServiceException
  423. {
  424. try
  425. {
  426. return userRepositories.getAccessibleRepositories( getPrincipal() );
  427. }
  428. catch ( ArchivaSecurityException e )
  429. {
  430. throw new ArchivaRestServiceException( "repositories.read.observable.error",
  431. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  432. }
  433. }
  434. @Override
  435. public List<ManagedRepository> getUserManagableRepositories() throws ArchivaRestServiceException {
  436. try
  437. {
  438. return userRepositories.getManagableRepositories( getPrincipal() );
  439. }
  440. catch ( ArchivaSecurityException e )
  441. {
  442. throw new ArchivaRestServiceException( "repositories.read.managable.error",
  443. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  444. }
  445. }
  446. @Override
  447. public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
  448. throws ArchivaRestServiceException
  449. {
  450. List<ProjectVersionReference> references = new ArrayList<>();
  451. // TODO: what if we get duplicates across repositories?
  452. RepositorySession repositorySession = repositorySessionFactory.createSession();
  453. try
  454. {
  455. MetadataResolver metadataResolver = repositorySession.getResolver();
  456. for ( String repoId : getObservableRepos() )
  457. {
  458. // TODO: what about if we want to see this irrespective of version?
  459. references.addAll(
  460. metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
  461. version ) );
  462. }
  463. }
  464. catch ( MetadataResolutionException e )
  465. {
  466. throw new ArchivaRestServiceException( e.getMessage(),
  467. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  468. }
  469. finally
  470. {
  471. repositorySession.close();
  472. }
  473. List<Artifact> artifacts = new ArrayList<>( references.size() );
  474. for ( ProjectVersionReference projectVersionReference : references )
  475. {
  476. artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
  477. projectVersionReference.getProjectVersion() ) );
  478. }
  479. return artifacts;
  480. }
  481. @Override
  482. public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
  483. throws ArchivaRestServiceException
  484. {
  485. ProjectVersionMetadata projectVersionMetadata =
  486. getProjectMetadata( groupId, artifactId, version, repositoryId );
  487. if ( projectVersionMetadata == null )
  488. {
  489. return Collections.emptyList();
  490. }
  491. MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
  492. if ( metadataFacet == null )
  493. {
  494. return Collections.emptyList();
  495. }
  496. Map<String, String> map = metadataFacet.toProperties();
  497. List<Entry> entries = new ArrayList<>( map.size() );
  498. for ( Map.Entry<String, String> entry : map.entrySet() )
  499. {
  500. entries.add( new Entry( entry.getKey(), entry.getValue() ) );
  501. }
  502. return entries;
  503. }
  504. @Override
  505. public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
  506. String repositoryId )
  507. throws ArchivaRestServiceException
  508. {
  509. ProjectVersionMetadata projectVersionMetadata =
  510. getProjectMetadata( groupId, artifactId, version, repositoryId );
  511. if ( projectVersionMetadata == null )
  512. {
  513. return Boolean.FALSE;
  514. }
  515. Map<String, String> properties = new HashMap<>();
  516. MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
  517. if ( metadataFacet != null && metadataFacet.toProperties() != null )
  518. {
  519. properties.putAll( metadataFacet.toProperties() );
  520. }
  521. else
  522. {
  523. metadataFacet = new GenericMetadataFacet();
  524. }
  525. properties.put( key, value );
  526. metadataFacet.fromProperties( properties );
  527. projectVersionMetadata.addFacet( metadataFacet );
  528. RepositorySession repositorySession = repositorySessionFactory.createSession();
  529. try
  530. {
  531. MetadataRepository metadataRepository = repositorySession.getRepository();
  532. metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
  533. repositorySession.save();
  534. }
  535. catch ( MetadataRepositoryException e )
  536. {
  537. log.error( e.getMessage(), e );
  538. throw new ArchivaRestServiceException( e.getMessage(),
  539. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  540. }
  541. finally
  542. {
  543. repositorySession.close();
  544. }
  545. return Boolean.TRUE;
  546. }
  547. @Override
  548. public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
  549. throws ArchivaRestServiceException
  550. {
  551. ProjectVersionMetadata projectVersionMetadata =
  552. getProjectMetadata( groupId, artifactId, version, repositoryId );
  553. if ( projectVersionMetadata == null )
  554. {
  555. return Boolean.FALSE;
  556. }
  557. GenericMetadataFacet metadataFacet =
  558. (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
  559. if ( metadataFacet != null && metadataFacet.toProperties() != null )
  560. {
  561. Map<String, String> properties = metadataFacet.toProperties();
  562. properties.remove( key );
  563. metadataFacet.setAdditionalProperties( properties );
  564. }
  565. else
  566. {
  567. return Boolean.TRUE;
  568. }
  569. RepositorySession repositorySession = repositorySessionFactory.createSession();
  570. try
  571. {
  572. MetadataRepository metadataRepository = repositorySession.getRepository();
  573. metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
  574. repositorySession.save();
  575. }
  576. catch ( MetadataRepositoryException e )
  577. {
  578. log.error( e.getMessage(), e );
  579. throw new ArchivaRestServiceException( e.getMessage(),
  580. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  581. }
  582. finally
  583. {
  584. repositorySession.close();
  585. }
  586. return Boolean.TRUE;
  587. }
  588. @Override
  589. public List<ArtifactContentEntry> getArtifactContentEntries( String groupId, String artifactId, String version,
  590. String classifier, String type, String path,
  591. String repositoryId )
  592. throws ArchivaRestServiceException
  593. {
  594. List<String> selectedRepos = getSelectedRepos( repositoryId );
  595. try
  596. {
  597. for ( String repoId : selectedRepos )
  598. {
  599. ManagedRepositoryContent managedRepositoryContent =
  600. getManagedRepositoryContent( repoId );
  601. ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
  602. StringUtils.isEmpty( type ) ? "jar" : type,
  603. repoId );
  604. Path file = managedRepositoryContent.toFile( archivaArtifact );
  605. if ( Files.exists(file) )
  606. {
  607. return readFileEntries( file, path, repoId );
  608. }
  609. }
  610. }
  611. catch ( IOException e )
  612. {
  613. log.error( e.getMessage(), e );
  614. throw new ArchivaRestServiceException( e.getMessage(),
  615. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  616. }
  617. catch ( RepositoryNotFoundException e )
  618. {
  619. log.error( e.getMessage(), e );
  620. throw new ArchivaRestServiceException( e.getMessage(),
  621. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  622. }
  623. catch ( RepositoryException e )
  624. {
  625. log.error( e.getMessage(), e );
  626. throw new ArchivaRestServiceException( e.getMessage(),
  627. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  628. }
  629. return Collections.emptyList();
  630. }
  631. @Override
  632. public List<Artifact> getArtifactDownloadInfos( String groupId, String artifactId, String version,
  633. String repositoryId )
  634. throws ArchivaRestServiceException
  635. {
  636. List<String> selectedRepos = getSelectedRepos( repositoryId );
  637. List<Artifact> artifactDownloadInfos = new ArrayList<>();
  638. try (RepositorySession session = repositorySessionFactory.createSession())
  639. {
  640. MetadataResolver metadataResolver = session.getResolver();
  641. for ( String repoId : selectedRepos )
  642. {
  643. List<ArtifactMetadata> artifacts = new ArrayList<>(
  644. metadataResolver.resolveArtifacts( session, repoId, groupId, artifactId, version ) );
  645. Collections.sort( artifacts, ArtifactMetadataVersionComparator.INSTANCE );
  646. if ( artifacts != null && !artifacts.isEmpty() )
  647. {
  648. return buildArtifacts( artifacts, repoId );
  649. }
  650. }
  651. }
  652. catch ( MetadataResolutionException e )
  653. {
  654. log.error( e.getMessage(), e );
  655. throw new ArchivaRestServiceException( e.getMessage(),
  656. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  657. }
  658. return artifactDownloadInfos;
  659. }
  660. @Override
  661. public ArtifactContent getArtifactContentText( String groupId, String artifactId, String version, String classifier,
  662. String type, String path, String repositoryId )
  663. throws ArchivaRestServiceException
  664. {
  665. List<String> selectedRepos = getSelectedRepos( repositoryId );
  666. try
  667. {
  668. for ( String repoId : selectedRepos )
  669. {
  670. ManagedRepositoryContent managedRepositoryContent = null;
  671. try
  672. {
  673. managedRepositoryContent = getManagedRepositoryContent( repoId );
  674. }
  675. catch ( RepositoryException e )
  676. {
  677. log.error("No repository content found for "+repoId);
  678. continue;
  679. }
  680. ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
  681. StringUtils.isEmpty( type ) ? "jar" : type,
  682. repoId );
  683. Path file = managedRepositoryContent.toFile( archivaArtifact );
  684. if ( !Files.exists(file) )
  685. {
  686. log.debug( "file: {} not exists for repository: {} try next repository", file, repoId );
  687. continue;
  688. }
  689. if ( StringUtils.isNotBlank( path ) )
  690. {
  691. // zip entry of the path -> path must a real file entry of the archive
  692. JarFile jarFile = new JarFile( file.toFile() );
  693. ZipEntry zipEntry = jarFile.getEntry( path );
  694. try (InputStream inputStream = jarFile.getInputStream( zipEntry ))
  695. {
  696. return new ArtifactContent( IOUtils.toString( inputStream, ARTIFACT_CONTENT_ENCODING ), repoId );
  697. }
  698. finally
  699. {
  700. closeQuietly( jarFile );
  701. }
  702. }
  703. return new ArtifactContent( new String(Files.readAllBytes( file ), ARTIFACT_CONTENT_ENCODING), repoId );
  704. }
  705. }
  706. catch ( IOException e )
  707. {
  708. log.error( e.getMessage(), e );
  709. throw new ArchivaRestServiceException( e.getMessage(),
  710. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  711. }
  712. log.debug( "artifact: {}:{}:{}:{}:{} not found", groupId, artifactId, version, classifier, type );
  713. // 404 ?
  714. return new ArtifactContent();
  715. }
  716. @Override
  717. public Boolean artifactAvailable( String groupId, String artifactId, String version, String classifier,
  718. String repositoryId )
  719. throws ArchivaRestServiceException
  720. {
  721. List<String> selectedRepos = getSelectedRepos( repositoryId );
  722. boolean snapshot = VersionUtil.isSnapshot( version );
  723. try
  724. {
  725. for ( String repoId : selectedRepos )
  726. {
  727. org.apache.archiva.repository.ManagedRepository managedRepo = repositoryRegistry.getManagedRepository(repoId);
  728. if ( ( snapshot && !managedRepo.getActiveReleaseSchemes().contains(ReleaseScheme.SNAPSHOT) ) || ( !snapshot
  729. && managedRepo.getActiveReleaseSchemes().contains(ReleaseScheme.SNAPSHOT) ) )
  730. {
  731. continue;
  732. }
  733. ManagedRepositoryContent managedRepositoryContent = getManagedRepositoryContent( repoId );
  734. // FIXME default to jar which can be wrong for war zip etc....
  735. ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version,
  736. StringUtils.isEmpty( classifier )
  737. ? ""
  738. : classifier, "jar", repoId );
  739. Path file = managedRepositoryContent.toFile( archivaArtifact );
  740. if ( file != null && Files.exists(file) )
  741. {
  742. return true;
  743. }
  744. // in case of SNAPSHOT we can have timestamped version locally !
  745. if ( StringUtils.endsWith( version, VersionUtil.SNAPSHOT ) )
  746. {
  747. Path metadataFile = file.getParent().resolve(MetadataTools.MAVEN_METADATA );
  748. if ( Files.exists(metadataFile) )
  749. {
  750. try
  751. {
  752. ArchivaRepositoryMetadata archivaRepositoryMetadata =
  753. MavenMetadataReader.read( metadataFile );
  754. int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
  755. String timeStamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
  756. // rebuild file name with timestamped version and build number
  757. String timeStampFileName = new StringBuilder( artifactId ).append( '-' ) //
  758. .append( StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) ) //
  759. .append( '-' ).append( timeStamp ) //
  760. .append( '-' ).append( Integer.toString( buildNumber ) ) //
  761. .append( ( StringUtils.isEmpty( classifier ) ? "" : "-" + classifier ) ) //
  762. .append( ".jar" ).toString();
  763. Path timeStampFile = file.getParent().resolve( timeStampFileName );
  764. log.debug( "try to find timestamped snapshot version file: {}", timeStampFile.toAbsolutePath() );
  765. if ( Files.exists(timeStampFile) )
  766. {
  767. return true;
  768. }
  769. }
  770. catch ( XMLException e )
  771. {
  772. log.warn( "skip fail to find timestamped snapshot file: {}", e.getMessage() );
  773. }
  774. }
  775. }
  776. String path = managedRepositoryContent.toPath( archivaArtifact );
  777. file = connectors.fetchFromProxies( managedRepositoryContent, path );
  778. if ( file != null && Files.exists(file) )
  779. {
  780. // download pom now
  781. String pomPath = StringUtils.substringBeforeLast( path, ".jar" ) + ".pom";
  782. connectors.fetchFromProxies( managedRepositoryContent, pomPath );
  783. return true;
  784. }
  785. }
  786. } catch ( RepositoryException e )
  787. {
  788. log.error( e.getMessage(), e );
  789. throw new ArchivaRestServiceException( e.getMessage(),
  790. Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
  791. }
  792. return false;
  793. }
  794. @Override
  795. public Boolean artifactAvailable( String groupId, String artifactId, String version, String repositoryId )
  796. throws ArchivaRestServiceException
  797. {
  798. return artifactAvailable( groupId, artifactId, version, null, repositoryId );
  799. }
  800. @Override
  801. public List<Artifact> getArtifacts( String repositoryId )
  802. throws ArchivaRestServiceException
  803. {
  804. RepositorySession repositorySession = repositorySessionFactory.createSession();
  805. try
  806. {
  807. List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifacts( repositoryId );
  808. return buildArtifacts( artifactMetadatas, repositoryId );
  809. }
  810. catch ( MetadataRepositoryException e )
  811. {
  812. throw new ArchivaRestServiceException( e.getMessage(), e );
  813. }
  814. finally
  815. {
  816. repositorySession.close();
  817. }
  818. }
  819. @Override
  820. public List<Artifact> getArtifactsByProjectVersionMetadata( String key, String value, String repositoryId )
  821. throws ArchivaRestServiceException
  822. {
  823. RepositorySession repositorySession = repositorySessionFactory.createSession();
  824. try
  825. {
  826. List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifactsByProjectVersionMetadata( key, value, repositoryId );
  827. return buildArtifacts( artifactMetadatas, repositoryId );
  828. }
  829. catch ( MetadataRepositoryException e )
  830. {
  831. throw new ArchivaRestServiceException( e.getMessage(), e );
  832. }
  833. finally
  834. {
  835. repositorySession.close();
  836. }
  837. }
  838. @Override
  839. public List<Artifact> getArtifactsByMetadata( String key, String value, String repositoryId )
  840. throws ArchivaRestServiceException
  841. {
  842. RepositorySession repositorySession = repositorySessionFactory.createSession();
  843. try
  844. {
  845. List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifactsByMetadata( key, value, repositoryId );
  846. return buildArtifacts( artifactMetadatas, repositoryId );
  847. }
  848. catch ( MetadataRepositoryException e )
  849. {
  850. throw new ArchivaRestServiceException( e.getMessage(), e );
  851. }
  852. finally
  853. {
  854. repositorySession.close();
  855. }
  856. }
  857. @Override
  858. public List<Artifact> getArtifactsByProperty( String key, String value, String repositoryId )
  859. throws ArchivaRestServiceException
  860. {
  861. RepositorySession repositorySession = repositorySessionFactory.createSession();
  862. try
  863. {
  864. List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifactsByProperty( key, value, repositoryId );
  865. return buildArtifacts( artifactMetadatas, repositoryId );
  866. }
  867. catch ( MetadataRepositoryException e )
  868. {
  869. throw new ArchivaRestServiceException( e.getMessage(), e );
  870. }
  871. finally
  872. {
  873. repositorySession.close();
  874. }
  875. }
  876. @Override
  877. public Boolean importMetadata( MetadataAddRequest metadataAddRequest, String repositoryId )
  878. throws ArchivaRestServiceException
  879. {
  880. boolean result = true;
  881. for ( Map.Entry<String, String> metadata : metadataAddRequest.getMetadatas().entrySet() )
  882. {
  883. result = addMetadata( metadataAddRequest.getGroupId(), metadataAddRequest.getArtifactId(),
  884. metadataAddRequest.getVersion(), metadata.getKey(), metadata.getValue(),
  885. repositoryId );
  886. if ( !result )
  887. {
  888. break;
  889. }
  890. }
  891. return result;
  892. }
  893. @Override
  894. public List<Artifact> searchArtifacts( String text, String repositoryId, Boolean exact )
  895. throws ArchivaRestServiceException
  896. {
  897. RepositorySession repositorySession = repositorySessionFactory.createSession();
  898. try
  899. {
  900. List<ArtifactMetadata> artifactMetadatas =
  901. repositorySession.getRepository().searchArtifacts( text, repositoryId, exact == null ? false : exact );
  902. return buildArtifacts( artifactMetadatas, repositoryId );
  903. }
  904. catch ( MetadataRepositoryException e )
  905. {
  906. throw new ArchivaRestServiceException( e.getMessage(), e );
  907. }
  908. finally
  909. {
  910. repositorySession.close();
  911. }
  912. }
  913. @Override
  914. public List<Artifact> searchArtifacts( String key, String text, String repositoryId, Boolean exact )
  915. throws ArchivaRestServiceException
  916. {
  917. RepositorySession repositorySession = repositorySessionFactory.createSession();
  918. try
  919. {
  920. List<ArtifactMetadata> artifactMetadatas =
  921. repositorySession.getRepository().searchArtifacts( key, text, repositoryId, exact == null ? false : exact );
  922. return buildArtifacts( artifactMetadatas, repositoryId );
  923. }
  924. catch ( MetadataRepositoryException e )
  925. {
  926. throw new ArchivaRestServiceException( e.getMessage(), e );
  927. }
  928. finally
  929. {
  930. repositorySession.close();
  931. }
  932. }
  933. //---------------------------
  934. // internals
  935. //---------------------------
  936. private void closeQuietly( JarFile jarFile )
  937. {
  938. if ( jarFile != null )
  939. {
  940. try
  941. {
  942. jarFile.close();
  943. }
  944. catch ( IOException e )
  945. {
  946. log.warn( "ignore error closing jarFile {}", jarFile.getName() );
  947. }
  948. }
  949. }
  950. protected List<ArtifactContentEntry> readFileEntries(final Path file, final String filterPath, final String repoId )
  951. throws IOException
  952. {
  953. String cleanedfilterPath = filterPath==null ? "" : (StringUtils.startsWith(filterPath, "/") ?
  954. StringUtils.substringAfter(filterPath, "/") : filterPath);
  955. Map<String, ArtifactContentEntry> artifactContentEntryMap = new HashMap<>();
  956. int filterDepth = StringUtils.countMatches( cleanedfilterPath, "/" );
  957. if (!StringUtils.endsWith(cleanedfilterPath,"/") && !StringUtils.isEmpty(cleanedfilterPath)) {
  958. filterDepth++;
  959. }
  960. JarFile jarFile = new JarFile( file.toFile() );
  961. try
  962. {
  963. Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
  964. while ( jarEntryEnumeration.hasMoreElements() )
  965. {
  966. JarEntry currentEntry = jarEntryEnumeration.nextElement();
  967. String cleanedEntryName = StringUtils.endsWith( currentEntry.getName(), "/" ) ? //
  968. StringUtils.substringBeforeLast( currentEntry.getName(), "/" ) : currentEntry.getName();
  969. String entryRootPath = getRootPath( cleanedEntryName );
  970. int depth = StringUtils.countMatches( cleanedEntryName, "/" );
  971. if ( StringUtils.isEmpty( cleanedfilterPath ) //
  972. && !artifactContentEntryMap.containsKey( entryRootPath ) //
  973. && depth == filterDepth )
  974. {
  975. artifactContentEntryMap.put( entryRootPath,
  976. new ArtifactContentEntry( entryRootPath, !currentEntry.isDirectory(),
  977. depth, repoId ) );
  978. }
  979. else
  980. {
  981. if ( StringUtils.startsWith( cleanedEntryName, cleanedfilterPath ) //
  982. && ( depth == filterDepth || ( !currentEntry.isDirectory() && depth == filterDepth ) ) )
  983. {
  984. artifactContentEntryMap.put( cleanedEntryName, new ArtifactContentEntry( cleanedEntryName,
  985. !currentEntry.isDirectory(),
  986. depth, repoId ) );
  987. }
  988. }
  989. }
  990. if ( StringUtils.isNotEmpty( cleanedfilterPath ) )
  991. {
  992. Map<String, ArtifactContentEntry> filteredArtifactContentEntryMap = new HashMap<>();
  993. for ( Map.Entry<String, ArtifactContentEntry> entry : artifactContentEntryMap.entrySet() )
  994. {
  995. filteredArtifactContentEntryMap.put( entry.getKey(), entry.getValue() );
  996. }
  997. List<ArtifactContentEntry> sorted = getSmallerDepthEntries( filteredArtifactContentEntryMap );
  998. if ( sorted == null )
  999. {
  1000. return Collections.emptyList();
  1001. }
  1002. Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
  1003. return sorted;
  1004. }
  1005. }
  1006. finally
  1007. {
  1008. if ( jarFile != null )
  1009. {
  1010. jarFile.close();
  1011. }
  1012. }
  1013. List<ArtifactContentEntry> sorted = new ArrayList<>( artifactContentEntryMap.values() );
  1014. Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
  1015. return sorted;
  1016. }
  1017. private List<ArtifactContentEntry> getSmallerDepthEntries( Map<String, ArtifactContentEntry> entries )
  1018. {
  1019. int smallestDepth = Integer.MAX_VALUE;
  1020. Map<Integer, List<ArtifactContentEntry>> perDepthList = new HashMap<>();
  1021. for ( Map.Entry<String, ArtifactContentEntry> entry : entries.entrySet() )
  1022. {
  1023. ArtifactContentEntry current = entry.getValue();
  1024. if ( current.getDepth() < smallestDepth )
  1025. {
  1026. smallestDepth = current.getDepth();
  1027. }
  1028. List<ArtifactContentEntry> currentList = perDepthList.get( current.getDepth() );
  1029. if ( currentList == null )
  1030. {
  1031. currentList = new ArrayList<>();
  1032. currentList.add( current );
  1033. perDepthList.put( current.getDepth(), currentList );
  1034. }
  1035. else
  1036. {
  1037. currentList.add( current );
  1038. }
  1039. }
  1040. return perDepthList.get( smallestDepth );
  1041. }
  1042. /**
  1043. * @param path
  1044. * @return org/apache -&gt; org , org -&gt; org
  1045. */
  1046. private String getRootPath( String path )
  1047. {
  1048. if ( StringUtils.contains( path, '/' ) )
  1049. {
  1050. return StringUtils.substringBefore( path, "/" );
  1051. }
  1052. return path;
  1053. }
  1054. private List<String> getSelectedRepos( String repositoryId )
  1055. throws ArchivaRestServiceException
  1056. {
  1057. List<String> selectedRepos = getObservableRepos();
  1058. if ( CollectionUtils.isEmpty( selectedRepos ) )
  1059. {
  1060. return Collections.emptyList();
  1061. }
  1062. if ( StringUtils.isNotEmpty( repositoryId ) )
  1063. {
  1064. // check user has karma on the repository
  1065. if ( !selectedRepos.contains( repositoryId ) )
  1066. {
  1067. throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
  1068. Response.Status.FORBIDDEN.getStatusCode(), null );
  1069. }
  1070. selectedRepos = Collections.singletonList( repositoryId );
  1071. }
  1072. return selectedRepos;
  1073. }
  1074. private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
  1075. Collection<String> repoIds, String n )
  1076. throws MetadataResolutionException
  1077. {
  1078. Set<String> subNamespaces = new LinkedHashSet<String>();
  1079. for ( String repoId : repoIds )
  1080. {
  1081. subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
  1082. }
  1083. if ( subNamespaces.size() != 1 )
  1084. {
  1085. log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
  1086. return n;
  1087. }
  1088. else
  1089. {
  1090. for ( String repoId : repoIds )
  1091. {
  1092. Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
  1093. if ( projects != null && !projects.isEmpty() )
  1094. {
  1095. log.debug( "{} is not collapsible as it has projects", n );
  1096. return n;
  1097. }
  1098. }
  1099. return collapseNamespaces( repositorySession, metadataResolver, repoIds,
  1100. n + "." + subNamespaces.iterator().next() );
  1101. }
  1102. }
  1103. public Cache<String, ProjectVersionMetadata> getVersionMetadataCache()
  1104. {
  1105. return versionMetadataCache;
  1106. }
  1107. public void setVersionMetadataCache( Cache<String, ProjectVersionMetadata> versionMetadataCache )
  1108. {
  1109. this.versionMetadataCache = versionMetadataCache;
  1110. }
  1111. }