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.collections4.ListUtils; 042import org.apache.commons.lang3.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 = null; 219 try 220 { 221 repositorySession = repositorySessionFactory.createSession(); 222 } 223 catch ( MetadataRepositoryException e ) 224 { 225 e.printStackTrace( ); 226 } 227 228 MetadataRepository metadataRepository = repositorySession.getRepository(); 229 230 Set<Artifact> artifactSet = new HashSet<>(); 231 232 try 233 { 234 for ( String repoId : checksumSearch.getRepositories() ) 235 { 236 Collection<ArtifactMetadata> artifactMetadatas = 237 metadataRepository.getArtifactsByChecksum( repositorySession, repoId, checksumSearch.getChecksum() ); 238 artifactSet.addAll( buildArtifacts( artifactMetadatas, repoId ) ); 239 } 240 241 return new ArrayList<>( artifactSet ); 242 243 } 244 catch ( MetadataRepositoryException e ) 245 { 246 log.error( e.getMessage(), e ); 247 throw new ArchivaRestServiceException( e.getMessage(), e ); 248 } 249 finally 250 { 251 repositorySession.closeQuietly(); 252 } 253 254 255 } 256 257 @Override 258 public StringList getObservablesRepoIds() 259 throws ArchivaRestServiceException 260 { 261 return new StringList( getObservableRepos() ); 262 } 263 264 @Override 265 public Response redirectToArtifactFile( String repositoryId, String groupId, String artifactId, String version, 266 String packaging, String classifier ) 267 throws ArchivaRestServiceException 268 { 269 try 270 { 271 // validate query 272 273 if ( StringUtils.isEmpty( groupId ) ) 274 { 275 return Response.status( new Response.StatusType() 276 { 277 @Override 278 public int getStatusCode() 279 { 280 return Response.Status.BAD_REQUEST.getStatusCode(); 281 } 282 283 @Override 284 public Response.Status.Family getFamily() 285 { 286 return Response.Status.BAD_REQUEST.getFamily(); 287 } 288 289 @Override 290 public String getReasonPhrase() 291 { 292 return "groupId mandatory"; 293 } 294 } ).build(); 295 } 296 297 if ( StringUtils.isEmpty( version ) ) 298 { 299 return Response.status( new Response.StatusType() 300 { 301 @Override 302 public int getStatusCode() 303 { 304 return Response.Status.BAD_REQUEST.getStatusCode(); 305 } 306 307 @Override 308 public Response.Status.Family getFamily() 309 { 310 return Response.Status.BAD_REQUEST.getFamily(); 311 } 312 313 @Override 314 public String getReasonPhrase() 315 { 316 return "version mandatory"; 317 } 318 } ).build(); 319 } 320 321 if ( StringUtils.isEmpty( artifactId ) ) 322 { 323 return Response.status( new Response.StatusType() 324 { 325 @Override 326 public int getStatusCode() 327 { 328 return Response.Status.BAD_REQUEST.getStatusCode(); 329 } 330 331 @Override 332 public Response.Status.Family getFamily() 333 { 334 return Response.Status.BAD_REQUEST.getFamily(); 335 } 336 337 @Override 338 public String getReasonPhrase() 339 { 340 return "artifactId mandatory"; 341 } 342 } ).build(); 343 } 344 345 SearchFields searchField = new SearchFields(); 346 searchField.setGroupId( groupId ); 347 searchField.setArtifactId( artifactId ); 348 searchField.setPackaging( StringUtils.isBlank( packaging ) ? "jar" : packaging ); 349 if ( !StringUtils.equals( version, LATEST_KEYWORD ) ) 350 { 351 searchField.setVersion( version ); 352 } 353 searchField.setClassifier( classifier ); 354 List<String> userRepos = getObservablesRepoIds().getStrings(); 355 searchField.setRepositories( 356 StringUtils.isEmpty( repositoryId ) ? userRepos : Arrays.asList( repositoryId ) ); 357 searchField.setExactSearch( true ); 358 SearchResults searchResults = repositorySearch.search( getPrincipal(), searchField, null ); 359 List<Artifact> artifacts = getArtifacts( searchResults ); 360 361 if ( artifacts.isEmpty() ) 362 { 363 return Response.status( new Response.StatusType() 364 { 365 @Override 366 public int getStatusCode() 367 { 368 return Response.Status.NO_CONTENT.getStatusCode(); 369 } 370 371 @Override 372 public Response.Status.Family getFamily() 373 { 374 return Response.Status.NO_CONTENT.getFamily(); 375 } 376 377 @Override 378 public String getReasonPhrase() 379 { 380 return "your query doesn't return any artifact"; 381 } 382 } ).build(); 383 } 384 385 // TODO improve that with querying lucene with null value for classifier 386 // so simple loop and retain only artifact with null classifier 387 if ( classifier == null ) 388 { 389 List<Artifact> filteredArtifacts = new ArrayList<>( artifacts.size() ); 390 for ( Artifact artifact : artifacts ) 391 { 392 if ( artifact.getClassifier() == null ) 393 { 394 filteredArtifacts.add( artifact ); 395 } 396 } 397 398 artifacts = filteredArtifacts; 399 } 400 401 // TODO return json result of the query ? 402 if ( artifacts.size() > 1 && !StringUtils.equals( version, LATEST_KEYWORD ) ) 403 { 404 return Response.status( new Response.StatusType() 405 { 406 @Override 407 public int getStatusCode() 408 { 409 return Response.Status.BAD_REQUEST.getStatusCode(); 410 } 411 412 @Override 413 public Response.Status.Family getFamily() 414 { 415 return Response.Status.BAD_REQUEST.getFamily(); 416 } 417 418 @Override 419 public String getReasonPhrase() 420 { 421 return "your query return more than one artifact"; 422 } 423 } ).build(); 424 } 425 426 // version is LATEST so we have to find the latest one from the result 427 if ( artifacts.size() > 1 && StringUtils.equals( version, LATEST_KEYWORD ) ) 428 { 429 TreeMap<String, Artifact> artifactPerVersion = new TreeMap<>( VersionComparator.getInstance() ); 430 431 for ( Artifact artifact : artifacts ) 432 { 433 artifactPerVersion.put( artifact.getVersion(), artifact ); 434 } 435 436 return Response.temporaryRedirect( 437 new URI( artifactPerVersion.lastEntry().getValue().getUrl() ) ).build(); 438 439 } 440 441 Artifact artifact = artifacts.get( 0 ); 442 443 return Response.temporaryRedirect( new URI( artifact.getUrl() ) ).build(); 444 } 445 catch ( Exception e ) 446 { 447 throw new ArchivaRestServiceException( e.getMessage(), e ); 448 } 449 } 450 451 452 //------------------------------------- 453 // internal 454 //------------------------------------- 455 protected List<Artifact> getArtifacts( SearchResults searchResults ) 456 throws ArchivaRestServiceException 457 { 458 459 if ( searchResults == null || searchResults.isEmpty() ) 460 { 461 return Collections.emptyList(); 462 } 463 List<Artifact> artifacts = new ArrayList<>( searchResults.getReturnedHitsCount() ); 464 for ( SearchResultHit hit : searchResults.getHits() ) 465 { 466 // duplicate Artifact one per available version 467 if ( hit.getVersions().size() > 0 ) 468 { 469 for ( String version : hit.getVersions() ) 470 { 471 472 Artifact versionned = getModelMapper().map( hit, Artifact.class ); 473 474 if ( StringUtils.isNotBlank( version ) ) 475 { 476 versionned.setVersion( version ); 477 versionned.setUrl( getArtifactUrl( versionned ) ); 478 479 artifacts.add( versionned ); 480 481 } 482 } 483 } 484 } 485 return artifacts; 486 } 487 488 489}