]> source.dussan.org Git - archiva.git/blob
7896ec50c75628139a5d2a64753951068735452e
[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.dependency.tree.maven2.DependencyTreeBuilder;
24 import org.apache.archiva.metadata.generic.GenericMetadataFacet;
25 import org.apache.archiva.metadata.model.ArtifactMetadata;
26 import org.apache.archiva.metadata.model.MetadataFacet;
27 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
28 import org.apache.archiva.metadata.model.ProjectVersionReference;
29 import org.apache.archiva.metadata.repository.MetadataRepository;
30 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
31 import org.apache.archiva.metadata.repository.MetadataResolutionException;
32 import org.apache.archiva.metadata.repository.MetadataResolver;
33 import org.apache.archiva.metadata.repository.RepositorySession;
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.repository.ManagedRepositoryContent;
38 import org.apache.archiva.repository.RepositoryContentFactory;
39 import org.apache.archiva.repository.RepositoryException;
40 import org.apache.archiva.repository.RepositoryNotFoundException;
41 import org.apache.archiva.rest.api.model.Artifact;
42 import org.apache.archiva.rest.api.model.ArtifactContent;
43 import org.apache.archiva.rest.api.model.ArtifactContentEntry;
44 import org.apache.archiva.rest.api.model.BrowseResult;
45 import org.apache.archiva.rest.api.model.BrowseResultEntry;
46 import org.apache.archiva.rest.api.model.Entry;
47 import org.apache.archiva.rest.api.model.TreeEntry;
48 import org.apache.archiva.rest.api.model.VersionsList;
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.rest.services.utils.ArtifactDownloadInfoBuilder;
53 import org.apache.archiva.rest.services.utils.TreeDependencyNodeVisitor;
54 import org.apache.archiva.security.ArchivaSecurityException;
55 import org.apache.commons.collections.CollectionUtils;
56 import org.apache.commons.io.FileUtils;
57 import org.apache.commons.io.IOUtils;
58 import org.apache.commons.lang.StringUtils;
59 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
60 import org.springframework.stereotype.Service;
61
62 import javax.inject.Inject;
63 import javax.ws.rs.core.Response;
64 import java.io.File;
65 import java.io.IOException;
66 import java.io.InputStream;
67 import java.util.ArrayList;
68 import java.util.Arrays;
69 import java.util.Collection;
70 import java.util.Collections;
71 import java.util.Enumeration;
72 import java.util.HashMap;
73 import java.util.LinkedHashSet;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Set;
77 import java.util.jar.JarEntry;
78 import java.util.jar.JarFile;
79 import java.util.zip.ZipEntry;
80
81 /**
82  * @author Olivier Lamy
83  * @since 1.4-M3
84  */
85 @Service( "browseService#rest" )
86 public class DefaultBrowseService
87     extends AbstractRestService
88     implements BrowseService
89 {
90
91     @Inject
92     private DependencyTreeBuilder dependencyTreeBuilder;
93
94     @Inject
95     private RepositoryContentFactory repositoryContentFactory;
96
97     public BrowseResult getRootGroups( String repositoryId )
98         throws ArchivaRestServiceException
99     {
100         List<String> selectedRepos = getSelectedRepos( repositoryId );
101
102         Set<String> namespaces = new LinkedHashSet<String>();
103
104         // TODO: this logic should be optional, particularly remembering we want to keep this code simple
105         //       it is located here to avoid the content repository implementation needing to do too much for what
106         //       is essentially presentation code
107         Set<String> namespacesToCollapse;
108         RepositorySession repositorySession = repositorySessionFactory.createSession();
109         try
110         {
111             MetadataResolver metadataResolver = repositorySession.getResolver();
112             namespacesToCollapse = new LinkedHashSet<String>();
113
114             for ( String repoId : selectedRepos )
115             {
116                 namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
117             }
118             for ( String n : namespacesToCollapse )
119             {
120                 // TODO: check performance of this
121                 namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
122             }
123         }
124         catch ( MetadataResolutionException e )
125         {
126             throw new ArchivaRestServiceException( e.getMessage(),
127                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
128         }
129         finally
130         {
131             repositorySession.close();
132         }
133
134         List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<BrowseResultEntry>( namespaces.size() );
135         for ( String namespace : namespaces )
136         {
137             browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
138         }
139
140         Collections.sort( browseGroupResultEntries );
141         return new BrowseResult( browseGroupResultEntries );
142     }
143
144     public BrowseResult browseGroupId( String groupId, String repositoryId )
145         throws ArchivaRestServiceException
146     {
147         List<String> selectedRepos = getSelectedRepos( repositoryId );
148
149         Set<String> projects = new LinkedHashSet<String>();
150
151         RepositorySession repositorySession = repositorySessionFactory.createSession();
152         Set<String> namespaces;
153         try
154         {
155             MetadataResolver metadataResolver = repositorySession.getResolver();
156
157             Set<String> namespacesToCollapse = new LinkedHashSet<String>();
158             for ( String repoId : selectedRepos )
159             {
160                 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
161
162                 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
163             }
164
165             // TODO: this logic should be optional, particularly remembering we want to keep this code simple
166             // it is located here to avoid the content repository implementation needing to do too much for what
167             // is essentially presentation code
168             namespaces = new LinkedHashSet<String>();
169             for ( String n : namespacesToCollapse )
170             {
171                 // TODO: check performance of this
172                 namespaces.add(
173                     collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
174             }
175         }
176         catch ( MetadataResolutionException e )
177         {
178             throw new ArchivaRestServiceException( e.getMessage(),
179                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
180         }
181         finally
182         {
183             repositorySession.close();
184         }
185         List<BrowseResultEntry> browseGroupResultEntries =
186             new ArrayList<BrowseResultEntry>( namespaces.size() + projects.size() );
187         for ( String namespace : namespaces )
188         {
189             browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
190         }
191         for ( String project : projects )
192         {
193             browseGroupResultEntries.add( new BrowseResultEntry( groupId + '.' + project, true ) );
194         }
195         Collections.sort( browseGroupResultEntries );
196         return new BrowseResult( browseGroupResultEntries );
197
198     }
199
200     public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
201         throws ArchivaRestServiceException
202     {
203         List<String> selectedRepos = getSelectedRepos( repositoryId );
204
205         try
206         {
207             Collection<String> versions = getVersions( selectedRepos, groupId, artifactId );
208             return new VersionsList( new ArrayList<String>( versions ) );
209         }
210         catch ( MetadataResolutionException e )
211         {
212             throw new ArchivaRestServiceException( e.getMessage(),
213                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
214         }
215
216     }
217
218     private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
219         throws MetadataResolutionException
220
221     {
222         RepositorySession repositorySession = repositorySessionFactory.createSession();
223         try
224         {
225             MetadataResolver metadataResolver = repositorySession.getResolver();
226
227             Set<String> versions = new LinkedHashSet<String>();
228
229             for ( String repoId : selectedRepos )
230             {
231                 Collection<String> projectVersions =
232                     metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId );
233                 versions.addAll( projectVersions );
234             }
235
236             List<String> sortedVersions = new ArrayList<String>( versions );
237
238             Collections.sort( sortedVersions, VersionComparator.getInstance() );
239
240             return sortedVersions;
241         }
242         finally
243         {
244             repositorySession.close();
245         }
246     }
247
248     public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
249                                                       String repositoryId )
250         throws ArchivaRestServiceException
251     {
252         List<String> selectedRepos = getSelectedRepos( repositoryId );
253
254         RepositorySession repositorySession = null;
255         try
256         {
257             repositorySession = repositorySessionFactory.createSession();
258
259             MetadataResolver metadataResolver = repositorySession.getResolver();
260
261             ProjectVersionMetadata versionMetadata = null;
262             for ( String repoId : selectedRepos )
263             {
264                 if ( versionMetadata == null || versionMetadata.isIncomplete() )
265                 {
266                     try
267                     {
268                         versionMetadata =
269                             metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
270                                                                     version );
271                     }
272                     catch ( MetadataResolutionException e )
273                     {
274                         log.error(
275                             "Skipping invalid metadata while compiling shared model for " + groupId + ":" + artifactId
276                                 + " in repo " + repoId + ": " + e.getMessage() );
277                     }
278                 }
279             }
280
281             return versionMetadata;
282         }
283         finally
284         {
285             if ( repositorySession != null )
286             {
287                 repositorySession.close();
288             }
289         }
290
291     }
292
293     public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
294         throws ArchivaRestServiceException
295     {
296
297         List<String> selectedRepos = getSelectedRepos( repositoryId );
298
299         RepositorySession repositorySession = null;
300         try
301         {
302
303             Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
304
305             repositorySession = repositorySessionFactory.createSession();
306
307             MetadataResolver metadataResolver = repositorySession.getResolver();
308
309             ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
310
311             MavenProjectFacet mavenFacet = new MavenProjectFacet();
312             mavenFacet.setGroupId( groupId );
313             mavenFacet.setArtifactId( artifactId );
314             sharedModel.addFacet( mavenFacet );
315
316             boolean isFirstVersion = true;
317
318             for ( String version : projectVersions )
319             {
320                 ProjectVersionMetadata versionMetadata = null;
321                 for ( String repoId : selectedRepos )
322                 {
323                     if ( versionMetadata == null || versionMetadata.isIncomplete() )
324                     {
325                         try
326                         {
327                             versionMetadata =
328                                 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
329                                                                         version );
330                         }
331                         catch ( MetadataResolutionException e )
332                         {
333                             log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
334                                            + artifactId + " in repo " + repoId + ": " + e.getMessage() );
335                         }
336                     }
337                 }
338
339                 if ( versionMetadata == null )
340                 {
341                     continue;
342                 }
343
344                 if ( isFirstVersion )
345                 {
346                     sharedModel = versionMetadata;
347                     sharedModel.setId( null );
348                 }
349                 else
350                 {
351                     MavenProjectFacet versionMetadataMavenFacet =
352                         (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
353                     if ( versionMetadataMavenFacet != null )
354                     {
355                         if ( mavenFacet.getPackaging() != null && !StringUtils.equalsIgnoreCase(
356                             mavenFacet.getPackaging(), versionMetadataMavenFacet.getPackaging() ) )
357                         {
358                             mavenFacet.setPackaging( null );
359                         }
360                     }
361
362                     if ( StringUtils.isEmpty( sharedModel.getName() ) && !StringUtils.isEmpty(
363                         versionMetadata.getName() ) )
364                     {
365                         sharedModel.setName( versionMetadata.getName() );
366                     }
367
368                     if ( sharedModel.getDescription() != null && !StringUtils.equalsIgnoreCase(
369                         sharedModel.getDescription(), versionMetadata.getDescription() ) )
370                     {
371                         sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
372                                                         ? versionMetadata.getDescription()
373                                                         : "" );
374                     }
375
376                     if ( sharedModel.getIssueManagement() != null && versionMetadata.getIssueManagement() != null
377                         && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
378                                                           versionMetadata.getIssueManagement().getUrl() ) )
379                     {
380                         sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
381                     }
382
383                     if ( sharedModel.getCiManagement() != null && versionMetadata.getCiManagement() != null
384                         && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
385                                                           versionMetadata.getCiManagement().getUrl() ) )
386                     {
387                         sharedModel.setCiManagement( versionMetadata.getCiManagement() );
388                     }
389
390                     if ( sharedModel.getOrganization() != null && versionMetadata.getOrganization() != null
391                         && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
392                                                           versionMetadata.getOrganization().getName() ) )
393                     {
394                         sharedModel.setOrganization( versionMetadata.getOrganization() );
395                     }
396
397                     if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(),
398                                                                                         versionMetadata.getUrl() ) )
399                     {
400                         sharedModel.setUrl( versionMetadata.getUrl() );
401                     }
402                 }
403
404                 isFirstVersion = false;
405             }
406             return sharedModel;
407         }
408         catch ( MetadataResolutionException e )
409         {
410             throw new ArchivaRestServiceException( e.getMessage(),
411                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
412         }
413         finally
414         {
415             if ( repositorySession != null )
416             {
417                 repositorySession.close();
418             }
419         }
420     }
421
422     public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
423         throws ArchivaRestServiceException
424     {
425         List<String> selectedRepos = getSelectedRepos( repositoryId );
426
427         List<TreeEntry> treeEntries = new ArrayList<TreeEntry>();
428         TreeDependencyNodeVisitor treeDependencyNodeVisitor = new TreeDependencyNodeVisitor( treeEntries );
429         try
430         {
431             dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version,
432                                                        treeDependencyNodeVisitor );
433         }
434         catch ( DependencyTreeBuilderException e )
435         {
436             throw new ArchivaRestServiceException( e.getMessage(),
437                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
438         }
439         return treeEntries;
440     }
441
442     public List<ManagedRepository> getUserRepositories()
443         throws ArchivaRestServiceException
444     {
445         try
446         {
447             return userRepositories.getAccessibleRepositories( getPrincipal() );
448         }
449         catch ( ArchivaSecurityException e )
450         {
451             throw new ArchivaRestServiceException( "repositories.read.observable.error",
452                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
453         }
454     }
455
456     public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
457         throws ArchivaRestServiceException
458     {
459         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
460         // TODO: what if we get duplicates across repositories?
461         RepositorySession repositorySession = repositorySessionFactory.createSession();
462         try
463         {
464             MetadataResolver metadataResolver = repositorySession.getResolver();
465             for ( String repoId : getObservableRepos() )
466             {
467                 // TODO: what about if we want to see this irrespective of version?
468                 references.addAll(
469                     metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
470                                                                version ) );
471             }
472         }
473         catch ( MetadataResolutionException e )
474         {
475             throw new ArchivaRestServiceException( e.getMessage(),
476                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
477         }
478         finally
479         {
480             repositorySession.close();
481         }
482
483         List<Artifact> artifacts = new ArrayList<Artifact>( references.size() );
484
485         for ( ProjectVersionReference projectVersionReference : references )
486         {
487             artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
488                                          projectVersionReference.getProjectVersion() ) );
489         }
490         return artifacts;
491     }
492
493     public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
494         throws ArchivaRestServiceException
495     {
496         ProjectVersionMetadata projectVersionMetadata =
497             getProjectMetadata( groupId, artifactId, version, repositoryId );
498         if ( projectVersionMetadata == null )
499         {
500             return Collections.emptyList();
501         }
502         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
503
504         if ( metadataFacet == null )
505         {
506             return Collections.emptyList();
507         }
508         Map<String, String> map = metadataFacet.toProperties();
509         List<Entry> entries = new ArrayList<Entry>( map.size() );
510
511         for ( Map.Entry<String, String> entry : map.entrySet() )
512         {
513             entries.add( new Entry( entry.getKey(), entry.getValue() ) );
514         }
515
516         return entries;
517     }
518
519     public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
520                                 String repositoryId )
521         throws ArchivaRestServiceException
522     {
523         ProjectVersionMetadata projectVersionMetadata =
524             getProjectMetadata( groupId, artifactId, version, repositoryId );
525
526         if ( projectVersionMetadata == null )
527         {
528             return Boolean.FALSE;
529         }
530
531         Map<String, String> properties = new HashMap<String, String>();
532
533         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
534
535         if ( metadataFacet != null && metadataFacet.toProperties() != null )
536         {
537             properties.putAll( metadataFacet.toProperties() );
538         }
539         else
540         {
541             metadataFacet = new GenericMetadataFacet();
542         }
543
544         properties.put( key, value );
545
546         metadataFacet.fromProperties( properties );
547
548         projectVersionMetadata.addFacet( metadataFacet );
549
550         RepositorySession repositorySession = repositorySessionFactory.createSession();
551
552         try
553         {
554             MetadataRepository metadataRepository = repositorySession.getRepository();
555
556             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
557
558             repositorySession.save();
559         }
560         catch ( MetadataRepositoryException e )
561         {
562             log.error( e.getMessage(), e );
563             throw new ArchivaRestServiceException( e.getMessage(),
564                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
565         }
566         finally
567         {
568             repositorySession.close();
569         }
570         return Boolean.TRUE;
571     }
572
573     public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
574         throws ArchivaRestServiceException
575     {
576         ProjectVersionMetadata projectVersionMetadata =
577             getProjectMetadata( groupId, artifactId, version, repositoryId );
578
579         if ( projectVersionMetadata == null )
580         {
581             return Boolean.FALSE;
582         }
583
584         GenericMetadataFacet metadataFacet =
585             (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
586
587         if ( metadataFacet != null && metadataFacet.toProperties() != null )
588         {
589             Map<String, String> properties = metadataFacet.toProperties();
590             properties.remove( key );
591             metadataFacet.setAdditionalProperties( properties );
592         }
593         else
594         {
595             return Boolean.TRUE;
596         }
597
598         RepositorySession repositorySession = repositorySessionFactory.createSession();
599
600         try
601         {
602             MetadataRepository metadataRepository = repositorySession.getRepository();
603
604             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
605
606             repositorySession.save();
607         }
608         catch ( MetadataRepositoryException e )
609         {
610             log.error( e.getMessage(), e );
611             throw new ArchivaRestServiceException( e.getMessage(),
612                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
613         }
614         finally
615         {
616             repositorySession.close();
617         }
618         return Boolean.TRUE;
619     }
620
621     public List<ArtifactContentEntry> getArtifactContentEntries( String groupId, String artifactId, String version,
622                                                                  String classifier, String type, String path,
623                                                                  String repositoryId )
624         throws ArchivaRestServiceException
625     {
626         List<String> selectedRepos = getSelectedRepos( repositoryId );
627         try
628         {
629             for ( String repoId : selectedRepos )
630             {
631
632                 ManagedRepositoryContent managedRepositoryContent =
633                     repositoryContentFactory.getManagedRepositoryContent( repoId );
634                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
635                                                                        StringUtils.isEmpty( type ) ? "jar" : type,
636                                                                        repoId );
637                 File file = managedRepositoryContent.toFile( archivaArtifact );
638                 if ( file.exists() )
639                 {
640                     return readFileEntries( file, path, repoId );
641                 }
642             }
643         }
644         catch ( IOException e )
645         {
646             log.error( e.getMessage(), e );
647             throw new ArchivaRestServiceException( e.getMessage(),
648                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
649         }
650         catch ( RepositoryNotFoundException e )
651         {
652             log.error( e.getMessage(), e );
653             throw new ArchivaRestServiceException( e.getMessage(),
654                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
655         }
656         catch ( RepositoryException e )
657         {
658             log.error( e.getMessage(), e );
659             throw new ArchivaRestServiceException( e.getMessage(),
660                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
661         }
662         return Collections.emptyList();
663     }
664
665     public List<Artifact> getArtifactDownloadInfos( String groupId, String artifactId, String version,
666                                                     String repositoryId )
667         throws ArchivaRestServiceException
668     {
669         List<String> selectedRepos = getSelectedRepos( repositoryId );
670
671         List<Artifact> artifactDownloadInfos = new ArrayList<Artifact>();
672
673         RepositorySession session = repositorySessionFactory.createSession();
674
675         MetadataResolver metadataResolver = session.getResolver();
676
677         try
678         {
679             for ( String repoId : selectedRepos )
680             {
681                 List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>(
682                     metadataResolver.resolveArtifacts( session, repoId, groupId, artifactId, version ) );
683                 Collections.sort( artifacts, ArtifactMetadataVersionComparator.INSTANCE );
684
685                 for ( ArtifactMetadata artifact : artifacts )
686                 {
687
688                     ArtifactDownloadInfoBuilder builder =
689                         new ArtifactDownloadInfoBuilder().forArtifactMetadata( artifact ).withManagedRepositoryContent(
690                             repositoryContentFactory.getManagedRepositoryContent( repoId ) );
691                     Artifact art = builder.build();
692
693                     art.setUrl( getArtifactUrl( art ) );
694                     artifactDownloadInfos.add( art );
695                 }
696
697             }
698         }
699         catch ( RepositoryException e )
700         {
701             log.error( e.getMessage(), e );
702             throw new ArchivaRestServiceException( e.getMessage(),
703                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
704         }
705         catch ( MetadataResolutionException e )
706         {
707             log.error( e.getMessage(), e );
708             throw new ArchivaRestServiceException( e.getMessage(),
709                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
710         }
711         finally
712         {
713             if ( session != null )
714             {
715                 session.close();
716             }
717         }
718
719         return artifactDownloadInfos;
720     }
721
722     public ArtifactContent getArtifactContentText( String groupId, String artifactId, String version, String classifier,
723                                                    String type, String path, String repositoryId )
724         throws ArchivaRestServiceException
725     {
726         List<String> selectedRepos = getSelectedRepos( repositoryId );
727         try
728         {
729             for ( String repoId : selectedRepos )
730             {
731
732                 ManagedRepositoryContent managedRepositoryContent =
733                     repositoryContentFactory.getManagedRepositoryContent( repoId );
734                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
735                                                                        StringUtils.isEmpty( type ) ? "jar" : type,
736                                                                        repoId );
737                 File file = managedRepositoryContent.toFile( archivaArtifact );
738                 if ( !file.exists() )
739                 {
740                     log.debug( "file: {} not exists for repository: {} try next repository", file, repoId );
741                     continue;
742                 }
743                 if ( StringUtils.isNotBlank( path ) )
744                 {
745                     // zip entry of the path -> path must a real file entry of the archive
746                     JarFile jarFile = new JarFile( file );
747                     ZipEntry zipEntry = jarFile.getEntry( path );
748                     InputStream inputStream = jarFile.getInputStream( zipEntry );
749                     try
750                     {
751                         return new ArtifactContent( IOUtils.toString( inputStream ), repoId );
752                     }
753                     finally
754                     {
755                         closeQuietly( jarFile );
756                         IOUtils.closeQuietly( inputStream );
757                     }
758                 }
759                 return new ArtifactContent( FileUtils.readFileToString( file ), repoId );
760             }
761         }
762         catch ( IOException 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 ( RepositoryNotFoundException e )
769         {
770             log.error( e.getMessage(), e );
771             throw new ArchivaRestServiceException( e.getMessage(),
772                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
773         }
774         catch ( RepositoryException e )
775         {
776             log.error( e.getMessage(), e );
777             throw new ArchivaRestServiceException( e.getMessage(),
778                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
779         }
780         log.debug( "artifact: {}:{}:{}:{}:{} not found",
781                    Arrays.asList( groupId, artifactId, version, classifier, type ).toArray( new String[5] ) );
782         // 404 ?
783         return new ArtifactContent();
784     }
785
786     private void closeQuietly( JarFile jarFile )
787     {
788         if ( jarFile != null )
789         {
790             try
791             {
792                 jarFile.close();
793             }
794             catch ( IOException e )
795             {
796                 log.warn( "ignore error closing jarFile {}", jarFile.getName() );
797             }
798         }
799     }
800
801     //---------------------------
802     // internals
803     //---------------------------
804
805     protected List<ArtifactContentEntry> readFileEntries( File file, String filterPath, String repoId )
806         throws IOException
807     {
808         Map<String, ArtifactContentEntry> artifactContentEntryMap = new HashMap<String, ArtifactContentEntry>();
809         int filterDepth = StringUtils.countMatches( filterPath, "/" );
810         /*if ( filterDepth == 0 )
811         {
812             filterDepth = 1;
813         }*/
814         JarFile jarFile = new JarFile( file );
815         try
816         {
817             Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
818             while ( jarEntryEnumeration.hasMoreElements() )
819             {
820                 JarEntry currentEntry = jarEntryEnumeration.nextElement();
821                 String cleanedEntryName =
822                     StringUtils.endsWith( currentEntry.getName(), "/" ) ? StringUtils.substringBeforeLast(
823                         currentEntry.getName(), "/" ) : currentEntry.getName();
824                 String entryRootPath = getRootPath( cleanedEntryName );
825                 int depth = StringUtils.countMatches( cleanedEntryName, "/" );
826                 if ( StringUtils.isEmpty( filterPath ) && !artifactContentEntryMap.containsKey( entryRootPath )
827                     && depth == filterDepth )
828                 {
829
830                     artifactContentEntryMap.put( entryRootPath,
831                                                  new ArtifactContentEntry( entryRootPath, !currentEntry.isDirectory(),
832                                                                            depth, repoId ) );
833                 }
834                 else
835                 {
836                     if ( StringUtils.startsWith( cleanedEntryName, filterPath ) && ( depth == filterDepth || (
837                         !currentEntry.isDirectory() && depth == filterDepth ) ) )
838                     {
839                         artifactContentEntryMap.put( cleanedEntryName, new ArtifactContentEntry( cleanedEntryName,
840                                                                                                  !currentEntry.isDirectory(),
841                                                                                                  depth, repoId ) );
842                     }
843                 }
844             }
845
846             if ( StringUtils.isNotEmpty( filterPath ) )
847             {
848                 Map<String, ArtifactContentEntry> filteredArtifactContentEntryMap =
849                     new HashMap<String, ArtifactContentEntry>();
850
851                 for ( Map.Entry<String, ArtifactContentEntry> entry : artifactContentEntryMap.entrySet() )
852                 {
853                     filteredArtifactContentEntryMap.put( entry.getKey(), entry.getValue() );
854                 }
855
856                 List<ArtifactContentEntry> sorted = getSmallerDepthEntries( filteredArtifactContentEntryMap );
857                 if ( sorted == null )
858                 {
859                     return Collections.emptyList();
860                 }
861                 Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
862                 return sorted;
863             }
864         }
865         finally
866         {
867             if ( jarFile != null )
868             {
869                 jarFile.close();
870             }
871         }
872         List<ArtifactContentEntry> sorted = new ArrayList<ArtifactContentEntry>( artifactContentEntryMap.values() );
873         Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
874         return sorted;
875     }
876
877     private List<ArtifactContentEntry> getSmallerDepthEntries( Map<String, ArtifactContentEntry> entries )
878     {
879         int smallestDepth = Integer.MAX_VALUE;
880         Map<Integer, List<ArtifactContentEntry>> perDepthList = new HashMap<Integer, List<ArtifactContentEntry>>();
881         for ( Map.Entry<String, ArtifactContentEntry> entry : entries.entrySet() )
882         {
883
884             ArtifactContentEntry current = entry.getValue();
885
886             if ( current.getDepth() < smallestDepth )
887             {
888                 smallestDepth = current.getDepth();
889             }
890
891             List<ArtifactContentEntry> currentList = perDepthList.get( current.getDepth() );
892
893             if ( currentList == null )
894             {
895                 currentList = new ArrayList<ArtifactContentEntry>();
896                 currentList.add( current );
897                 perDepthList.put( current.getDepth(), currentList );
898             }
899             else
900             {
901                 currentList.add( current );
902             }
903
904         }
905
906         return perDepthList.get( smallestDepth );
907     }
908
909     /**
910      * @param path
911      * @return org/apache -> org , org -> org
912      */
913     private String getRootPath( String path )
914     {
915         if ( StringUtils.contains( path, '/' ) )
916         {
917             return StringUtils.substringBefore( path, "/" );
918         }
919         return path;
920     }
921
922     private List<String> getSelectedRepos( String repositoryId )
923         throws ArchivaRestServiceException
924     {
925
926         List<String> selectedRepos = getObservableRepos();
927
928         if ( CollectionUtils.isEmpty( selectedRepos ) )
929         {
930             // FIXME 403 ???
931             return Collections.emptyList();
932         }
933
934         if ( StringUtils.isNotEmpty( repositoryId ) )
935         {
936             // check user has karma on the repository
937             if ( !selectedRepos.contains( repositoryId ) )
938             {
939                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
940                                                        Response.Status.FORBIDDEN.getStatusCode(), null );
941             }
942             selectedRepos = Collections.singletonList( repositoryId );
943         }
944         return selectedRepos;
945     }
946
947
948     private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
949                                        Collection<String> repoIds, String n )
950         throws MetadataResolutionException
951     {
952         Set<String> subNamespaces = new LinkedHashSet<String>();
953         for ( String repoId : repoIds )
954         {
955             subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
956         }
957         if ( subNamespaces.size() != 1 )
958         {
959             log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
960             return n;
961         }
962         else
963         {
964             for ( String repoId : repoIds )
965             {
966                 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
967                 if ( projects != null && !projects.isEmpty() )
968                 {
969                     log.debug( "{} is not collapsible as it has projects", n );
970                     return n;
971                 }
972             }
973             return collapseNamespaces( repositorySession, metadataResolver, repoIds,
974                                        n + "." + subNamespaces.iterator().next() );
975         }
976     }
977 }