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;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.IOException;
28  import java.security.MessageDigest;
29  import java.security.NoSuchAlgorithmException;
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.List;
34  
35  import org.apache.lucene.queryparser.classic.ParseException;
36  import org.apache.lucene.search.Query;
37  import org.apache.maven.index.context.ContextMemberProvider;
38  import org.apache.maven.index.context.DefaultIndexingContext;
39  import org.apache.maven.index.context.ExistingLuceneIndexMismatchException;
40  import org.apache.maven.index.context.IndexCreator;
41  import org.apache.maven.index.context.IndexingContext;
42  import org.apache.maven.index.context.MergedIndexingContext;
43  import org.apache.maven.index.expr.SearchExpression;
44  import org.apache.maven.index.expr.SearchTypedStringSearchExpression;
45  import org.apache.maven.index.expr.SourcedSearchExpression;
46  import org.apache.maven.index.util.IndexCreatorSorter;
47  
48  /**
49   * A default {@link Indexer} implementation.
50   *
51   * @author Tamas Cservenak
52   */
53  @Singleton
54  @Named
55  public class DefaultIndexer implements Indexer {
56  
57      private final SearchEngine searcher;
58  
59      private final IndexerEngine indexerEngine;
60  
61      private final QueryCreator queryCreator;
62  
63      @Inject
64      public DefaultIndexer(SearchEngine searcher, IndexerEngine indexerEngine, QueryCreator queryCreator) {
65          this.searcher = searcher;
66          this.indexerEngine = indexerEngine;
67          this.queryCreator = queryCreator;
68      }
69  
70      // ----------------------------------------------------------------------------
71      // Contexts
72      // ----------------------------------------------------------------------------
73  
74      public IndexingContext createIndexingContext(
75              String id,
76              String repositoryId,
77              File repository,
78              File indexDirectory,
79              String repositoryUrl,
80              String indexUpdateUrl,
81              boolean searchable,
82              boolean reclaim,
83              List<? extends IndexCreator> indexers)
84              throws IOException, ExistingLuceneIndexMismatchException, IllegalArgumentException {
85          final IndexingContext context = new DefaultIndexingContext(
86                  id,
87                  repositoryId,
88                  repository,
89                  indexDirectory,
90                  repositoryUrl,
91                  indexUpdateUrl,
92                  IndexCreatorSorter.sort(indexers),
93                  reclaim);
94          context.setSearchable(searchable);
95          return context;
96      }
97  
98      public IndexingContext createMergedIndexingContext(
99              String id,
100             String repositoryId,
101             File repository,
102             File indexDirectory,
103             boolean searchable,
104             ContextMemberProvider membersProvider)
105             throws IOException {
106         IndexingContext context =
107                 new MergedIndexingContext(id, repositoryId, repository, indexDirectory, searchable, membersProvider);
108         return context;
109     }
110 
111     public void closeIndexingContext(IndexingContext context, boolean deleteFiles) throws IOException {
112         context.close(deleteFiles);
113     }
114 
115     // ----------------------------------------------------------------------------
116     // Modifying
117     // ----------------------------------------------------------------------------
118 
119     public void addArtifactToIndex(ArtifactContext ac, IndexingContext context) throws IOException {
120         if (ac != null) {
121             indexerEngine.update(context, ac);
122 
123             context.commit();
124         }
125     }
126 
127     public void addArtifactsToIndex(Collection<ArtifactContext> ac, IndexingContext context) throws IOException {
128         if (ac != null && !ac.isEmpty()) {
129             for (ArtifactContext actx : ac) {
130                 indexerEngine.update(context, actx);
131             }
132 
133             context.commit();
134         }
135     }
136 
137     public void deleteArtifactsFromIndex(Collection<ArtifactContext> ac, IndexingContext context) throws IOException {
138         if (ac != null && !ac.isEmpty()) {
139             for (ArtifactContext actx : ac) {
140                 indexerEngine.remove(context, actx);
141 
142                 context.commit();
143             }
144         }
145     }
146 
147     // ----------------------------------------------------------------------------
148     // Searching
149     // ----------------------------------------------------------------------------
150 
151     public FlatSearchResponse searchFlat(FlatSearchRequest request) throws IOException {
152         if (request.getContexts().isEmpty()) {
153             return new FlatSearchResponse(request.getQuery(), 0, Collections.emptySet());
154         } else {
155             return searcher.forceSearchFlatPaged(request, request.getContexts());
156         }
157     }
158 
159     public IteratorSearchResponse searchIterator(IteratorSearchRequest request) throws IOException {
160         if (request.getContexts().isEmpty()) {
161             return IteratorSearchResponse.empty(request.getQuery());
162         } else {
163             return searcher.forceSearchIteratorPaged(request, request.getContexts());
164         }
165     }
166 
167     public GroupedSearchResponse searchGrouped(GroupedSearchRequest request) throws IOException {
168         if (request.getContexts().isEmpty()) {
169             return new GroupedSearchResponse(request.getQuery(), 0, Collections.emptyMap());
170         } else {
171             // search targeted
172             return searcher.forceSearchGrouped(request, request.getContexts());
173         }
174     }
175 
176     // ----------------------------------------------------------------------------
177     // Identification
178     // ----------------------------------------------------------------------------
179 
180     public Collection<ArtifactInfo> identify(final File artifact, final Collection<IndexingContext> contexts)
181             throws IOException {
182         try (FileInputStream is = new FileInputStream(artifact)) {
183             final MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
184             final byte[] buff = new byte[4096];
185             int n;
186             while ((n = is.read(buff)) > -1) {
187                 sha1.update(buff, 0, n);
188             }
189             byte[] digest = sha1.digest();
190             return identify(constructQuery(MAVEN.SHA1, new SourcedSearchExpression(encode(digest))), contexts);
191         } catch (NoSuchAlgorithmException ex) {
192             throw new IOException("Unable to calculate digest", ex);
193         }
194     }
195 
196     public Collection<ArtifactInfo> identify(Query query, Collection<IndexingContext> contexts) throws IOException {
197         try (IteratorSearchResponse result = searcher.searchIteratorPaged(new IteratorSearchRequest(query), contexts)) {
198             final List<ArtifactInfo> ais = new ArrayList<>(result.getTotalHitsCount());
199             for (ArtifactInfo ai : result) {
200                 ais.add(ai);
201             }
202             return ais;
203         }
204     }
205 
206     // ----------------------------------------------------------------------------
207     // Query construction
208     // ----------------------------------------------------------------------------
209 
210     public Query constructQuery(Field field, SearchExpression expression) throws IllegalArgumentException {
211         try {
212             return queryCreator.constructQuery(field, expression);
213         } catch (ParseException e) {
214             throw new IllegalArgumentException(e);
215         }
216     }
217 
218     public Query constructQuery(Field field, String expression, SearchType searchType) throws IllegalArgumentException {
219         return constructQuery(field, new SearchTypedStringSearchExpression(expression, searchType));
220     }
221     // ==
222 
223     private static final char[] DIGITS = "0123456789abcdef".toCharArray();
224 
225     private static String encode(byte[] digest) {
226         char[] buff = new char[digest.length * 2];
227 
228         int n = 0;
229 
230         for (byte b : digest) {
231             buff[n++] = DIGITS[(0xF0 & b) >> 4];
232             buff[n++] = DIGITS[0x0F & b];
233         }
234 
235         return new String(buff);
236     }
237 }