View Javadoc
1   package org.apache.maven.index.examples;
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 javax.inject.Inject;
23  import javax.inject.Named;
24  import javax.inject.Singleton;
25  
26  import org.apache.lucene.document.Document;
27  import org.apache.lucene.index.IndexReader;
28  import org.apache.lucene.index.MultiBits;
29  import org.apache.lucene.search.BooleanClause.Occur;
30  import org.apache.lucene.search.BooleanQuery;
31  import org.apache.lucene.search.IndexSearcher;
32  import org.apache.lucene.search.Query;
33  import org.apache.lucene.util.Bits;
34  import org.apache.maven.index.ArtifactInfo;
35  import org.apache.maven.index.ArtifactInfoFilter;
36  import org.apache.maven.index.ArtifactInfoGroup;
37  import org.apache.maven.index.Field;
38  import org.apache.maven.index.FlatSearchRequest;
39  import org.apache.maven.index.FlatSearchResponse;
40  import org.apache.maven.index.GroupedSearchRequest;
41  import org.apache.maven.index.GroupedSearchResponse;
42  import org.apache.maven.index.Grouping;
43  import org.apache.maven.index.Indexer;
44  import org.apache.maven.index.IteratorSearchRequest;
45  import org.apache.maven.index.IteratorSearchResponse;
46  import org.apache.maven.index.MAVEN;
47  import org.apache.maven.index.context.IndexCreator;
48  import org.apache.maven.index.context.IndexUtils;
49  import org.apache.maven.index.context.IndexingContext;
50  import org.apache.maven.index.expr.SourcedSearchExpression;
51  import org.apache.maven.index.expr.UserInputSearchExpression;
52  import org.apache.maven.index.search.grouping.GAGrouping;
53  import org.apache.maven.index.updater.IndexUpdateRequest;
54  import org.apache.maven.index.updater.IndexUpdateResult;
55  import org.apache.maven.index.updater.IndexUpdater;
56  import org.apache.maven.index.updater.ResourceFetcher;
57  import org.codehaus.plexus.util.StringUtils;
58  import org.eclipse.aether.util.version.GenericVersionScheme;
59  import org.eclipse.aether.version.InvalidVersionSpecificationException;
60  import org.eclipse.aether.version.Version;
61  
62  import java.io.File;
63  import java.io.FileNotFoundException;
64  import java.io.IOException;
65  import java.io.InputStream;
66  import java.net.HttpURLConnection;
67  import java.net.URI;
68  import java.net.http.HttpClient;
69  import java.net.http.HttpRequest;
70  import java.net.http.HttpResponse;
71  import java.time.Duration;
72  import java.time.Instant;
73  import java.util.ArrayList;
74  import java.util.Collections;
75  import java.util.Date;
76  import java.util.List;
77  import java.util.Map;
78  
79  import static java.util.Objects.requireNonNull;
80  
81  /**
82   * Collection of some use cases.
83   */
84  @Singleton
85  @Named
86  public class BasicUsageExample
87  {
88      private final Indexer indexer;
89  
90      private final IndexUpdater indexUpdater;
91  
92      private final Map<String, IndexCreator> indexCreators;
93  
94      private IndexingContext centralContext;
95  
96      @Inject
97      public BasicUsageExample( Indexer indexer, IndexUpdater indexUpdater, Map<String, IndexCreator> indexCreators )
98      {
99          this.indexer = requireNonNull( indexer );
100         this.indexUpdater = requireNonNull( indexUpdater );
101         this.indexCreators = requireNonNull( indexCreators );
102     }
103 
104     public void perform()
105         throws IOException, InvalidVersionSpecificationException
106     {
107         // Files where local cache is (if any) and Lucene Index should be located
108         File centralLocalCache = new File( "target/central-cache" );
109         File centralIndexDir = new File( "target/central-index" );
110 
111         // Creators we want to use (search for fields it defines)
112         List<IndexCreator> indexers = new ArrayList<>();
113         indexers.add( requireNonNull( indexCreators.get( "min" ) ) );
114         indexers.add( requireNonNull( indexCreators.get( "jarContent" ) ) );
115         indexers.add( requireNonNull( indexCreators.get( "maven-plugin" ) ) );
116 
117         // Create context for central repository index
118         centralContext =
119             indexer.createIndexingContext( "central-context", "central", centralLocalCache, centralIndexDir,
120                                            "https://repo1.maven.org/maven2", null, true, true, indexers );
121 
122         // Update the index (incremental update will happen if this is not 1st run and files are not deleted)
123         // This whole block below should not be executed on every app start, but rather controlled by some configuration
124         // since this block will always emit at least one HTTP GET. Central indexes are updated once a week, but
125         // other index sources might have different index publishing frequency.
126         // Preferred frequency is once a week.
127         if ( true )
128         {
129             Instant updateStart = Instant.now();
130             System.out.println( "Updating Index..." );
131             System.out.println( "This might take a while on first run, so please be patient!" );
132 
133             Date centralContextCurrentTimestamp = centralContext.getTimestamp();
134             IndexUpdateRequest updateRequest = new IndexUpdateRequest( centralContext, new Java11HttpClient() );
135             IndexUpdateResult updateResult = indexUpdater.fetchAndUpdateIndex( updateRequest );
136             if ( updateResult.isFullUpdate() )
137             {
138                 System.out.println( "Full update happened!" );
139             }
140             else if ( updateResult.getTimestamp().equals( centralContextCurrentTimestamp ) )
141             {
142                 System.out.println( "No update needed, index is up to date!" );
143             }
144             else
145             {
146                 System.out.println(
147                     "Incremental update happened, change covered " + centralContextCurrentTimestamp + " - "
148                         + updateResult.getTimestamp() + " period." );
149             }
150 
151             System.out.println( "Finished in " + Duration.between( updateStart, Instant.now() ).getSeconds() + " sec" );
152             System.out.println();
153         }
154 
155         System.out.println();
156         System.out.println( "Using index" );
157         System.out.println( "===========" );
158         System.out.println();
159 
160         // ====
161         // Case:
162         // dump all the GAVs
163         // NOTE: will not actually execute do this below, is too long to do (Central is HUGE), but is here as code
164         // example
165         if ( false )
166         {
167             final IndexSearcher searcher = centralContext.acquireIndexSearcher();
168             try
169             {
170                 final IndexReader ir = searcher.getIndexReader();
171                 Bits liveDocs = MultiBits.getLiveDocs( ir );
172                 for ( int i = 0; i < ir.maxDoc(); i++ )
173                 {
174                     if ( liveDocs == null || liveDocs.get( i ) )
175                     {
176                         final Document doc = ir.document( i );
177                         final ArtifactInfo ai = IndexUtils.constructArtifactInfo( doc, centralContext );
178                         System.out.println( ai.getGroupId() + ":" + ai.getArtifactId() + ":" + ai.getVersion() + ":"
179                                                 + ai.getClassifier() + " (sha1=" + ai.getSha1() + ")" );
180                     }
181                 }
182             }
183             finally
184             {
185                 centralContext.releaseIndexSearcher( searcher );
186             }
187         }
188 
189         // ====
190         // Case:
191         // Search for all GAVs with known G and A and having version greater than V
192 
193         final GenericVersionScheme versionScheme = new GenericVersionScheme();
194         final String versionString = "3.1.0";
195         final Version version = versionScheme.parseVersion( versionString );
196 
197         // construct the query for known GA
198         final Query groupIdQ =
199             indexer.constructQuery( MAVEN.GROUP_ID, new SourcedSearchExpression( "org.apache.maven" ) );
200         final Query artifactIdQ =
201             indexer.constructQuery( MAVEN.ARTIFACT_ID, new SourcedSearchExpression( "maven-plugin-api" ) );
202 
203         final BooleanQuery query = new BooleanQuery.Builder()
204             .add( groupIdQ, Occur.MUST )
205             .add( artifactIdQ, Occur.MUST )
206             // we want "jar" artifacts only
207             .add( indexer.constructQuery( MAVEN.PACKAGING, new SourcedSearchExpression( "jar" ) ), Occur.MUST )
208             // we want main artifacts only (no classifier)
209             // Note: this below is unfinished API, needs fixing
210             .add( indexer.constructQuery( MAVEN.CLASSIFIER,
211                     new SourcedSearchExpression( Field.NOT_PRESENT ) ), Occur.MUST_NOT )
212             .build();
213 
214         // construct the filter to express "V greater than"
215         final ArtifactInfoFilter versionFilter = ( ctx, ai ) ->
216         {
217             try
218             {
219                 final Version aiV = versionScheme.parseVersion( ai.getVersion() );
220                 // Use ">=" if you are INCLUSIVE
221                 return aiV.compareTo( version ) > 0;
222             }
223             catch ( InvalidVersionSpecificationException e )
224             {
225                 // do something here? be safe and include?
226                 return true;
227             }
228         };
229 
230         System.out.println(
231             "Searching for all GAVs with org.apache.maven:maven-plugin-api having V greater than 3.1.0" );
232         final IteratorSearchRequest request =
233             new IteratorSearchRequest( query, Collections.singletonList( centralContext ), versionFilter );
234         final IteratorSearchResponse response = indexer.searchIterator( request );
235         for ( ArtifactInfo ai : response )
236         {
237             System.out.println( ai.toString() );
238         }
239 
240         // Case:
241         // Use index
242         // Searching for some artifact
243         Query gidQ =
244             indexer.constructQuery( MAVEN.GROUP_ID, new SourcedSearchExpression( "org.apache.maven.indexer" ) );
245         Query aidQ = indexer.constructQuery( MAVEN.ARTIFACT_ID, new SourcedSearchExpression( "indexer-core" ) );
246 
247         BooleanQuery bq = new BooleanQuery.Builder()
248                 .add( gidQ, Occur.MUST )
249                 .add( aidQ, Occur.MUST )
250                 .build();
251 
252         searchAndDump( indexer, "all artifacts under GA org.apache.maven.indexer:indexer-core", bq );
253 
254         // Searching for some main artifact
255         bq = new BooleanQuery.Builder()
256                 .add( gidQ, Occur.MUST )
257                 .add( aidQ, Occur.MUST )
258                 .add( indexer.constructQuery( MAVEN.CLASSIFIER, new SourcedSearchExpression( "*" ) ), Occur.MUST_NOT )
259                 .build();
260 
261         searchAndDump( indexer, "main artifacts under GA org.apache.maven.indexer:indexer-core", bq );
262 
263         // doing sha1 search
264         searchAndDump( indexer, "SHA1 7ab67e6b20e5332a7fb4fdf2f019aec4275846c2",
265                        indexer.constructQuery( MAVEN.SHA1,
266                                                new SourcedSearchExpression( "7ab67e6b20e5332a7fb4fdf2f019aec4275846c2" )
267                        )
268         );
269 
270         searchAndDump( indexer, "SHA1 7ab67e6b20 (partial hash)",
271                        indexer.constructQuery( MAVEN.SHA1, new UserInputSearchExpression( "7ab67e6b20" ) ) );
272 
273         // doing classname search (incomplete classname)
274         searchAndDump( indexer, "classname DefaultNexusIndexer (note: Central does not publish classes in the index)",
275                        indexer.constructQuery( MAVEN.CLASSNAMES,
276                                                new UserInputSearchExpression( "DefaultNexusIndexer" ) ) );
277 
278         // doing search for all "canonical" maven plugins latest versions
279         bq = new BooleanQuery.Builder()
280             .add( indexer.constructQuery( MAVEN.PACKAGING, new SourcedSearchExpression( "maven-plugin" ) ), Occur.MUST )
281             .add( indexer.constructQuery( MAVEN.GROUP_ID,
282                     new SourcedSearchExpression( "org.apache.maven.plugins" ) ), Occur.MUST )
283             .build();
284 
285         searchGroupedAndDumpFlat( indexer, "all \"canonical\" maven plugins", bq, new GAGrouping() );
286 
287         // doing search for all archetypes latest versions
288         searchGroupedAndDump( indexer, "all maven archetypes (latest versions)",
289                               indexer.constructQuery( MAVEN.PACKAGING,
290                                                       new SourcedSearchExpression( "maven-archetype" ) ),
291                               new GAGrouping() );
292 
293         // close cleanly
294         indexer.closeIndexingContext( centralContext, false );
295     }
296 
297     public void searchAndDump( Indexer nexusIndexer, String descr, Query q )
298         throws IOException
299     {
300         System.out.println( "Searching for " + descr );
301 
302         FlatSearchResponse response = nexusIndexer.searchFlat( new FlatSearchRequest( q, centralContext ) );
303 
304         for ( ArtifactInfo ai : response.getResults() )
305         {
306             System.out.println( ai.toString() );
307         }
308 
309         System.out.println( "------" );
310         System.out.println( "Total: " + response.getTotalHitsCount() );
311         System.out.println();
312     }
313 
314     private static final int MAX_WIDTH = 60;
315 
316     public void searchGroupedAndDumpFlat( Indexer nexusIndexer, String descr, Query q, Grouping g )
317             throws IOException
318     {
319         System.out.println( "Searching for " + descr );
320 
321         GroupedSearchResponse response = nexusIndexer.searchGrouped( new GroupedSearchRequest( q, g, centralContext ) );
322 
323         for ( Map.Entry<String, ArtifactInfoGroup> entry : response.getResults().entrySet() )
324         {
325             ArtifactInfo ai = entry.getValue().getArtifactInfos().iterator().next();
326             System.out.println( "* " + ai.getGroupId() + ":" + ai.getArtifactId() + ":" + ai.getVersion() );
327         }
328 
329         System.out.println( "------" );
330         System.out.println( "Total record hits: " + response.getTotalHitsCount() );
331         System.out.println();
332     }
333 
334     public void searchGroupedAndDump( Indexer nexusIndexer, String descr, Query q, Grouping g )
335         throws IOException
336     {
337         System.out.println( "Searching for " + descr );
338 
339         GroupedSearchResponse response = nexusIndexer.searchGrouped( new GroupedSearchRequest( q, g, centralContext ) );
340 
341         for ( Map.Entry<String, ArtifactInfoGroup> entry : response.getResults().entrySet() )
342         {
343             ArtifactInfo ai = entry.getValue().getArtifactInfos().iterator().next();
344             System.out.println( "* Entry " + ai );
345             System.out.println( "  Latest version:  " + ai.getVersion() );
346             System.out.println( StringUtils.isBlank( ai.getDescription() )
347                                     ? "No description in plugin's POM."
348                                     : StringUtils.abbreviate( ai.getDescription(), MAX_WIDTH ) );
349             System.out.println();
350         }
351 
352         System.out.println( "------" );
353         System.out.println( "Total record hits: " + response.getTotalHitsCount() );
354         System.out.println();
355     }
356 
357     private static class Java11HttpClient implements ResourceFetcher
358     {
359         private final HttpClient client = HttpClient.newBuilder().followRedirects( HttpClient.Redirect.NEVER ).build();
360 
361         private URI uri;
362 
363         @Override
364         public void connect( String id, String url ) throws IOException
365         {
366             this.uri = URI.create( url + "/" );
367         }
368 
369         @Override
370         public void disconnect() throws IOException
371         {
372 
373         }
374 
375         @Override
376         public InputStream retrieve( String name ) throws IOException, FileNotFoundException
377         {
378             HttpRequest request = HttpRequest.newBuilder()
379                     .uri( uri.resolve( name ) )
380                     .GET()
381                     .build();
382             try
383             {
384                 HttpResponse<InputStream> response = client.send( request, HttpResponse.BodyHandlers.ofInputStream() );
385                 if ( response.statusCode() == HttpURLConnection.HTTP_OK )
386                 {
387                     return response.body();
388                 }
389                 else
390                 {
391                     throw new IOException( "Unexpected response: " + response );
392                 }
393             }
394             catch ( InterruptedException e )
395             {
396                 Thread.currentThread().interrupt();
397                 throw new IOException( e );
398             }
399         }
400     }
401 
402 }