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