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