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