001package org.apache.archiva.rest.services; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.archiva.common.utils.VersionComparator; 023import org.apache.archiva.indexer.search.RepositorySearch; 024import org.apache.archiva.indexer.search.RepositorySearchException; 025import org.apache.archiva.indexer.search.SearchFields; 026import org.apache.archiva.indexer.search.SearchResultHit; 027import org.apache.archiva.indexer.search.SearchResultLimits; 028import org.apache.archiva.indexer.search.SearchResults; 029import org.apache.archiva.maven2.model.Artifact; 030import org.apache.archiva.metadata.model.ArtifactMetadata; 031import org.apache.archiva.metadata.repository.MetadataRepository; 032import org.apache.archiva.metadata.repository.MetadataRepositoryException; 033import org.apache.archiva.metadata.repository.RepositorySession; 034import org.apache.archiva.metadata.repository.RepositorySessionFactory; 035import org.apache.archiva.rest.api.model.ChecksumSearch; 036import org.apache.archiva.rest.api.model.GroupIdList; 037import org.apache.archiva.rest.api.model.SearchRequest; 038import org.apache.archiva.rest.api.model.StringList; 039import org.apache.archiva.rest.api.services.ArchivaRestServiceException; 040import org.apache.archiva.rest.api.services.SearchService; 041import org.apache.commons.collections.ListUtils; 042import org.apache.commons.lang.StringUtils; 043import org.springframework.stereotype.Service; 044 045import javax.inject.Inject; 046import javax.ws.rs.core.Response; 047import java.net.URI; 048import java.util.ArrayList; 049import java.util.Arrays; 050import java.util.Collection; 051import java.util.Collections; 052import java.util.HashSet; 053import java.util.List; 054import java.util.Set; 055import java.util.TreeMap; 056 057/** 058 * @author Olivier Lamy 059 */ 060@Service( "searchService#rest" ) 061public class DefaultSearchService 062 extends AbstractRestService 063 implements SearchService 064{ 065 066 private static final String LATEST_KEYWORD = "LATEST"; 067 068 @Inject 069 private RepositorySearch repositorySearch; 070 071 @Inject 072 private RepositorySessionFactory repositorySessionFactory; 073 074 @Override 075 public List<Artifact> quickSearch( String queryString ) 076 throws ArchivaRestServiceException 077 { 078 if ( StringUtils.isBlank( queryString ) ) 079 { 080 return Collections.emptyList( ); 081 } 082 083 SearchResultLimits limits = new SearchResultLimits( 0 ); 084 try 085 { 086 SearchResults searchResults = 087 repositorySearch.search( getPrincipal( ), getObservableRepos( ), queryString, limits, 088 Collections.<String>emptyList( ) ); 089 return getArtifacts( searchResults ); 090 091 } 092 catch ( RepositorySearchException e ) 093 { 094 log.error( e.getMessage( ), e ); 095 throw new ArchivaRestServiceException( e.getMessage( ), e ); 096 } 097 } 098 099 @Override 100 public List<Artifact> quickSearchWithRepositories( SearchRequest searchRequest ) 101 throws ArchivaRestServiceException 102 { 103 String queryString = searchRequest.getQueryTerms( ); 104 if ( StringUtils.isBlank( queryString ) ) 105 { 106 return Collections.emptyList( ); 107 } 108 List<String> repositories = searchRequest.getRepositories( ); 109 if ( repositories == null || repositories.isEmpty( ) ) 110 { 111 repositories = getObservableRepos( ); 112 } 113 SearchResultLimits limits = 114 new SearchResultLimits( searchRequest.getPageSize( ), searchRequest.getSelectedPage( ) ); 115 try 116 { 117 SearchResults searchResults = repositorySearch.search( getPrincipal( ), repositories, queryString, limits, 118 Collections.<String>emptyList( ) ); 119 return getArtifacts( searchResults ); 120 121 } 122 catch ( RepositorySearchException e ) 123 { 124 log.error( e.getMessage( ), e ); 125 throw new ArchivaRestServiceException( e.getMessage( ), e ); 126 } 127 } 128 129 @Override 130 public List<Artifact> getArtifactVersions( String groupId, String artifactId, String packaging ) 131 throws ArchivaRestServiceException 132 { 133 if ( StringUtils.isBlank( groupId ) || StringUtils.isBlank( artifactId ) ) 134 { 135 return Collections.emptyList( ); 136 } 137 SearchFields searchField = new SearchFields( ); 138 searchField.setGroupId( groupId ); 139 searchField.setArtifactId( artifactId ); 140 searchField.setPackaging( StringUtils.isBlank( packaging ) ? "jar" : packaging ); 141 searchField.setRepositories( getObservableRepos( ) ); 142 143 try 144 { 145 SearchResults searchResults = repositorySearch.search( getPrincipal( ), searchField, null ); 146 return getArtifacts( searchResults ); 147 } 148 catch ( RepositorySearchException e ) 149 { 150 log.error( e.getMessage( ), e ); 151 throw new ArchivaRestServiceException( e.getMessage( ), e ); 152 } 153 } 154 155 @Override 156 public List<Artifact> searchArtifacts( SearchRequest searchRequest ) 157 throws ArchivaRestServiceException 158 { 159 if ( searchRequest == null ) 160 { 161 return Collections.emptyList( ); 162 } 163 SearchFields searchField = getModelMapper( ).map( searchRequest, SearchFields.class ); 164 SearchResultLimits limits = new SearchResultLimits( 0 ); 165 limits.setPageSize( searchRequest.getPageSize( ) ); 166 167 // if no repos set we use ones available for the user 168 if ( searchField.getRepositories( ) == null || searchField.getRepositories( ).isEmpty( ) ) 169 { 170 searchField.setRepositories( getObservableRepos( ) ); 171 } 172 173 try 174 { 175 SearchResults searchResults = repositorySearch.search( getPrincipal( ), searchField, limits ); 176 return getArtifacts( searchResults ); 177 } 178 catch ( RepositorySearchException e ) 179 { 180 log.error( e.getMessage( ), e ); 181 throw new ArchivaRestServiceException( e.getMessage( ), e ); 182 } 183 } 184 185 @Override 186 public GroupIdList getAllGroupIds( List<String> selectedRepos ) 187 throws ArchivaRestServiceException 188 { 189 List<String> observableRepos = getObservableRepos( ); 190 List<String> repos = ListUtils.intersection( observableRepos, selectedRepos ); 191 if ( repos == null || repos.isEmpty( ) ) 192 { 193 return new GroupIdList( Collections.<String>emptyList( ) ); 194 } 195 try 196 { 197 return new GroupIdList( new ArrayList<>( repositorySearch.getAllGroupIds( getPrincipal( ), repos ) ) ); 198 } 199 catch ( RepositorySearchException e ) 200 { 201 log.error( e.getMessage( ), e ); 202 throw new ArchivaRestServiceException( e.getMessage( ), e ); 203 } 204 205 } 206 207 208 public List<Artifact> getArtifactByChecksum( ChecksumSearch checksumSearch ) 209 throws ArchivaRestServiceException 210 { 211 212 // if no repos set we use ones available for the user 213 if ( checksumSearch.getRepositories( ) == null || checksumSearch.getRepositories( ).isEmpty( ) ) 214 { 215 checksumSearch.setRepositories( getObservableRepos( ) ); 216 } 217 218 RepositorySession repositorySession = repositorySessionFactory.createSession( ); 219 220 MetadataRepository metadataRepository = repositorySession.getRepository( ); 221 222 Set<Artifact> artifactSet = new HashSet<>( ); 223 224 try 225 { 226 for ( String repoId : checksumSearch.getRepositories( ) ) 227 { 228 Collection<ArtifactMetadata> artifactMetadatas = 229 metadataRepository.getArtifactsByChecksum( repoId, checksumSearch.getChecksum( ) ); 230 artifactSet.addAll( buildArtifacts( artifactMetadatas, repoId ) ); 231 } 232 233 return new ArrayList<>( artifactSet ); 234 235 } 236 catch ( MetadataRepositoryException e ) 237 { 238 log.error( e.getMessage( ), e ); 239 throw new ArchivaRestServiceException( e.getMessage( ), e ); 240 } 241 finally 242 { 243 repositorySession.closeQuietly( ); 244 } 245 246 247 } 248 249 @Override 250 public StringList getObservablesRepoIds( ) 251 throws ArchivaRestServiceException 252 { 253 return new StringList( getObservableRepos( ) ); 254 } 255 256 @Override 257 public Response redirectToArtifactFile( String repositoryId, String groupId, String artifactId, String version, 258 String packaging, String classifier, Boolean literalVersion ) 259 throws ArchivaRestServiceException 260 { 261 try 262 { 263 // validate query 264 265 if ( StringUtils.isEmpty( groupId ) ) 266 { 267 return Response.status( new Response.StatusType( ) 268 { 269 @Override 270 public int getStatusCode( ) 271 { 272 return Response.Status.BAD_REQUEST.getStatusCode( ); 273 } 274 275 @Override 276 public Response.Status.Family getFamily( ) 277 { 278 return Response.Status.BAD_REQUEST.getFamily( ); 279 } 280 281 @Override 282 public String getReasonPhrase( ) 283 { 284 return "groupId mandatory"; 285 } 286 } ).build( ); 287 } 288 289 if ( StringUtils.isEmpty( version ) ) 290 { 291 return Response.status( new Response.StatusType( ) 292 { 293 @Override 294 public int getStatusCode( ) 295 { 296 return Response.Status.BAD_REQUEST.getStatusCode( ); 297 } 298 299 @Override 300 public Response.Status.Family getFamily( ) 301 { 302 return Response.Status.BAD_REQUEST.getFamily( ); 303 } 304 305 @Override 306 public String getReasonPhrase( ) 307 { 308 return "version mandatory"; 309 } 310 } ).build( ); 311 } 312 313 if ( StringUtils.isEmpty( artifactId ) ) 314 { 315 return Response.status( new Response.StatusType( ) 316 { 317 @Override 318 public int getStatusCode( ) 319 { 320 return Response.Status.BAD_REQUEST.getStatusCode( ); 321 } 322 323 @Override 324 public Response.Status.Family getFamily( ) 325 { 326 return Response.Status.BAD_REQUEST.getFamily( ); 327 } 328 329 @Override 330 public String getReasonPhrase( ) 331 { 332 return "artifactId mandatory"; 333 } 334 } ).build( ); 335 } 336 337 SearchFields searchField = new SearchFields( ); 338 searchField.setGroupId( groupId ); 339 searchField.setArtifactId( artifactId ); 340 searchField.setPackaging( StringUtils.isBlank( packaging ) ? "jar" : packaging ); 341 if ( literalVersion.booleanValue( ) || !StringUtils.equals( version, LATEST_KEYWORD ) ) 342 { 343 searchField.setVersion( version ); 344 } 345 searchField.setClassifier( classifier ); 346 List<String> userRepos = getObservablesRepoIds( ).getStrings( ); 347 searchField.setRepositories( 348 StringUtils.isEmpty( repositoryId ) ? userRepos : Arrays.asList( repositoryId ) ); 349 log.debug( "Searching repository {}", repositoryId ); 350 searchField.setExactSearch( true ); 351 SearchResults searchResults = repositorySearch.search( getPrincipal( ), searchField, null ); 352 List<Artifact> artifacts = getArtifacts( searchResults, repositoryId ); 353 354 if ( artifacts.isEmpty( ) ) 355 { 356 return Response.status( new Response.StatusType( ) 357 { 358 @Override 359 public int getStatusCode( ) 360 { 361 return Response.Status.NO_CONTENT.getStatusCode( ); 362 } 363 364 @Override 365 public Response.Status.Family getFamily( ) 366 { 367 return Response.Status.NO_CONTENT.getFamily( ); 368 } 369 370 @Override 371 public String getReasonPhrase( ) 372 { 373 return "your query doesn't return any artifact"; 374 } 375 } ).build( ); 376 } 377 378 // TODO improve that with querying lucene with null value for classifier 379 // so simple loop and retain only artifact with null classifier 380 if ( classifier == null ) 381 { 382 List<Artifact> filteredArtifacts = new ArrayList<>( artifacts.size( ) ); 383 for ( Artifact artifact : artifacts ) 384 { 385 if ( artifact.getClassifier( ) == null ) 386 { 387 filteredArtifacts.add( artifact ); 388 } 389 } 390 391 artifacts = filteredArtifacts; 392 } 393 394 // TODO return json result of the query ? 395 if ( artifacts.size( ) > 1 && ( literalVersion || !StringUtils.equals( version, LATEST_KEYWORD ) ) ) 396 { 397 return Response.status( new Response.StatusType( ) 398 { 399 @Override 400 public int getStatusCode( ) 401 { 402 return Response.Status.BAD_REQUEST.getStatusCode( ); 403 } 404 405 @Override 406 public Response.Status.Family getFamily( ) 407 { 408 return Response.Status.BAD_REQUEST.getFamily( ); 409 } 410 411 @Override 412 public String getReasonPhrase( ) 413 { 414 return "your query return more than one artifact"; 415 } 416 } ).build( ); 417 } 418 419 // version is LATEST so we have to find the latest one from the result 420 if ( !literalVersion && ( artifacts.size( ) > 1 && StringUtils.equals( version, LATEST_KEYWORD ) ) ) 421 { 422 TreeMap<String, Artifact> artifactPerVersion = new TreeMap<>( VersionComparator.getInstance( ) ); 423 424 for ( Artifact artifact : artifacts ) 425 { 426 artifactPerVersion.put( artifact.getVersion( ), artifact ); 427 } 428 429 return Response.temporaryRedirect( 430 new URI( artifactPerVersion.lastEntry( ).getValue( ).getUrl( ) ) ).build( ); 431 432 } 433 434 Artifact artifact = artifacts.get( 0 ); 435 log.debug( "Returning artifact {}, {}", artifact.getUrl( ), artifact.getRepositoryId( ) ); 436 return Response.temporaryRedirect( new URI( artifact.getUrl( ) ) ).build( ); 437 } 438 catch ( Exception e ) 439 { 440 throw new ArchivaRestServiceException( e.getMessage( ), e ); 441 } 442 } 443 444 protected List<Artifact> getArtifacts( SearchResults searchResults ) 445 throws ArchivaRestServiceException 446 { 447 return getArtifacts( searchResults, null ); 448 } 449 450 //------------------------------------- 451 // internal 452 //------------------------------------- 453 protected List<Artifact> getArtifacts( SearchResults searchResults, String repositoryId ) 454 throws ArchivaRestServiceException 455 { 456 457 if ( searchResults == null || searchResults.isEmpty() ) 458 { 459 return Collections.emptyList(); 460 } 461 List<Artifact> artifacts = new ArrayList<>( searchResults.getReturnedHitsCount() ); 462 for ( SearchResultHit hit : searchResults.getHits() ) 463 { 464 // duplicate Artifact one per available version 465 if ( hit.getVersions().size() > 0 ) 466 { 467 for ( String version : hit.getVersions() ) 468 { 469 470 Artifact versionned = getModelMapper().map( hit, Artifact.class ); 471 472 if ( StringUtils.isNotBlank( version ) ) 473 { 474 versionned.setVersion( version ); 475 versionned.setUrl( getArtifactUrl( versionned, repositoryId ) ); 476 477 artifacts.add( versionned ); 478 479 } 480 } 481 } 482 } 483 return artifacts; 484 } 485 486 487}