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