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 ) 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 ( !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 searchField.setExactSearch( true ); 350 SearchResults searchResults = repositorySearch.search( getPrincipal(), searchField, null ); 351 List<Artifact> artifacts = getArtifacts( searchResults ); 352 353 if ( artifacts.isEmpty() ) 354 { 355 return Response.status( new Response.StatusType() 356 { 357 @Override 358 public int getStatusCode() 359 { 360 return Response.Status.NO_CONTENT.getStatusCode(); 361 } 362 363 @Override 364 public Response.Status.Family getFamily() 365 { 366 return Response.Status.NO_CONTENT.getFamily(); 367 } 368 369 @Override 370 public String getReasonPhrase() 371 { 372 return "your query doesn't return any artifact"; 373 } 374 } ).build(); 375 } 376 377 // TODO improve that with querying lucene with null value for classifier 378 // so simple loop and retain only artifact with null classifier 379 if ( classifier == null ) 380 { 381 List<Artifact> filteredArtifacts = new ArrayList<>( artifacts.size() ); 382 for ( Artifact artifact : artifacts ) 383 { 384 if ( artifact.getClassifier() == null ) 385 { 386 filteredArtifacts.add( artifact ); 387 } 388 } 389 390 artifacts = filteredArtifacts; 391 } 392 393 // TODO return json result of the query ? 394 if ( artifacts.size() > 1 && !StringUtils.equals( version, LATEST_KEYWORD ) ) 395 { 396 return Response.status( new Response.StatusType() 397 { 398 @Override 399 public int getStatusCode() 400 { 401 return Response.Status.BAD_REQUEST.getStatusCode(); 402 } 403 404 @Override 405 public Response.Status.Family getFamily() 406 { 407 return Response.Status.BAD_REQUEST.getFamily(); 408 } 409 410 @Override 411 public String getReasonPhrase() 412 { 413 return "your query return more than one artifact"; 414 } 415 } ).build(); 416 } 417 418 // version is LATEST so we have to find the latest one from the result 419 if ( artifacts.size() > 1 && StringUtils.equals( version, LATEST_KEYWORD ) ) 420 { 421 TreeMap<String, Artifact> artifactPerVersion = new TreeMap<>( VersionComparator.getInstance() ); 422 423 for ( Artifact artifact : artifacts ) 424 { 425 artifactPerVersion.put( artifact.getVersion(), artifact ); 426 } 427 428 return Response.temporaryRedirect( 429 new URI( artifactPerVersion.lastEntry().getValue().getUrl() ) ).build(); 430 431 } 432 433 Artifact artifact = artifacts.get( 0 ); 434 435 return Response.temporaryRedirect( new URI( artifact.getUrl() ) ).build(); 436 } 437 catch ( Exception e ) 438 { 439 throw new ArchivaRestServiceException( e.getMessage(), e ); 440 } 441 } 442 443 444 //------------------------------------- 445 // internal 446 //------------------------------------- 447 protected List<Artifact> getArtifacts( SearchResults searchResults ) 448 throws ArchivaRestServiceException 449 { 450 451 if ( searchResults == null || searchResults.isEmpty() ) 452 { 453 return Collections.emptyList(); 454 } 455 List<Artifact> artifacts = new ArrayList<>( searchResults.getReturnedHitsCount() ); 456 for ( SearchResultHit hit : searchResults.getHits() ) 457 { 458 // duplicate Artifact one per available version 459 if ( hit.getVersions().size() > 0 ) 460 { 461 for ( String version : hit.getVersions() ) 462 { 463 464 Artifact versionned = getModelMapper().map( hit, Artifact.class ); 465 466 if ( StringUtils.isNotBlank( version ) ) 467 { 468 versionned.setVersion( version ); 469 versionned.setUrl( getArtifactUrl( versionned ) ); 470 471 artifacts.add( versionned ); 472 473 } 474 } 475 } 476 } 477 return artifacts; 478 } 479 480 481}