]> source.dussan.org Git - archiva.git/blob
59a922b98b56ae7815200f8f0d4b012c44abcf10
[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 net.sf.beanlib.provider.replicator.BeanReplicator;
22 import org.apache.archiva.admin.model.beans.ManagedRepository;
23 import org.apache.archiva.common.utils.VersionComparator;
24 import org.apache.archiva.dependency.tree.maven2.DependencyTreeBuilder;
25 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
26 import org.apache.archiva.metadata.repository.MetadataResolutionException;
27 import org.apache.archiva.metadata.repository.MetadataResolver;
28 import org.apache.archiva.metadata.repository.RepositorySession;
29 import org.apache.archiva.metadata.repository.storage.maven2.MavenProjectFacet;
30 import org.apache.archiva.rest.api.model.Artifact;
31 import org.apache.archiva.rest.api.model.BrowseResult;
32 import org.apache.archiva.rest.api.model.BrowseResultEntry;
33 import org.apache.archiva.rest.api.model.TreeEntry;
34 import org.apache.archiva.rest.api.model.VersionsList;
35 import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
36 import org.apache.archiva.rest.api.services.BrowseService;
37 import org.apache.archiva.security.ArchivaSecurityException;
38 import org.apache.commons.collections.CollectionUtils;
39 import org.apache.commons.lang.StringUtils;
40 import org.apache.maven.shared.dependency.tree.DependencyNode;
41 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
42 import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
43 import org.springframework.stereotype.Service;
44
45 import javax.inject.Inject;
46 import javax.ws.rs.core.Response;
47 import java.util.ArrayList;
48 import java.util.Collection;
49 import java.util.Collections;
50 import java.util.LinkedHashSet;
51 import java.util.List;
52 import java.util.Set;
53
54 /**
55  * @author Olivier Lamy
56  * @since 1.4-M3
57  */
58 @Service( "browseService#rest" )
59 public class DefaultBrowseService
60     extends AbstractRestService
61     implements BrowseService
62 {
63
64     @Inject
65     private DependencyTreeBuilder dependencyTreeBuilder;
66
67     public BrowseResult getRootGroups( String repositoryId )
68         throws ArchivaRestServiceException
69     {
70         List<String> selectedRepos = getObservableRepos();
71         if ( CollectionUtils.isEmpty( selectedRepos ) )
72         {
73             // FIXME 403 ???
74             return new BrowseResult();
75         }
76
77         if ( StringUtils.isNotEmpty( repositoryId ) )
78         {
79             // check user has karma on the repository
80             if ( !selectedRepos.contains( repositoryId ) )
81             {
82                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
83                                                        Response.Status.FORBIDDEN.getStatusCode() );
84             }
85             selectedRepos = Collections.singletonList( repositoryId );
86         }
87
88         Set<String> namespaces = new LinkedHashSet<String>();
89
90         // TODO: this logic should be optional, particularly remembering we want to keep this code simple
91         //       it is located here to avoid the content repository implementation needing to do too much for what
92         //       is essentially presentation code
93         Set<String> namespacesToCollapse;
94         RepositorySession repositorySession = repositorySessionFactory.createSession();
95         try
96         {
97             MetadataResolver metadataResolver = repositorySession.getResolver();
98             namespacesToCollapse = new LinkedHashSet<String>();
99
100             for ( String repoId : selectedRepos )
101             {
102                 namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
103             }
104             for ( String n : namespacesToCollapse )
105             {
106                 // TODO: check performance of this
107                 namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
108             }
109         }
110         catch ( MetadataResolutionException e )
111         {
112             throw new ArchivaRestServiceException( e.getMessage(),
113                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
114         }
115         finally
116         {
117             repositorySession.close();
118         }
119
120         List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<BrowseResultEntry>( namespaces.size() );
121         for ( String namespace : namespaces )
122         {
123             browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
124         }
125
126         Collections.sort( browseGroupResultEntries );
127         return new BrowseResult( browseGroupResultEntries );
128     }
129
130     public BrowseResult browseGroupId( String groupId, String repositoryId )
131         throws ArchivaRestServiceException
132     {
133
134         List<String> selectedRepos = getObservableRepos();
135         if ( CollectionUtils.isEmpty( selectedRepos ) )
136         {
137             // FIXME 403 ???
138             return new BrowseResult();
139         }
140
141         if ( StringUtils.isNotEmpty( repositoryId ) )
142         {
143             // check user has karma on the repository
144             if ( !selectedRepos.contains( repositoryId ) )
145             {
146                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
147                                                        Response.Status.FORBIDDEN.getStatusCode() );
148             }
149             selectedRepos = Collections.singletonList( repositoryId );
150         }
151
152         Set<String> projects = new LinkedHashSet<String>();
153
154         RepositorySession repositorySession = repositorySessionFactory.createSession();
155         Set<String> namespaces;
156         try
157         {
158             MetadataResolver metadataResolver = repositorySession.getResolver();
159
160             Set<String> namespacesToCollapse = new LinkedHashSet<String>();
161             for ( String repoId : selectedRepos )
162             {
163                 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
164
165                 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
166             }
167
168             // TODO: this logic should be optional, particularly remembering we want to keep this code simple
169             // it is located here to avoid the content repository implementation needing to do too much for what
170             // is essentially presentation code
171             namespaces = new LinkedHashSet<String>();
172             for ( String n : namespacesToCollapse )
173             {
174                 // TODO: check performance of this
175                 namespaces.add(
176                     collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
177             }
178         }
179         catch ( MetadataResolutionException e )
180         {
181             throw new ArchivaRestServiceException( e.getMessage(),
182                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
183         }
184         finally
185         {
186             repositorySession.close();
187         }
188         List<BrowseResultEntry> browseGroupResultEntries =
189             new ArrayList<BrowseResultEntry>( namespaces.size() + projects.size() );
190         for ( String namespace : namespaces )
191         {
192             browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
193         }
194         for ( String project : projects )
195         {
196             browseGroupResultEntries.add( new BrowseResultEntry( groupId + '.' + project, true ) );
197         }
198         Collections.sort( browseGroupResultEntries );
199         return new BrowseResult( browseGroupResultEntries );
200
201     }
202
203     public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
204         throws ArchivaRestServiceException
205     {
206         List<String> selectedRepos = getObservableRepos();
207         if ( CollectionUtils.isEmpty( selectedRepos ) )
208         {
209             // FIXME 403 ???
210             return new VersionsList();
211         }
212
213         if ( StringUtils.isNotEmpty( repositoryId ) )
214         {
215             // check user has karma on the repository
216             if ( !selectedRepos.contains( repositoryId ) )
217             {
218                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
219                                                        Response.Status.FORBIDDEN.getStatusCode() );
220             }
221             selectedRepos = Collections.singletonList( repositoryId );
222         }
223
224         try
225         {
226             return new VersionsList( new ArrayList<String>( getVersions( selectedRepos, groupId, artifactId ) ) );
227         }
228         catch ( MetadataResolutionException e )
229         {
230             throw new ArchivaRestServiceException( e.getMessage(),
231                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
232         }
233
234     }
235
236     private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
237         throws MetadataResolutionException
238
239     {
240         RepositorySession repositorySession = repositorySessionFactory.createSession();
241         try
242         {
243             MetadataResolver metadataResolver = repositorySession.getResolver();
244
245             Set<String> versions = new LinkedHashSet<String>();
246
247             for ( String repoId : selectedRepos )
248             {
249                 versions.addAll(
250                     metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId ) );
251             }
252
253             List<String> sortedVersions = new ArrayList<String>( versions );
254
255             Collections.sort( sortedVersions, VersionComparator.getInstance() );
256
257             return sortedVersions;
258         }
259         finally
260         {
261             repositorySession.close();
262         }
263     }
264
265     public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
266                                                       String repositoryId )
267         throws ArchivaRestServiceException
268     {
269         List<String> selectedRepos = getObservableRepos();
270
271         if ( CollectionUtils.isEmpty( selectedRepos ) )
272         {
273             // FIXME 403 ???
274             return null;
275         }
276
277         if ( StringUtils.isNotEmpty( repositoryId ) )
278         {
279             // check user has karma on the repository
280             if ( !selectedRepos.contains( repositoryId ) )
281             {
282                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
283                                                        Response.Status.FORBIDDEN.getStatusCode() );
284             }
285             selectedRepos = Collections.singletonList( repositoryId );
286         }
287
288         RepositorySession repositorySession = null;
289         try
290         {
291             repositorySession = repositorySessionFactory.createSession();
292
293             MetadataResolver metadataResolver = repositorySession.getResolver();
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(
309                             "Skipping invalid metadata while compiling shared model for " + groupId + ":" + artifactId
310                                 + " in repo " + repoId + ": " + e.getMessage() );
311                     }
312                 }
313             }
314
315             return versionMetadata;
316         }
317         finally
318         {
319             if ( repositorySession != null )
320             {
321                 repositorySession.close();
322             }
323         }
324
325     }
326
327     public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
328         throws ArchivaRestServiceException
329     {
330
331         List<String> selectedRepos = getObservableRepos();
332
333         if ( CollectionUtils.isEmpty( selectedRepos ) )
334         {
335             // FIXME 403 ???
336             return null;
337         }
338
339         if ( StringUtils.isNotEmpty( repositoryId ) )
340         {
341             // check user has karma on the repository
342             if ( !selectedRepos.contains( repositoryId ) )
343             {
344                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
345                                                        Response.Status.FORBIDDEN.getStatusCode() );
346             }
347             selectedRepos = Collections.singletonList( repositoryId );
348         }
349
350         RepositorySession repositorySession = null;
351         try
352         {
353
354             Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
355
356             repositorySession = repositorySessionFactory.createSession();
357
358             MetadataResolver metadataResolver = repositorySession.getResolver();
359
360             ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
361
362             MavenProjectFacet mavenFacet = new MavenProjectFacet();
363             mavenFacet.setGroupId( groupId );
364             mavenFacet.setArtifactId( artifactId );
365             sharedModel.addFacet( mavenFacet );
366
367             boolean isFirstVersion = true;
368
369             for ( String version : projectVersions )
370             {
371                 ProjectVersionMetadata versionMetadata = null;
372                 for ( String repoId : selectedRepos )
373                 {
374                     if ( versionMetadata == null || versionMetadata.isIncomplete() )
375                     {
376                         try
377                         {
378                             versionMetadata =
379                                 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
380                                                                         version );
381                         }
382                         catch ( MetadataResolutionException e )
383                         {
384                             log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
385                                            + artifactId + " in repo " + repoId + ": " + e.getMessage() );
386                         }
387                     }
388                 }
389
390                 if ( versionMetadata == null )
391                 {
392                     continue;
393                 }
394
395                 if ( isFirstVersion )
396                 {
397                     sharedModel = versionMetadata;
398                     sharedModel.setId( null );
399                 }
400                 else
401                 {
402                     MavenProjectFacet versionMetadataMavenFacet =
403                         (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
404                     if ( versionMetadataMavenFacet != null )
405                     {
406                         if ( mavenFacet.getPackaging() != null && !StringUtils.equalsIgnoreCase(
407                             mavenFacet.getPackaging(), versionMetadataMavenFacet.getPackaging() ) )
408                         {
409                             mavenFacet.setPackaging( null );
410                         }
411                     }
412
413                     if ( StringUtils.isEmpty( sharedModel.getName() ) && !StringUtils.isEmpty(
414                         versionMetadata.getName() ) )
415                     {
416                         sharedModel.setName( versionMetadata.getName() );
417                     }
418
419                     if ( sharedModel.getDescription() != null && !StringUtils.equalsIgnoreCase(
420                         sharedModel.getDescription(), versionMetadata.getDescription() ) )
421                     {
422                         sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
423                                                         ? versionMetadata.getDescription()
424                                                         : "" );
425                     }
426
427                     if ( sharedModel.getIssueManagement() != null && versionMetadata.getIssueManagement() != null
428                         && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
429                                                           versionMetadata.getIssueManagement().getUrl() ) )
430                     {
431                         sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
432                     }
433
434                     if ( sharedModel.getCiManagement() != null && versionMetadata.getCiManagement() != null
435                         && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
436                                                           versionMetadata.getCiManagement().getUrl() ) )
437                     {
438                         sharedModel.setCiManagement( versionMetadata.getCiManagement() );
439                     }
440
441                     if ( sharedModel.getOrganization() != null && versionMetadata.getOrganization() != null
442                         && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
443                                                           versionMetadata.getOrganization().getName() ) )
444                     {
445                         sharedModel.setOrganization( versionMetadata.getOrganization() );
446                     }
447
448                     if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(),
449                                                                                         versionMetadata.getUrl() ) )
450                     {
451                         sharedModel.setUrl( versionMetadata.getUrl() );
452                     }
453                 }
454
455                 isFirstVersion = false;
456             }
457             return sharedModel;
458         }
459         catch ( MetadataResolutionException e )
460         {
461             throw new ArchivaRestServiceException( e.getMessage(),
462                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
463         }
464         finally
465         {
466             if ( repositorySession != null )
467             {
468                 repositorySession.close();
469             }
470         }
471     }
472
473     public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
474         throws ArchivaRestServiceException
475     {
476         List<String> selectedRepos = getObservableRepos();
477
478         if ( CollectionUtils.isEmpty( selectedRepos ) )
479         {
480             // FIXME 403 ???
481             return null;
482         }
483
484         if ( StringUtils.isNotEmpty( repositoryId ) )
485         {
486             // check user has karma on the repository
487             if ( !selectedRepos.contains( repositoryId ) )
488             {
489                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
490                                                        Response.Status.FORBIDDEN.getStatusCode() );
491             }
492             selectedRepos = Collections.singletonList( repositoryId );
493         }
494
495         List<TreeEntry> treeEntries = new ArrayList<TreeEntry>();
496         TreeDependencyNodeVisitor treeDependencyNodeVisitor = new TreeDependencyNodeVisitor( treeEntries );
497         try
498         {
499             dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version,
500                                                        treeDependencyNodeVisitor );
501         }
502         catch ( DependencyTreeBuilderException e )
503         {
504             throw new ArchivaRestServiceException( e.getMessage(),
505                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
506         }
507         return treeEntries;
508     }
509
510     private static class TreeDependencyNodeVisitor
511         implements DependencyNodeVisitor
512     {
513         final List<TreeEntry> treeEntries;
514
515         private TreeEntry currentEntry;
516
517         private DependencyNode firstNode;
518
519         private TreeDependencyNodeVisitor( List<TreeEntry> treeEntries )
520         {
521             this.treeEntries = treeEntries;
522         }
523
524         public boolean visit( DependencyNode node )
525         {
526             if ( firstNode == null )
527             {
528                 firstNode = node;
529             }
530             if ( currentEntry == null )
531             {
532                 currentEntry =
533                     new TreeEntry( new BeanReplicator().replicateBean( node.getArtifact(), Artifact.class ) );
534                 treeEntries.add( currentEntry );
535             }
536             else
537             {
538                 if ( node.getChildren().isEmpty() )
539                 {
540                     currentEntry.getChilds().add(
541                         new TreeEntry( new BeanReplicator().replicateBean( node.getArtifact(), Artifact.class ) ) );
542                 }
543             }
544
545             if ( !node.getChildren().isEmpty() )
546             {
547                 for ( DependencyNode dependencyNode : (List<DependencyNode>) node.getChildren() )
548                 {
549                     if ( dependencyNode.getChildren().isEmpty() )
550                     {
551                         this.currentEntry.getChilds().add( new TreeEntry(
552                             new BeanReplicator().replicateBean( dependencyNode.getArtifact(), Artifact.class ) ) );
553                     }
554                     else
555                     {
556                         TreeEntry backup = this.currentEntry;
557                         this.currentEntry = new TreeEntry(
558                             new BeanReplicator().replicateBean( dependencyNode.getArtifact(), Artifact.class ) );
559                         visit( dependencyNode );
560                         this.currentEntry = backup;
561                     }
562                 }
563             }
564
565             return true;
566         }
567
568         public boolean endVisit( DependencyNode node )
569         {
570             firstNode = null;
571             return true;
572         }
573
574     }
575
576     public List<ManagedRepository> getUserRepositories()
577         throws ArchivaRestServiceException
578     {
579         try
580         {
581             return userRepositories.getAccessibleRepositories( getPrincipal() );
582         }
583         catch ( ArchivaSecurityException e )
584         {
585             throw new ArchivaRestServiceException( "repositories.read.observable.error",
586                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
587         }
588     }
589
590     //---------------------------
591     // internals
592     //---------------------------
593
594     private List<String> getSortedList( Set<String> set )
595     {
596         List<String> list = new ArrayList<String>( set );
597         Collections.sort( list );
598         return list;
599     }
600
601     private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
602                                        Collection<String> repoIds, String n )
603         throws MetadataResolutionException
604     {
605         Set<String> subNamespaces = new LinkedHashSet<String>();
606         for ( String repoId : repoIds )
607         {
608             subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
609         }
610         if ( subNamespaces.size() != 1 )
611         {
612             log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
613             return n;
614         }
615         else
616         {
617             for ( String repoId : repoIds )
618             {
619                 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
620                 if ( projects != null && !projects.isEmpty() )
621                 {
622                     log.debug( "{} is not collapsible as it has projects", n );
623                     return n;
624                 }
625             }
626             return collapseNamespaces( repositorySession, metadataResolver, repoIds,
627                                        n + "." + subNamespaces.iterator().next() );
628         }
629     }
630 }