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 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.BrowseResult;
36 import org.apache.archiva.rest.api.model.BrowseResultEntry;
37 import org.apache.archiva.rest.api.model.Entry;
38 import org.apache.archiva.rest.api.model.TreeEntry;
39 import org.apache.archiva.rest.api.model.VersionsList;
40 import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
41 import org.apache.archiva.rest.api.services.BrowseService;
42 import org.apache.archiva.rest.services.utils.TreeDependencyNodeVisitor;
43 import org.apache.archiva.security.ArchivaSecurityException;
44 import org.apache.commons.collections.CollectionUtils;
45 import org.apache.commons.lang.StringUtils;
46 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
47 import org.springframework.stereotype.Service;
49 import javax.inject.Inject;
50 import javax.ws.rs.core.Response;
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.Collections;
54 import java.util.HashMap;
55 import java.util.LinkedHashSet;
56 import java.util.List;
61 * @author Olivier Lamy
64 @Service( "browseService#rest" )
65 public class DefaultBrowseService
66 extends AbstractRestService
67 implements BrowseService
71 private DependencyTreeBuilder dependencyTreeBuilder;
73 public BrowseResult getRootGroups( String repositoryId )
74 throws ArchivaRestServiceException
76 List<String> selectedRepos = getObservableRepos();
77 if ( CollectionUtils.isEmpty( selectedRepos ) )
80 return new BrowseResult();
83 if ( StringUtils.isNotEmpty( repositoryId ) )
85 // check user has karma on the repository
86 if ( !selectedRepos.contains( repositoryId ) )
88 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
89 Response.Status.FORBIDDEN.getStatusCode() );
91 selectedRepos = Collections.singletonList( repositoryId );
94 Set<String> namespaces = new LinkedHashSet<String>();
96 // TODO: this logic should be optional, particularly remembering we want to keep this code simple
97 // it is located here to avoid the content repository implementation needing to do too much for what
98 // is essentially presentation code
99 Set<String> namespacesToCollapse;
100 RepositorySession repositorySession = repositorySessionFactory.createSession();
103 MetadataResolver metadataResolver = repositorySession.getResolver();
104 namespacesToCollapse = new LinkedHashSet<String>();
106 for ( String repoId : selectedRepos )
108 namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
110 for ( String n : namespacesToCollapse )
112 // TODO: check performance of this
113 namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
116 catch ( MetadataResolutionException e )
118 throw new ArchivaRestServiceException( e.getMessage(),
119 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
123 repositorySession.close();
126 List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<BrowseResultEntry>( namespaces.size() );
127 for ( String namespace : namespaces )
129 browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
132 Collections.sort( browseGroupResultEntries );
133 return new BrowseResult( browseGroupResultEntries );
136 public BrowseResult browseGroupId( String groupId, String repositoryId )
137 throws ArchivaRestServiceException
140 List<String> selectedRepos = getObservableRepos();
141 if ( CollectionUtils.isEmpty( selectedRepos ) )
144 return new BrowseResult();
147 if ( StringUtils.isNotEmpty( repositoryId ) )
149 // check user has karma on the repository
150 if ( !selectedRepos.contains( repositoryId ) )
152 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
153 Response.Status.FORBIDDEN.getStatusCode() );
155 selectedRepos = Collections.singletonList( repositoryId );
158 Set<String> projects = new LinkedHashSet<String>();
160 RepositorySession repositorySession = repositorySessionFactory.createSession();
161 Set<String> namespaces;
164 MetadataResolver metadataResolver = repositorySession.getResolver();
166 Set<String> namespacesToCollapse = new LinkedHashSet<String>();
167 for ( String repoId : selectedRepos )
169 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
171 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
174 // TODO: this logic should be optional, particularly remembering we want to keep this code simple
175 // it is located here to avoid the content repository implementation needing to do too much for what
176 // is essentially presentation code
177 namespaces = new LinkedHashSet<String>();
178 for ( String n : namespacesToCollapse )
180 // TODO: check performance of this
182 collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
185 catch ( MetadataResolutionException e )
187 throw new ArchivaRestServiceException( e.getMessage(),
188 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
192 repositorySession.close();
194 List<BrowseResultEntry> browseGroupResultEntries =
195 new ArrayList<BrowseResultEntry>( namespaces.size() + projects.size() );
196 for ( String namespace : namespaces )
198 browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
200 for ( String project : projects )
202 browseGroupResultEntries.add( new BrowseResultEntry( groupId + '.' + project, true ) );
204 Collections.sort( browseGroupResultEntries );
205 return new BrowseResult( browseGroupResultEntries );
209 public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
210 throws ArchivaRestServiceException
212 List<String> selectedRepos = getObservableRepos();
213 if ( CollectionUtils.isEmpty( selectedRepos ) )
216 return new VersionsList();
219 if ( StringUtils.isNotEmpty( repositoryId ) )
221 // check user has karma on the repository
222 if ( !selectedRepos.contains( repositoryId ) )
224 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
225 Response.Status.FORBIDDEN.getStatusCode() );
227 selectedRepos = Collections.singletonList( repositoryId );
232 return new VersionsList( new ArrayList<String>( getVersions( selectedRepos, groupId, artifactId ) ) );
234 catch ( MetadataResolutionException e )
236 throw new ArchivaRestServiceException( e.getMessage(),
237 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
242 private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
243 throws MetadataResolutionException
246 RepositorySession repositorySession = repositorySessionFactory.createSession();
249 MetadataResolver metadataResolver = repositorySession.getResolver();
251 Set<String> versions = new LinkedHashSet<String>();
253 for ( String repoId : selectedRepos )
256 metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId ) );
259 List<String> sortedVersions = new ArrayList<String>( versions );
261 Collections.sort( sortedVersions, VersionComparator.getInstance() );
263 return sortedVersions;
267 repositorySession.close();
271 public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
272 String repositoryId )
273 throws ArchivaRestServiceException
275 List<String> selectedRepos = getObservableRepos();
277 if ( CollectionUtils.isEmpty( selectedRepos ) )
283 if ( StringUtils.isNotEmpty( repositoryId ) )
285 // check user has karma on the repository
286 if ( !selectedRepos.contains( repositoryId ) )
288 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
289 Response.Status.FORBIDDEN.getStatusCode() );
291 selectedRepos = Collections.singletonList( repositoryId );
294 RepositorySession repositorySession = null;
297 repositorySession = repositorySessionFactory.createSession();
299 MetadataResolver metadataResolver = repositorySession.getResolver();
301 ProjectVersionMetadata versionMetadata = null;
302 for ( String repoId : selectedRepos )
304 if ( versionMetadata == null || versionMetadata.isIncomplete() )
309 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
312 catch ( MetadataResolutionException e )
315 "Skipping invalid metadata while compiling shared model for " + groupId + ":" + artifactId
316 + " in repo " + repoId + ": " + e.getMessage() );
321 return versionMetadata;
325 if ( repositorySession != null )
327 repositorySession.close();
333 public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
334 throws ArchivaRestServiceException
337 List<String> selectedRepos = getObservableRepos();
339 if ( CollectionUtils.isEmpty( selectedRepos ) )
345 if ( StringUtils.isNotEmpty( repositoryId ) )
347 // check user has karma on the repository
348 if ( !selectedRepos.contains( repositoryId ) )
350 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
351 Response.Status.FORBIDDEN.getStatusCode() );
353 selectedRepos = Collections.singletonList( repositoryId );
356 RepositorySession repositorySession = null;
360 Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
362 repositorySession = repositorySessionFactory.createSession();
364 MetadataResolver metadataResolver = repositorySession.getResolver();
366 ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
368 MavenProjectFacet mavenFacet = new MavenProjectFacet();
369 mavenFacet.setGroupId( groupId );
370 mavenFacet.setArtifactId( artifactId );
371 sharedModel.addFacet( mavenFacet );
373 boolean isFirstVersion = true;
375 for ( String version : projectVersions )
377 ProjectVersionMetadata versionMetadata = null;
378 for ( String repoId : selectedRepos )
380 if ( versionMetadata == null || versionMetadata.isIncomplete() )
385 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
388 catch ( MetadataResolutionException e )
390 log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
391 + artifactId + " in repo " + repoId + ": " + e.getMessage() );
396 if ( versionMetadata == null )
401 if ( isFirstVersion )
403 sharedModel = versionMetadata;
404 sharedModel.setId( null );
408 MavenProjectFacet versionMetadataMavenFacet =
409 (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
410 if ( versionMetadataMavenFacet != null )
412 if ( mavenFacet.getPackaging() != null && !StringUtils.equalsIgnoreCase(
413 mavenFacet.getPackaging(), versionMetadataMavenFacet.getPackaging() ) )
415 mavenFacet.setPackaging( null );
419 if ( StringUtils.isEmpty( sharedModel.getName() ) && !StringUtils.isEmpty(
420 versionMetadata.getName() ) )
422 sharedModel.setName( versionMetadata.getName() );
425 if ( sharedModel.getDescription() != null && !StringUtils.equalsIgnoreCase(
426 sharedModel.getDescription(), versionMetadata.getDescription() ) )
428 sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
429 ? versionMetadata.getDescription()
433 if ( sharedModel.getIssueManagement() != null && versionMetadata.getIssueManagement() != null
434 && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
435 versionMetadata.getIssueManagement().getUrl() ) )
437 sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
440 if ( sharedModel.getCiManagement() != null && versionMetadata.getCiManagement() != null
441 && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
442 versionMetadata.getCiManagement().getUrl() ) )
444 sharedModel.setCiManagement( versionMetadata.getCiManagement() );
447 if ( sharedModel.getOrganization() != null && versionMetadata.getOrganization() != null
448 && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
449 versionMetadata.getOrganization().getName() ) )
451 sharedModel.setOrganization( versionMetadata.getOrganization() );
454 if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(),
455 versionMetadata.getUrl() ) )
457 sharedModel.setUrl( versionMetadata.getUrl() );
461 isFirstVersion = false;
465 catch ( MetadataResolutionException e )
467 throw new ArchivaRestServiceException( e.getMessage(),
468 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
472 if ( repositorySession != null )
474 repositorySession.close();
479 public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
480 throws ArchivaRestServiceException
482 List<String> selectedRepos = getObservableRepos();
484 if ( CollectionUtils.isEmpty( selectedRepos ) )
490 if ( StringUtils.isNotEmpty( repositoryId ) )
492 // check user has karma on the repository
493 if ( !selectedRepos.contains( repositoryId ) )
495 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
496 Response.Status.FORBIDDEN.getStatusCode() );
498 selectedRepos = Collections.singletonList( repositoryId );
501 List<TreeEntry> treeEntries = new ArrayList<TreeEntry>();
502 TreeDependencyNodeVisitor treeDependencyNodeVisitor = new TreeDependencyNodeVisitor( treeEntries );
505 dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version,
506 treeDependencyNodeVisitor );
508 catch ( DependencyTreeBuilderException e )
510 throw new ArchivaRestServiceException( e.getMessage(),
511 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
516 public List<ManagedRepository> getUserRepositories()
517 throws ArchivaRestServiceException
521 return userRepositories.getAccessibleRepositories( getPrincipal() );
523 catch ( ArchivaSecurityException e )
525 throw new ArchivaRestServiceException( "repositories.read.observable.error",
526 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
530 public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
531 throws ArchivaRestServiceException
533 List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
534 // TODO: what if we get duplicates across repositories?
535 RepositorySession repositorySession = repositorySessionFactory.createSession();
538 MetadataResolver metadataResolver = repositorySession.getResolver();
539 for ( String repoId : getObservableRepos() )
541 // TODO: what about if we want to see this irrespective of version?
543 metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
547 catch ( MetadataResolutionException e )
549 throw new ArchivaRestServiceException( e.getMessage(),
550 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
554 repositorySession.close();
557 List<Artifact> artifacts = new ArrayList<Artifact>( references.size() );
559 for ( ProjectVersionReference projectVersionReference : references )
561 artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
562 projectVersionReference.getProjectVersion() ) );
567 public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
568 throws ArchivaRestServiceException
570 ProjectVersionMetadata projectVersionMetadata =
571 getProjectMetadata( groupId, artifactId, version, repositoryId );
572 if ( projectVersionMetadata == null )
574 return Collections.emptyList();
576 MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
578 if ( metadataFacet == null )
580 return Collections.emptyList();
582 Map<String, String> map = metadataFacet.toProperties();
583 List<Entry> entries = new ArrayList<Entry>( map.size() );
585 for ( Map.Entry<String, String> entry : map.entrySet() )
587 entries.add( new Entry( entry.getKey(), entry.getValue() ) );
593 public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
594 String repositoryId )
595 throws ArchivaRestServiceException
597 ProjectVersionMetadata projectVersionMetadata =
598 getProjectMetadata( groupId, artifactId, version, repositoryId );
600 if ( projectVersionMetadata == null )
602 return Boolean.FALSE;
605 Map<String, String> properties = new HashMap<String, String>();
607 MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
609 if ( metadataFacet != null && metadataFacet.toProperties() != null )
611 properties.putAll( metadataFacet.toProperties() );
615 metadataFacet = new GenericMetadataFacet();
618 properties.put( key, value );
620 metadataFacet.fromProperties( properties );
622 projectVersionMetadata.addFacet( metadataFacet );
624 RepositorySession repositorySession = repositorySessionFactory.createSession();
628 MetadataRepository metadataRepository = repositorySession.getRepository();
630 metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
632 repositorySession.save();
634 catch ( MetadataRepositoryException e )
636 log.error( e.getMessage(), e );
637 throw new ArchivaRestServiceException( e.getMessage(),
638 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
642 repositorySession.close();
647 public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
648 throws ArchivaRestServiceException
650 ProjectVersionMetadata projectVersionMetadata =
651 getProjectMetadata( groupId, artifactId, version, repositoryId );
653 if ( projectVersionMetadata == null )
655 return Boolean.FALSE;
658 GenericMetadataFacet metadataFacet =
659 (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
661 if ( metadataFacet != null && metadataFacet.toProperties() != null )
663 Map<String, String> properties = metadataFacet.toProperties();
664 properties.remove( key );
665 metadataFacet.setAdditionalProperties( properties );
672 RepositorySession repositorySession = repositorySessionFactory.createSession();
676 MetadataRepository metadataRepository = repositorySession.getRepository();
678 metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
680 repositorySession.save();
682 catch ( MetadataRepositoryException e )
684 log.error( e.getMessage(), e );
685 throw new ArchivaRestServiceException( e.getMessage(),
686 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
690 repositorySession.close();
695 //---------------------------
697 //---------------------------
699 private List<String> getSortedList( Set<String> set )
701 List<String> list = new ArrayList<String>( set );
702 Collections.sort( list );
706 private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
707 Collection<String> repoIds, String n )
708 throws MetadataResolutionException
710 Set<String> subNamespaces = new LinkedHashSet<String>();
711 for ( String repoId : repoIds )
713 subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
715 if ( subNamespaces.size() != 1 )
717 log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
722 for ( String repoId : repoIds )
724 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
725 if ( projects != null && !projects.isEmpty() )
727 log.debug( "{} is not collapsible as it has projects", n );
731 return collapseNamespaces( repositorySession, metadataResolver, repoIds,
732 n + "." + subNamespaces.iterator().next() );