001 package org.apache.archiva.indexer.search; 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 022 import org.apache.archiva.admin.model.RepositoryAdminException; 023 import org.apache.archiva.admin.model.beans.ManagedRepository; 024 import org.apache.archiva.admin.model.beans.ProxyConnector; 025 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin; 026 import org.apache.archiva.admin.model.proxyconnector.ProxyConnectorAdmin; 027 import org.apache.archiva.common.plexusbridge.MavenIndexerUtils; 028 import org.apache.archiva.common.plexusbridge.PlexusSisuBridge; 029 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException; 030 import org.apache.archiva.indexer.util.SearchUtil; 031 import org.apache.commons.lang.StringUtils; 032 import org.apache.lucene.search.BooleanClause.Occur; 033 import org.apache.lucene.search.BooleanQuery; 034 import org.apache.maven.index.ArtifactInfo; 035 import org.apache.maven.index.FlatSearchRequest; 036 import org.apache.maven.index.FlatSearchResponse; 037 import org.apache.maven.index.MAVEN; 038 import org.apache.maven.index.NexusIndexer; 039 import org.apache.maven.index.OSGI; 040 import org.apache.maven.index.context.IndexCreator; 041 import org.apache.maven.index.context.IndexingContext; 042 import org.apache.maven.index.expr.SourcedSearchExpression; 043 import org.apache.maven.index.expr.UserInputSearchExpression; 044 import org.slf4j.Logger; 045 import org.slf4j.LoggerFactory; 046 import org.springframework.stereotype.Service; 047 048 import javax.inject.Inject; 049 import java.io.IOException; 050 import java.util.ArrayList; 051 import java.util.Collection; 052 import java.util.Collections; 053 import java.util.HashSet; 054 import java.util.List; 055 import java.util.Map; 056 import java.util.Set; 057 058 /** 059 * RepositorySearch implementation which uses the Maven Indexer for searching. 060 */ 061 @Service("repositorySearch#maven") 062 public class MavenRepositorySearch 063 implements RepositorySearch 064 { 065 private Logger log = LoggerFactory.getLogger( getClass() ); 066 067 private NexusIndexer indexer; 068 069 private ManagedRepositoryAdmin managedRepositoryAdmin; 070 071 private ProxyConnectorAdmin proxyConnectorAdmin; 072 073 private MavenIndexerUtils mavenIndexerUtils; 074 075 protected MavenRepositorySearch() 076 { 077 // for test purpose 078 } 079 080 @Inject 081 public MavenRepositorySearch( PlexusSisuBridge plexusSisuBridge, ManagedRepositoryAdmin managedRepositoryAdmin, 082 MavenIndexerUtils mavenIndexerUtils, ProxyConnectorAdmin proxyConnectorAdmin ) 083 throws PlexusSisuBridgeException 084 { 085 this.indexer = plexusSisuBridge.lookup( NexusIndexer.class ); 086 this.managedRepositoryAdmin = managedRepositoryAdmin; 087 this.mavenIndexerUtils = mavenIndexerUtils; 088 this.proxyConnectorAdmin = proxyConnectorAdmin; 089 } 090 091 /** 092 * @see RepositorySearch#search(String, List, String, SearchResultLimits, List) 093 */ 094 public SearchResults search( String principal, List<String> selectedRepos, String term, SearchResultLimits limits, 095 List<String> previousSearchTerms ) 096 throws RepositorySearchException 097 { 098 List<String> indexingContextIds = addIndexingContexts( selectedRepos ); 099 100 // since upgrade to nexus 2.0.0, query has changed from g:[QUERIED TERM]* to g:*[QUERIED TERM]* 101 // resulting to more wildcard searches so we need to increase max clause count 102 BooleanQuery.setMaxClauseCount( Integer.MAX_VALUE ); 103 BooleanQuery q = new BooleanQuery(); 104 105 if ( previousSearchTerms == null || previousSearchTerms.isEmpty() ) 106 { 107 constructQuery( term, q ); 108 } 109 else 110 { 111 for ( String previousTerm : previousSearchTerms ) 112 { 113 BooleanQuery iQuery = new BooleanQuery(); 114 constructQuery( previousTerm, iQuery ); 115 116 q.add( iQuery, Occur.MUST ); 117 } 118 119 BooleanQuery iQuery = new BooleanQuery(); 120 constructQuery( term, iQuery ); 121 q.add( iQuery, Occur.MUST ); 122 } 123 124 // we retun only artifacts without classifier in quick search, olamy cannot find a way to say with this field empty 125 // FIXME cannot find a way currently to setup this in constructQuery !!! 126 return search( limits, q, indexingContextIds, NoClassifierArtifactInfoFilter.LIST, selectedRepos, false ); 127 128 } 129 130 /** 131 * @see RepositorySearch#search(String, SearchFields, SearchResultLimits) 132 */ 133 public SearchResults search( String principal, SearchFields searchFields, SearchResultLimits limits ) 134 throws RepositorySearchException 135 { 136 if ( searchFields.getRepositories() == null ) 137 { 138 throw new RepositorySearchException( "Repositories cannot be null." ); 139 } 140 141 List<String> indexingContextIds = addIndexingContexts( searchFields.getRepositories() ); 142 143 // if no index found in the specified ones return an empty search result instead of doing a search on all index 144 // olamy: IMHO doesn't make sense 145 if ( !searchFields.getRepositories().isEmpty() && ( indexingContextIds == null 146 || indexingContextIds.isEmpty() ) ) 147 { 148 return new SearchResults(); 149 } 150 151 BooleanQuery q = new BooleanQuery(); 152 if ( StringUtils.isNotBlank( searchFields.getGroupId() ) ) 153 { 154 q.add( indexer.constructQuery( MAVEN.GROUP_ID, new UserInputSearchExpression( searchFields.getGroupId() ) ), 155 Occur.MUST ); 156 } 157 158 if ( StringUtils.isNotBlank( searchFields.getArtifactId() ) ) 159 { 160 q.add( indexer.constructQuery( MAVEN.ARTIFACT_ID, 161 new UserInputSearchExpression( searchFields.getArtifactId() ) ), 162 Occur.MUST ); 163 } 164 165 if ( StringUtils.isNotBlank( searchFields.getVersion() ) ) 166 { 167 q.add( indexer.constructQuery( MAVEN.VERSION, new SourcedSearchExpression( searchFields.getVersion() ) ), 168 Occur.MUST ); 169 } 170 171 if ( StringUtils.isNotBlank( searchFields.getPackaging() ) ) 172 { 173 q.add( 174 indexer.constructQuery( MAVEN.PACKAGING, new UserInputSearchExpression( searchFields.getPackaging() ) ), 175 Occur.MUST ); 176 } 177 178 if ( StringUtils.isNotBlank( searchFields.getClassName() ) ) 179 { 180 q.add( indexer.constructQuery( MAVEN.CLASSNAMES, 181 new UserInputSearchExpression( searchFields.getClassName() ) ), Occur.MUST ); 182 } 183 184 if ( StringUtils.isNotBlank( searchFields.getBundleSymbolicName() ) ) 185 { 186 q.add( indexer.constructQuery( OSGI.SYMBOLIC_NAME, 187 new UserInputSearchExpression( searchFields.getBundleSymbolicName() ) ), 188 Occur.MUST ); 189 } 190 191 if ( StringUtils.isNotBlank( searchFields.getBundleVersion() ) ) 192 { 193 q.add( indexer.constructQuery( OSGI.VERSION, 194 new UserInputSearchExpression( searchFields.getBundleVersion() ) ), 195 Occur.MUST ); 196 } 197 198 if ( StringUtils.isNotBlank( searchFields.getBundleExportPackage() ) ) 199 { 200 q.add( indexer.constructQuery( OSGI.EXPORT_PACKAGE, 201 new UserInputSearchExpression( searchFields.getBundleExportPackage() ) ), 202 Occur.MUST ); 203 } 204 205 if ( StringUtils.isNotBlank( searchFields.getBundleExportService() ) ) 206 { 207 q.add( indexer.constructQuery( OSGI.EXPORT_SERVICE, 208 new UserInputSearchExpression( searchFields.getBundleExportService() ) ), 209 Occur.MUST ); 210 } 211 212 if ( StringUtils.isNotBlank( searchFields.getBundleImportPackage() ) ) 213 { 214 q.add( indexer.constructQuery( OSGI.IMPORT_PACKAGE, 215 new UserInputSearchExpression( searchFields.getBundleImportPackage() ) ), 216 Occur.MUST ); 217 } 218 219 if ( StringUtils.isNotBlank( searchFields.getBundleName() ) ) 220 { 221 q.add( indexer.constructQuery( OSGI.NAME, new UserInputSearchExpression( searchFields.getBundleName() ) ), 222 Occur.MUST ); 223 } 224 225 if ( StringUtils.isNotBlank( searchFields.getBundleImportPackage() ) ) 226 { 227 q.add( indexer.constructQuery( OSGI.IMPORT_PACKAGE, 228 new UserInputSearchExpression( searchFields.getBundleImportPackage() ) ), 229 Occur.MUST ); 230 } 231 232 if ( StringUtils.isNotBlank( searchFields.getBundleRequireBundle() ) ) 233 { 234 q.add( indexer.constructQuery( OSGI.REQUIRE_BUNDLE, 235 new UserInputSearchExpression( searchFields.getBundleRequireBundle() ) ), 236 Occur.MUST ); 237 } 238 239 if ( StringUtils.isNotBlank( searchFields.getClassifier() ) ) 240 { 241 q.add( indexer.constructQuery( MAVEN.CLASSIFIER, 242 new UserInputSearchExpression( searchFields.getClassifier() ) ), 243 Occur.MUST ); 244 } 245 246 if ( q.getClauses() == null || q.getClauses().length <= 0 ) 247 { 248 throw new RepositorySearchException( "No search fields set." ); 249 } 250 251 return search( limits, q, indexingContextIds, Collections.<ArtifactInfoFilter>emptyList(), 252 searchFields.getRepositories(), searchFields.isIncludePomArtifacts() ); 253 } 254 255 private SearchResults search( SearchResultLimits limits, BooleanQuery q, List<String> indexingContextIds, 256 List<? extends ArtifactInfoFilter> filters, List<String> selectedRepos, 257 boolean includePoms ) 258 throws RepositorySearchException 259 { 260 261 try 262 { 263 FlatSearchRequest request = new FlatSearchRequest( q ); 264 request.setContexts( getIndexingContexts( indexingContextIds ) ); 265 266 FlatSearchResponse response = indexer.searchFlat( request ); 267 268 if ( response == null || response.getTotalHits() == 0 ) 269 { 270 SearchResults results = new SearchResults(); 271 results.setLimits( limits ); 272 return results; 273 } 274 275 return convertToSearchResults( response, limits, filters, selectedRepos, includePoms ); 276 } 277 catch ( IOException e ) 278 { 279 throw new RepositorySearchException( e.getMessage(), e ); 280 } 281 catch ( RepositoryAdminException e ) 282 { 283 throw new RepositorySearchException( e.getMessage(), e ); 284 } 285 286 } 287 288 private List<IndexingContext> getIndexingContexts( List<String> ids ) 289 { 290 List<IndexingContext> contexts = new ArrayList<IndexingContext>( ids.size() ); 291 292 for ( String id : ids ) 293 { 294 IndexingContext context = indexer.getIndexingContexts().get( id ); 295 if ( context != null ) 296 { 297 contexts.add( context ); 298 } 299 else 300 { 301 log.warn( "context with id {} not exists", id ); 302 } 303 } 304 305 return contexts; 306 } 307 308 private void constructQuery( String term, BooleanQuery q ) 309 { 310 q.add( indexer.constructQuery( MAVEN.GROUP_ID, new UserInputSearchExpression( term ) ), Occur.SHOULD ); 311 q.add( indexer.constructQuery( MAVEN.ARTIFACT_ID, new UserInputSearchExpression( term ) ), Occur.SHOULD ); 312 q.add( indexer.constructQuery( MAVEN.VERSION, new UserInputSearchExpression( term ) ), Occur.SHOULD ); 313 q.add( indexer.constructQuery( MAVEN.PACKAGING, new UserInputSearchExpression( term ) ), Occur.SHOULD ); 314 q.add( indexer.constructQuery( MAVEN.CLASSNAMES, new UserInputSearchExpression( term ) ), Occur.SHOULD ); 315 316 //Query query = 317 // new WildcardQuery( new Term( MAVEN.CLASSNAMES.getFieldName(), "*" ) ); 318 //q.add( query, Occur.MUST_NOT ); 319 // olamy IMHO we could set this option as at least one must match 320 //q.setMinimumNumberShouldMatch( 1 ); 321 } 322 323 324 /** 325 * @param selectedRepos 326 * @return indexing contextId used 327 */ 328 private List<String> addIndexingContexts( List<String> selectedRepos ) 329 { 330 Set<String> indexingContextIds = new HashSet<String>(); 331 for ( String repo : selectedRepos ) 332 { 333 try 334 { 335 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repo ); 336 337 if ( repoConfig != null ) 338 { 339 340 IndexingContext context = managedRepositoryAdmin.createIndexContext( repoConfig ); 341 if ( context.isSearchable() ) 342 { 343 indexingContextIds.addAll( getRemoteIndexingContextIds( repo ) ); 344 indexingContextIds.add( context.getId() ); 345 } 346 else 347 { 348 log.warn( "indexingContext with id {} not searchable", repoConfig.getId() ); 349 } 350 351 } 352 else 353 { 354 log.warn( "Repository '{}' not found in configuration.", repo ); 355 } 356 } 357 catch ( RepositoryAdminException e ) 358 { 359 log.warn( "RepositoryAdminException occured while accessing index of repository '{}' : {}", repo, 360 e.getMessage() ); 361 continue; 362 } 363 } 364 365 return new ArrayList<String>( indexingContextIds ); 366 } 367 368 369 public Set<String> getRemoteIndexingContextIds( String managedRepoId ) 370 throws RepositoryAdminException 371 { 372 Set<String> ids = new HashSet<String>(); 373 374 List<ProxyConnector> proxyConnectors = proxyConnectorAdmin.getProxyConnectorAsMap().get( managedRepoId ); 375 376 if ( proxyConnectors == null || proxyConnectors.isEmpty() ) 377 { 378 return ids; 379 } 380 381 for ( ProxyConnector proxyConnector : proxyConnectors ) 382 { 383 String remoteId = "remote-" + proxyConnector.getTargetRepoId(); 384 IndexingContext context = indexer.getIndexingContexts().get( remoteId ); 385 if ( context != null && context.isSearchable() ) 386 { 387 ids.add( remoteId ); 388 } 389 } 390 391 return ids; 392 } 393 394 public Collection<String> getAllGroupIds( String principal, List<String> selectedRepos ) 395 throws RepositorySearchException 396 { 397 List<IndexingContext> indexContexts = getIndexingContexts( selectedRepos ); 398 399 if ( indexContexts == null || indexContexts.isEmpty() ) 400 { 401 return Collections.emptyList(); 402 } 403 404 try 405 { 406 Set<String> allGroupIds = new HashSet<String>(); 407 for ( IndexingContext indexingContext : indexContexts ) 408 { 409 allGroupIds.addAll( indexingContext.getAllGroups() ); 410 } 411 return allGroupIds; 412 } 413 catch ( IOException e ) 414 { 415 throw new RepositorySearchException( e.getMessage(), e ); 416 } 417 418 } 419 420 421 protected List<? extends IndexCreator> getAllIndexCreators() 422 { 423 return mavenIndexerUtils.getAllIndexCreators(); 424 } 425 426 427 private SearchResults convertToSearchResults( FlatSearchResponse response, SearchResultLimits limits, 428 List<? extends ArtifactInfoFilter> artifactInfoFilters, 429 List<String> selectedRepos, boolean includePoms ) 430 throws RepositoryAdminException 431 { 432 SearchResults results = new SearchResults(); 433 Set<ArtifactInfo> artifactInfos = response.getResults(); 434 435 for ( ArtifactInfo artifactInfo : artifactInfos ) 436 { 437 if ( StringUtils.equalsIgnoreCase( "pom", artifactInfo.fextension ) && !includePoms ) 438 { 439 continue; 440 } 441 String id = SearchUtil.getHitId( artifactInfo.groupId, artifactInfo.artifactId, artifactInfo.classifier, 442 artifactInfo.packaging ); 443 Map<String, SearchResultHit> hitsMap = results.getHitsMap(); 444 445 if ( !applyArtifactInfoFilters( artifactInfo, artifactInfoFilters, hitsMap ) ) 446 { 447 continue; 448 } 449 450 SearchResultHit hit = hitsMap.get( id ); 451 if ( hit != null ) 452 { 453 if ( !hit.getVersions().contains( artifactInfo.version ) ) 454 { 455 hit.addVersion( artifactInfo.version ); 456 } 457 } 458 else 459 { 460 hit = new SearchResultHit(); 461 hit.setArtifactId( artifactInfo.artifactId ); 462 hit.setGroupId( artifactInfo.groupId ); 463 hit.setRepositoryId( artifactInfo.repository ); 464 hit.addVersion( artifactInfo.version ); 465 hit.setBundleExportPackage( artifactInfo.bundleExportPackage ); 466 hit.setBundleExportService( artifactInfo.bundleExportService ); 467 hit.setBundleSymbolicName( artifactInfo.bundleSymbolicName ); 468 hit.setBundleVersion( artifactInfo.bundleVersion ); 469 hit.setBundleDescription( artifactInfo.bundleDescription ); 470 hit.setBundleDocUrl( artifactInfo.bundleDocUrl ); 471 hit.setBundleRequireBundle( artifactInfo.bundleRequireBundle ); 472 hit.setBundleImportPackage( artifactInfo.bundleImportPackage ); 473 hit.setBundleLicense( artifactInfo.bundleLicense ); 474 hit.setBundleName( artifactInfo.bundleName ); 475 hit.setContext( artifactInfo.context ); 476 hit.setGoals( artifactInfo.goals ); 477 hit.setPrefix( artifactInfo.prefix ); 478 hit.setPackaging( artifactInfo.packaging ); 479 hit.setClassifier( artifactInfo.classifier ); 480 hit.setFileExtension( artifactInfo.fextension ); 481 hit.setUrl( getBaseUrl( artifactInfo, selectedRepos ) ); 482 } 483 484 results.addHit( id, hit ); 485 } 486 487 results.setTotalHits( response.getTotalHitsCount() ); 488 results.setTotalHitsMapSize( results.getHitsMap().values().size() ); 489 results.setReturnedHitsCount( response.getReturnedHitsCount() ); 490 results.setLimits( limits ); 491 492 if ( limits == null || limits.getSelectedPage() == SearchResultLimits.ALL_PAGES ) 493 { 494 return results; 495 } 496 else 497 { 498 return paginate( results ); 499 } 500 } 501 502 /** 503 * calculate baseUrl without the context and base Archiva Url 504 * 505 * @param artifactInfo 506 * @return 507 */ 508 protected String getBaseUrl( ArtifactInfo artifactInfo, List<String> selectedRepos ) 509 throws RepositoryAdminException 510 { 511 StringBuilder sb = new StringBuilder(); 512 if ( StringUtils.startsWith( artifactInfo.context, "remote-" ) ) 513 { 514 // it's a remote index result we search a managed which proxying this remote and on which 515 // current user has read karma 516 String managedRepoId = 517 getManagedRepoId( StringUtils.substringAfter( artifactInfo.context, "remote-" ), selectedRepos ); 518 if ( managedRepoId != null ) 519 { 520 sb.append( '/' ).append( managedRepoId ); 521 artifactInfo.context = managedRepoId; 522 } 523 } 524 else 525 { 526 sb.append( '/' ).append( artifactInfo.context ); 527 } 528 529 sb.append( '/' ).append( StringUtils.replaceChars( artifactInfo.groupId, '.', '/' ) ); 530 sb.append( '/' ).append( artifactInfo.artifactId ); 531 sb.append( '/' ).append( artifactInfo.version ); 532 sb.append( '/' ).append( artifactInfo.artifactId ); 533 sb.append( '-' ).append( artifactInfo.version ); 534 if ( StringUtils.isNotBlank( artifactInfo.classifier ) ) 535 { 536 sb.append( '-' ).append( artifactInfo.classifier ); 537 } 538 // maven-plugin packaging is a jar 539 if ( StringUtils.equals( "maven-plugin", artifactInfo.packaging ) ) 540 { 541 sb.append( "jar" ); 542 } 543 else 544 { 545 sb.append( '.' ).append( artifactInfo.packaging ); 546 } 547 548 return sb.toString(); 549 } 550 551 /** 552 * return a managed repo for a remote result 553 * 554 * @param remoteRepo 555 * @param selectedRepos 556 * @return 557 * @throws RepositoryAdminException 558 */ 559 private String getManagedRepoId( String remoteRepo, List<String> selectedRepos ) 560 throws RepositoryAdminException 561 { 562 Map<String, List<ProxyConnector>> proxyConnectorMap = proxyConnectorAdmin.getProxyConnectorAsMap(); 563 if ( proxyConnectorMap == null || proxyConnectorMap.isEmpty() ) 564 { 565 return null; 566 } 567 if ( selectedRepos != null && !selectedRepos.isEmpty() ) 568 { 569 for ( Map.Entry<String, List<ProxyConnector>> entry : proxyConnectorMap.entrySet() ) 570 { 571 if ( selectedRepos.contains( entry.getKey() ) ) 572 { 573 for ( ProxyConnector proxyConnector : entry.getValue() ) 574 { 575 if ( StringUtils.equals( remoteRepo, proxyConnector.getTargetRepoId() ) ) 576 { 577 return proxyConnector.getSourceRepoId(); 578 } 579 } 580 } 581 } 582 } 583 584 // we don't find in search selected repos so return the first one 585 for ( Map.Entry<String, List<ProxyConnector>> entry : proxyConnectorMap.entrySet() ) 586 { 587 588 for ( ProxyConnector proxyConnector : entry.getValue() ) 589 { 590 if ( StringUtils.equals( remoteRepo, proxyConnector.getTargetRepoId() ) ) 591 { 592 return proxyConnector.getSourceRepoId(); 593 } 594 } 595 596 } 597 return null; 598 } 599 600 private boolean applyArtifactInfoFilters( ArtifactInfo artifactInfo, 601 List<? extends ArtifactInfoFilter> artifactInfoFilters, 602 Map<String, SearchResultHit> currentResult ) 603 { 604 if ( artifactInfoFilters == null || artifactInfoFilters.isEmpty() ) 605 { 606 return true; 607 } 608 609 for ( ArtifactInfoFilter filter : artifactInfoFilters ) 610 { 611 if ( !filter.addArtifactInResult( artifactInfo, currentResult ) ) 612 { 613 return false; 614 } 615 } 616 return true; 617 } 618 619 protected SearchResults paginate( SearchResults results ) 620 { 621 SearchResultLimits limits = results.getLimits(); 622 SearchResults paginated = new SearchResults(); 623 624 int fetchCount = limits.getPageSize(); 625 int offset = ( limits.getSelectedPage() * limits.getPageSize() ); 626 627 if ( fetchCount > results.getTotalHits() ) 628 { 629 fetchCount = results.getTotalHits(); 630 } 631 632 // Goto offset. 633 if ( offset < results.getTotalHits() ) 634 { 635 // only process if the offset is within the hit count. 636 for ( int i = 0; i < fetchCount; i++ ) 637 { 638 // Stop fetching if we are past the total # of available hits. 639 if ( offset + i >= results.getHits().size() ) 640 { 641 break; 642 } 643 644 SearchResultHit hit = results.getHits().get( ( offset + i ) ); 645 if ( hit != null ) 646 { 647 String id = SearchUtil.getHitId( hit.getGroupId(), hit.getArtifactId(), hit.getClassifier(), 648 hit.getPackaging() ); 649 paginated.addHit( id, hit ); 650 } 651 else 652 { 653 break; 654 } 655 } 656 } 657 paginated.setTotalHits( results.getTotalHits() ); 658 paginated.setReturnedHitsCount( paginated.getHits().size() ); 659 paginated.setTotalHitsMapSize( results.getTotalHitsMapSize() ); 660 paginated.setLimits( limits ); 661 662 return paginated; 663 } 664 665 666 }