View Javadoc

1   package org.apache.maven.index;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0    
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Date;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.lucene.index.Term;
34  import org.apache.lucene.queryParser.ParseException;
35  import org.apache.lucene.search.FilteredQuery;
36  import org.apache.lucene.search.PrefixQuery;
37  import org.apache.lucene.search.Query;
38  import org.apache.lucene.search.QueryWrapperFilter;
39  import org.apache.lucene.search.TermQuery;
40  import org.apache.lucene.store.Directory;
41  import org.apache.lucene.store.FSDirectory;
42  import org.apache.maven.index.ArtifactInfo;
43  import org.apache.maven.index.ArtifactInfoGroup;
44  import org.apache.maven.index.Field;
45  import org.apache.maven.index.FlatSearchRequest;
46  import org.apache.maven.index.FlatSearchResponse;
47  import org.apache.maven.index.GroupedSearchRequest;
48  import org.apache.maven.index.GroupedSearchResponse;
49  import org.apache.maven.index.IteratorSearchRequest;
50  import org.apache.maven.index.IteratorSearchResponse;
51  import org.apache.maven.index.MAVEN;
52  import org.apache.maven.index.MatchHighlight;
53  import org.apache.maven.index.MatchHighlightMode;
54  import org.apache.maven.index.MatchHighlightRequest;
55  import org.apache.maven.index.NexusIndexer;
56  import org.apache.maven.index.SearchType;
57  import org.apache.maven.index.context.IndexingContext;
58  import org.apache.maven.index.packer.DefaultIndexPacker;
59  import org.apache.maven.index.search.grouping.GAGrouping;
60  import org.apache.maven.index.search.grouping.GGrouping;
61  import org.apache.maven.index.updater.DefaultIndexUpdater;
62  
63  public class FullIndexNexusIndexerTest
64      extends DefaultIndexNexusIndexerTest
65  {
66      @Override
67      protected void prepareNexusIndexer( NexusIndexer nexusIndexer )
68          throws Exception
69      {
70          context = nexusIndexer.addIndexingContext( "test-default", "test", repo, indexDir, null, null, FULL_CREATORS );
71  
72          assertNull( context.getTimestamp() ); // unknown upon creation
73  
74          nexusIndexer.scan( context );
75  
76          assertNotNull( context.getTimestamp() );
77      }
78  
79      public void testSearchGroupedClasses()
80          throws Exception
81      {
82          {
83              Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "com/thoughtworks/qdox", SearchType.SCORED );
84              GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
85              GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
86              Map<String, ArtifactInfoGroup> r = response.getResults();
87  
88              assertEquals( r.toString(), 2, r.size() ); // qdox and testng
89  
90              assertTrue( r.containsKey( "qdox : qdox" ) );
91              assertTrue( r.containsKey( "org.testng : testng" ) );
92              assertEquals( "qdox : qdox", r.get( "qdox : qdox" ).getGroupKey() );
93              assertEquals( "org.testng : testng", r.get( "org.testng : testng" ).getGroupKey() );
94          }
95  
96          {
97              Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "com.thoughtworks.qdox", SearchType.SCORED );
98              GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
99              GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
100             Map<String, ArtifactInfoGroup> r = response.getResults();
101             assertEquals( r.toString(), 2, r.size() );
102 
103             assertTrue( r.containsKey( "qdox : qdox" ) );
104             assertTrue( r.containsKey( "org.testng : testng" ) );
105             assertEquals( "qdox : qdox", r.get( "qdox : qdox" ).getGroupKey() );
106             assertEquals( "org.testng : testng", r.get( "org.testng : testng" ).getGroupKey() );
107         }
108 
109         {
110             Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "thoughtworks", SearchType.SCORED );
111             GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
112             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
113             Map<String, ArtifactInfoGroup> r = response.getResults();
114             assertEquals( r.toString(), 2, r.size() );
115             assertTrue( r.containsKey( "qdox : qdox" ) );
116             assertTrue( r.containsKey( "org.testng : testng" ) );
117             assertEquals( "qdox : qdox", r.get( "qdox : qdox" ).getGroupKey() );
118             assertEquals( "org.testng : testng", r.get( "org.testng : testng" ).getGroupKey() );
119         }
120 
121         {
122             // an implicit class name wildcard
123             Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "Logger", SearchType.SCORED );
124             GroupedSearchRequest request = new GroupedSearchRequest( q, new GGrouping() );
125             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
126 
127             Map<String, ArtifactInfoGroup> r = response.getResults();
128             assertEquals( r.toString(), 2, r.size() );
129 
130             Iterator<ArtifactInfoGroup> it = r.values().iterator();
131 
132             ArtifactInfoGroup ig1 = it.next();
133             assertEquals( r.toString(), "org.slf4j", ig1.getGroupKey() );
134 
135             ArtifactInfoGroup ig2 = it.next();
136             assertEquals( r.toString(), "org.testng", ig2.getGroupKey() );
137         }
138 
139         {
140             // a lower case search
141             Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "logger", SearchType.SCORED );
142             GroupedSearchRequest request = new GroupedSearchRequest( q, new GGrouping() );
143             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
144             Map<String, ArtifactInfoGroup> r = response.getResults();
145             assertEquals( r.toString(), 2, r.size() );
146 
147             Iterator<ArtifactInfoGroup> it = r.values().iterator();
148 
149             ArtifactInfoGroup ig1 = it.next();
150             assertEquals( r.toString(), "org.slf4j", ig1.getGroupKey() );
151 
152             ArtifactInfoGroup ig2 = it.next();
153             assertEquals( r.toString(), "org.testng", ig2.getGroupKey() );
154         }
155 
156         {
157             // explicit class name wildcard without terminator
158             // Since 4.0 query starting with * is illegal
159             // Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "*.Logger", SearchType.SCORED );
160             Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, ".Logger", SearchType.SCORED );
161             GroupedSearchRequest request = new GroupedSearchRequest( q, new GGrouping() );
162             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
163             Map<String, ArtifactInfoGroup> r = response.getResults();
164             assertEquals( r.toString(), 2, r.size() );
165             Iterator<ArtifactInfoGroup> it = r.values().iterator();
166             ArtifactInfoGroup ig1 = it.next();
167             assertEquals( r.toString(), "org.slf4j", ig1.getGroupKey() );
168             ArtifactInfoGroup ig2 = it.next();
169             assertEquals( r.toString(), "org.testng", ig2.getGroupKey() );
170         }
171 
172         {
173             // explicit class name wildcard with terminator
174             // Since 4.0 query starting with * is illegal
175             // Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "*.Logger ", SearchType.SCORED );
176             Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, ".Logger ", SearchType.SCORED );
177             GroupedSearchRequest request = new GroupedSearchRequest( q, new GGrouping() );
178             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
179             Map<String, ArtifactInfoGroup> r = response.getResults();
180             assertEquals( r.toString(), 2, r.size() );
181             Iterator<ArtifactInfoGroup> it = r.values().iterator();
182             ArtifactInfoGroup ig1 = it.next();
183             assertEquals( r.toString(), "org.slf4j", ig1.getGroupKey() );
184             ArtifactInfoGroup ig2 = it.next();
185             assertEquals( r.toString(), "org.testng", ig2.getGroupKey() );
186         }
187 
188         {
189             // a class name wildcard
190             // Since 4.0 query starting with * is illegal
191             // Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "*Logger", SearchType.SCORED );
192             Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "Logger", SearchType.SCORED );
193             GroupedSearchRequest request = new GroupedSearchRequest( q, new GGrouping() );
194             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
195             Map<String, ArtifactInfoGroup> r = response.getResults();
196             // Results are less, since no PREFIX searches anymore!
197             // assertEquals( r.toString(), 3, r.size() );
198             assertEquals( r.toString(), 2, r.size() );
199 
200             Iterator<ArtifactInfoGroup> it = r.values().iterator();
201 
202             // Results are less, since no PREFIX searches anymore!
203             // ArtifactInfoGroup ig1 = it.next();
204             // assertEquals( r.toString(), "commons-logging", ig1.getGroupKey() ); // Jdk14Logger and LogKitLogger
205 
206             ArtifactInfoGroup ig2 = it.next();
207             assertEquals( r.toString(), "org.slf4j", ig2.getGroupKey() );
208 
209             ArtifactInfoGroup ig3 = it.next();
210             assertEquals( r.toString(), "org.testng", ig3.getGroupKey() );
211         }
212 
213         {
214             // exact class name
215             Query q =
216                 nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "org/apache/commons/logging/LogConfigurationException",
217                     SearchType.SCORED );
218             GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
219             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
220 
221             Map<String, ArtifactInfoGroup> r = response.getResults();
222             assertEquals( r.toString(), 2, r.size() ); // jcl104-over-slf4j and commons-logging
223         }
224 
225         {
226             // implicit class name pattern
227             Query q =
228                 nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "org.apache.commons.logging.LogConfigurationException",
229                     SearchType.SCORED );
230             GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
231             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
232 
233             Map<String, ArtifactInfoGroup> r = response.getResults();
234             assertEquals( r.toString(), 2, r.size() ); // jcl104-over-slf4j and commons-logging
235         }
236 
237         {
238             // exact class name
239             Query q =
240                 nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "org.apache.commons.logging.LogConfigurationException",
241                     SearchType.EXACT );
242             GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
243             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
244 
245             Map<String, ArtifactInfoGroup> r = response.getResults();
246             assertEquals( r.toString(), 2, r.size() ); // jcl104-over-slf4j and commons-logging
247         }
248 
249         {
250             // package name prefix
251             Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "org.apache.commons.logging", SearchType.SCORED );
252             GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
253             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
254 
255             Map<String, ArtifactInfoGroup> r = response.getResults();
256             assertEquals( r.toString(), 2, r.size() ); // jcl104-over-slf4j and commons-logging
257         }
258 
259         {
260             // Since 4.0, queries cannot start with '*'
261             // Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "*slf4j*Logg*", SearchType.SCORED );
262             Query q = nexusIndexer.constructQuery( MAVEN.CLASSNAMES, "slf4j.Logg*", SearchType.SCORED );
263             GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
264             GroupedSearchResponse response = nexusIndexer.searchGrouped( request );
265 
266             Map<String, ArtifactInfoGroup> r = response.getResults();
267             // Error is fixed, see below
268             assertEquals( r.toString(), 1, r.size() );
269 
270             {
271                 ArtifactInfoGroup ig = r.values().iterator().next();
272                 ArrayList<ArtifactInfo> list1 = new ArrayList<ArtifactInfo>( ig.getArtifactInfos() );
273                 assertEquals( r.toString(), 2, list1.size() );
274 
275                 ArtifactInfo ai1 = list1.get( 0 );
276                 assertEquals( "org.slf4j", ai1.groupId );
277                 assertEquals( "slf4j-api", ai1.artifactId );
278                 assertEquals( "1.4.2", ai1.version );
279                 ArtifactInfo ai2 = list1.get( 1 );
280                 assertEquals( "org.slf4j", ai2.groupId );
281                 assertEquals( "slf4j-api", ai2.artifactId );
282                 assertEquals( "1.4.1", ai2.version );
283             }
284 
285             // this is fixed now, no more false hit
286             // {
287             // // This was error, since slf4j-log4j12 DOES NOT HAVE any class for this search!
288             // ArtifactInfoGroup ig = r.get( "org.slf4j : slf4j-log4j12" );
289             // ArrayList<ArtifactInfo> list = new ArrayList<ArtifactInfo>( ig.getArtifactInfos() );
290             // assertEquals( list.toString(), 1, list.size() );
291             //
292             // ArtifactInfo ai = list.get( 0 );
293             // assertEquals( "org.slf4j", ai.groupId );
294             // assertEquals( "slf4j-log4j12", ai.artifactId );
295             // assertEquals( "1.4.1", ai.version );
296             // }
297         }
298     }
299 
300     public void testSearchArchetypes()
301         throws Exception
302     {
303         // TermQuery tq = new TermQuery(new Term(ArtifactInfo.PACKAGING, "maven-archetype"));
304         // BooleanQuery bq = new BooleanQuery();
305         // bq.add(new WildcardQuery(new Term(ArtifactInfo.GROUP_ID, term + "*")), Occur.SHOULD);
306         // bq.add(new WildcardQuery(new Term(ArtifactInfo.ARTIFACT_ID, term + "*")), Occur.SHOULD);
307         // FilteredQuery query = new FilteredQuery(tq, new QueryWrapperFilter(bq));
308 
309         Query q = new TermQuery( new Term( ArtifactInfo.PACKAGING, "maven-archetype" ) );
310         FlatSearchResponse response = nexusIndexer.searchFlat( new FlatSearchRequest( q ) );
311         Collection<ArtifactInfo> r = response.getResults();
312 
313         assertEquals( 4, r.size() );
314 
315         Iterator<ArtifactInfo> it = r.iterator();
316         {
317             ArtifactInfo ai = it.next();
318             assertEquals( "org.apache.directory.server", ai.groupId );
319             assertEquals( "apacheds-schema-archetype", ai.artifactId );
320             assertEquals( "1.0.2", ai.version );
321         }
322         {
323             ArtifactInfo ai = it.next();
324             assertEquals( "org.apache.servicemix.tooling", ai.groupId );
325             assertEquals( "servicemix-service-engine", ai.artifactId );
326             assertEquals( "3.1", ai.version );
327         }
328         {
329             ArtifactInfo ai = it.next();
330             assertEquals( "org.terracotta.maven.archetypes", ai.groupId );
331             assertEquals( "pojo-archetype", ai.artifactId );
332             assertEquals( "1.0.3", ai.version );
333         }
334         {
335             ArtifactInfo ai = it.next();
336             assertEquals( "proptest", ai.groupId );
337             assertEquals( "proptest-archetype", ai.artifactId );
338             assertEquals( "1.0", ai.version );
339         }
340     }
341 
342     public void testIndexTimestamp()
343         throws Exception
344     {
345         ByteArrayOutputStream os = new ByteArrayOutputStream();
346 
347         DefaultIndexPacker.packIndexArchive( context, os );
348 
349         Thread.sleep( 1000L );
350 
351         File newIndex = new File( getBasedir(), "target/test-new" );
352 
353         Directory newIndexDir = FSDirectory.open( newIndex );
354 
355         DefaultIndexUpdater.unpackIndexArchive( new ByteArrayInputStream( os.toByteArray() ), newIndexDir, context );
356 
357         IndexingContext newContext =
358             nexusIndexer.addIndexingContext( "test-new", "test", null, newIndexDir, null, null, DEFAULT_CREATORS );
359 
360         assertEquals( context.getTimestamp().getTime(), newContext.getTimestamp().getTime() );
361 
362         assertEquals( context.getTimestamp(), newContext.getTimestamp() );
363 
364         // make sure context has the same artifacts
365 
366         Query query = nexusIndexer.constructQuery( MAVEN.GROUP_ID, "qdox", SearchType.SCORED );
367 
368         FlatSearchRequest request = new FlatSearchRequest( query, newContext );
369         FlatSearchResponse response = nexusIndexer.searchFlat( request );
370         Collection<ArtifactInfo> r = response.getResults();
371 
372         assertEquals( 2, r.size() );
373 
374         List<ArtifactInfo> list = new ArrayList<ArtifactInfo>( r );
375 
376         assertEquals( 2, list.size() );
377 
378         ArtifactInfo ai = list.get( 0 );
379 
380         assertEquals( "1.6.1", ai.version );
381 
382         ai = list.get( 1 );
383 
384         assertEquals( "1.5", ai.version );
385 
386         assertEquals( "test", ai.repository );
387 
388         Date timestamp = newContext.getTimestamp();
389 
390         newContext.close( false );
391 
392         newIndexDir = FSDirectory.open( newIndex );
393 
394         DefaultIndexUpdater.unpackIndexArchive( new ByteArrayInputStream( os.toByteArray() ), newIndexDir, context );
395 
396         newContext =
397             nexusIndexer.addIndexingContext( "test-new", "test", null, newIndexDir, null, null, DEFAULT_CREATORS );
398 
399         assertEquals( timestamp, newContext.getTimestamp() );
400 
401         newContext.close( true );
402 
403         assertFalse( new File( newIndex, "timestamp" ).exists() );
404     }
405 
406     public void testArchetype()
407         throws Exception
408     {
409         String term = "proptest";
410 
411         Query bq = new PrefixQuery( new Term( ArtifactInfo.GROUP_ID, term ) );
412         TermQuery tq = new TermQuery( new Term( ArtifactInfo.PACKAGING, "maven-archetype" ) );
413         Query query = new FilteredQuery( tq, new QueryWrapperFilter( bq ) );
414 
415         FlatSearchResponse response = nexusIndexer.searchFlat( new FlatSearchRequest( query ) );
416 
417         Collection<ArtifactInfo> r = response.getResults();
418 
419         assertEquals( r.toString(), 1, r.size() );
420     }
421 
422     public void testArchetypePackaging()
423         throws Exception
424     {
425         Query query = new TermQuery( new Term( ArtifactInfo.PACKAGING, "maven-archetype" ) );
426         FlatSearchResponse response = nexusIndexer.searchFlat( new FlatSearchRequest( query ) );
427         assertEquals( response.getResults().toString(), 4, response.getTotalHits() );
428     }
429 
430     public void testBrokenJar()
431         throws Exception
432     {
433         Query q = nexusIndexer.constructQuery( MAVEN.ARTIFACT_ID, "brokenjar", SearchType.SCORED );
434 
435         FlatSearchRequest searchRequest = new FlatSearchRequest( q );
436 
437         FlatSearchResponse response = nexusIndexer.searchFlat( searchRequest );
438 
439         Set<ArtifactInfo> r = response.getResults();
440 
441         assertEquals( r.toString(), 1, r.size() );
442 
443         ArtifactInfo ai = r.iterator().next();
444 
445         assertEquals( "brokenjar", ai.groupId );
446         assertEquals( "brokenjar", ai.artifactId );
447         assertEquals( "1.0", ai.version );
448         assertEquals( null, ai.classNames );
449     }
450 
451     public void testMissingPom()
452         throws Exception
453     {
454         Query q = nexusIndexer.constructQuery( MAVEN.ARTIFACT_ID, "missingpom", SearchType.SCORED );
455 
456         FlatSearchRequest searchRequest = new FlatSearchRequest( q );
457 
458         FlatSearchResponse response = nexusIndexer.searchFlat( searchRequest );
459 
460         Set<ArtifactInfo> r = response.getResults();
461 
462         assertEquals( r.toString(), 1, r.size() );
463 
464         ArtifactInfo ai = r.iterator().next();
465 
466         assertEquals( "missingpom", ai.groupId );
467         assertEquals( "missingpom", ai.artifactId );
468         assertEquals( "1.0", ai.version );
469         // See Nexus 2318. It should be null for a jar without classes
470         assertNull( ai.classNames );
471     }
472 
473     // ==
474 
475     protected IteratorSearchRequest createHighlightedRequest( Field field, String text, SearchType type )
476         throws ParseException
477     {
478         Query q = nexusIndexer.constructQuery( field, text, type );
479 
480         IteratorSearchRequest request = new IteratorSearchRequest( q );
481 
482         request.getMatchHighlightRequests().add( new MatchHighlightRequest( field, q, MatchHighlightMode.HTML ) );
483 
484         return request;
485     }
486 
487     public void testClassnameSearchNgWithHighlighting()
488         throws Exception
489     {
490         IteratorSearchRequest request = createHighlightedRequest( MAVEN.CLASSNAMES, "Logger", SearchType.SCORED );
491 
492         IteratorSearchResponse response = nexusIndexer.searchIterator( request );
493 
494         for ( ArtifactInfo ai : response )
495         {
496             // These are _all_ classnames
497             String classnames = ai.classNames;
498 
499             for ( MatchHighlight mh : ai.getMatchHighlights() )
500             {
501                 for ( String highlighted : mh.getHighlightedMatch() )
502                 {
503                     // Logger and LoggerFactory
504                     assertTrue( "Class name should be highlighted", highlighted.contains( "<B>Logger" ) );
505                     assertFalse( "Class name should not contain \"/\" alone (but okay within HTML, see above!)",
506                         highlighted.matches( "\\p{Lower}/\\p{Upper}" ) );
507                     assertFalse( "Class name should not begin with \".\" or \"/\"", highlighted.startsWith( "." )
508                         || highlighted.startsWith( "/" ) );
509                 }
510             }
511         }
512 
513         assertEquals( "found in jcl104-over-slf4j and commons-logging", 5, response.getTotalHits() );
514     }
515 
516     public void testGAVSearchNgWithHighlighting()
517         throws Exception
518     {
519         IteratorSearchRequest request = createHighlightedRequest( MAVEN.GROUP_ID, "commons", SearchType.SCORED );
520 
521         IteratorSearchResponse response = nexusIndexer.searchIterator( request );
522 
523         for ( ArtifactInfo ai : response )
524         {
525             for ( MatchHighlight mh : ai.getMatchHighlights() )
526             {
527                 assertTrue(
528                     "Group ID should be highlighted",
529                     mh.getHighlightedMatch().contains( "<B>commons</B>-logging" )
530                         || mh.getHighlightedMatch().contains( "<B>commons</B>-cli" ) );
531             }
532         }
533 
534         assertEquals( "found in commons-logging and commons-cli", 15, response.getTotalHits() );
535     }
536 }