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.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;
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;
62 * @author Olivier Lamy
65 @Service( "browseService#rest" )
66 public class DefaultBrowseService
67 extends AbstractRestService
68 implements BrowseService
72 private DependencyTreeBuilder dependencyTreeBuilder;
74 public BrowseResult getRootGroups( String repositoryId )
75 throws ArchivaRestServiceException
77 List<String> selectedRepos = getSelectedRepos( repositoryId );
79 Set<String> namespaces = new LinkedHashSet<String>();
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();
88 MetadataResolver metadataResolver = repositorySession.getResolver();
89 namespacesToCollapse = new LinkedHashSet<String>();
91 for ( String repoId : selectedRepos )
93 namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
95 for ( String n : namespacesToCollapse )
97 // TODO: check performance of this
98 namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
101 catch ( MetadataResolutionException e )
103 throw new ArchivaRestServiceException( e.getMessage(),
104 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
108 repositorySession.close();
111 List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<BrowseResultEntry>( namespaces.size() );
112 for ( String namespace : namespaces )
114 browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
117 Collections.sort( browseGroupResultEntries );
118 return new BrowseResult( browseGroupResultEntries );
121 public BrowseResult browseGroupId( String groupId, String repositoryId )
122 throws ArchivaRestServiceException
124 List<String> selectedRepos = getSelectedRepos( repositoryId );
126 Set<String> projects = new LinkedHashSet<String>();
128 RepositorySession repositorySession = repositorySessionFactory.createSession();
129 Set<String> namespaces;
132 MetadataResolver metadataResolver = repositorySession.getResolver();
134 Set<String> namespacesToCollapse = new LinkedHashSet<String>();
135 for ( String repoId : selectedRepos )
137 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
139 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
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 )
148 // TODO: check performance of this
150 collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
153 catch ( MetadataResolutionException e )
155 throw new ArchivaRestServiceException( e.getMessage(),
156 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
160 repositorySession.close();
162 List<BrowseResultEntry> browseGroupResultEntries =
163 new ArrayList<BrowseResultEntry>( namespaces.size() + projects.size() );
164 for ( String namespace : namespaces )
166 browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
168 for ( String project : projects )
170 browseGroupResultEntries.add( new BrowseResultEntry( groupId + '.' + project, true ) );
172 Collections.sort( browseGroupResultEntries );
173 return new BrowseResult( browseGroupResultEntries );
177 public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
178 throws ArchivaRestServiceException
180 List<String> selectedRepos = getSelectedRepos( repositoryId );
184 return new VersionsList( new ArrayList<String>( getVersions( selectedRepos, groupId, artifactId ) ) );
186 catch ( MetadataResolutionException e )
188 throw new ArchivaRestServiceException( e.getMessage(),
189 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
194 private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
195 throws MetadataResolutionException
198 RepositorySession repositorySession = repositorySessionFactory.createSession();
201 MetadataResolver metadataResolver = repositorySession.getResolver();
203 Set<String> versions = new LinkedHashSet<String>();
205 for ( String repoId : selectedRepos )
208 metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId ) );
211 List<String> sortedVersions = new ArrayList<String>( versions );
213 Collections.sort( sortedVersions, VersionComparator.getInstance() );
215 return sortedVersions;
219 repositorySession.close();
223 public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
224 String repositoryId )
225 throws ArchivaRestServiceException
227 List<String> selectedRepos = getSelectedRepos( repositoryId );
229 RepositorySession repositorySession = null;
232 repositorySession = repositorySessionFactory.createSession();
234 MetadataResolver metadataResolver = repositorySession.getResolver();
236 ProjectVersionMetadata versionMetadata = null;
237 for ( String repoId : selectedRepos )
239 if ( versionMetadata == null || versionMetadata.isIncomplete() )
244 metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
247 catch ( MetadataResolutionException e )
250 "Skipping invalid metadata while compiling shared model for " + groupId + ":" + artifactId
251 + " in repo " + repoId + ": " + e.getMessage() );
256 return versionMetadata;
260 if ( repositorySession != null )
262 repositorySession.close();
268 public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
269 throws ArchivaRestServiceException
272 List<String> selectedRepos = getSelectedRepos( repositoryId );
274 RepositorySession repositorySession = null;
278 Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
280 repositorySession = repositorySessionFactory.createSession();
282 MetadataResolver metadataResolver = repositorySession.getResolver();
284 ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
286 MavenProjectFacet mavenFacet = new MavenProjectFacet();
287 mavenFacet.setGroupId( groupId );
288 mavenFacet.setArtifactId( artifactId );
289 sharedModel.addFacet( mavenFacet );
291 boolean isFirstVersion = true;
293 for ( String version : projectVersions )
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 )
308 log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
309 + artifactId + " in repo " + repoId + ": " + e.getMessage() );
314 if ( versionMetadata == null )
319 if ( isFirstVersion )
321 sharedModel = versionMetadata;
322 sharedModel.setId( null );
326 MavenProjectFacet versionMetadataMavenFacet =
327 (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
328 if ( versionMetadataMavenFacet != null )
330 if ( mavenFacet.getPackaging() != null && !StringUtils.equalsIgnoreCase(
331 mavenFacet.getPackaging(), versionMetadataMavenFacet.getPackaging() ) )
333 mavenFacet.setPackaging( null );
337 if ( StringUtils.isEmpty( sharedModel.getName() ) && !StringUtils.isEmpty(
338 versionMetadata.getName() ) )
340 sharedModel.setName( versionMetadata.getName() );
343 if ( sharedModel.getDescription() != null && !StringUtils.equalsIgnoreCase(
344 sharedModel.getDescription(), versionMetadata.getDescription() ) )
346 sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
347 ? versionMetadata.getDescription()
351 if ( sharedModel.getIssueManagement() != null && versionMetadata.getIssueManagement() != null
352 && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
353 versionMetadata.getIssueManagement().getUrl() ) )
355 sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
358 if ( sharedModel.getCiManagement() != null && versionMetadata.getCiManagement() != null
359 && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
360 versionMetadata.getCiManagement().getUrl() ) )
362 sharedModel.setCiManagement( versionMetadata.getCiManagement() );
365 if ( sharedModel.getOrganization() != null && versionMetadata.getOrganization() != null
366 && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
367 versionMetadata.getOrganization().getName() ) )
369 sharedModel.setOrganization( versionMetadata.getOrganization() );
372 if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(),
373 versionMetadata.getUrl() ) )
375 sharedModel.setUrl( versionMetadata.getUrl() );
379 isFirstVersion = false;
383 catch ( MetadataResolutionException e )
385 throw new ArchivaRestServiceException( e.getMessage(),
386 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
390 if ( repositorySession != null )
392 repositorySession.close();
397 public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
398 throws ArchivaRestServiceException
400 List<String> selectedRepos = getSelectedRepos( repositoryId );
402 List<TreeEntry> treeEntries = new ArrayList<TreeEntry>();
403 TreeDependencyNodeVisitor treeDependencyNodeVisitor = new TreeDependencyNodeVisitor( treeEntries );
406 dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version,
407 treeDependencyNodeVisitor );
409 catch ( DependencyTreeBuilderException e )
411 throw new ArchivaRestServiceException( e.getMessage(),
412 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
417 public List<ManagedRepository> getUserRepositories()
418 throws ArchivaRestServiceException
422 return userRepositories.getAccessibleRepositories( getPrincipal() );
424 catch ( ArchivaSecurityException e )
426 throw new ArchivaRestServiceException( "repositories.read.observable.error",
427 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
431 public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
432 throws ArchivaRestServiceException
434 List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
435 // TODO: what if we get duplicates across repositories?
436 RepositorySession repositorySession = repositorySessionFactory.createSession();
439 MetadataResolver metadataResolver = repositorySession.getResolver();
440 for ( String repoId : getObservableRepos() )
442 // TODO: what about if we want to see this irrespective of version?
444 metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
448 catch ( MetadataResolutionException e )
450 throw new ArchivaRestServiceException( e.getMessage(),
451 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
455 repositorySession.close();
458 List<Artifact> artifacts = new ArrayList<Artifact>( references.size() );
460 for ( ProjectVersionReference projectVersionReference : references )
462 artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
463 projectVersionReference.getProjectVersion() ) );
468 public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
469 throws ArchivaRestServiceException
471 ProjectVersionMetadata projectVersionMetadata =
472 getProjectMetadata( groupId, artifactId, version, repositoryId );
473 if ( projectVersionMetadata == null )
475 return Collections.emptyList();
477 MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
479 if ( metadataFacet == null )
481 return Collections.emptyList();
483 Map<String, String> map = metadataFacet.toProperties();
484 List<Entry> entries = new ArrayList<Entry>( map.size() );
486 for ( Map.Entry<String, String> entry : map.entrySet() )
488 entries.add( new Entry( entry.getKey(), entry.getValue() ) );
494 public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
495 String repositoryId )
496 throws ArchivaRestServiceException
498 ProjectVersionMetadata projectVersionMetadata =
499 getProjectMetadata( groupId, artifactId, version, repositoryId );
501 if ( projectVersionMetadata == null )
503 return Boolean.FALSE;
506 Map<String, String> properties = new HashMap<String, String>();
508 MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
510 if ( metadataFacet != null && metadataFacet.toProperties() != null )
512 properties.putAll( metadataFacet.toProperties() );
516 metadataFacet = new GenericMetadataFacet();
519 properties.put( key, value );
521 metadataFacet.fromProperties( properties );
523 projectVersionMetadata.addFacet( metadataFacet );
525 RepositorySession repositorySession = repositorySessionFactory.createSession();
529 MetadataRepository metadataRepository = repositorySession.getRepository();
531 metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
533 repositorySession.save();
535 catch ( MetadataRepositoryException e )
537 log.error( e.getMessage(), e );
538 throw new ArchivaRestServiceException( e.getMessage(),
539 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
543 repositorySession.close();
548 public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
549 throws ArchivaRestServiceException
551 ProjectVersionMetadata projectVersionMetadata =
552 getProjectMetadata( groupId, artifactId, version, repositoryId );
554 if ( projectVersionMetadata == null )
556 return Boolean.FALSE;
559 GenericMetadataFacet metadataFacet =
560 (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
562 if ( metadataFacet != null && metadataFacet.toProperties() != null )
564 Map<String, String> properties = metadataFacet.toProperties();
565 properties.remove( key );
566 metadataFacet.setAdditionalProperties( properties );
573 RepositorySession repositorySession = repositorySessionFactory.createSession();
577 MetadataRepository metadataRepository = repositorySession.getRepository();
579 metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
581 repositorySession.save();
583 catch ( MetadataRepositoryException e )
585 log.error( e.getMessage(), e );
586 throw new ArchivaRestServiceException( e.getMessage(),
587 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() );
591 repositorySession.close();
596 public List<ArtifactContentEntry> getArtifactContentEntries( String groupId, String artifactId, String version,
597 String path, String repositoryId )
598 throws ArchivaRestServiceException
603 //---------------------------
605 //---------------------------
607 private List<String> getSelectedRepos( String repositoryId )
608 throws ArchivaRestServiceException
611 List<String> selectedRepos = getObservableRepos();
613 if ( CollectionUtils.isEmpty( selectedRepos ) )
619 if ( StringUtils.isNotEmpty( repositoryId ) )
621 // check user has karma on the repository
622 if ( !selectedRepos.contains( repositoryId ) )
624 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
625 Response.Status.FORBIDDEN.getStatusCode() );
627 selectedRepos = Collections.singletonList( repositoryId );
629 return selectedRepos;
632 private List<String> getSortedList( Set<String> set )
634 List<String> list = new ArrayList<String>( set );
635 Collections.sort( list );
639 private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
640 Collection<String> repoIds, String n )
641 throws MetadataResolutionException
643 Set<String> subNamespaces = new LinkedHashSet<String>();
644 for ( String repoId : repoIds )
646 subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
648 if ( subNamespaces.size() != 1 )
650 log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
655 for ( String repoId : repoIds )
657 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
658 if ( projects != null && !projects.isEmpty() )
660 log.debug( "{} is not collapsible as it has projects", n );
664 return collapseNamespaces( repositorySession, metadataResolver, repoIds,
665 n + "." + subNamespaces.iterator().next() );