]> source.dussan.org Git - archiva.git/blob
93e36284d0c3edcd716258eb45fef58738c98c5a
[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<String>();
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<String>();
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<String>();
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
479             return dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version );
480
481         }
482         catch ( Exception e )
483         {
484             log.error( e.getMessage(), e );
485         }
486
487         return Collections.emptyList();
488     }
489
490     @Override
491     public List<ManagedRepository> getUserRepositories()
492         throws ArchivaRestServiceException
493     {
494         try
495         {
496             return userRepositories.getAccessibleRepositories( getPrincipal() );
497         }
498         catch ( ArchivaSecurityException e )
499         {
500             throw new ArchivaRestServiceException( "repositories.read.observable.error",
501                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
502         }
503     }
504
505     @Override
506     public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
507         throws ArchivaRestServiceException
508     {
509         List<ProjectVersionReference> references = new ArrayList<>();
510         // TODO: what if we get duplicates across repositories?
511         RepositorySession repositorySession = repositorySessionFactory.createSession();
512         try
513         {
514             MetadataResolver metadataResolver = repositorySession.getResolver();
515             for ( String repoId : getObservableRepos() )
516             {
517                 // TODO: what about if we want to see this irrespective of version?
518                 references.addAll(
519                     metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
520                                                                version ) );
521             }
522         }
523         catch ( MetadataResolutionException e )
524         {
525             throw new ArchivaRestServiceException( e.getMessage(),
526                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
527         }
528         finally
529         {
530             repositorySession.close();
531         }
532
533         List<Artifact> artifacts = new ArrayList<>( references.size() );
534
535         for ( ProjectVersionReference projectVersionReference : references )
536         {
537             artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
538                                          projectVersionReference.getProjectVersion() ) );
539         }
540         return artifacts;
541     }
542
543     @Override
544     public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
545         throws ArchivaRestServiceException
546     {
547         ProjectVersionMetadata projectVersionMetadata =
548             getProjectMetadata( groupId, artifactId, version, repositoryId );
549         if ( projectVersionMetadata == null )
550         {
551             return Collections.emptyList();
552         }
553         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
554
555         if ( metadataFacet == null )
556         {
557             return Collections.emptyList();
558         }
559         Map<String, String> map = metadataFacet.toProperties();
560         List<Entry> entries = new ArrayList<>( map.size() );
561
562         for ( Map.Entry<String, String> entry : map.entrySet() )
563         {
564             entries.add( new Entry( entry.getKey(), entry.getValue() ) );
565         }
566
567         return entries;
568     }
569
570     @Override
571     public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
572                                 String repositoryId )
573         throws ArchivaRestServiceException
574     {
575         ProjectVersionMetadata projectVersionMetadata =
576             getProjectMetadata( groupId, artifactId, version, repositoryId );
577
578         if ( projectVersionMetadata == null )
579         {
580             return Boolean.FALSE;
581         }
582
583         Map<String, String> properties = new HashMap<>();
584
585         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
586
587         if ( metadataFacet != null && metadataFacet.toProperties() != null )
588         {
589             properties.putAll( metadataFacet.toProperties() );
590         }
591         else
592         {
593             metadataFacet = new GenericMetadataFacet();
594         }
595
596         properties.put( key, value );
597
598         metadataFacet.fromProperties( properties );
599
600         projectVersionMetadata.addFacet( metadataFacet );
601
602         RepositorySession repositorySession = repositorySessionFactory.createSession();
603
604         try
605         {
606             MetadataRepository metadataRepository = repositorySession.getRepository();
607
608             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
609
610             repositorySession.save();
611         }
612         catch ( MetadataRepositoryException e )
613         {
614             log.error( e.getMessage(), e );
615             throw new ArchivaRestServiceException( e.getMessage(),
616                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
617         }
618         finally
619         {
620             repositorySession.close();
621         }
622         return Boolean.TRUE;
623     }
624
625     @Override
626     public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
627         throws ArchivaRestServiceException
628     {
629         ProjectVersionMetadata projectVersionMetadata =
630             getProjectMetadata( groupId, artifactId, version, repositoryId );
631
632         if ( projectVersionMetadata == null )
633         {
634             return Boolean.FALSE;
635         }
636
637         GenericMetadataFacet metadataFacet =
638             (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
639
640         if ( metadataFacet != null && metadataFacet.toProperties() != null )
641         {
642             Map<String, String> properties = metadataFacet.toProperties();
643             properties.remove( key );
644             metadataFacet.setAdditionalProperties( properties );
645         }
646         else
647         {
648             return Boolean.TRUE;
649         }
650
651         RepositorySession repositorySession = repositorySessionFactory.createSession();
652
653         try
654         {
655             MetadataRepository metadataRepository = repositorySession.getRepository();
656
657             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
658
659             repositorySession.save();
660         }
661         catch ( MetadataRepositoryException e )
662         {
663             log.error( e.getMessage(), e );
664             throw new ArchivaRestServiceException( e.getMessage(),
665                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
666         }
667         finally
668         {
669             repositorySession.close();
670         }
671         return Boolean.TRUE;
672     }
673
674     @Override
675     public List<ArtifactContentEntry> getArtifactContentEntries( String groupId, String artifactId, String version,
676                                                                  String classifier, String type, String path,
677                                                                  String repositoryId )
678         throws ArchivaRestServiceException
679     {
680         List<String> selectedRepos = getSelectedRepos( repositoryId );
681         try
682         {
683             for ( String repoId : selectedRepos )
684             {
685
686                 ManagedRepositoryContent managedRepositoryContent =
687                     repositoryContentFactory.getManagedRepositoryContent( repoId );
688                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
689                                                                        StringUtils.isEmpty( type ) ? "jar" : type,
690                                                                        repoId );
691                 File file = managedRepositoryContent.toFile( archivaArtifact );
692                 if ( file.exists() )
693                 {
694                     return readFileEntries( file, path, repoId );
695                 }
696             }
697         }
698         catch ( IOException e )
699         {
700             log.error( e.getMessage(), e );
701             throw new ArchivaRestServiceException( e.getMessage(),
702                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
703         }
704         catch ( RepositoryNotFoundException e )
705         {
706             log.error( e.getMessage(), e );
707             throw new ArchivaRestServiceException( e.getMessage(),
708                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
709         }
710         catch ( RepositoryException e )
711         {
712             log.error( e.getMessage(), e );
713             throw new ArchivaRestServiceException( e.getMessage(),
714                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
715         }
716         return Collections.emptyList();
717     }
718
719     @Override
720     public List<Artifact> getArtifactDownloadInfos( String groupId, String artifactId, String version,
721                                                     String repositoryId )
722         throws ArchivaRestServiceException
723     {
724         List<String> selectedRepos = getSelectedRepos( repositoryId );
725
726         List<Artifact> artifactDownloadInfos = new ArrayList<>();
727
728         RepositorySession session = repositorySessionFactory.createSession();
729
730         MetadataResolver metadataResolver = session.getResolver();
731
732         try
733         {
734             for ( String repoId : selectedRepos )
735             {
736                 List<ArtifactMetadata> artifacts = new ArrayList<>(
737                     metadataResolver.resolveArtifacts( session, repoId, groupId, artifactId, version ) );
738                 Collections.sort( artifacts, ArtifactMetadataVersionComparator.INSTANCE );
739                 if ( artifacts != null && !artifacts.isEmpty() )
740                 {
741                     return buildArtifacts( artifacts, repoId );
742                 }
743             }
744         }
745         catch ( MetadataResolutionException e )
746         {
747             log.error( e.getMessage(), e );
748             throw new ArchivaRestServiceException( e.getMessage(),
749                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
750         }
751         finally
752         {
753             if ( session != null )
754             {
755                 session.closeQuietly();
756             }
757         }
758
759         return artifactDownloadInfos;
760     }
761
762     @Override
763     public ArtifactContent getArtifactContentText( String groupId, String artifactId, String version, String classifier,
764                                                    String type, String path, String repositoryId )
765         throws ArchivaRestServiceException
766     {
767         List<String> selectedRepos = getSelectedRepos( repositoryId );
768         try
769         {
770             for ( String repoId : selectedRepos )
771             {
772
773                 ManagedRepositoryContent managedRepositoryContent =
774                     repositoryContentFactory.getManagedRepositoryContent( repoId );
775                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
776                                                                        StringUtils.isEmpty( type ) ? "jar" : type,
777                                                                        repoId );
778                 File file = managedRepositoryContent.toFile( archivaArtifact );
779                 if ( !file.exists() )
780                 {
781                     log.debug( "file: {} not exists for repository: {} try next repository", file, repoId );
782                     continue;
783                 }
784                 if ( StringUtils.isNotBlank( path ) )
785                 {
786                     // zip entry of the path -> path must a real file entry of the archive
787                     JarFile jarFile = new JarFile( file );
788                     ZipEntry zipEntry = jarFile.getEntry( path );
789                     InputStream inputStream = jarFile.getInputStream( zipEntry );
790                     try
791                     {
792                         return new ArtifactContent( IOUtils.toString( inputStream ), repoId );
793                     }
794                     finally
795                     {
796                         closeQuietly( jarFile );
797                         IOUtils.closeQuietly( inputStream );
798                     }
799                 }
800                 return new ArtifactContent( FileUtils.readFileToString( file ), repoId );
801             }
802         }
803         catch ( IOException e )
804         {
805             log.error( e.getMessage(), e );
806             throw new ArchivaRestServiceException( e.getMessage(),
807                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
808         }
809         catch ( RepositoryNotFoundException e )
810         {
811             log.error( e.getMessage(), e );
812             throw new ArchivaRestServiceException( e.getMessage(),
813                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
814         }
815         catch ( RepositoryException e )
816         {
817             log.error( e.getMessage(), e );
818             throw new ArchivaRestServiceException( e.getMessage(),
819                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
820         }
821         log.debug( "artifact: {}:{}:{}:{}:{} not found", groupId, artifactId, version, classifier, type );
822         // 404 ?
823         return new ArtifactContent();
824     }
825
826     @Override
827     public Boolean artifactAvailable( String groupId, String artifactId, String version, String classifier,
828                                       String repositoryId )
829         throws ArchivaRestServiceException
830     {
831         List<String> selectedRepos = getSelectedRepos( repositoryId );
832
833         boolean snapshot = VersionUtil.isSnapshot( version );
834
835         try
836         {
837             for ( String repoId : selectedRepos )
838             {
839
840                 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repoId );
841
842                 if ( ( snapshot && !managedRepository.isSnapshots() ) || ( !snapshot
843                     && managedRepository.isSnapshots() ) )
844                 {
845                     continue;
846                 }
847                 ManagedRepositoryContent managedRepositoryContent =
848                     repositoryContentFactory.getManagedRepositoryContent( repoId );
849                 // FIXME default to jar which can be wrong for war zip etc....
850                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version,
851                                                                        StringUtils.isEmpty( classifier )
852                                                                            ? ""
853                                                                            : classifier, "jar", repoId
854                 );
855                 File file = managedRepositoryContent.toFile( archivaArtifact );
856
857                 if ( file != null && file.exists() )
858                 {
859                     return true;
860                 }
861
862                 // in case of SNAPSHOT we can have timestamped version locally !
863                 if ( StringUtils.endsWith( version, VersionUtil.SNAPSHOT ) )
864                 {
865                     File metadataFile = new File( file.getParent(), MetadataTools.MAVEN_METADATA );
866                     if ( metadataFile.exists() )
867                     {
868                         try
869                         {
870                             ArchivaRepositoryMetadata archivaRepositoryMetadata =
871                                 MavenMetadataReader.read( metadataFile );
872                             int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
873                             String timeStamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
874                             // rebuild file name with timestamped version and build number
875                             String timeStampFileName = new StringBuilder( artifactId ).append( '-' ) //
876                                 .append( StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) ) //
877                                 .append( '-' ).append( timeStamp ) //
878                                 .append( '-' ).append( Integer.toString( buildNumber ) ) //
879                                 .append( ( StringUtils.isEmpty( classifier ) ? "" : "-" + classifier ) ) //
880                                 .append( ".jar" ).toString();
881
882                             File timeStampFile = new File( file.getParent(), timeStampFileName );
883                             log.debug( "try to find timestamped snapshot version file: {}", timeStampFile.getPath() );
884                             if ( timeStampFile.exists() )
885                             {
886                                 return true;
887                             }
888                         }
889                         catch ( XMLException e )
890                         {
891                             log.warn( "skip fail to find timestamped snapshot file: {}", e.getMessage() );
892                         }
893                     }
894                 }
895
896                 String path = managedRepositoryContent.toPath( archivaArtifact );
897
898                 file = connectors.fetchFromProxies( managedRepositoryContent, path );
899
900                 if ( file != null && file.exists() )
901                 {
902                     // download pom now
903                     String pomPath = StringUtils.substringBeforeLast( path, ".jar" ) + ".pom";
904                     connectors.fetchFromProxies( managedRepositoryContent, pomPath );
905                     return true;
906                 }
907             }
908         }
909         catch ( RepositoryAdminException e )
910         {
911             log.error( e.getMessage(), e );
912             throw new ArchivaRestServiceException( e.getMessage(),
913                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
914         }
915         catch ( RepositoryException e )
916         {
917             log.error( e.getMessage(), e );
918             throw new ArchivaRestServiceException( e.getMessage(),
919                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
920         }
921
922         return false;
923     }
924
925     @Override
926     public Boolean artifactAvailable( String groupId, String artifactId, String version, String repositoryId )
927         throws ArchivaRestServiceException
928     {
929         return artifactAvailable( groupId, artifactId, version, null, repositoryId );
930     }
931
932     @Override
933     public List<Artifact> getArtifacts( String repositoryId )
934         throws ArchivaRestServiceException
935     {
936         RepositorySession repositorySession = repositorySessionFactory.createSession();
937         try
938         {
939             List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifacts( repositoryId );
940             return buildArtifacts( artifactMetadatas, repositoryId );
941         }
942         catch ( MetadataRepositoryException e )
943         {
944             throw new ArchivaRestServiceException( e.getMessage(), e );
945         }
946         finally
947         {
948             repositorySession.close();
949         }
950     }
951
952     @Override
953     public Boolean importMetadata( MetadataAddRequest metadataAddRequest, String repositoryId )
954         throws ArchivaRestServiceException
955     {
956         boolean result = true;
957         for ( Map.Entry<String, String> metadata : metadataAddRequest.getMetadatas().entrySet() )
958         {
959             result = addMetadata( metadataAddRequest.getGroupId(), metadataAddRequest.getArtifactId(),
960                                   metadataAddRequest.getVersion(), metadata.getKey(), metadata.getValue(),
961                                   repositoryId );
962             if ( !result )
963             {
964                 break;
965             }
966         }
967         return result;
968     }
969
970     //---------------------------
971     // internals
972     //---------------------------
973
974     private void closeQuietly( JarFile jarFile )
975     {
976         if ( jarFile != null )
977         {
978             try
979             {
980                 jarFile.close();
981             }
982             catch ( IOException e )
983             {
984                 log.warn( "ignore error closing jarFile {}", jarFile.getName() );
985             }
986         }
987     }
988
989     protected List<ArtifactContentEntry> readFileEntries( File file, String filterPath, String repoId )
990         throws IOException
991     {
992         Map<String, ArtifactContentEntry> artifactContentEntryMap = new HashMap<>();
993         int filterDepth = StringUtils.countMatches( filterPath, "/" );
994         /*if ( filterDepth == 0 )
995         {
996             filterDepth = 1;
997         }*/
998         JarFile jarFile = new JarFile( file );
999         try
1000         {
1001             Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
1002             while ( jarEntryEnumeration.hasMoreElements() )
1003             {
1004                 JarEntry currentEntry = jarEntryEnumeration.nextElement();
1005                 String cleanedEntryName = StringUtils.endsWith( currentEntry.getName(), "/" ) ? //
1006                     StringUtils.substringBeforeLast( currentEntry.getName(), "/" ) : currentEntry.getName();
1007                 String entryRootPath = getRootPath( cleanedEntryName );
1008                 int depth = StringUtils.countMatches( cleanedEntryName, "/" );
1009                 if ( StringUtils.isEmpty( filterPath ) //
1010                     && !artifactContentEntryMap.containsKey( entryRootPath ) //
1011                     && depth == filterDepth )
1012                 {
1013
1014                     artifactContentEntryMap.put( entryRootPath,
1015                                                  new ArtifactContentEntry( entryRootPath, !currentEntry.isDirectory(),
1016                                                                            depth, repoId )
1017                     );
1018                 }
1019                 else
1020                 {
1021                     if ( StringUtils.startsWith( cleanedEntryName, filterPath ) //
1022                         && ( depth == filterDepth || ( !currentEntry.isDirectory() && depth == filterDepth ) ) )
1023                     {
1024                         artifactContentEntryMap.put( cleanedEntryName, new ArtifactContentEntry( cleanedEntryName,
1025                                                                                                  !currentEntry.isDirectory(),
1026                                                                                                  depth, repoId ) );
1027                     }
1028                 }
1029             }
1030
1031             if ( StringUtils.isNotEmpty( filterPath ) )
1032             {
1033                 Map<String, ArtifactContentEntry> filteredArtifactContentEntryMap = new HashMap<>();
1034
1035                 for ( Map.Entry<String, ArtifactContentEntry> entry : artifactContentEntryMap.entrySet() )
1036                 {
1037                     filteredArtifactContentEntryMap.put( entry.getKey(), entry.getValue() );
1038                 }
1039
1040                 List<ArtifactContentEntry> sorted = getSmallerDepthEntries( filteredArtifactContentEntryMap );
1041                 if ( sorted == null )
1042                 {
1043                     return Collections.emptyList();
1044                 }
1045                 Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
1046                 return sorted;
1047             }
1048         }
1049         finally
1050         {
1051             if ( jarFile != null )
1052             {
1053                 jarFile.close();
1054             }
1055         }
1056         List<ArtifactContentEntry> sorted = new ArrayList<>( artifactContentEntryMap.values() );
1057         Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
1058         return sorted;
1059     }
1060
1061     private List<ArtifactContentEntry> getSmallerDepthEntries( Map<String, ArtifactContentEntry> entries )
1062     {
1063         int smallestDepth = Integer.MAX_VALUE;
1064         Map<Integer, List<ArtifactContentEntry>> perDepthList = new HashMap<>();
1065         for ( Map.Entry<String, ArtifactContentEntry> entry : entries.entrySet() )
1066         {
1067
1068             ArtifactContentEntry current = entry.getValue();
1069
1070             if ( current.getDepth() < smallestDepth )
1071             {
1072                 smallestDepth = current.getDepth();
1073             }
1074
1075             List<ArtifactContentEntry> currentList = perDepthList.get( current.getDepth() );
1076
1077             if ( currentList == null )
1078             {
1079                 currentList = new ArrayList<>();
1080                 currentList.add( current );
1081                 perDepthList.put( current.getDepth(), currentList );
1082             }
1083             else
1084             {
1085                 currentList.add( current );
1086             }
1087
1088         }
1089
1090         return perDepthList.get( smallestDepth );
1091     }
1092
1093     /**
1094      * @param path
1095      * @return org/apache -&gt; org , org -&gt; org
1096      */
1097     private String getRootPath( String path )
1098     {
1099         if ( StringUtils.contains( path, '/' ) )
1100         {
1101             return StringUtils.substringBefore( path, "/" );
1102         }
1103         return path;
1104     }
1105
1106     private List<String> getSelectedRepos( String repositoryId )
1107         throws ArchivaRestServiceException
1108     {
1109
1110         List<String> selectedRepos = getObservableRepos();
1111
1112         if ( CollectionUtils.isEmpty( selectedRepos ) )
1113         {
1114             return Collections.emptyList();
1115         }
1116
1117         if ( StringUtils.isNotEmpty( repositoryId ) )
1118         {
1119             // check user has karma on the repository
1120             if ( !selectedRepos.contains( repositoryId ) )
1121             {
1122                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
1123                                                        Response.Status.FORBIDDEN.getStatusCode(), null );
1124             }
1125             selectedRepos = Collections.singletonList( repositoryId );
1126         }
1127         return selectedRepos;
1128     }
1129
1130
1131     private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
1132                                        Collection<String> repoIds, String n )
1133         throws MetadataResolutionException
1134     {
1135         Set<String> subNamespaces = new LinkedHashSet<String>();
1136         for ( String repoId : repoIds )
1137         {
1138             subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
1139         }
1140         if ( subNamespaces.size() != 1 )
1141         {
1142             log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
1143             return n;
1144         }
1145         else
1146         {
1147             for ( String repoId : repoIds )
1148             {
1149                 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
1150                 if ( projects != null && !projects.isEmpty() )
1151                 {
1152                     log.debug( "{} is not collapsible as it has projects", n );
1153                     return n;
1154                 }
1155             }
1156             return collapseNamespaces( repositorySession, metadataResolver, repoIds,
1157                                        n + "." + subNamespaces.iterator().next() );
1158         }
1159     }
1160
1161     public Cache getVersionMetadataCache()
1162     {
1163         return versionMetadataCache;
1164     }
1165
1166     public void setVersionMetadataCache( Cache versionMetadataCache )
1167     {
1168         this.versionMetadataCache = versionMetadataCache;
1169     }
1170 }