]> source.dussan.org Git - archiva.git/blob
8b0b1198309e9f2e3d47b89aa2a1d410516bd78b
[archiva.git] /
1 package org.apache.archiva.rest.services;
2 /*
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  */
20
21 import org.apache.archiva.admin.model.beans.ManagedRepository;
22 import org.apache.archiva.common.utils.VersionComparator;
23 import org.apache.archiva.dependency.tree.maven2.DependencyTreeBuilder;
24 import org.apache.archiva.metadata.generic.GenericMetadataFacet;
25 import org.apache.archiva.metadata.model.MetadataFacet;
26 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
27 import org.apache.archiva.metadata.model.ProjectVersionReference;
28 import org.apache.archiva.metadata.repository.MetadataRepository;
29 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
30 import org.apache.archiva.metadata.repository.MetadataResolutionException;
31 import org.apache.archiva.metadata.repository.MetadataResolver;
32 import org.apache.archiva.metadata.repository.RepositorySession;
33 import org.apache.archiva.metadata.repository.storage.maven2.MavenProjectFacet;
34 import org.apache.archiva.rest.api.model.Artifact;
35 import org.apache.archiva.rest.api.model.ArtifactContentEntry;
36 import org.apache.archiva.rest.api.model.BrowseResult;
37 import org.apache.archiva.rest.api.model.BrowseResultEntry;
38 import org.apache.archiva.rest.api.model.Entry;
39 import org.apache.archiva.rest.api.model.TreeEntry;
40 import org.apache.archiva.rest.api.model.VersionsList;
41 import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
42 import org.apache.archiva.rest.api.services.BrowseService;
43 import org.apache.archiva.rest.services.utils.TreeDependencyNodeVisitor;
44 import org.apache.archiva.security.ArchivaSecurityException;
45 import org.apache.commons.collections.CollectionUtils;
46 import org.apache.commons.lang.StringUtils;
47 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
48 import org.springframework.stereotype.Service;
49
50 import javax.inject.Inject;
51 import javax.ws.rs.core.Response;
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.HashMap;
56 import java.util.LinkedHashSet;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Set;
60
61 /**
62  * @author Olivier Lamy
63  * @since 1.4-M3
64  */
65 @Service( "browseService#rest" )
66 public class DefaultBrowseService
67     extends AbstractRestService
68     implements BrowseService
69 {
70
71     @Inject
72     private DependencyTreeBuilder dependencyTreeBuilder;
73
74     public BrowseResult getRootGroups( String repositoryId )
75         throws ArchivaRestServiceException
76     {
77         List<String> selectedRepos = getSelectedRepos( repositoryId );
78
79         Set<String> namespaces = new LinkedHashSet<String>();
80
81         // TODO: this logic should be optional, particularly remembering we want to keep this code simple
82         //       it is located here to avoid the content repository implementation needing to do too much for what
83         //       is essentially presentation code
84         Set<String> namespacesToCollapse;
85         RepositorySession repositorySession = repositorySessionFactory.createSession();
86         try
87         {
88             MetadataResolver metadataResolver = repositorySession.getResolver();
89             namespacesToCollapse = new LinkedHashSet<String>();
90
91             for ( String repoId : selectedRepos )
92             {
93                 namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
94             }
95             for ( String n : namespacesToCollapse )
96             {
97                 // TODO: check performance of this
98                 namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
99             }
100         }
101         catch ( MetadataResolutionException e )
102         {
103             throw new ArchivaRestServiceException( e.getMessage(),
104                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
105         }
106         finally
107         {
108             repositorySession.close();
109         }
110
111         List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<BrowseResultEntry>( namespaces.size() );
112         for ( String namespace : namespaces )
113         {
114             browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
115         }
116
117         Collections.sort( browseGroupResultEntries );
118         return new BrowseResult( browseGroupResultEntries );
119     }
120
121     public BrowseResult browseGroupId( String groupId, String repositoryId )
122         throws ArchivaRestServiceException
123     {
124         List<String> selectedRepos = getSelectedRepos( repositoryId );
125
126         Set<String> projects = new LinkedHashSet<String>();
127
128         RepositorySession repositorySession = repositorySessionFactory.createSession();
129         Set<String> namespaces;
130         try
131         {
132             MetadataResolver metadataResolver = repositorySession.getResolver();
133
134             Set<String> namespacesToCollapse = new LinkedHashSet<String>();
135             for ( String repoId : selectedRepos )
136             {
137                 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
138
139                 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
140             }
141
142             // TODO: this logic should be optional, particularly remembering we want to keep this code simple
143             // it is located here to avoid the content repository implementation needing to do too much for what
144             // is essentially presentation code
145             namespaces = new LinkedHashSet<String>();
146             for ( String n : namespacesToCollapse )
147             {
148                 // TODO: check performance of this
149                 namespaces.add(
150                     collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
151             }
152         }
153         catch ( MetadataResolutionException e )
154         {
155             throw new ArchivaRestServiceException( e.getMessage(),
156                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
157         }
158         finally
159         {
160             repositorySession.close();
161         }
162         List<BrowseResultEntry> browseGroupResultEntries =
163             new ArrayList<BrowseResultEntry>( namespaces.size() + projects.size() );
164         for ( String namespace : namespaces )
165         {
166             browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
167         }
168         for ( String project : projects )
169         {
170             browseGroupResultEntries.add( new BrowseResultEntry( groupId + '.' + project, true ) );
171         }
172         Collections.sort( browseGroupResultEntries );
173         return new BrowseResult( browseGroupResultEntries );
174
175     }
176
177     public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
178         throws ArchivaRestServiceException
179     {
180         List<String> selectedRepos = getSelectedRepos( repositoryId );
181
182         try
183         {
184             return new VersionsList( new ArrayList<String>( getVersions( selectedRepos, groupId, artifactId ) ) );
185         }
186         catch ( MetadataResolutionException e )
187         {
188             throw new ArchivaRestServiceException( e.getMessage(),
189                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
190         }
191
192     }
193
194     private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
195         throws MetadataResolutionException
196
197     {
198         RepositorySession repositorySession = repositorySessionFactory.createSession();
199         try
200         {
201             MetadataResolver metadataResolver = repositorySession.getResolver();
202
203             Set<String> versions = new LinkedHashSet<String>();
204
205             for ( String repoId : selectedRepos )
206             {
207                 versions.addAll(
208                     metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId ) );
209             }
210
211             List<String> sortedVersions = new ArrayList<String>( versions );
212
213             Collections.sort( sortedVersions, VersionComparator.getInstance() );
214
215             return sortedVersions;
216         }
217         finally
218         {
219             repositorySession.close();
220         }
221     }
222
223     public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
224                                                       String repositoryId )
225         throws ArchivaRestServiceException
226     {
227         List<String> selectedRepos = getSelectedRepos( repositoryId );
228
229         RepositorySession repositorySession = null;
230         try
231         {
232             repositorySession = repositorySessionFactory.createSession();
233
234             MetadataResolver metadataResolver = repositorySession.getResolver();
235
236             ProjectVersionMetadata versionMetadata = null;
237             for ( String repoId : selectedRepos )
238             {
239                 if ( versionMetadata == null || versionMetadata.isIncomplete() )
240                 {
241                     try
242                     {
243                         versionMetadata =
244                             metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
245                                                                     version );
246                     }
247                     catch ( MetadataResolutionException e )
248                     {
249                         log.error(
250                             "Skipping invalid metadata while compiling shared model for " + groupId + ":" + artifactId
251                                 + " in repo " + repoId + ": " + e.getMessage() );
252                     }
253                 }
254             }
255
256             return versionMetadata;
257         }
258         finally
259         {
260             if ( repositorySession != null )
261             {
262                 repositorySession.close();
263             }
264         }
265
266     }
267
268     public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
269         throws ArchivaRestServiceException
270     {
271
272         List<String> selectedRepos = getSelectedRepos( repositoryId );
273
274         RepositorySession repositorySession = null;
275         try
276         {
277
278             Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
279
280             repositorySession = repositorySessionFactory.createSession();
281
282             MetadataResolver metadataResolver = repositorySession.getResolver();
283
284             ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
285
286             MavenProjectFacet mavenFacet = new MavenProjectFacet();
287             mavenFacet.setGroupId( groupId );
288             mavenFacet.setArtifactId( artifactId );
289             sharedModel.addFacet( mavenFacet );
290
291             boolean isFirstVersion = true;
292
293             for ( String version : projectVersions )
294             {
295                 ProjectVersionMetadata versionMetadata = null;
296                 for ( String repoId : selectedRepos )
297                 {
298                     if ( versionMetadata == null || versionMetadata.isIncomplete() )
299                     {
300                         try
301                         {
302                             versionMetadata =
303                                 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
304                                                                         version );
305                         }
306                         catch ( MetadataResolutionException e )
307                         {
308                             log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
309                                            + artifactId + " in repo " + repoId + ": " + e.getMessage() );
310                         }
311                     }
312                 }
313
314                 if ( versionMetadata == null )
315                 {
316                     continue;
317                 }
318
319                 if ( isFirstVersion )
320                 {
321                     sharedModel = versionMetadata;
322                     sharedModel.setId( null );
323                 }
324                 else
325                 {
326                     MavenProjectFacet versionMetadataMavenFacet =
327                         (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
328                     if ( versionMetadataMavenFacet != null )
329                     {
330                         if ( mavenFacet.getPackaging() != null && !StringUtils.equalsIgnoreCase(
331                             mavenFacet.getPackaging(), versionMetadataMavenFacet.getPackaging() ) )
332                         {
333                             mavenFacet.setPackaging( null );
334                         }
335                     }
336
337                     if ( StringUtils.isEmpty( sharedModel.getName() ) && !StringUtils.isEmpty(
338                         versionMetadata.getName() ) )
339                     {
340                         sharedModel.setName( versionMetadata.getName() );
341                     }
342
343                     if ( sharedModel.getDescription() != null && !StringUtils.equalsIgnoreCase(
344                         sharedModel.getDescription(), versionMetadata.getDescription() ) )
345                     {
346                         sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
347                                                         ? versionMetadata.getDescription()
348                                                         : "" );
349                     }
350
351                     if ( sharedModel.getIssueManagement() != null && versionMetadata.getIssueManagement() != null
352                         && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
353                                                           versionMetadata.getIssueManagement().getUrl() ) )
354                     {
355                         sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
356                     }
357
358                     if ( sharedModel.getCiManagement() != null && versionMetadata.getCiManagement() != null
359                         && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
360                                                           versionMetadata.getCiManagement().getUrl() ) )
361                     {
362                         sharedModel.setCiManagement( versionMetadata.getCiManagement() );
363                     }
364
365                     if ( sharedModel.getOrganization() != null && versionMetadata.getOrganization() != null
366                         && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
367                                                           versionMetadata.getOrganization().getName() ) )
368                     {
369                         sharedModel.setOrganization( versionMetadata.getOrganization() );
370                     }
371
372                     if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(),
373                                                                                         versionMetadata.getUrl() ) )
374                     {
375                         sharedModel.setUrl( versionMetadata.getUrl() );
376                     }
377                 }
378
379                 isFirstVersion = false;
380             }
381             return sharedModel;
382         }
383         catch ( MetadataResolutionException e )
384         {
385             throw new ArchivaRestServiceException( e.getMessage(),
386                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
387         }
388         finally
389         {
390             if ( repositorySession != null )
391             {
392                 repositorySession.close();
393             }
394         }
395     }
396
397     public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
398         throws ArchivaRestServiceException
399     {
400         List<String> selectedRepos = getSelectedRepos( repositoryId );
401
402         List<TreeEntry> treeEntries = new ArrayList<TreeEntry>();
403         TreeDependencyNodeVisitor treeDependencyNodeVisitor = new TreeDependencyNodeVisitor( treeEntries );
404         try
405         {
406             dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version,
407                                                        treeDependencyNodeVisitor );
408         }
409         catch ( DependencyTreeBuilderException e )
410         {
411             throw new ArchivaRestServiceException( e.getMessage(),
412                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
413         }
414         return treeEntries;
415     }
416
417     public List<ManagedRepository> getUserRepositories()
418         throws ArchivaRestServiceException
419     {
420         try
421         {
422             return userRepositories.getAccessibleRepositories( getPrincipal() );
423         }
424         catch ( ArchivaSecurityException e )
425         {
426             throw new ArchivaRestServiceException( "repositories.read.observable.error",
427                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
428         }
429     }
430
431     public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
432         throws ArchivaRestServiceException
433     {
434         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
435         // TODO: what if we get duplicates across repositories?
436         RepositorySession repositorySession = repositorySessionFactory.createSession();
437         try
438         {
439             MetadataResolver metadataResolver = repositorySession.getResolver();
440             for ( String repoId : getObservableRepos() )
441             {
442                 // TODO: what about if we want to see this irrespective of version?
443                 references.addAll(
444                     metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
445                                                                version ) );
446             }
447         }
448         catch ( MetadataResolutionException e )
449         {
450             throw new ArchivaRestServiceException( e.getMessage(),
451                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
452         }
453         finally
454         {
455             repositorySession.close();
456         }
457
458         List<Artifact> artifacts = new ArrayList<Artifact>( references.size() );
459
460         for ( ProjectVersionReference projectVersionReference : references )
461         {
462             artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
463                                          projectVersionReference.getProjectVersion() ) );
464         }
465         return artifacts;
466     }
467
468     public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
469         throws ArchivaRestServiceException
470     {
471         ProjectVersionMetadata projectVersionMetadata =
472             getProjectMetadata( groupId, artifactId, version, repositoryId );
473         if ( projectVersionMetadata == null )
474         {
475             return Collections.emptyList();
476         }
477         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
478
479         if ( metadataFacet == null )
480         {
481             return Collections.emptyList();
482         }
483         Map<String, String> map = metadataFacet.toProperties();
484         List<Entry> entries = new ArrayList<Entry>( map.size() );
485
486         for ( Map.Entry<String, String> entry : map.entrySet() )
487         {
488             entries.add( new Entry( entry.getKey(), entry.getValue() ) );
489         }
490
491         return entries;
492     }
493
494     public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
495                                 String repositoryId )
496         throws ArchivaRestServiceException
497     {
498         ProjectVersionMetadata projectVersionMetadata =
499             getProjectMetadata( groupId, artifactId, version, repositoryId );
500
501         if ( projectVersionMetadata == null )
502         {
503             return Boolean.FALSE;
504         }
505
506         Map<String, String> properties = new HashMap<String, String>();
507
508         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
509
510         if ( metadataFacet != null && metadataFacet.toProperties() != null )
511         {
512             properties.putAll( metadataFacet.toProperties() );
513         }
514         else
515         {
516             metadataFacet = new GenericMetadataFacet();
517         }
518
519         properties.put( key, value );
520
521         metadataFacet.fromProperties( properties );
522
523         projectVersionMetadata.addFacet( metadataFacet );
524
525         RepositorySession repositorySession = repositorySessionFactory.createSession();
526
527         try
528         {
529             MetadataRepository metadataRepository = repositorySession.getRepository();
530
531             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
532
533             repositorySession.save();
534         }
535         catch ( MetadataRepositoryException e )
536         {
537             log.error( e.getMessage(), e );
538             throw new ArchivaRestServiceException( e.getMessage(),
539                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
540         }
541         finally
542         {
543             repositorySession.close();
544         }
545         return Boolean.TRUE;
546     }
547
548     public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
549         throws ArchivaRestServiceException
550     {
551         ProjectVersionMetadata projectVersionMetadata =
552             getProjectMetadata( groupId, artifactId, version, repositoryId );
553
554         if ( projectVersionMetadata == null )
555         {
556             return Boolean.FALSE;
557         }
558
559         GenericMetadataFacet metadataFacet =
560             (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
561
562         if ( metadataFacet != null && metadataFacet.toProperties() != null )
563         {
564             Map<String, String> properties = metadataFacet.toProperties();
565             properties.remove( key );
566             metadataFacet.setAdditionalProperties( properties );
567         }
568         else
569         {
570             return Boolean.TRUE;
571         }
572
573         RepositorySession repositorySession = repositorySessionFactory.createSession();
574
575         try
576         {
577             MetadataRepository metadataRepository = repositorySession.getRepository();
578
579             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
580
581             repositorySession.save();
582         }
583         catch ( MetadataRepositoryException e )
584         {
585             log.error( e.getMessage(), e );
586             throw new ArchivaRestServiceException( e.getMessage(),
587                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
588         }
589         finally
590         {
591             repositorySession.close();
592         }
593         return Boolean.TRUE;
594     }
595
596     public List<ArtifactContentEntry> getArtifactContentEntries( String groupId, String artifactId, String version,
597                                                                  String path, String repositoryId )
598         throws ArchivaRestServiceException
599     {
600         return null;
601     }
602
603     //---------------------------
604     // internals
605     //---------------------------
606
607     private List<String> getSelectedRepos( String repositoryId )
608         throws ArchivaRestServiceException
609     {
610
611         List<String> selectedRepos = getObservableRepos();
612
613         if ( CollectionUtils.isEmpty( selectedRepos ) )
614         {
615             // FIXME 403 ???
616             return null;
617         }
618
619         if ( StringUtils.isNotEmpty( repositoryId ) )
620         {
621             // check user has karma on the repository
622             if ( !selectedRepos.contains( repositoryId ) )
623             {
624                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
625                                                        Response.Status.FORBIDDEN.getStatusCode() );
626             }
627             selectedRepos = Collections.singletonList( repositoryId );
628         }
629         return selectedRepos;
630     }
631
632     private List<String> getSortedList( Set<String> set )
633     {
634         List<String> list = new ArrayList<String>( set );
635         Collections.sort( list );
636         return list;
637     }
638
639     private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
640                                        Collection<String> repoIds, String n )
641         throws MetadataResolutionException
642     {
643         Set<String> subNamespaces = new LinkedHashSet<String>();
644         for ( String repoId : repoIds )
645         {
646             subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
647         }
648         if ( subNamespaces.size() != 1 )
649         {
650             log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
651             return n;
652         }
653         else
654         {
655             for ( String repoId : repoIds )
656             {
657                 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
658                 if ( projects != null && !projects.isEmpty() )
659                 {
660                     log.debug( "{} is not collapsible as it has projects", n );
661                     return n;
662                 }
663             }
664             return collapseNamespaces( repositorySession, metadataResolver, repoIds,
665                                        n + "." + subNamespaces.iterator().next() );
666         }
667     }
668 }