1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.index.context;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.nio.channels.FileChannel;
24 import java.nio.channels.FileLock;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.StandardOpenOption;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Date;
31 import java.util.HashSet;
32 import java.util.LinkedHashSet;
33 import java.util.List;
34 import java.util.Set;
35 import java.util.concurrent.atomic.AtomicReference;
36
37 import org.apache.lucene.analysis.Analyzer;
38 import org.apache.lucene.document.Document;
39 import org.apache.lucene.document.Field;
40 import org.apache.lucene.document.StoredField;
41 import org.apache.lucene.index.CorruptIndexException;
42 import org.apache.lucene.index.DirectoryReader;
43 import org.apache.lucene.index.IndexReader;
44 import org.apache.lucene.index.IndexWriter;
45 import org.apache.lucene.index.IndexWriterConfig;
46 import org.apache.lucene.index.MultiBits;
47 import org.apache.lucene.index.StoredFields;
48 import org.apache.lucene.index.Term;
49 import org.apache.lucene.search.IndexSearcher;
50 import org.apache.lucene.search.SearcherManager;
51 import org.apache.lucene.search.TermQuery;
52 import org.apache.lucene.search.TopScoreDocCollector;
53 import org.apache.lucene.store.Directory;
54 import org.apache.lucene.store.FSDirectory;
55 import org.apache.lucene.store.FSLockFactory;
56 import org.apache.lucene.store.Lock;
57 import org.apache.lucene.store.LockObtainFailedException;
58 import org.apache.lucene.util.Bits;
59 import org.apache.maven.index.ArtifactInfo;
60 import org.apache.maven.index.IndexerField;
61 import org.apache.maven.index.artifact.GavCalculator;
62 import org.apache.maven.index.artifact.M2GavCalculator;
63 import org.codehaus.plexus.util.StringUtils;
64
65
66
67
68
69
70
71 public class DefaultIndexingContext extends AbstractIndexingContext {
72
73
74
75 private static final String INDEX_DIRECTORY = ".index";
76
77 public static final String FLD_DESCRIPTOR = "DESCRIPTOR";
78
79 public static final String FLD_DESCRIPTOR_CONTENTS = "NexusIndex";
80
81 public static final String FLD_IDXINFO = "IDXINFO";
82
83 public static final String VERSION = "1.0";
84
85 private static final Term DESCRIPTOR_TERM = new Term(FLD_DESCRIPTOR, FLD_DESCRIPTOR_CONTENTS);
86
87 private Directory indexDirectory;
88
89 private TrackingLockFactory lockFactory;
90
91 private File indexDirectoryFile;
92
93 private String id;
94
95 private boolean searchable;
96
97 private String repositoryId;
98
99 private File repository;
100
101 private String repositoryUrl;
102
103 private String indexUpdateUrl;
104
105 private NexusIndexWriter indexWriter;
106
107 private SearcherManager searcherManager;
108
109 private Date timestamp;
110
111 private List<? extends IndexCreator> indexCreators;
112
113
114
115
116
117
118 private GavCalculator gavCalculator;
119
120 private DefaultIndexingContext(
121 String id,
122 String repositoryId,
123 File repository,
124 String repositoryUrl,
125 String indexUpdateUrl,
126 List<? extends IndexCreator> indexCreators,
127 Directory indexDirectory,
128 TrackingLockFactory lockFactory,
129 boolean reclaimIndex,
130 File indexDirectoryFile)
131 throws ExistingLuceneIndexMismatchException, IOException {
132
133 this.id = id;
134
135 this.searchable = true;
136
137 this.repositoryId = repositoryId;
138
139 this.repository = repository;
140
141 this.repositoryUrl = repositoryUrl;
142
143 this.indexUpdateUrl = indexUpdateUrl;
144
145 this.indexWriter = null;
146
147 this.searcherManager = null;
148
149 this.indexCreators = indexCreators;
150
151 this.indexDirectory = indexDirectory;
152
153 this.lockFactory = lockFactory;
154
155
156
157
158
159 for (IndexCreator indexCreator : indexCreators) {
160 indexCreator.getIndexerFields();
161 }
162
163 this.gavCalculator = new M2GavCalculator();
164
165 prepareIndex(reclaimIndex);
166
167 setIndexDirectoryFile(indexDirectoryFile);
168 }
169
170 private DefaultIndexingContext(
171 String id,
172 String repositoryId,
173 File repository,
174 File indexDirectoryFile,
175 TrackingLockFactory lockFactory,
176 String repositoryUrl,
177 String indexUpdateUrl,
178 List<? extends IndexCreator> indexCreators,
179 boolean reclaimIndex)
180 throws IOException, ExistingLuceneIndexMismatchException {
181 this(
182 id,
183 repositoryId,
184 repository,
185 repositoryUrl,
186 indexUpdateUrl,
187 indexCreators,
188 FSDirectory.open(indexDirectoryFile.toPath(), lockFactory),
189 lockFactory,
190 reclaimIndex,
191 indexDirectoryFile);
192 }
193
194 public DefaultIndexingContext(
195 String id,
196 String repositoryId,
197 File repository,
198 File indexDirectoryFile,
199 String repositoryUrl,
200 String indexUpdateUrl,
201 List<? extends IndexCreator> indexCreators,
202 boolean reclaimIndex)
203 throws IOException, ExistingLuceneIndexMismatchException {
204 this(
205 id,
206 repositoryId,
207 repository,
208 indexDirectoryFile,
209 new TrackingLockFactory(FSLockFactory.getDefault()),
210 repositoryUrl,
211 indexUpdateUrl,
212 indexCreators,
213 reclaimIndex);
214 }
215
216 @Deprecated
217 public DefaultIndexingContext(
218 String id,
219 String repositoryId,
220 File repository,
221 Directory indexDirectory,
222 String repositoryUrl,
223 String indexUpdateUrl,
224 List<? extends IndexCreator> indexCreators,
225 boolean reclaimIndex)
226 throws IOException, ExistingLuceneIndexMismatchException {
227 this(
228 id,
229 repositoryId,
230 repository,
231 repositoryUrl,
232 indexUpdateUrl,
233 indexCreators,
234 indexDirectory,
235 null,
236 reclaimIndex,
237 indexDirectory instanceof FSDirectory
238 ? ((FSDirectory) indexDirectory).getDirectory().toFile()
239 : null);
240 }
241
242 public Directory getIndexDirectory() {
243 return indexDirectory;
244 }
245
246
247
248
249
250 protected void setIndexDirectoryFile(File dir) throws IOException {
251 if (dir == null) {
252
253 this.indexDirectoryFile =
254 Files.createTempDirectory("mindexer-ctx" + id).toFile();
255 this.indexDirectoryFile.deleteOnExit();
256 } else {
257 this.indexDirectoryFile = dir;
258 }
259 }
260
261 public File getIndexDirectoryFile() {
262 return indexDirectoryFile;
263 }
264
265 private void prepareIndex(boolean reclaimIndex) throws IOException, ExistingLuceneIndexMismatchException {
266 if (DirectoryReader.indexExists(indexDirectory)) {
267 try {
268
269 try {
270 indexDirectory.obtainLock(IndexWriter.WRITE_LOCK_NAME).close();
271 } catch (LockObtainFailedException failed) {
272 unlockForcibly(lockFactory, indexDirectory);
273 }
274
275 openAndWarmup();
276
277 checkAndUpdateIndexDescriptor(reclaimIndex);
278 } catch (IOException e) {
279 if (reclaimIndex) {
280 prepareCleanIndex(true);
281 } else {
282 throw e;
283 }
284 }
285 } else {
286 prepareCleanIndex(false);
287 }
288
289 timestamp = IndexUtils.getTimestamp(indexDirectory);
290 }
291
292 private void prepareCleanIndex(boolean deleteExisting) throws IOException {
293 if (deleteExisting) {
294 closeReaders();
295
296
297 try {
298 indexDirectory.obtainLock(IndexWriter.WRITE_LOCK_NAME).close();
299 } catch (LockObtainFailedException failed) {
300 unlockForcibly(lockFactory, indexDirectory);
301 }
302
303 deleteIndexFiles(true);
304 }
305
306 openAndWarmup();
307
308 if (StringUtils.isEmpty(getRepositoryId())) {
309 throw new IllegalArgumentException("The repositoryId cannot be null when creating new repository!");
310 }
311
312 storeDescriptor();
313 }
314
315 private void checkAndUpdateIndexDescriptor(boolean reclaimIndex)
316 throws IOException, ExistingLuceneIndexMismatchException {
317 if (reclaimIndex) {
318
319 storeDescriptor();
320 return;
321 }
322
323
324 if (getSize() > 0) {
325 final TopScoreDocCollector collector = TopScoreDocCollector.create(1, Integer.MAX_VALUE);
326 final IndexSearcher indexSearcher = acquireIndexSearcher();
327 try {
328 indexSearcher.search(new TermQuery(DESCRIPTOR_TERM), collector);
329
330 if (collector.getTotalHits() == 0) {
331 throw new ExistingLuceneIndexMismatchException("The existing index has no NexusIndexer descriptor");
332 }
333
334 if (collector.getTotalHits() > 1) {
335
336 storeDescriptor();
337 } else {
338
339 Document descriptor = indexSearcher.storedFields().document(collector.topDocs().scoreDocs[0].doc);
340 String[] h = StringUtils.split(descriptor.get(FLD_IDXINFO), ArtifactInfo.FS);
341
342 String repoId = h[1];
343
344
345
346
347
348
349
350
351 if (getRepositoryId() == null) {
352 repositoryId = repoId;
353 } else if (!getRepositoryId().equals(repoId)) {
354 throw new ExistingLuceneIndexMismatchException(
355 "The existing index is for repository "
356 + "[" + repoId + "] and not for repository [" + getRepositoryId() + "]");
357 }
358 }
359 } finally {
360 releaseIndexSearcher(indexSearcher);
361 }
362 }
363 }
364
365 private void storeDescriptor() throws IOException {
366 Document hdr = new Document();
367
368 hdr.add(new Field(FLD_DESCRIPTOR, FLD_DESCRIPTOR_CONTENTS, IndexerField.KEYWORD_STORED));
369
370 hdr.add(new StoredField(
371 FLD_IDXINFO, VERSION + ArtifactInfo.FS + getRepositoryId(), IndexerField.KEYWORD_STORED));
372
373 IndexWriter w = getIndexWriter();
374
375 w.updateDocument(DESCRIPTOR_TERM, hdr);
376
377 w.commit();
378 }
379
380 private void deleteIndexFiles(boolean full) throws IOException {
381 if (indexDirectory != null) {
382 String[] names = indexDirectory.listAll();
383
384 if (names != null) {
385
386 for (String name : names) {
387 if (!(name.equals(INDEX_PACKER_PROPERTIES_FILE) || name.equals(INDEX_UPDATER_PROPERTIES_FILE))) {
388 indexDirectory.deleteFile(name);
389 }
390 }
391 }
392
393 if (full) {
394 try {
395 indexDirectory.deleteFile(INDEX_PACKER_PROPERTIES_FILE);
396 } catch (IOException ioe) {
397
398 }
399
400 try {
401 indexDirectory.deleteFile(INDEX_UPDATER_PROPERTIES_FILE);
402 } catch (IOException ioe) {
403
404 }
405 }
406
407 IndexUtils.deleteTimestamp(indexDirectory);
408 }
409 }
410
411
412
413 public boolean isSearchable() {
414 return searchable;
415 }
416
417 public void setSearchable(boolean searchable) {
418 this.searchable = searchable;
419 }
420
421 public String getId() {
422 return id;
423 }
424
425 public void updateTimestamp() throws IOException {
426 updateTimestamp(false);
427 }
428
429 public void updateTimestamp(boolean save) throws IOException {
430 updateTimestamp(save, new Date());
431 }
432
433 public void updateTimestamp(boolean save, Date timestamp) throws IOException {
434 this.timestamp = timestamp;
435
436 if (save) {
437 IndexUtils.updateTimestamp(indexDirectory, getTimestamp());
438 }
439 }
440
441 public Date getTimestamp() {
442 return timestamp;
443 }
444
445 public int getSize() throws IOException {
446 final IndexSearcher is = acquireIndexSearcher();
447 try {
448 return is.getIndexReader().numDocs();
449 } finally {
450 releaseIndexSearcher(is);
451 }
452 }
453
454 public String getRepositoryId() {
455 return repositoryId;
456 }
457
458 public File getRepository() {
459 return repository;
460 }
461
462 public String getRepositoryUrl() {
463 return repositoryUrl;
464 }
465
466 public String getIndexUpdateUrl() {
467 if (repositoryUrl != null) {
468 if (indexUpdateUrl == null || indexUpdateUrl.isBlank()) {
469 return repositoryUrl + (repositoryUrl.endsWith("/") ? "" : "/") + INDEX_DIRECTORY;
470 }
471 }
472 return indexUpdateUrl;
473 }
474
475 public Analyzer getAnalyzer() {
476 return new NexusAnalyzer();
477 }
478
479 protected void openAndWarmup() throws IOException {
480
481 if (indexWriter != null) {
482 indexWriter.close();
483
484 indexWriter = null;
485 }
486 if (searcherManager != null) {
487 searcherManager.close();
488
489 searcherManager = null;
490 }
491
492 this.indexWriter = new NexusIndexWriter(getIndexDirectory(), getWriterConfig());
493 this.indexWriter.commit();
494 this.searcherManager = new SearcherManager(indexWriter, false, false, new NexusIndexSearcherFactory(this));
495 }
496
497
498
499
500
501
502 protected IndexWriterConfig getWriterConfig() {
503 return NexusIndexWriter.defaultConfig();
504 }
505
506 public IndexWriter getIndexWriter() throws IOException {
507 return indexWriter;
508 }
509
510 public IndexSearcher acquireIndexSearcher() throws IOException {
511
512 searcherManager.maybeRefresh();
513 return searcherManager.acquire();
514 }
515
516 public void releaseIndexSearcher(final IndexSearcher is) throws IOException {
517 if (is == null) {
518 return;
519 }
520 searcherManager.release(is);
521 }
522
523 public void commit() throws IOException {
524 getIndexWriter().commit();
525 }
526
527 public void rollback() throws IOException {
528 getIndexWriter().rollback();
529 }
530
531 public synchronized void optimize() throws CorruptIndexException, IOException {
532 commit();
533 }
534
535 public synchronized void close(boolean deleteFiles) throws IOException {
536 if (indexDirectory != null) {
537 IndexUtils.updateTimestamp(indexDirectory, getTimestamp());
538 closeReaders();
539 if (deleteFiles) {
540 deleteIndexFiles(true);
541 }
542 indexDirectory.close();
543 }
544 indexDirectory = null;
545 }
546
547 public synchronized void purge() throws IOException {
548 closeReaders();
549 deleteIndexFiles(true);
550 try {
551 prepareIndex(true);
552 } catch (ExistingLuceneIndexMismatchException e) {
553
554 }
555 rebuildGroups();
556 updateTimestamp(true, null);
557 }
558
559 public synchronized void replace(Directory directory) throws IOException {
560 replace(directory, null, null);
561 }
562
563 public synchronized void replace(Directory directory, Set<String> allGroups, Set<String> rootGroups)
564 throws IOException {
565 final Date ts = IndexUtils.getTimestamp(directory);
566 closeReaders();
567 deleteIndexFiles(false);
568 IndexUtils.copyDirectory(directory, indexDirectory);
569 openAndWarmup();
570
571 storeDescriptor();
572 if (allGroups == null && rootGroups == null) {
573 rebuildGroups();
574 } else {
575 if (allGroups != null) {
576 setAllGroups(allGroups);
577 }
578 if (rootGroups != null) {
579 setRootGroups(rootGroups);
580 }
581 }
582 updateTimestamp(true, ts);
583 optimize();
584 }
585
586 public synchronized void merge(Directory directory) throws IOException {
587 merge(directory, null);
588 }
589
590 public synchronized void merge(Directory directory, DocumentFilter filter) throws IOException {
591 merge(directory, null, null, null);
592 }
593
594 public synchronized void merge(
595 Directory directory, DocumentFilter filter, Set<String> allGroups, Set<String> rootGroups)
596 throws IOException {
597 final IndexSearcher s = acquireIndexSearcher();
598 try {
599 final IndexWriter w = getIndexWriter();
600 try (IndexReader directoryReader = DirectoryReader.open(directory)) {
601 int numDocs = directoryReader.maxDoc();
602
603 Bits liveDocs = MultiBits.getLiveDocs(directoryReader);
604 StoredFields storedFields = directoryReader.storedFields();
605 for (int i = 0; i < numDocs; i++) {
606 if (liveDocs != null && !liveDocs.get(i)) {
607 continue;
608 }
609
610 Document d = storedFields.document(i);
611 if (filter != null && !filter.accept(d)) {
612 continue;
613 }
614
615 String uinfo = d.get(ArtifactInfo.UINFO);
616 if (uinfo != null) {
617 TopScoreDocCollector collector = TopScoreDocCollector.create(1, 1);
618 s.search(new TermQuery(new Term(ArtifactInfo.UINFO, uinfo)), collector);
619 if (collector.getTotalHits() == 0) {
620 w.addDocument(IndexUtils.updateDocument(d, this, false));
621 }
622 } else {
623 String deleted = d.get(ArtifactInfo.DELETED);
624
625 if (deleted != null) {
626
627
628
629 w.deleteDocuments(new Term(ArtifactInfo.UINFO, deleted));
630 w.addDocument(d);
631 }
632 }
633 }
634
635 } finally {
636 commit();
637 }
638 if (allGroups == null && rootGroups == null) {
639 rebuildGroups();
640 } else {
641 if (rootGroups != null) {
642 this.rootGroups.get().addAll(rootGroups);
643 }
644 if (allGroups != null) {
645 this.allGroups.get().addAll(allGroups);
646 }
647 }
648 Date mergedTimestamp = IndexUtils.getTimestamp(directory);
649
650 if (getTimestamp() != null && mergedTimestamp != null && mergedTimestamp.after(getTimestamp())) {
651
652 updateTimestamp(true, mergedTimestamp);
653 } else {
654 updateTimestamp(true);
655 }
656 optimize();
657 } finally {
658 releaseIndexSearcher(s);
659 }
660 }
661
662 private void closeReaders() throws CorruptIndexException, IOException {
663 if (searcherManager != null) {
664 searcherManager.close();
665 searcherManager = null;
666 }
667 if (indexWriter != null) {
668 indexWriter.close();
669 indexWriter = null;
670 }
671 }
672
673 public GavCalculator getGavCalculator() {
674 return gavCalculator;
675 }
676
677 public List<IndexCreator> getIndexCreators() {
678 return Collections.unmodifiableList(indexCreators);
679 }
680
681
682
683 public synchronized void rebuildGroups() throws IOException {
684 final IndexSearcher is = acquireIndexSearcher();
685 try {
686 final IndexReader r = is.getIndexReader();
687
688 Set<String> rootGroups = new LinkedHashSet<>();
689 Set<String> allGroups = new LinkedHashSet<>();
690
691 int numDocs = r.maxDoc();
692 Bits liveDocs = MultiBits.getLiveDocs(r);
693 StoredFields storedFields = r.storedFields();
694
695 for (int i = 0; i < numDocs; i++) {
696 if (liveDocs != null && !liveDocs.get(i)) {
697 continue;
698 }
699
700 Document d = storedFields.document(i);
701
702 String uinfo = d.get(ArtifactInfo.UINFO);
703
704 if (uinfo != null) {
705 String group = uinfo.substring(0, uinfo.indexOf(ArtifactInfo.FS));
706 int n = group.indexOf('.');
707 rootGroups.add(n > -1 ? group.substring(0, n) : group);
708 allGroups.add(group);
709 }
710 }
711
712 setRootGroups(rootGroups);
713 setAllGroups(allGroups);
714
715 optimize();
716 } finally {
717 releaseIndexSearcher(is);
718 }
719 }
720
721 public Set<String> getAllGroups() {
722 return allGroups.get();
723 }
724
725 public synchronized void setAllGroups(Collection<String> groups) {
726 allGroups.set(new HashSet<>(groups));
727 }
728
729 public Set<String> getRootGroups() throws IOException {
730 return rootGroups.get();
731 }
732
733 public synchronized void setRootGroups(Collection<String> groups) {
734 rootGroups.set(new HashSet<>(groups));
735 }
736
737 private final AtomicReference<Set<String>> rootGroups = new AtomicReference<>(new HashSet<>());
738
739 private final AtomicReference<Set<String>> allGroups = new AtomicReference<>(new HashSet<>());
740
741 @Override
742 public String toString() {
743 return id + " : " + timestamp;
744 }
745
746 private static void unlockForcibly(final TrackingLockFactory lockFactory, final Directory dir) throws IOException {
747
748
749
750
751
752 if (lockFactory != null) {
753 final Set<? extends Lock> emittedLocks = lockFactory.getEmittedLocks(IndexWriter.WRITE_LOCK_NAME);
754 for (Lock emittedLock : emittedLocks) {
755 emittedLock.close();
756 }
757 }
758 if (dir instanceof FSDirectory) {
759 final FSDirectory fsdir = (FSDirectory) dir;
760 final Path dirPath = fsdir.getDirectory();
761 if (Files.isDirectory(dirPath)) {
762 Path lockPath = dirPath.resolve(IndexWriter.WRITE_LOCK_NAME);
763 try {
764 lockPath = lockPath.toRealPath();
765 } catch (IOException ioe) {
766
767 return;
768 }
769 try (FileChannel fc = FileChannel.open(lockPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
770 final FileLock lck = fc.tryLock();
771 if (lck == null) {
772
773 throw new LockObtainFailedException("Lock held by another process: " + lockPath);
774 } else {
775
776 lck.close();
777 }
778 }
779 Files.delete(lockPath);
780 }
781 }
782 }
783 }