From 85047d3bb6163b3223ec9a4b90dae2a968d6bcb6 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Tue, 20 May 2014 15:04:47 +1000 Subject: [PATCH] [MRM-1843] provide mechanism to obtain the latest version of an artifact start download api currently redirect --- .../indexer/search/MavenRepositorySearch.java | 62 +++++++++++++++---- .../archiva/indexer/search/SearchFields.java | 17 +++++ .../rest/api/services/SearchService.java | 15 ++++- .../rest/services/AbstractRestService.java | 29 +++++++-- .../rest/services/DefaultSearchService.java | 50 ++++++++++++++- 5 files changed, 152 insertions(+), 21 deletions(-) diff --git a/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/archiva/indexer/search/MavenRepositorySearch.java b/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/archiva/indexer/search/MavenRepositorySearch.java index 2a41526e2..848ca78c1 100644 --- a/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/archiva/indexer/search/MavenRepositorySearch.java +++ b/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/archiva/indexer/search/MavenRepositorySearch.java @@ -29,16 +29,23 @@ import org.apache.archiva.common.plexusbridge.PlexusSisuBridge; import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException; import org.apache.archiva.indexer.util.SearchUtil; import org.apache.commons.lang.StringUtils; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.Query; import org.apache.maven.index.ArtifactInfo; import org.apache.maven.index.FlatSearchRequest; import org.apache.maven.index.FlatSearchResponse; import org.apache.maven.index.MAVEN; import org.apache.maven.index.NexusIndexer; import org.apache.maven.index.OSGI; +import org.apache.maven.index.QueryCreator; +import org.apache.maven.index.SearchType; import org.apache.maven.index.context.IndexCreator; import org.apache.maven.index.context.IndexingContext; +import org.apache.maven.index.expr.SearchExpression; +import org.apache.maven.index.expr.SearchTyped; import org.apache.maven.index.expr.SourcedSearchExpression; import org.apache.maven.index.expr.UserInputSearchExpression; import org.slf4j.Logger; @@ -58,7 +65,7 @@ import java.util.Set; /** * RepositorySearch implementation which uses the Maven Indexer for searching. */ -@Service("repositorySearch#maven") +@Service( "repositorySearch#maven" ) public class MavenRepositorySearch implements RepositorySearch { @@ -66,6 +73,8 @@ public class MavenRepositorySearch private NexusIndexer indexer; + private QueryCreator queryCreator; + private ManagedRepositoryAdmin managedRepositoryAdmin; private ProxyConnectorAdmin proxyConnectorAdmin; @@ -83,6 +92,7 @@ public class MavenRepositorySearch throws PlexusSisuBridgeException { this.indexer = plexusSisuBridge.lookup( NexusIndexer.class ); + this.queryCreator = plexusSisuBridge.lookup( QueryCreator.class ); this.managedRepositoryAdmin = managedRepositoryAdmin; this.mavenIndexerUtils = mavenIndexerUtils; this.proxyConnectorAdmin = proxyConnectorAdmin; @@ -153,28 +163,35 @@ public class MavenRepositorySearch BooleanQuery q = new BooleanQuery(); if ( StringUtils.isNotBlank( searchFields.getGroupId() ) ) { - q.add( indexer.constructQuery( MAVEN.GROUP_ID, new UserInputSearchExpression( searchFields.getGroupId() ) ), - Occur.MUST ); + q.add( indexer.constructQuery( MAVEN.GROUP_ID, searchFields.isExactSearch() + ? new SourcedSearchExpression( searchFields.getGroupId() ) + : new UserInputSearchExpression( searchFields.getGroupId() ) + ), Occur.MUST + ); } if ( StringUtils.isNotBlank( searchFields.getArtifactId() ) ) { q.add( indexer.constructQuery( MAVEN.ARTIFACT_ID, - new UserInputSearchExpression( searchFields.getArtifactId() ) ), Occur.MUST + searchFields.isExactSearch() + ? new SourcedSearchExpression( searchFields.getArtifactId() ) + : new UserInputSearchExpression( searchFields.getArtifactId() ) + ), Occur.MUST ); } if ( StringUtils.isNotBlank( searchFields.getVersion() ) ) { - q.add( indexer.constructQuery( MAVEN.VERSION, new SourcedSearchExpression( searchFields.getVersion() ) ), - Occur.MUST ); + q.add( indexer.constructQuery( MAVEN.VERSION, searchFields.isExactSearch() ? new SourcedSearchExpression( + searchFields.getVersion() ) : new SourcedSearchExpression( searchFields.getVersion() ) ), Occur.MUST ); } if ( StringUtils.isNotBlank( searchFields.getPackaging() ) ) { - q.add( - indexer.constructQuery( MAVEN.PACKAGING, new UserInputSearchExpression( searchFields.getPackaging() ) ), - Occur.MUST ); + q.add( indexer.constructQuery( MAVEN.PACKAGING, searchFields.isExactSearch() ? new SourcedSearchExpression( + searchFields.getPackaging() ) : new UserInputSearchExpression( searchFields.getPackaging() ) ), + Occur.MUST + ); } if ( StringUtils.isNotBlank( searchFields.getClassName() ) ) @@ -247,10 +264,16 @@ public class MavenRepositorySearch if ( StringUtils.isNotBlank( searchFields.getClassifier() ) ) { - q.add( indexer.constructQuery( MAVEN.CLASSIFIER, - new UserInputSearchExpression( searchFields.getClassifier() ) ), Occur.MUST + q.add( indexer.constructQuery( MAVEN.CLASSIFIER, searchFields.isExactSearch() ? new SourcedSearchExpression( + searchFields.getClassifier() ) : new UserInputSearchExpression( searchFields.getClassifier() ) ), + Occur.MUST ); } + else if ( searchFields.isExactSearch() ) + { + //TODO improvement in case of exact search and no classifier we must query for classifier with null value + // currently it's done in DefaultSearchService with some filtering + } if ( q.getClauses() == null || q.getClauses().length <= 0 ) { @@ -261,6 +284,23 @@ public class MavenRepositorySearch searchFields.getRepositories(), searchFields.isIncludePomArtifacts() ); } + private static class NullSearch implements SearchTyped, SearchExpression + { + private static final NullSearch INSTANCE = new NullSearch(); + + @Override + public String getStringValue() + { + return "[[NULL_VALUE]]"; + } + + @Override + public SearchType getSearchType() + { + return SearchType.EXACT; + } + } + private SearchResults search( SearchResultLimits limits, BooleanQuery q, List indexingContextIds, List filters, List selectedRepos, boolean includePoms ) diff --git a/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/archiva/indexer/search/SearchFields.java b/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/archiva/indexer/search/SearchFields.java index fd2353423..e5844a765 100644 --- a/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/archiva/indexer/search/SearchFields.java +++ b/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/archiva/indexer/search/SearchFields.java @@ -114,6 +114,13 @@ public class SearchFields private String classifier; + /** + * we use exact String matching search + * + * @since 2.1.0 + */ + private boolean exactSearch = false; + public SearchFields() { // no op @@ -281,6 +288,16 @@ public class SearchFields this.bundleRequireBundle = bundleRequireBundle; } + public boolean isExactSearch() + { + return exactSearch; + } + + public void setExactSearch( boolean exactSearch ) + { + this.exactSearch = exactSearch; + } + @Override public String toString() { diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/SearchService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/SearchService.java index de884541c..319abdfeb 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/SearchService.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/SearchService.java @@ -21,10 +21,10 @@ package org.apache.archiva.rest.api.services; import org.apache.archiva.maven2.model.Artifact; +import org.apache.archiva.redback.authorization.RedbackAuthorization; import org.apache.archiva.rest.api.model.GroupIdList; import org.apache.archiva.rest.api.model.SearchRequest; import org.apache.archiva.rest.api.model.StringList; -import org.apache.archiva.redback.authorization.RedbackAuthorization; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -32,6 +32,7 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import java.util.List; @Path( "/searchService/" ) @@ -60,8 +61,7 @@ public interface SearchService @POST @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) @RedbackAuthorization( noPermission = true, noRestriction = true ) - - List quickSearchWithRepositories( SearchRequest searchRequest ) + List quickSearchWithRepositories( SearchRequest searchRequest ) throws ArchivaRestServiceException; /** @@ -127,4 +127,13 @@ public interface SearchService throws ArchivaRestServiceException; */ + @GET + @Path( "/artifact" ) + @Produces( "text/html" ) + @RedbackAuthorization( noPermission = true, noRestriction = true ) + Response redirectToArtifactFile( @QueryParam( "r" ) String repositoryId, @QueryParam( "g" ) String groupId, + @QueryParam( "a" ) String artifactId, @QueryParam( "v" ) String version, + @QueryParam( "p" ) String packaging, @QueryParam( "c" ) String classifier ) + throws ArchivaRestServiceException; + } diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/AbstractRestService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/AbstractRestService.java index 6fb7d0bd0..3443625e5 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/AbstractRestService.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/AbstractRestService.java @@ -57,6 +57,7 @@ import org.springframework.context.ApplicationContext; import javax.inject.Inject; import javax.inject.Named; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import java.util.ArrayList; @@ -87,7 +88,7 @@ public abstract class AbstractRestService * FIXME: this could be multiple implementations and needs to be configured. */ @Inject - @Named( value = "repositorySessionFactory" ) + @Named(value = "repositorySessionFactory") protected RepositorySessionFactory repositorySessionFactory; @Inject @@ -100,17 +101,20 @@ public abstract class AbstractRestService protected RepositoryContentFactory repositoryContentFactory; @Inject - @Named( value = "archivaTaskScheduler#repository" ) + @Named(value = "archivaTaskScheduler#repository") protected DefaultRepositoryArchivaTaskScheduler repositoryTaskScheduler; @Inject - @Named( value = "userConfiguration#default" ) + @Named(value = "userConfiguration#default") protected UserConfiguration config; @Context protected HttpServletRequest httpServletRequest; + @Context + protected HttpServletResponse httpServletResponse; + protected AuditInformation getAuditInformation() { RedbackRequestInformation redbackRequestInformation = RedbackAuthenticationThreadLocal.get(); @@ -212,6 +216,13 @@ public abstract class AbstractRestService */ protected String getArtifactUrl( Artifact artifact ) throws ArchivaRestServiceException + { + return getArtifactUrl( artifact, null ); + } + + + protected String getArtifactUrl( Artifact artifact, String repositoryId ) + throws ArchivaRestServiceException { try { @@ -225,10 +236,16 @@ public abstract class AbstractRestService sb.append( "/repository" ); - // FIXME when artifact come from a remote repository when have here the remote repo id + // when artifact come from a remote repository when have here the remote repo id // we must replace it with a valid managed one available for the user. - - sb.append( '/' ).append( artifact.getContext() ); + if ( StringUtils.isEmpty( repositoryId ) ) + { + sb.append( '/' ).append( artifact.getContext() ); + } + else + { + sb.append( '/' ).append( repositoryId ); + } sb.append( '/' ).append( StringUtils.replaceChars( artifact.getGroupId(), '.', '/' ) ); sb.append( '/' ).append( artifact.getArtifactId() ); diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultSearchService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultSearchService.java index d944893aa..9dae9491d 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultSearchService.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultSearchService.java @@ -37,14 +37,17 @@ import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import javax.inject.Inject; +import javax.ws.rs.core.Response; +import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; /** * @author Olivier Lamy */ -@Service( "searchService#rest" ) +@Service("searchService#rest") public class DefaultSearchService extends AbstractRestService implements SearchService @@ -205,6 +208,51 @@ public class DefaultSearchService return new StringList( getObservableRepos() ); } + @Override + public Response redirectToArtifactFile( String repositoryId, String groupId, String artifactId, String version, + String packaging, String classifier ) + throws ArchivaRestServiceException + { + try + { + + SearchFields searchField = new SearchFields(); + searchField.setGroupId( groupId ); + searchField.setArtifactId( artifactId ); + searchField.setPackaging( StringUtils.isBlank( packaging ) ? "jar" : packaging ); + searchField.setVersion( version ); + searchField.setClassifier( classifier ); + searchField.setRepositories( Arrays.asList( repositoryId ) ); + searchField.setExactSearch( true ); + SearchResults searchResults = repositorySearch.search( getPrincipal(), searchField, null ); + List artifacts = getArtifacts( searchResults ); + + // TODO improve that with querying lucene with null value for classifier + // so simple loop and retain only artifact with null classifier + if ( classifier == null ) + { + List filteredArtifacts = new ArrayList<>( artifacts.size() ); + for ( Artifact artifact : artifacts ) + { + if ( artifact.getClassifier() == null ) + { + filteredArtifacts.add( artifact ); + } + } + + artifacts = filteredArtifacts; + } + + String artifactUrl = getArtifactUrl( artifacts.get( 0 ), repositoryId ); + + return Response.temporaryRedirect( new URI( artifactUrl ) ).build(); + } + catch ( Exception e ) + { + throw new ArchivaRestServiceException( e.getMessage(), e ); + } + } + //------------------------------------- // internal //------------------------------------- -- 2.39.5