1 package org.apache.archiva.rest.services;
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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
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;
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;
55 * @author Olivier Lamy
58 @Service( "browseService#rest" )
59 public class DefaultBrowseService
60 extends AbstractRestService
61 implements BrowseService
65 private DependencyTreeBuilder dependencyTreeBuilder;
67 public BrowseResult getRootGroups( String repositoryId )
68 throws ArchivaRestServiceException
70 List<String> selectedRepos = getObservableRepos();
71 if ( CollectionUtils.isEmpty( selectedRepos ) )
74 return new BrowseResult();
77 if ( StringUtils.isNotEmpty( repositoryId ) )
79 // check user has karma on the repository
80 if ( !selectedRepos.contains( repositoryId ) )
82 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
83 Response.Status.FORBIDDEN.getStatusCode() );
85 selectedRepos = Collections.singletonList( repositoryId );
88 Set<String> namespaces = new LinkedHashSet<String>();
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();
97 MetadataResolver metadataResolver = repositorySession.getResolver();
98 namespacesToCollapse = new LinkedHashSet<String>();
100 for ( String repoId : selectedRepos )
102 namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
104 for ( String n : namespacesToCollapse )
106 // TODO: check performance of this
107 namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
110 catch ( MetadataResolutionException e )
112 throw new ArchivaRestServiceException( e.getMessage(),
113 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
117 repositorySession.close();
120 List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<BrowseResultEntry>( namespaces.size() );
121 for ( String namespace : namespaces )
123 browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
126 Collections.sort( browseGroupResultEntries );
127 return new BrowseResult( browseGroupResultEntries );
130 public BrowseResult browseGroupId( String groupId, String repositoryId )
131 throws ArchivaRestServiceException
134 List<String> selectedRepos = getObservableRepos();
135 if ( CollectionUtils.isEmpty( selectedRepos ) )
138 return new BrowseResult();
141 if ( StringUtils.isNotEmpty( repositoryId ) )
143 // check user has karma on the repository
144 if ( !selectedRepos.contains( repositoryId ) )
146 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
147 Response.Status.FORBIDDEN.getStatusCode() );
149 selectedRepos = Collections.singletonList( repositoryId );
152 Set<String> projects = new LinkedHashSet<String>();
154 RepositorySession repositorySession = repositorySessionFactory.createSession();
155 Set<String> namespaces;
158 MetadataResolver metadataResolver = repositorySession.getResolver();
160 Set<String> namespacesToCollapse = new LinkedHashSet<String>();
161 for ( String repoId : selectedRepos )
163 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
165 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
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 )
174 // TODO: check performance of this
176 collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
179 catch ( MetadataResolutionException e )
181 throw new ArchivaRestServiceException( e.getMessage(),
182 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
186 repositorySession.close();
188 List<BrowseResultEntry> browseGroupResultEntries =
189 new ArrayList<BrowseResultEntry>( namespaces.size() + projects.size() );
190 for ( String namespace : namespaces )
192 browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
194 for ( String project : projects )
196 browseGroupResultEntries.add( new BrowseResultEntry( groupId + '.' + project, true ) );
198 Collections.sort( browseGroupResultEntries );
199 return new BrowseResult( browseGroupResultEntries );
203 public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
204 throws ArchivaRestServiceException
206 List<String> selectedRepos = getObservableRepos();
207 if ( CollectionUtils.isEmpty( selectedRepos ) )
210 return new VersionsList();
213 if ( StringUtils.isNotEmpty( repositoryId ) )
215 // check user has karma on the repository
216 if ( !selectedRepos.contains( repositoryId ) )
218 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
219 Response.Status.FORBIDDEN.getStatusCode() );
221 selectedRepos = Collections.singletonList( repositoryId );
226 return new VersionsList( new ArrayList<String>( getVersions( selectedRepos, groupId, artifactId ) ) );
228 catch ( MetadataResolutionException e )
230 throw new ArchivaRestServiceException( e.getMessage(),
231 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
236 private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
237 throws MetadataResolutionException
240 RepositorySession repositorySession = repositorySessionFactory.createSession();
243 MetadataResolver metadataResolver = repositorySession.getResolver();
245 Set<String> versions = new LinkedHashSet<String>();
247 for ( String repoId : selectedRepos )
250 metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId ) );
253 List<String> sortedVersions = new ArrayList<String>( versions );
255 Collections.sort( sortedVersions, VersionComparator.getInstance() );
257 return sortedVersions;
261 repositorySession.close();
265 public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
266 String repositoryId )
267 throws ArchivaRestServiceException
269 List<String> selectedRepos = getObservableRepos();
271 if ( CollectionUtils.isEmpty( selectedRepos ) )
277 if ( StringUtils.isNotEmpty( repositoryId ) )
279 // check user has karma on the repository
280 if ( !selectedRepos.contains( repositoryId ) )
282 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
283 Response.Status.FORBIDDEN.getStatusCode() );
285 selectedRepos = Collections.singletonList( repositoryId );
288 RepositorySession repositorySession = null;
291 repositorySession = repositorySessionFactory.createSession();
293 MetadataResolver metadataResolver = repositorySession.getResolver();
295 ProjectVersionMetadata versionMetadata = null;
296 for ( String repoId : selectedRepos )
298 if ( versionMetadata == null || versionMetadata.isIncomplete() )
303 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
306 catch ( MetadataResolutionException e )
309 "Skipping invalid metadata while compiling shared model for " + groupId + ":" + artifactId
310 + " in repo " + repoId + ": " + e.getMessage() );
315 return versionMetadata;
319 if ( repositorySession != null )
321 repositorySession.close();
327 public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
328 throws ArchivaRestServiceException
331 List<String> selectedRepos = getObservableRepos();
333 if ( CollectionUtils.isEmpty( selectedRepos ) )
339 if ( StringUtils.isNotEmpty( repositoryId ) )
341 // check user has karma on the repository
342 if ( !selectedRepos.contains( repositoryId ) )
344 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
345 Response.Status.FORBIDDEN.getStatusCode() );
347 selectedRepos = Collections.singletonList( repositoryId );
350 RepositorySession repositorySession = null;
354 Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
356 repositorySession = repositorySessionFactory.createSession();
358 MetadataResolver metadataResolver = repositorySession.getResolver();
360 ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
362 MavenProjectFacet mavenFacet = new MavenProjectFacet();
363 mavenFacet.setGroupId( groupId );
364 mavenFacet.setArtifactId( artifactId );
365 sharedModel.addFacet( mavenFacet );
367 boolean isFirstVersion = true;
369 for ( String version : projectVersions )
371 ProjectVersionMetadata versionMetadata = null;
372 for ( String repoId : selectedRepos )
374 if ( versionMetadata == null || versionMetadata.isIncomplete() )
379 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
382 catch ( MetadataResolutionException e )
384 log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
385 + artifactId + " in repo " + repoId + ": " + e.getMessage() );
390 if ( versionMetadata == null )
395 if ( isFirstVersion )
397 sharedModel = versionMetadata;
398 sharedModel.setId( null );
402 MavenProjectFacet versionMetadataMavenFacet =
403 (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
404 if ( versionMetadataMavenFacet != null )
406 if ( mavenFacet.getPackaging() != null && !StringUtils.equalsIgnoreCase(
407 mavenFacet.getPackaging(), versionMetadataMavenFacet.getPackaging() ) )
409 mavenFacet.setPackaging( null );
413 if ( StringUtils.isEmpty( sharedModel.getName() ) && !StringUtils.isEmpty(
414 versionMetadata.getName() ) )
416 sharedModel.setName( versionMetadata.getName() );
419 if ( sharedModel.getDescription() != null && !StringUtils.equalsIgnoreCase(
420 sharedModel.getDescription(), versionMetadata.getDescription() ) )
422 sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
423 ? versionMetadata.getDescription()
427 if ( sharedModel.getIssueManagement() != null && versionMetadata.getIssueManagement() != null
428 && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
429 versionMetadata.getIssueManagement().getUrl() ) )
431 sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
434 if ( sharedModel.getCiManagement() != null && versionMetadata.getCiManagement() != null
435 && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
436 versionMetadata.getCiManagement().getUrl() ) )
438 sharedModel.setCiManagement( versionMetadata.getCiManagement() );
441 if ( sharedModel.getOrganization() != null && versionMetadata.getOrganization() != null
442 && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
443 versionMetadata.getOrganization().getName() ) )
445 sharedModel.setOrganization( versionMetadata.getOrganization() );
448 if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(),
449 versionMetadata.getUrl() ) )
451 sharedModel.setUrl( versionMetadata.getUrl() );
455 isFirstVersion = false;
459 catch ( MetadataResolutionException e )
461 throw new ArchivaRestServiceException( e.getMessage(),
462 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
466 if ( repositorySession != null )
468 repositorySession.close();
473 public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
474 throws ArchivaRestServiceException
476 List<String> selectedRepos = getObservableRepos();
478 if ( CollectionUtils.isEmpty( selectedRepos ) )
484 if ( StringUtils.isNotEmpty( repositoryId ) )
486 // check user has karma on the repository
487 if ( !selectedRepos.contains( repositoryId ) )
489 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
490 Response.Status.FORBIDDEN.getStatusCode() );
492 selectedRepos = Collections.singletonList( repositoryId );
495 List<TreeEntry> treeEntries = new ArrayList<TreeEntry>();
496 TreeDependencyNodeVisitor treeDependencyNodeVisitor = new TreeDependencyNodeVisitor( treeEntries );
499 dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version,
500 treeDependencyNodeVisitor );
502 catch ( DependencyTreeBuilderException e )
504 throw new ArchivaRestServiceException( e.getMessage(),
505 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
510 private static class TreeDependencyNodeVisitor
511 implements DependencyNodeVisitor
513 final List<TreeEntry> treeEntries;
515 private TreeEntry currentEntry;
517 private DependencyNode firstNode;
519 private TreeDependencyNodeVisitor( List<TreeEntry> treeEntries )
521 this.treeEntries = treeEntries;
524 public boolean visit( DependencyNode node )
526 if ( firstNode == null )
530 if ( currentEntry == null )
533 new TreeEntry( new BeanReplicator().replicateBean( node.getArtifact(), Artifact.class ) );
534 treeEntries.add( currentEntry );
538 if ( node.getChildren().isEmpty() )
540 currentEntry.getChilds().add(
541 new TreeEntry( new BeanReplicator().replicateBean( node.getArtifact(), Artifact.class ) ) );
545 if ( !node.getChildren().isEmpty() )
547 for ( DependencyNode dependencyNode : (List<DependencyNode>) node.getChildren() )
549 if ( dependencyNode.getChildren().isEmpty() )
551 this.currentEntry.getChilds().add( new TreeEntry(
552 new BeanReplicator().replicateBean( dependencyNode.getArtifact(), Artifact.class ) ) );
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;
568 public boolean endVisit( DependencyNode node )
576 public List<ManagedRepository> getUserRepositories()
577 throws ArchivaRestServiceException
581 return userRepositories.getAccessibleRepositories( getPrincipal() );
583 catch ( ArchivaSecurityException e )
585 throw new ArchivaRestServiceException( "repositories.read.observable.error",
586 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
590 //---------------------------
592 //---------------------------
594 private List<String> getSortedList( Set<String> set )
596 List<String> list = new ArrayList<String>( set );
597 Collections.sort( list );
601 private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
602 Collection<String> repoIds, String n )
603 throws MetadataResolutionException
605 Set<String> subNamespaces = new LinkedHashSet<String>();
606 for ( String repoId : repoIds )
608 subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
610 if ( subNamespaces.size() != 1 )
612 log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
617 for ( String repoId : repoIds )
619 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
620 if ( projects != null && !projects.isEmpty() )
622 log.debug( "{} is not collapsible as it has projects", n );
626 return collapseNamespaces( repositorySession, metadataResolver, repoIds,
627 n + "." + subNamespaces.iterator().next() );