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 java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  
27  import org.apache.lucene.document.Document;
28  import org.apache.lucene.index.CorruptIndexException;
29  import org.apache.lucene.index.IndexReader;
30  import org.apache.lucene.index.MultiBits;
31  import org.apache.lucene.index.Term;
32  import org.apache.lucene.search.IndexSearcher;
33  import org.apache.lucene.search.TermQuery;
34  import org.apache.lucene.search.TopScoreDocCollector;
35  import org.apache.lucene.util.Bits;
36  import org.apache.maven.index.context.IndexingContext;
37  
38  /**
39   * A default scanning listener
40   *
41   * @author Eugene Kuleshov
42   */
43  public class DefaultScannerListener implements ArtifactScanningListener {
44      private final IndexingContext context;
45  
46      private final IndexerEngine indexerEngine;
47  
48      private final boolean update;
49  
50      private final ArtifactScanningListener listener;
51  
52      private final Set<String> uinfos = new HashSet<>();
53  
54      private final Set<String> processedUinfos = new HashSet<>();
55  
56      private final Set<String> allGroups = new HashSet<>();
57  
58      private final Set<String> groups = new HashSet<>();
59  
60      private final List<Exception> exceptions = new ArrayList<>();
61  
62      private int count = 0;
63  
64      public DefaultScannerListener(
65              IndexingContext context, //
66              IndexerEngine indexerEngine,
67              boolean update, //
68              ArtifactScanningListener listener) {
69          this.context = context;
70          this.indexerEngine = indexerEngine;
71          this.update = update;
72          this.listener = listener;
73      }
74  
75      public void scanningStarted(IndexingContext ctx) {
76          try {
77              if (update) {
78                  initialize(ctx);
79              }
80          } catch (IOException ex) {
81              exceptions.add(ex);
82          }
83  
84          if (listener != null) {
85              listener.scanningStarted(ctx);
86          }
87      }
88  
89      public void artifactDiscovered(ArtifactContext ac) {
90          String uinfo = ac.getArtifactInfo().getUinfo();
91  
92          // TODO: scattered across commented out changes while I was fixing NEXUS-2712, cstamas
93          // These changes should be applied by borks too much the fragile indexer
94  
95          // if ( VersionUtils.isSnapshot( ac.getArtifactInfo().version ) && processedUinfos.contains( uinfo ) )
96          if (processedUinfos.contains(uinfo)) {
97              return; // skip individual snapshots
98          }
99  
100         boolean adding = processedUinfos.add(uinfo);
101 
102         if (uinfos.contains(uinfo)) {
103             // already indexed
104             uinfos.remove(uinfo);
105             return;
106         }
107 
108         try {
109             if (listener != null) {
110                 listener.artifactDiscovered(ac);
111             }
112 
113             if (adding) {
114                 indexerEngine.index(context, ac);
115             } else {
116                 indexerEngine.update(context, ac);
117             }
118 
119             for (Exception e : ac.getErrors()) {
120                 artifactError(ac, e);
121             }
122 
123             groups.add(ac.getArtifactInfo().getRootGroup());
124             allGroups.add(ac.getArtifactInfo().getGroupId());
125 
126             count++;
127         } catch (IOException ex) {
128             artifactError(ac, ex);
129         }
130     }
131 
132     public void scanningFinished(IndexingContext ctx, ScanningResult result) {
133         result.setTotalFiles(count);
134 
135         for (Exception ex : exceptions) {
136             result.addException(ex);
137         }
138 
139         try {
140             context.optimize();
141 
142             context.setRootGroups(groups);
143 
144             context.setAllGroups(allGroups);
145 
146             if (update && !context.isReceivingUpdates()) {
147                 removeDeletedArtifacts(context, result, result.getRequest().getStartingPath());
148             }
149         } catch (IOException ex) {
150             result.addException(ex);
151         }
152 
153         if (listener != null) {
154             listener.scanningFinished(ctx, result);
155         }
156 
157         if (result.getDeletedFiles() > 0 || result.getTotalFiles() > 0) {
158             try {
159                 context.updateTimestamp(true);
160 
161                 context.optimize();
162             } catch (Exception ex) {
163                 result.addException(ex);
164             }
165         }
166     }
167 
168     public void artifactError(ArtifactContext ac, Exception e) {
169         exceptions.add(e);
170 
171         if (listener != null) {
172             listener.artifactError(ac, e);
173         }
174     }
175 
176     private void initialize(IndexingContext ctx) throws IOException, CorruptIndexException {
177         final IndexSearcher indexSearcher = ctx.acquireIndexSearcher();
178         try {
179             final IndexReader r = indexSearcher.getIndexReader();
180             Bits liveDocs = MultiBits.getLiveDocs(r);
181 
182             for (int i = 0; i < r.maxDoc(); i++) {
183                 if (liveDocs == null || liveDocs.get(i)) {
184                     Document d = r.document(i);
185 
186                     String uinfo = d.get(ArtifactInfo.UINFO);
187 
188                     if (uinfo != null) {
189                         // if ctx is receiving updates (in other words, is a proxy),
190                         // there is no need to build a huge Set of strings with all uinfo's
191                         // as deletion detection in those cases have no effect. Also, the
192                         // removeDeletedArtifacts() method, that uses info gathered in this set
193                         // is invoked with same condition. As indexes of Central are getting huge,
194                         // the set grows enormously too, but is actually not used
195                         if (!ctx.isReceivingUpdates()) {
196                             uinfos.add(uinfo);
197                         }
198 
199                         // add all existing groupIds to the lists, as they will
200                         // not be "discovered" and would be missing from the new list..
201                         String groupId = uinfo.substring(0, uinfo.indexOf('|'));
202                         int n = groupId.indexOf('.');
203                         groups.add(n == -1 ? groupId : groupId.substring(0, n));
204                         allGroups.add(groupId);
205                     }
206                 }
207             }
208         } finally {
209             ctx.releaseIndexSearcher(indexSearcher);
210         }
211     }
212 
213     private void removeDeletedArtifacts(IndexingContext context, ScanningResult result, String contextPath)
214             throws IOException {
215         int deleted = 0;
216 
217         final IndexSearcher indexSearcher = context.acquireIndexSearcher();
218         try {
219             for (String uinfo : uinfos) {
220                 TopScoreDocCollector collector = TopScoreDocCollector.create(1, Integer.MAX_VALUE);
221 
222                 indexSearcher.search(new TermQuery(new Term(ArtifactInfo.UINFO, uinfo)), collector);
223 
224                 if (collector.getTotalHits() > 0) {
225                     String[] ra = ArtifactInfo.FS_PATTERN.split(uinfo);
226 
227                     ArtifactInfo ai = new ArtifactInfo();
228 
229                     ai.setRepository(context.getRepositoryId());
230 
231                     ai.setGroupId(ra[0]);
232 
233                     ai.setArtifactId(ra[1]);
234 
235                     ai.setVersion(ra[2]);
236 
237                     if (ra.length > 3) {
238                         ai.setClassifier(ArtifactInfo.renvl(ra[3]));
239                     }
240 
241                     if (ra.length > 4) {
242                         ai.setPackaging(ArtifactInfo.renvl(ra[4]));
243                     }
244 
245                     // minimal ArtifactContext for removal
246                     ArtifactContext ac = new ArtifactContext(null, null, null, ai, ai.calculateGav());
247 
248                     for (int i = 0; i < collector.getTotalHits(); i++) {
249                         if (contextPath == null
250                                 || context.getGavCalculator()
251                                         .gavToPath(ac.getGav())
252                                         .startsWith(contextPath)) {
253                             indexerEngine.remove(context, ac);
254                         }
255 
256                         deleted++;
257                     }
258                 }
259             }
260         } finally {
261             context.releaseIndexSearcher(indexSearcher);
262         }
263 
264         if (deleted > 0) {
265             context.commit();
266         }
267 
268         result.setDeletedFiles(deleted);
269     }
270 }