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