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