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.search.backend.indexer.internal;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.stream.StreamSupport;
29  
30  import org.apache.lucene.search.BooleanClause;
31  import org.apache.lucene.search.BooleanQuery;
32  import org.apache.lucene.search.Query;
33  import org.apache.maven.index.ArtifactAvailability;
34  import org.apache.maven.index.ArtifactInfo;
35  import org.apache.maven.index.GroupedSearchRequest;
36  import org.apache.maven.index.GroupedSearchResponse;
37  import org.apache.maven.index.Indexer;
38  import org.apache.maven.index.IteratorSearchRequest;
39  import org.apache.maven.index.IteratorSearchResponse;
40  import org.apache.maven.index.SearchType;
41  import org.apache.maven.index.context.IndexingContext;
42  import org.apache.maven.index.expr.SourcedSearchExpression;
43  import org.apache.maven.index.search.grouping.GAGrouping;
44  import org.apache.maven.search.api.MAVEN;
45  import org.apache.maven.search.api.Record;
46  import org.apache.maven.search.api.SearchRequest;
47  import org.apache.maven.search.api.request.Field;
48  import org.apache.maven.search.api.request.FieldQuery;
49  import org.apache.maven.search.api.request.Paging;
50  import org.apache.maven.search.api.support.SearchBackendSupport;
51  import org.apache.maven.search.backend.indexer.IndexerCoreSearchBackend;
52  import org.apache.maven.search.backend.indexer.IndexerCoreSearchResponse;
53  
54  import static java.util.Objects.requireNonNull;
55  
56  /**
57   * An engine to perform search trough single repository index (endpoint).
58   */
59  public class IndexerCoreSearchBackendImpl extends SearchBackendSupport implements IndexerCoreSearchBackend {
60      private static final Map<Field, org.apache.maven.index.Field> FIELD_TRANSLATION;
61  
62      static {
63          HashMap<Field, org.apache.maven.index.Field> map = new HashMap<>();
64          map.put(MAVEN.GROUP_ID, org.apache.maven.index.MAVEN.GROUP_ID);
65          map.put(MAVEN.ARTIFACT_ID, org.apache.maven.index.MAVEN.ARTIFACT_ID);
66          map.put(MAVEN.VERSION, org.apache.maven.index.MAVEN.VERSION);
67          map.put(MAVEN.CLASSIFIER, org.apache.maven.index.MAVEN.CLASSIFIER);
68          map.put(MAVEN.PACKAGING, org.apache.maven.index.MAVEN.PACKAGING);
69          map.put(MAVEN.CLASS_NAME, org.apache.maven.index.MAVEN.CLASSNAMES);
70          map.put(MAVEN.FQ_CLASS_NAME, org.apache.maven.index.MAVEN.CLASSNAMES);
71          map.put(MAVEN.SHA1, org.apache.maven.index.MAVEN.SHA1);
72          FIELD_TRANSLATION = Collections.unmodifiableMap(map);
73      }
74  
75      private final Indexer indexer;
76  
77      private final IndexingContext indexingContext;
78  
79      /**
80       * Creates backend instance using provided indexer and context.
81       */
82      public IndexerCoreSearchBackendImpl(Indexer indexer, IndexingContext indexingContext) {
83          super(indexingContext.getId(), indexingContext.getRepositoryId());
84          this.indexer = requireNonNull(indexer);
85          this.indexingContext = indexingContext;
86      }
87  
88      @Override
89      public IndexingContext getIndexingContext() {
90          return indexingContext;
91      }
92  
93      @Override
94      public IndexerCoreSearchResponse search(SearchRequest searchRequest) throws IOException {
95          Paging paging = searchRequest.getPaging();
96          int totalHitsCount;
97          List<ArtifactInfo> artifactInfos = new ArrayList<>(paging.getPageSize());
98          List<Record> page = new ArrayList<>(paging.getPageSize());
99  
100         // if GA present in query: doing flat, otherwise grouped search to mimic SMO
101         HashSet<Field> searchedFields = new HashSet<>();
102         Query query = toQuery(searchedFields, searchRequest.getQuery());
103         if (searchedFields.contains(MAVEN.SHA1)
104                 || (searchedFields.contains(MAVEN.GROUP_ID) && searchedFields.contains(MAVEN.ARTIFACT_ID))) {
105             if (!searchedFields.contains(MAVEN.CLASSIFIER)) {
106                 query = new BooleanQuery.Builder()
107                         .add(new BooleanClause(query, BooleanClause.Occur.MUST))
108                         .add(
109                                 indexer.constructQuery(
110                                         org.apache.maven.index.MAVEN.CLASSIFIER,
111                                         new SourcedSearchExpression(org.apache.maven.index.Field.NOT_PRESENT)),
112                                 BooleanClause.Occur.MUST_NOT)
113                         .build();
114             }
115             IteratorSearchRequest iteratorSearchRequest =
116                     new IteratorSearchRequest(query, Collections.singletonList(indexingContext));
117             iteratorSearchRequest.setCount(paging.getPageSize());
118             iteratorSearchRequest.setStart(paging.getPageSize() * paging.getPageOffset());
119 
120             try (IteratorSearchResponse iteratorSearchResponse = indexer.searchIterator(iteratorSearchRequest)) {
121                 totalHitsCount = iteratorSearchResponse.getTotalHitsCount();
122                 StreamSupport.stream(iteratorSearchResponse.iterator().spliterator(), false)
123                         .sorted(ArtifactInfo.VERSION_COMPARATOR)
124                         .forEach(ai -> {
125                             artifactInfos.add(ai);
126                             page.add(convert(ai, null));
127                         });
128             }
129             return new IndexerCoreSearchResponseImpl(searchRequest, totalHitsCount, page, query, artifactInfos);
130         } else {
131             GroupedSearchRequest groupedSearchRequest =
132                     new GroupedSearchRequest(query, new GAGrouping(), indexingContext);
133 
134             try (GroupedSearchResponse groupedSearchResponse = indexer.searchGrouped(groupedSearchRequest)) {
135                 totalHitsCount = groupedSearchResponse.getResults().size();
136                 groupedSearchResponse.getResults().values().stream()
137                         .skip((long) paging.getPageSize() * paging.getPageOffset())
138                         .limit(paging.getPageSize())
139                         .forEach(aig -> {
140                             ArtifactInfo ai = aig.getArtifactInfos().iterator().next();
141                             artifactInfos.add(ai);
142                             page.add(convert(ai, aig.getArtifactInfos().size()));
143                         });
144             }
145             return new IndexerCoreSearchResponseImpl(searchRequest, totalHitsCount, page, query, artifactInfos);
146         }
147     }
148 
149     private Query toQuery(HashSet<Field> searchedFields, org.apache.maven.search.api.request.Query query) {
150         if (query instanceof org.apache.maven.search.api.request.BooleanQuery.And) {
151             org.apache.maven.search.api.request.BooleanQuery bq =
152                     (org.apache.maven.search.api.request.BooleanQuery) query;
153             return new BooleanQuery.Builder()
154                     .add(new BooleanClause(toQuery(searchedFields, bq.getLeft()), BooleanClause.Occur.MUST))
155                     .add(new BooleanClause(toQuery(searchedFields, bq.getRight()), BooleanClause.Occur.MUST))
156                     .build();
157         } else if (query instanceof FieldQuery) {
158             FieldQuery fq = (FieldQuery) query;
159             org.apache.maven.index.Field icFieldName = FIELD_TRANSLATION.get(fq.getField());
160             if (icFieldName != null) {
161                 searchedFields.add(fq.getField());
162                 if (fq.getValue().endsWith("*")) {
163                     return indexer.constructQuery(icFieldName, fq.getValue(), SearchType.SCORED);
164                 } else {
165                     return indexer.constructQuery(icFieldName, fq.getValue(), SearchType.EXACT);
166                 }
167             } else {
168                 throw new IllegalArgumentException("Unsupported Indexer field: " + fq.getField());
169             }
170         }
171         return new BooleanQuery.Builder()
172                 .add(new BooleanClause(
173                         indexer.constructQuery(
174                                 org.apache.maven.index.MAVEN.GROUP_ID, query.getValue(), SearchType.SCORED),
175                         BooleanClause.Occur.SHOULD))
176                 .add(new BooleanClause(
177                         indexer.constructQuery(
178                                 org.apache.maven.index.MAVEN.ARTIFACT_ID, query.getValue(), SearchType.SCORED),
179                         BooleanClause.Occur.SHOULD))
180                 .add(new BooleanClause(
181                         indexer.constructQuery(org.apache.maven.index.MAVEN.NAME, query.getValue(), SearchType.SCORED),
182                         BooleanClause.Occur.SHOULD))
183                 .build();
184     }
185 
186     private Record convert(ArtifactInfo ai, /* nullable */ Integer versionCount) {
187         HashMap<Field, Object> result = new HashMap<>();
188 
189         mayPut(result, MAVEN.GROUP_ID, ai.getGroupId());
190         mayPut(result, MAVEN.ARTIFACT_ID, ai.getArtifactId());
191         mayPut(result, MAVEN.VERSION, ai.getVersion());
192         mayPut(result, MAVEN.PACKAGING, ai.getPackaging());
193         mayPut(result, MAVEN.CLASSIFIER, ai.getClassifier());
194         mayPut(result, MAVEN.FILE_EXTENSION, ai.getFileExtension());
195 
196         mayPut(result, MAVEN.VERSION_COUNT, versionCount);
197 
198         mayPut(result, MAVEN.HAS_SOURCE, ai.getSourcesExists() == ArtifactAvailability.PRESENT);
199         mayPut(result, MAVEN.HAS_JAVADOC, ai.getJavadocExists() == ArtifactAvailability.PRESENT);
200         mayPut(result, MAVEN.HAS_GPG_SIGNATURE, ai.getSignatureExists() == ArtifactAvailability.PRESENT);
201 
202         return new Record(getBackendId(), getRepositoryId(), ai.getUinfo(), ai.getLastModified(), result);
203     }
204 
205     private static void mayPut(Map<Field, Object> result, Field fieldName, /* nullable */ Object value) {
206         if (value != null) {
207             result.put(fieldName, value);
208         }
209     }
210 }