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.TreeEntry;
38 import org.apache.archiva.rest.api.model.VersionsList;
39 import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
40 import org.apache.archiva.rest.api.services.BrowseService;
41 import org.apache.archiva.rest.services.utils.TreeDependencyNodeVisitor;
42 import org.apache.archiva.security.ArchivaSecurityException;
43 import org.apache.commons.collections.CollectionUtils;
44 import org.apache.commons.lang.StringUtils;
45 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
46 import org.springframework.stereotype.Service;
48 import javax.inject.Inject;
49 import javax.ws.rs.core.Response;
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.LinkedHashSet;
55 import java.util.List;
60 * @author Olivier Lamy
63 @Service( "browseService#rest" )
64 public class DefaultBrowseService
65 extends AbstractRestService
66 implements BrowseService
70 private DependencyTreeBuilder dependencyTreeBuilder;
72 public BrowseResult getRootGroups( String repositoryId )
73 throws ArchivaRestServiceException
75 List<String> selectedRepos = getObservableRepos();
76 if ( CollectionUtils.isEmpty( selectedRepos ) )
79 return new BrowseResult();
82 if ( StringUtils.isNotEmpty( repositoryId ) )
84 // check user has karma on the repository
85 if ( !selectedRepos.contains( repositoryId ) )
87 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
88 Response.Status.FORBIDDEN.getStatusCode() );
90 selectedRepos = Collections.singletonList( repositoryId );
93 Set<String> namespaces = new LinkedHashSet<String>();
95 // TODO: this logic should be optional, particularly remembering we want to keep this code simple
96 // it is located here to avoid the content repository implementation needing to do too much for what
97 // is essentially presentation code
98 Set<String> namespacesToCollapse;
99 RepositorySession repositorySession = repositorySessionFactory.createSession();
102 MetadataResolver metadataResolver = repositorySession.getResolver();
103 namespacesToCollapse = new LinkedHashSet<String>();
105 for ( String repoId : selectedRepos )
107 namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
109 for ( String n : namespacesToCollapse )
111 // TODO: check performance of this
112 namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
115 catch ( MetadataResolutionException e )
117 throw new ArchivaRestServiceException( e.getMessage(),
118 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
122 repositorySession.close();
125 List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<BrowseResultEntry>( namespaces.size() );
126 for ( String namespace : namespaces )
128 browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
131 Collections.sort( browseGroupResultEntries );
132 return new BrowseResult( browseGroupResultEntries );
135 public BrowseResult browseGroupId( String groupId, String repositoryId )
136 throws ArchivaRestServiceException
139 List<String> selectedRepos = getObservableRepos();
140 if ( CollectionUtils.isEmpty( selectedRepos ) )
143 return new BrowseResult();
146 if ( StringUtils.isNotEmpty( repositoryId ) )
148 // check user has karma on the repository
149 if ( !selectedRepos.contains( repositoryId ) )
151 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
152 Response.Status.FORBIDDEN.getStatusCode() );
154 selectedRepos = Collections.singletonList( repositoryId );
157 Set<String> projects = new LinkedHashSet<String>();
159 RepositorySession repositorySession = repositorySessionFactory.createSession();
160 Set<String> namespaces;
163 MetadataResolver metadataResolver = repositorySession.getResolver();
165 Set<String> namespacesToCollapse = new LinkedHashSet<String>();
166 for ( String repoId : selectedRepos )
168 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
170 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
173 // TODO: this logic should be optional, particularly remembering we want to keep this code simple
174 // it is located here to avoid the content repository implementation needing to do too much for what
175 // is essentially presentation code
176 namespaces = new LinkedHashSet<String>();
177 for ( String n : namespacesToCollapse )
179 // TODO: check performance of this
181 collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
184 catch ( MetadataResolutionException e )
186 throw new ArchivaRestServiceException( e.getMessage(),
187 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
191 repositorySession.close();
193 List<BrowseResultEntry> browseGroupResultEntries =
194 new ArrayList<BrowseResultEntry>( namespaces.size() + projects.size() );
195 for ( String namespace : namespaces )
197 browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
199 for ( String project : projects )
201 browseGroupResultEntries.add( new BrowseResultEntry( groupId + '.' + project, true ) );
203 Collections.sort( browseGroupResultEntries );
204 return new BrowseResult( browseGroupResultEntries );
208 public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
209 throws ArchivaRestServiceException
211 List<String> selectedRepos = getObservableRepos();
212 if ( CollectionUtils.isEmpty( selectedRepos ) )
215 return new VersionsList();
218 if ( StringUtils.isNotEmpty( repositoryId ) )
220 // check user has karma on the repository
221 if ( !selectedRepos.contains( repositoryId ) )
223 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
224 Response.Status.FORBIDDEN.getStatusCode() );
226 selectedRepos = Collections.singletonList( repositoryId );
231 return new VersionsList( new ArrayList<String>( getVersions( selectedRepos, groupId, artifactId ) ) );
233 catch ( MetadataResolutionException e )
235 throw new ArchivaRestServiceException( e.getMessage(),
236 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
241 private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
242 throws MetadataResolutionException
245 RepositorySession repositorySession = repositorySessionFactory.createSession();
248 MetadataResolver metadataResolver = repositorySession.getResolver();
250 Set<String> versions = new LinkedHashSet<String>();
252 for ( String repoId : selectedRepos )
255 metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId ) );
258 List<String> sortedVersions = new ArrayList<String>( versions );
260 Collections.sort( sortedVersions, VersionComparator.getInstance() );
262 return sortedVersions;
266 repositorySession.close();
270 public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
271 String repositoryId )
272 throws ArchivaRestServiceException
274 List<String> selectedRepos = getObservableRepos();
276 if ( CollectionUtils.isEmpty( selectedRepos ) )
282 if ( StringUtils.isNotEmpty( repositoryId ) )
284 // check user has karma on the repository
285 if ( !selectedRepos.contains( repositoryId ) )
287 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
288 Response.Status.FORBIDDEN.getStatusCode() );
290 selectedRepos = Collections.singletonList( repositoryId );
293 RepositorySession repositorySession = null;
296 repositorySession = repositorySessionFactory.createSession();
298 MetadataResolver metadataResolver = repositorySession.getResolver();
300 ProjectVersionMetadata versionMetadata = null;
301 for ( String repoId : selectedRepos )
303 if ( versionMetadata == null || versionMetadata.isIncomplete() )
308 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
311 catch ( MetadataResolutionException e )
314 "Skipping invalid metadata while compiling shared model for " + groupId + ":" + artifactId
315 + " in repo " + repoId + ": " + e.getMessage() );
320 return versionMetadata;
324 if ( repositorySession != null )
326 repositorySession.close();
332 public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
333 throws ArchivaRestServiceException
336 List<String> selectedRepos = getObservableRepos();
338 if ( CollectionUtils.isEmpty( selectedRepos ) )
344 if ( StringUtils.isNotEmpty( repositoryId ) )
346 // check user has karma on the repository
347 if ( !selectedRepos.contains( repositoryId ) )
349 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
350 Response.Status.FORBIDDEN.getStatusCode() );
352 selectedRepos = Collections.singletonList( repositoryId );
355 RepositorySession repositorySession = null;
359 Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
361 repositorySession = repositorySessionFactory.createSession();
363 MetadataResolver metadataResolver = repositorySession.getResolver();
365 ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
367 MavenProjectFacet mavenFacet = new MavenProjectFacet();
368 mavenFacet.setGroupId( groupId );
369 mavenFacet.setArtifactId( artifactId );
370 sharedModel.addFacet( mavenFacet );
372 boolean isFirstVersion = true;
374 for ( String version : projectVersions )
376 ProjectVersionMetadata versionMetadata = null;
377 for ( String repoId : selectedRepos )
379 if ( versionMetadata == null || versionMetadata.isIncomplete() )
384 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
387 catch ( MetadataResolutionException e )
389 log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
390 + artifactId + " in repo " + repoId + ": " + e.getMessage() );
395 if ( versionMetadata == null )
400 if ( isFirstVersion )
402 sharedModel = versionMetadata;
403 sharedModel.setId( null );
407 MavenProjectFacet versionMetadataMavenFacet =
408 (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
409 if ( versionMetadataMavenFacet != null )
411 if ( mavenFacet.getPackaging() != null && !StringUtils.equalsIgnoreCase(
412 mavenFacet.getPackaging(), versionMetadataMavenFacet.getPackaging() ) )
414 mavenFacet.setPackaging( null );
418 if ( StringUtils.isEmpty( sharedModel.getName() ) && !StringUtils.isEmpty(
419 versionMetadata.getName() ) )
421 sharedModel.setName( versionMetadata.getName() );
424 if ( sharedModel.getDescription() != null && !StringUtils.equalsIgnoreCase(
425 sharedModel.getDescription(), versionMetadata.getDescription() ) )
427 sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
428 ? versionMetadata.getDescription()
432 if ( sharedModel.getIssueManagement() != null && versionMetadata.getIssueManagement() != null
433 && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
434 versionMetadata.getIssueManagement().getUrl() ) )
436 sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
439 if ( sharedModel.getCiManagement() != null && versionMetadata.getCiManagement() != null
440 && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
441 versionMetadata.getCiManagement().getUrl() ) )
443 sharedModel.setCiManagement( versionMetadata.getCiManagement() );
446 if ( sharedModel.getOrganization() != null && versionMetadata.getOrganization() != null
447 && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
448 versionMetadata.getOrganization().getName() ) )
450 sharedModel.setOrganization( versionMetadata.getOrganization() );
453 if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(),
454 versionMetadata.getUrl() ) )
456 sharedModel.setUrl( versionMetadata.getUrl() );
460 isFirstVersion = false;
464 catch ( MetadataResolutionException e )
466 throw new ArchivaRestServiceException( e.getMessage(),
467 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
471 if ( repositorySession != null )
473 repositorySession.close();
478 public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
479 throws ArchivaRestServiceException
481 List<String> selectedRepos = getObservableRepos();
483 if ( CollectionUtils.isEmpty( selectedRepos ) )
489 if ( StringUtils.isNotEmpty( repositoryId ) )
491 // check user has karma on the repository
492 if ( !selectedRepos.contains( repositoryId ) )
494 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
495 Response.Status.FORBIDDEN.getStatusCode() );
497 selectedRepos = Collections.singletonList( repositoryId );
500 List<TreeEntry> treeEntries = new ArrayList<TreeEntry>();
501 TreeDependencyNodeVisitor treeDependencyNodeVisitor = new TreeDependencyNodeVisitor( treeEntries );
504 dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version,
505 treeDependencyNodeVisitor );
507 catch ( DependencyTreeBuilderException e )
509 throw new ArchivaRestServiceException( e.getMessage(),
510 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
515 public List<ManagedRepository> getUserRepositories()
516 throws ArchivaRestServiceException
520 return userRepositories.getAccessibleRepositories( getPrincipal() );
522 catch ( ArchivaSecurityException e )
524 throw new ArchivaRestServiceException( "repositories.read.observable.error",
525 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
529 public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
530 throws ArchivaRestServiceException
532 List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
533 // TODO: what if we get duplicates across repositories?
534 RepositorySession repositorySession = repositorySessionFactory.createSession();
537 MetadataResolver metadataResolver = repositorySession.getResolver();
538 for ( String repoId : getObservableRepos() )
540 // TODO: what about if we want to see this irrespective of version?
542 metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
546 catch ( MetadataResolutionException e )
548 throw new ArchivaRestServiceException( e.getMessage(),
549 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
553 repositorySession.close();
556 List<Artifact> artifacts = new ArrayList<Artifact>( references.size() );
558 for ( ProjectVersionReference projectVersionReference : references )
560 artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
561 projectVersionReference.getProjectVersion() ) );
566 public Map<String, String> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
567 throws ArchivaRestServiceException
569 ProjectVersionMetadata projectVersionMetadata =
570 getProjectMetadata( groupId, artifactId, version, repositoryId );
571 if ( projectVersionMetadata == null )
573 return Collections.emptyMap();
575 MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
577 if ( metadataFacet == null )
579 return Collections.emptyMap();
582 return metadataFacet.toProperties();
585 public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
586 String repositoryId )
587 throws ArchivaRestServiceException
589 ProjectVersionMetadata projectVersionMetadata =
590 getProjectMetadata( groupId, artifactId, version, repositoryId );
592 if ( projectVersionMetadata == null )
594 return Boolean.FALSE;
597 Map<String, String> properties = new HashMap<String, String>();
599 MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
601 if ( metadataFacet != null && metadataFacet.toProperties() != null )
603 properties.putAll( metadataFacet.toProperties() );
607 metadataFacet = new GenericMetadataFacet();
610 properties.put( key, value );
612 metadataFacet.fromProperties( properties );
614 projectVersionMetadata.addFacet( metadataFacet );
616 RepositorySession repositorySession = repositorySessionFactory.createSession();
620 MetadataRepository metadataRepository = repositorySession.getRepository();
622 metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
624 repositorySession.save();
626 catch ( MetadataRepositoryException e )
628 log.error( e.getMessage(), e );
629 throw new ArchivaRestServiceException( e.getMessage(),
630 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
634 repositorySession.close();
639 public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
640 throws ArchivaRestServiceException
642 ProjectVersionMetadata projectVersionMetadata =
643 getProjectMetadata( groupId, artifactId, version, repositoryId );
645 if ( projectVersionMetadata == null )
647 return Boolean.FALSE;
650 GenericMetadataFacet metadataFacet =
651 (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
653 if ( metadataFacet != null && metadataFacet.toProperties() != null )
655 Map<String, String> properties = metadataFacet.toProperties();
656 properties.remove( key );
657 metadataFacet.setAdditionalProperties( properties );
664 RepositorySession repositorySession = repositorySessionFactory.createSession();
668 MetadataRepository metadataRepository = repositorySession.getRepository();
670 metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
672 repositorySession.save();
674 catch ( MetadataRepositoryException e )
676 log.error( e.getMessage(), e );
677 throw new ArchivaRestServiceException( e.getMessage(),
678 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
682 repositorySession.close();
687 //---------------------------
689 //---------------------------
691 private List<String> getSortedList( Set<String> set )
693 List<String> list = new ArrayList<String>( set );
694 Collections.sort( list );
698 private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
699 Collection<String> repoIds, String n )
700 throws MetadataResolutionException
702 Set<String> subNamespaces = new LinkedHashSet<String>();
703 for ( String repoId : repoIds )
705 subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
707 if ( subNamespaces.size() != 1 )
709 log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
714 for ( String repoId : repoIds )
716 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
717 if ( projects != null && !projects.isEmpty() )
719 log.debug( "{} is not collapsible as it has projects", n );
723 return collapseNamespaces( repositorySession, metadataResolver, repoIds,
724 n + "." + subNamespaces.iterator().next() );