From a6fc548e9762db1d58eb7df78d1f941b3735ebe0 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Thu, 5 Apr 2012 13:04:10 +0000 Subject: [PATCH] [MRM-1620] add a tab to browse artifact content in artifact detail view implement artifact content browsing REST api git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@1309830 13f79535-47bb-0310-9956-ffa450edef68 --- .../rest/api/model/ArtifactContentEntry.java | 55 +++++- .../rest/api/services/BrowseService.java | 8 +- .../rest/services/DefaultBrowseService.java | 175 +++++++++++++++++- .../rest/services/BrowseServiceTest.java | 53 ++++++ .../archiva/webdav/RepositoryServlet.java | 8 +- 5 files changed, 291 insertions(+), 8 deletions(-) diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/ArtifactContentEntry.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/ArtifactContentEntry.java index fac87f2ca..7a9b2ff91 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/ArtifactContentEntry.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/ArtifactContentEntry.java @@ -31,15 +31,18 @@ public class ArtifactContentEntry private boolean file; + private int depth; + public ArtifactContentEntry() { // no op } - public ArtifactContentEntry( String name, boolean file ) + public ArtifactContentEntry( String name, boolean file, int depth ) { this.name = name; this.file = file; + this.depth = depth; } public String getName() @@ -62,6 +65,16 @@ public class ArtifactContentEntry this.file = file; } + public int getDepth() + { + return depth; + } + + public void setDepth( int depth ) + { + this.depth = depth; + } + @Override public String toString() { @@ -69,7 +82,47 @@ public class ArtifactContentEntry sb.append( "ArtifactContentEntry" ); sb.append( "{name='" ).append( name ).append( '\'' ); sb.append( ", file=" ).append( file ); + sb.append( ", depth=" ).append( depth ); sb.append( '}' ); return sb.toString(); } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( !( o instanceof ArtifactContentEntry ) ) + { + return false; + } + + ArtifactContentEntry that = (ArtifactContentEntry) o; + + if ( depth != that.depth ) + { + return false; + } + if ( file != that.file ) + { + return false; + } + if ( !name.equals( that.name ) ) + { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = name.hashCode(); + result = 31 * result + ( file ? 1 : 0 ); + result = 31 * result + depth; + return result; + } } diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/BrowseService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/BrowseService.java index ff628931b..0afa9dbc2 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/BrowseService.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/BrowseService.java @@ -146,10 +146,16 @@ public interface BrowseService @QueryParam( "repositoryId" ) String repositoryId ) throws ArchivaRestServiceException; + @Path( "artifactContentEntries/{g}/{a}/{v}" ) + @GET + @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) + @RedbackAuthorization( noPermission = true, noRestriction = true ) List getArtifactContentEntries( @PathParam( "g" ) String groupId, @PathParam( "a" ) String artifactId, @PathParam( "v" ) String version, - @PathParam( "" ) String path, + @QueryParam( "c" ) String classifier, + @QueryParam( "t" ) String type, + @QueryParam( "p" ) String path, @QueryParam( "repositoryId" ) String repositoryId ) throws ArchivaRestServiceException; } diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultBrowseService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultBrowseService.java index 8b0b11983..85a0da594 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultBrowseService.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultBrowseService.java @@ -31,6 +31,11 @@ import org.apache.archiva.metadata.repository.MetadataResolutionException; import org.apache.archiva.metadata.repository.MetadataResolver; import org.apache.archiva.metadata.repository.RepositorySession; import org.apache.archiva.metadata.repository.storage.maven2.MavenProjectFacet; +import org.apache.archiva.model.ArchivaArtifact; +import org.apache.archiva.repository.ManagedRepositoryContent; +import org.apache.archiva.repository.RepositoryContentFactory; +import org.apache.archiva.repository.RepositoryException; +import org.apache.archiva.repository.RepositoryNotFoundException; import org.apache.archiva.rest.api.model.Artifact; import org.apache.archiva.rest.api.model.ArtifactContentEntry; import org.apache.archiva.rest.api.model.BrowseResult; @@ -40,6 +45,7 @@ import org.apache.archiva.rest.api.model.TreeEntry; import org.apache.archiva.rest.api.model.VersionsList; import org.apache.archiva.rest.api.services.ArchivaRestServiceException; import org.apache.archiva.rest.api.services.BrowseService; +import org.apache.archiva.rest.services.utils.ArtifactContentEntryComparator; import org.apache.archiva.rest.services.utils.TreeDependencyNodeVisitor; import org.apache.archiva.security.ArchivaSecurityException; import org.apache.commons.collections.CollectionUtils; @@ -49,14 +55,19 @@ import org.springframework.stereotype.Service; import javax.inject.Inject; import javax.ws.rs.core.Response; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; /** * @author Olivier Lamy @@ -71,6 +82,9 @@ public class DefaultBrowseService @Inject private DependencyTreeBuilder dependencyTreeBuilder; + @Inject + private RepositoryContentFactory repositoryContentFactory; + public BrowseResult getRootGroups( String repositoryId ) throws ArchivaRestServiceException { @@ -594,16 +608,173 @@ public class DefaultBrowseService } public List getArtifactContentEntries( String groupId, String artifactId, String version, - String path, String repositoryId ) + String classifier, String type, String path, + String repositoryId ) throws ArchivaRestServiceException { - return null; + List selectedRepos = getSelectedRepos( repositoryId ); + try + { + for ( String repoId : selectedRepos ) + { + + ManagedRepositoryContent managedRepositoryContent = + repositoryContentFactory.getManagedRepositoryContent( repoId ); + ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier, + StringUtils.isEmpty( type ) ? "jar" : type, + repositoryId ); + File file = managedRepositoryContent.toFile( archivaArtifact ); + if ( file.exists() ) + { + return readFileEntries( file, path ); + } + } + } + catch ( IOException e ) + { + log.error( e.getMessage(), e ); + throw new ArchivaRestServiceException( e.getMessage(), + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() ); + } + catch ( RepositoryNotFoundException e ) + { + log.error( e.getMessage(), e ); + throw new ArchivaRestServiceException( e.getMessage(), + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() ); + } + catch ( RepositoryException e ) + { + log.error( e.getMessage(), e ); + throw new ArchivaRestServiceException( e.getMessage(), + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() ); + } + return Collections.emptyList(); } //--------------------------- // internals //--------------------------- + protected List readFileEntries( File file, String filterPath ) + throws IOException + { + Map artifactContentEntryMap = new HashMap(); + int filterDepth = StringUtils.countMatches( filterPath, "/" ); + if ( filterDepth == 0 ) + { + filterDepth = 1; + } + JarFile jarFile = new JarFile( file ); + try + { + Enumeration jarEntryEnumeration = jarFile.entries(); + while ( jarEntryEnumeration.hasMoreElements() ) + { + JarEntry entry = jarEntryEnumeration.nextElement(); + String entryName = entry.getName(); + String entryRootPath = getRootPath( entryName ); + int depth = StringUtils.countMatches( entryName, "/" ); + if ( StringUtils.isEmpty( filterPath ) && !artifactContentEntryMap.containsKey( entryRootPath ) ) + { + + artifactContentEntryMap.put( entryRootPath, + new ArtifactContentEntry( entryRootPath, !entry.isDirectory(), + depth ) ); + } + else + { + if ( StringUtils.startsWith( entryName, filterPath ) && ( depth > filterDepth || ( + !entry.isDirectory() && depth == filterDepth ) ) ) + { + // remove last / + String cleanedEntryName = StringUtils.endsWith( entryName, "/" ) + ? StringUtils.substringBeforeLast( entryName, "/" ) + : entryName; + artifactContentEntryMap.put( cleanedEntryName, + new ArtifactContentEntry( cleanedEntryName, !entry.isDirectory(), + depth ) ); + } + } + } + + if ( StringUtils.isNotEmpty( filterPath ) ) + { + // apply more filtering here + // search entries filterPath/blabla + Map filteredArtifactContentEntryMap = + new HashMap(); + + for ( Map.Entry entry : artifactContentEntryMap.entrySet() ) + { + filteredArtifactContentEntryMap.put( entry.getKey(), entry.getValue() ); + } + + List sorted = getSmallerDepthEntries( filteredArtifactContentEntryMap ); + if ( sorted == null ) + { + return Collections.emptyList(); + } + Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE ); + return sorted; + } + } + finally + { + if ( jarFile != null ) + { + jarFile.close(); + } + } + List sorted = new ArrayList( artifactContentEntryMap.values() ); + Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE ); + return sorted; + } + + private List getSmallerDepthEntries( Map entries ) + { + int smallestDepth = Integer.MAX_VALUE; + Map> perDepthList = new HashMap>(); + for ( Map.Entry entry : entries.entrySet() ) + { + + ArtifactContentEntry current = entry.getValue(); + + if ( current.getDepth() < smallestDepth ) + { + smallestDepth = current.getDepth(); + } + + List currentList = perDepthList.get( current.getDepth() ); + + if ( currentList == null ) + { + currentList = new ArrayList(); + currentList.add( current ); + perDepthList.put( current.getDepth(), currentList ); + } + else + { + currentList.add( current ); + } + + } + + return perDepthList.get( smallestDepth ); + } + + /** + * @param path + * @return org/apache -> org , org -> org + */ + private String getRootPath( String path ) + { + if ( StringUtils.contains( path, '/' ) ) + { + return StringUtils.substringBefore( path, "/" ); + } + return path; + } + private List getSelectedRepos( String repositoryId ) throws ArchivaRestServiceException { diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/BrowseServiceTest.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/BrowseServiceTest.java index cc1bedefe..854886f8a 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/BrowseServiceTest.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/BrowseServiceTest.java @@ -19,6 +19,7 @@ package org.apache.archiva.rest.services; */ import org.apache.archiva.metadata.model.ProjectVersionMetadata; +import org.apache.archiva.rest.api.model.ArtifactContentEntry; import org.apache.archiva.rest.api.model.BrowseResult; import org.apache.archiva.rest.api.model.BrowseResultEntry; import org.apache.archiva.rest.api.model.Entry; @@ -222,4 +223,56 @@ public class BrowseServiceTest deleteTestRepo( testRepoId ); } + + @Test + public void readArtifactContentEntries() + throws Exception + { + String testRepoId = "test-repo"; + // force guest user creation if not exists + if ( getUserService( authorizationHeader ).getGuestUser() == null ) + { + assertNotNull( getUserService( authorizationHeader ).createGuestUser() ); + } + + createAndIndexRepo( testRepoId, new File( getBasedir(), "src/test/repo-with-osgi" ).getAbsolutePath(), false ); + + BrowseService browseService = getBrowseService( authorizationHeader, true ); + + List artifactContentEntries = + browseService.getArtifactContentEntries( "commons-logging", "commons-logging", "1.1", null, null, null, + testRepoId ); + + log.info( "artifactContentEntries: {}", artifactContentEntries ); + + assertThat( artifactContentEntries ).isNotNull().isNotEmpty().hasSize( 2 ).contains( + new ArtifactContentEntry( "org", false, 1 ), new ArtifactContentEntry( "META-INF", false, 1 ) ); + deleteTestRepo( testRepoId ); + } + + @Test + public void readArtifactContentEntriesRootPath() + throws Exception + { + String testRepoId = "test-repo"; + // force guest user creation if not exists + if ( getUserService( authorizationHeader ).getGuestUser() == null ) + { + assertNotNull( getUserService( authorizationHeader ).createGuestUser() ); + } + + createAndIndexRepo( testRepoId, new File( getBasedir(), "src/test/repo-with-osgi" ).getAbsolutePath(), false ); + + BrowseService browseService = getBrowseService( authorizationHeader, true ); + + List artifactContentEntries = + browseService.getArtifactContentEntries( "commons-logging", "commons-logging", "1.1", null, null, "org", + testRepoId ); + + log.info( "artifactContentEntries: {}", artifactContentEntries ); + + assertThat( artifactContentEntries ).isNotNull().isNotEmpty().hasSize( 1 ).contains( + new ArtifactContentEntry( "org/apache", false, 2 ) ); + deleteTestRepo( testRepoId ); + } } diff --git a/archiva-modules/archiva-web/archiva-webdav/src/main/java/org/apache/archiva/webdav/RepositoryServlet.java b/archiva-modules/archiva-web/archiva-webdav/src/main/java/org/apache/archiva/webdav/RepositoryServlet.java index 4b7d31c30..c9b6e807e 100644 --- a/archiva-modules/archiva-web/archiva-webdav/src/main/java/org/apache/archiva/webdav/RepositoryServlet.java +++ b/archiva-modules/archiva-web/archiva-webdav/src/main/java/org/apache/archiva/webdav/RepositoryServlet.java @@ -22,6 +22,9 @@ package org.apache.archiva.webdav; import org.apache.archiva.admin.model.RepositoryAdminException; import org.apache.archiva.admin.model.beans.ManagedRepository; import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin; +import org.apache.archiva.configuration.ArchivaConfiguration; +import org.apache.archiva.configuration.ConfigurationEvent; +import org.apache.archiva.configuration.ConfigurationListener; import org.apache.archiva.security.ServletAuthenticator; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavLocatorFactory; @@ -35,9 +38,6 @@ import org.apache.jackrabbit.webdav.WebdavRequestImpl; import org.apache.jackrabbit.webdav.WebdavResponse; import org.apache.jackrabbit.webdav.WebdavResponseImpl; import org.apache.jackrabbit.webdav.server.AbstractWebdavServlet; -import org.apache.archiva.configuration.ArchivaConfiguration; -import org.apache.archiva.configuration.ConfigurationEvent; -import org.apache.archiva.configuration.ConfigurationListener; import org.codehaus.redback.integration.filter.authentication.HttpAuthenticator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -189,7 +189,7 @@ public class RepositoryServlet if ( !repoDir.mkdirs() ) { // Skip invalid directories. - log( "Unable to create missing directory for " + repo.getLocation() ); + log.info( "Unable to create missing directory for {}", repo.getLocation() ); continue; } } -- 2.39.5