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