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.BufferedReader;
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.FileReader;
27  import java.io.IOException;
28  import java.io.PrintWriter;
29  import java.io.StringWriter;
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  
37  import org.apache.lucene.index.Term;
38  import org.apache.lucene.search.BooleanClause.Occur;
39  import org.apache.lucene.search.BooleanQuery;
40  import org.apache.lucene.search.Query;
41  import org.apache.lucene.search.TermQuery;
42  import org.apache.lucene.search.WildcardQuery;
43  import org.apache.lucene.store.RAMDirectory;
44  import org.apache.maven.index.context.IndexCreator;
45  import org.apache.maven.index.context.IndexingContext;
46  import org.apache.maven.index.context.MergedIndexingContext;
47  import org.apache.maven.index.context.StaticContextMemberProvider;
48  import org.apache.maven.index.context.UnsupportedExistingLuceneIndexException;
49  import org.apache.maven.index.creator.MinimalArtifactInfoIndexCreator;
50  import org.apache.maven.index.packer.DefaultIndexPacker;
51  import org.apache.maven.index.search.grouping.GAGrouping;
52  import org.apache.maven.index.updater.DefaultIndexUpdater;
53  import org.codehaus.plexus.util.StringUtils;
54  
55  /** @author Jason van Zyl */
56  public class NexusIndexerTest
57      extends AbstractIndexCreatorHelper
58  {
59      private IndexingContext context;
60  
61      public void testQueryCreatorNG()
62          throws Exception
63      {
64          NexusIndexer indexer = prepare();
65  
66          Query q = null;
67  
68          // exact search against field stored in both ways (tokenized/untokenized)
69          q = indexer.constructQuery( MAVEN.GROUP_ID, "commons-loggin*", SearchType.EXACT );
70  
71          // g:commons-loggin*
72          assertEquals( MinimalArtifactInfoIndexCreator.FLD_GROUP_ID_KW.getKey() + ":commons-loggin*", q.toString() );
73  
74          // scored search against field stored in both ways (tokenized/untokenized)
75          q = indexer.constructQuery( MAVEN.GROUP_ID, "commons-loggin*", SearchType.SCORED );
76  
77          // g:commons-loggin* (groupId:commons groupId:loggin*)
78          assertEquals( "g:commons-loggin* ((+groupId:commons +groupId:loggin*) groupId:\"commons loggin\")",
79              q.toString() );
80  
81          // keyword search against field stored in both ways (tokenized/untokenized)
82          q = indexer.constructQuery( MAVEN.GROUP_ID, "commons-logging", SearchType.EXACT );
83  
84          assertEquals( MinimalArtifactInfoIndexCreator.FLD_GROUP_ID_KW.getKey() + ":commons-logging", q.toString() );
85  
86          // keyword search against field having untokenized indexerField only
87          q = indexer.constructQuery( MAVEN.PACKAGING, "maven-archetype", SearchType.EXACT );
88  
89          assertEquals( MinimalArtifactInfoIndexCreator.FLD_PACKAGING.getKey() + ":maven-archetype", q.toString() );
90  
91          // scored search against field having untokenized indexerField only
92          q = indexer.constructQuery( MAVEN.PACKAGING, "maven-archetype", SearchType.SCORED );
93  
94          assertEquals( "p:maven-archetype p:maven-archetype*^0.8", q.toString() );
95  
96          // scored search against field having untokenized indexerField only
97          q = indexer.constructQuery( MAVEN.ARTIFACT_ID, "commons-logging", SearchType.SCORED );
98  
99          assertEquals(
100             "(a:commons-logging a:commons-logging*^0.8) ((+artifactId:commons +artifactId:logging*) artifactId:\"commons logging\")",
101             q.toString() );
102 
103         // scored search against field having tokenized IndexerField only (should be impossible).
104         q = indexer.constructQuery( MAVEN.NAME, "Some artifact name from Pom", SearchType.SCORED );
105 
106         assertEquals( "(+n:some +n:artifact +n:name +n:from +n:pom*) n:\"some artifact name from pom\"", q.toString() );
107 
108         // keyword search against field having tokenized IndexerField only (should be impossible).
109         q = indexer.constructQuery( MAVEN.NAME, "some artifact name from Pom", SearchType.EXACT );
110 
111         assertNull( q );
112     }
113 
114     public void performQueryCreatorNGSearch( NexusIndexer indexer, IndexingContext context )
115         throws Exception
116     {
117         String qstr = null;
118         Query q = null;
119 
120         IteratorSearchRequest req = null;
121 
122         IteratorSearchResponse res = null;
123 
124         // case01: "the most usual" case:
125         // explanation: commons-logging should top the results, but commons-cli will be at the end too (lower score but
126         // matched "commons")
127         qstr = "commons-logg";
128         q = indexer.constructQuery( MAVEN.GROUP_ID, qstr, SearchType.SCORED );
129 
130         req = new IteratorSearchRequest( q, context );
131 
132         res = indexer.searchIterator( req );
133 
134         checkResults( MAVEN.GROUP_ID, qstr, q, res,
135             getTestFile( "src/test/resources/testQueryCreatorNGSearch/case01.txt" ) );
136 
137         // case02: "the most usual" case:
138         // explanation: commons-logging should top the results, but commons-cli will be at the end too (lower score but
139         // matched "commons")
140         qstr = "commons logg";
141         q = indexer.constructQuery( MAVEN.GROUP_ID, qstr, SearchType.SCORED );
142 
143         req = new IteratorSearchRequest( q, context );
144 
145         res = indexer.searchIterator( req );
146 
147         checkResults( MAVEN.GROUP_ID, qstr, q, res,
148             getTestFile( "src/test/resources/testQueryCreatorNGSearch/case02.txt" ) );
149 
150         // case03: "the most usual" case:
151         // explanation: all "commons" matches, but commons-cli tops since it's _shorter_! (see Lucene Scoring)
152         qstr = "commons";
153         q = indexer.constructQuery( MAVEN.GROUP_ID, qstr, SearchType.SCORED );
154 
155         req = new IteratorSearchRequest( q, context );
156 
157         res = indexer.searchIterator( req );
158 
159         checkResults( MAVEN.GROUP_ID, qstr, q, res,
160             getTestFile( "src/test/resources/testQueryCreatorNGSearch/case03.txt" ) );
161 
162         // case04: "the most usual" case:
163         // explanation: only commons-logging matches, no commons-cli
164         qstr = "log";
165         q = indexer.constructQuery( MAVEN.GROUP_ID, qstr, SearchType.SCORED );
166 
167         req = new IteratorSearchRequest( q, context );
168 
169         res = indexer.searchIterator( req );
170 
171         checkResults( MAVEN.GROUP_ID, qstr, q, res,
172             getTestFile( "src/test/resources/testQueryCreatorNGSearch/case04.txt" ) );
173 
174         // case05: "the most usual" case:
175         // many matches, but at the top only the _exact_ matches for "1.0", and below all artifacts that have versions
176         // that contains letters "1" and "0".
177         qstr = "1.0";
178         q = indexer.constructQuery( MAVEN.VERSION, "1.0", SearchType.SCORED );
179 
180         req = new IteratorSearchRequest( q, context );
181 
182         res = indexer.searchIterator( req );
183 
184         checkResults( MAVEN.VERSION, qstr, q, res,
185             getTestFile( "src/test/resources/testQueryCreatorNGSearch/case05.txt" ) );
186 
187         // case06: "the most usual" case (for apps), "selection":
188         // explanation: exactly only those artifacts, that has version "1.0"
189         qstr = "1.0";
190         q = indexer.constructQuery( MAVEN.VERSION, qstr, SearchType.EXACT );
191 
192         req = new IteratorSearchRequest( q, context );
193 
194         res = indexer.searchIterator( req );
195 
196         checkResults( MAVEN.VERSION, qstr, q, res,
197             getTestFile( "src/test/resources/testQueryCreatorNGSearch/case06.txt" ) );
198 
199         // and comes the "trick", i will perform single _selection_!
200         // I want to ensure there is an artifact present!
201         // explanation: see for yourself ;)
202         BooleanQuery bq = new BooleanQuery();
203 
204         Query g = indexer.constructQuery( MAVEN.GROUP_ID, "commons-logging", SearchType.EXACT );
205         Query a = indexer.constructQuery( MAVEN.ARTIFACT_ID, "commons-logging", SearchType.EXACT );
206         Query v = indexer.constructQuery( MAVEN.VERSION, "1.0.4", SearchType.EXACT );
207         Query p = indexer.constructQuery( MAVEN.PACKAGING, "jar", SearchType.EXACT );
208         Query c = indexer.constructQuery( MAVEN.CLASSIFIER, Field.NOT_PRESENT, SearchType.EXACT );
209 
210         // so, I am looking up GAVP (for content of those look above) that _has no_ classifier
211         bq.add( g, Occur.MUST );
212         bq.add( a, Occur.MUST );
213         bq.add( v, Occur.MUST );
214         bq.add( p, Occur.MUST );
215         bq.add( c, Occur.MUST_NOT );
216 
217         // invoking the old method (was present since day 1), that will return the match only and if only there is 1 hit
218         Collection<ArtifactInfo> ais = indexer.identify( bq, Collections.singletonList( context ) );
219 
220         assertEquals( 1, ais.size() );
221 
222         ArtifactInfo ai = ais.iterator().next();
223 
224         // null means not "identified", so we want non-null response
225         assertTrue( ai != null );
226 
227         // we assure we found what we wanted
228         assertEquals( "commons-logging:commons-logging:1.0.4:null:jar", ai.toString() );
229     }
230 
231     public void testQueryCreatorNGSearch()
232         throws Exception
233     {
234         NexusIndexer indexer = prepare();
235 
236         performQueryCreatorNGSearch( indexer, context );
237     }
238 
239     public void testQueryCreatorNGSearchOnMergedContext()
240         throws Exception
241     {
242         NexusIndexer indexer = prepare();
243 
244         File indexMergedDir = super.getDirectory( "index/testMerged" );
245 
246         IndexingContext mergedContext =
247             new MergedIndexingContext( "test", "merged", context.getRepository(), indexMergedDir, true,
248                 new StaticContextMemberProvider( Collections.singletonList( context ) ) );
249 
250         performQueryCreatorNGSearch( indexer, mergedContext );
251     }
252 
253     /**
254      * Will "print" the result set, and suck up a file and compare the two
255      */
256     public void checkResults( Field field, String query, Query q, IteratorSearchResponse res, File expectedResults )
257         throws IOException
258     {
259         // switch used for easy data collection from console (for saving new "expected" results after you assured they
260         // are fine)
261         boolean print = true;
262 
263         StringWriter sw = new StringWriter();
264 
265         PrintWriter pw = new PrintWriter( sw );
266 
267         String line = null;
268 
269         line =
270             "### Searched for field " + field.toString() + " using query \"" + query + "\" (QC create LQL \""
271                 + q.toString() + "\")";
272 
273         if ( print )
274         {
275             System.out.println( line );
276         }
277 
278         pw.println( line );
279 
280         int totalHits = 0;
281 
282         for ( ArtifactInfo ai : res )
283         {
284             line = ai.context + " :: " + ai.toString();
285 
286             if ( print )
287             {
288                 System.out.println( line );
289             }
290 
291             pw.println( line );
292 
293             totalHits++;
294         }
295 
296         line = "### TOTAL:" + totalHits + " (response said " + res.getTotalHits() + ")";
297 
298         if ( print )
299         {
300             System.out.println( line );
301         }
302 
303         pw.println( line );
304 
305         // compare results! Load up the reference file, but avoid line ending issues, so just read it line by line. and
306         // produce it in very same fashion that previous one was
307         StringWriter ressw = new StringWriter();
308         PrintWriter respw = new PrintWriter( ressw );
309 
310         BufferedReader reader = new BufferedReader( new FileReader( expectedResults ) );
311         String currentline = null;
312 
313         while ( ( currentline = reader.readLine() ) != null )
314         {
315             respw.println( currentline );
316         }
317 
318         String shouldBe = ressw.toString();
319         String whatWeHave = sw.toString();
320 
321         // we compare those two
322         assertEquals( "Search results inconsistent!", shouldBe, whatWeHave );
323     }
324 
325     public void testSearchIterator()
326         throws Exception
327     {
328         NexusIndexer indexer = prepare();
329 
330         Query q = indexer.constructQuery( MAVEN.GROUP_ID, "qdox", SearchType.SCORED );
331 
332         IteratorSearchRequest request = new IteratorSearchRequest( q );
333 
334         IteratorSearchResponse response = indexer.searchIterator( request );
335 
336         assertEquals( 2, response.getTotalHits() );
337 
338         for ( ArtifactInfo ai : response.getResults() )
339         {
340             assertEquals( "GroupId must match \"qdox\"!", "qdox", ai.groupId );
341         }
342     }
343 
344     public void testSearchIteratorWithFilter()
345         throws Exception
346     {
347         NexusIndexer indexer = prepare();
348 
349         Query q = indexer.constructQuery( MAVEN.GROUP_ID, "qdox", SearchType.SCORED );
350 
351         IteratorSearchRequest request = new IteratorSearchRequest( q, new ArtifactInfoFilter()
352         {
353             public boolean accepts( IndexingContext ctx, ArtifactInfo ai )
354             {
355                 // we reject version "1.5" for fun
356                 return !StringUtils.equals( ai.version, "1.5" );
357             }
358         } );
359 
360         IteratorSearchResponse response = indexer.searchIterator( request );
361 
362         assertEquals( 2, response.getTotalHits() );
363 
364         assertTrue( "Iterator has to have next (2 found, 1 filtered out)", response.getResults().hasNext() );
365 
366         ArtifactInfo ai = response.getResults().next();
367 
368         assertEquals( "1.5 is filtered out, so 1.6.1 must appear here!", "1.6.1", ai.version );
369     }
370 
371     public void testSearchGrouped()
372         throws Exception
373     {
374         NexusIndexer indexer = prepare();
375 
376         {
377             Query q = indexer.constructQuery( MAVEN.GROUP_ID, "qdox", SearchType.SCORED );
378             GroupedSearchRequest request = new GroupedSearchRequest( q, new GAGrouping() );
379             GroupedSearchResponse response = indexer.searchGrouped( request );
380             Map<String, ArtifactInfoGroup> r = response.getResults();
381             assertEquals( 1, r.size() );
382 
383             ArtifactInfoGroup gi0 = r.values().iterator().next();
384             assertEquals( "qdox : qdox", gi0.getGroupKey() );
385             List<ArtifactInfo> list = new ArrayList<ArtifactInfo>( gi0.getArtifactInfos() );
386             ArtifactInfo ai0 = list.get( 0 );
387             assertEquals( "1.6.1", ai0.version );
388             ArtifactInfo ai1 = list.get( 1 );
389             assertEquals( "1.5", ai1.version );
390             assertEquals( "test", ai1.repository );
391         }
392         {
393             WildcardQuery q = new WildcardQuery( new Term( ArtifactInfo.UINFO, "commons-log*" ) );
394             GroupedSearchRequest request =
395                 new GroupedSearchRequest( q, new GAGrouping(), String.CASE_INSENSITIVE_ORDER );
396             GroupedSearchResponse response = indexer.searchGrouped( request );
397             Map<String, ArtifactInfoGroup> r = response.getResults();
398             assertEquals( 1, r.size() );
399 
400             ArtifactInfoGroup gi1 = r.values().iterator().next();
401             assertEquals( "commons-logging : commons-logging", gi1.getGroupKey() );
402         }
403     }
404 
405     public void testSearchFlat()
406         throws Exception
407     {
408         NexusIndexer indexer = prepare();
409 
410         {
411             WildcardQuery q = new WildcardQuery( new Term( ArtifactInfo.UINFO, "*testng*" ) );
412             FlatSearchResponse response = indexer.searchFlat( new FlatSearchRequest( q ) );
413             Set<ArtifactInfo> r = response.getResults();
414             assertEquals( r.toString(), 4, r.size() );
415         }
416 
417         {
418             BooleanQuery bq = new BooleanQuery( true );
419             bq.add( new WildcardQuery( new Term( ArtifactInfo.GROUP_ID, "testng*" ) ), Occur.SHOULD );
420             bq.add( new WildcardQuery( new Term( ArtifactInfo.ARTIFACT_ID, "testng*" ) ), Occur.SHOULD );
421             bq.setMinimumNumberShouldMatch( 1 );
422 
423             FlatSearchResponse response = indexer.searchFlat( new FlatSearchRequest( bq ) );
424             Set<ArtifactInfo> r = response.getResults();
425 
426             assertEquals( r.toString(), 4, r.size() );
427         }
428     }
429 
430     public void testSearchPackaging()
431         throws Exception
432     {
433         NexusIndexer indexer = prepare();
434 
435         WildcardQuery q = new WildcardQuery( new Term( ArtifactInfo.PACKAGING, "maven-plugin" ) );
436         FlatSearchResponse response = indexer.searchFlat( new FlatSearchRequest( q ) );
437         Set<ArtifactInfo> r = response.getResults();
438         assertEquals( r.toString(), 2, r.size() );
439     }
440 
441     public void testIdentity()
442         throws Exception
443     {
444         NexusIndexer nexus = prepare();
445 
446         // Search using SHA1 to find qdox 1.5
447 
448         Collection<ArtifactInfo> ais = nexus.identify( MAVEN.SHA1, "4d2db265eddf1576cb9d896abc90c7ba46b48d87" );
449 
450         assertEquals( 1, ais.size() );
451 
452         ArtifactInfo ai = ais.iterator().next();
453 
454         assertNotNull( ai );
455 
456         assertEquals( "qdox", ai.groupId );
457 
458         assertEquals( "qdox", ai.artifactId );
459 
460         assertEquals( "1.5", ai.version );
461 
462         assertEquals( "test", ai.repository );
463 
464         // Using a file
465 
466         File artifact = new File( getBasedir(), "src/test/repo/qdox/qdox/1.5/qdox-1.5.jar" );
467 
468         ais = nexus.identify( artifact );
469 
470         assertEquals( 1, ais.size() );
471 
472         ai = ais.iterator().next();
473 
474         assertNotNull( ai );
475 
476         assertEquals( "qdox", ai.groupId );
477 
478         assertEquals( "qdox", ai.artifactId );
479 
480         assertEquals( "1.5", ai.version );
481 
482         assertEquals( "test", ai.repository );
483     }
484 
485     public void testUpdateArtifact()
486         throws Exception
487     {
488         NexusIndexer indexer = prepare();
489 
490         Query q =
491             new TermQuery( new Term( ArtifactInfo.UINFO, "org.apache.maven.plugins|maven-core-it-plugin|1.0|NA" ) );
492 
493         FlatSearchRequest request = new FlatSearchRequest( q );
494 
495         FlatSearchResponse response1 = indexer.searchFlat( request );
496         Collection<ArtifactInfo> res1 = response1.getResults();
497         assertEquals( 1, res1.size() );
498 
499         ArtifactInfo ai = res1.iterator().next();
500 
501         assertEquals( "Maven Core Integration Test Plugin", ai.name );
502 
503         long oldSize = ai.size;
504 
505         ai.name = "bla bla bla";
506 
507         ai.size += 100;
508 
509         IndexingContext indexingContext = indexer.getIndexingContexts().get( "test" );
510 
511         // String fname = indexingContext.getRepository().getAbsolutePath() + "/" + ai.groupId.replace( '.', '/' ) + "/"
512         // + ai.artifactId + "/" + ai.version + "/" + ai.artifactId + "-" + ai.version;
513 
514         // File pom = new File( fname + ".pom" );
515 
516         // File artifact = new File( fname + ".jar" );
517 
518         indexer.addArtifactToIndex( new ArtifactContext( null, null, null, ai, null ), indexingContext );
519 
520         FlatSearchResponse response2 = indexer.searchFlat( request );
521         Collection<ArtifactInfo> res2 = response2.getResults();
522         assertEquals( 1, res2.size() );
523 
524         ArtifactInfo ai2 = res2.iterator().next();
525 
526         assertEquals( oldSize + 100, ai2.size );
527 
528         assertEquals( "bla bla bla", ai2.name );
529     }
530 
531     public void testUnpack()
532         throws Exception
533     {
534         NexusIndexer indexer = prepare();
535 
536         String indexId = context.getId();
537         String repositoryId = context.getRepositoryId();
538         File repository = context.getRepository();
539         String repositoryUrl = context.getRepositoryUrl();
540         List<IndexCreator> indexCreators = context.getIndexCreators();
541         // Directory directory = context.getIndexDirectory();
542 
543         RAMDirectory newDirectory = new RAMDirectory();
544 
545         ByteArrayOutputStream bos = new ByteArrayOutputStream();
546 
547         DefaultIndexPacker.packIndexArchive( context, bos );
548 
549         DefaultIndexUpdater.unpackIndexArchive( new ByteArrayInputStream( bos.toByteArray() ), newDirectory, //
550             context );
551 
552         indexer.removeIndexingContext( context, false );
553 
554         indexer.addIndexingContext( indexId, //
555             repositoryId, repository, newDirectory, repositoryUrl, null, indexCreators );
556 
557         WildcardQuery q = new WildcardQuery( new Term( ArtifactInfo.PACKAGING, "maven-plugin" ) );
558         FlatSearchResponse response = indexer.searchFlat( new FlatSearchRequest( q ) );
559         Collection<ArtifactInfo> infos = response.getResults();
560 
561         assertEquals( infos.toString(), 2, infos.size() );
562     }
563 
564     private NexusIndexer prepare()
565         throws Exception, IOException, UnsupportedExistingLuceneIndexException
566     {
567         NexusIndexer indexer = lookup( NexusIndexer.class );
568 
569         // Directory indexDir = new RAMDirectory();
570         File indexDir = super.getDirectory( "index/test" );
571         super.deleteDirectory( indexDir );
572 
573         File repo = new File( getBasedir(), "src/test/repo" );
574 
575         context = indexer.addIndexingContext( "test", "test", repo, indexDir, null, null, DEFAULT_CREATORS );
576         indexer.scan( context );
577 
578         // IndexReader indexReader = context.getIndexSearcher().getIndexReader();
579         // int numDocs = indexReader.numDocs();
580         // for ( int i = 0; i < numDocs; i++ )
581         // {
582         // Document doc = indexReader.document( i );
583         // System.err.println( i + " : " + doc.get( ArtifactInfo.UINFO));
584         //
585         // }
586         return indexer;
587     }
588 
589     // private void printDocs(NexusIndexer nexus) throws IOException
590     // {
591     // IndexingContext context = nexus.getIndexingContexts().get("test");
592     // IndexReader reader = context.getIndexSearcher().getIndexReader();
593     // int numDocs = reader.numDocs();
594     // for (int i = 0; i < numDocs; i++) {
595     // Document doc = reader.document(i);
596     // System.err.println(i + " " + doc.get(ArtifactInfo.UINFO) + " : " + doc.get(ArtifactInfo.PACKAGING));
597     // }
598     // }
599 }