Skip to content

Commit e2f7528

Browse files
authored
Annotation cache (#4059)
fixes #4028
1 parent ab0e0c1 commit e2f7528

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2878
-742
lines changed

apiary.apib

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,24 @@ This will also mark the project as not indexed.
384384
### delete history cache for a project [DELETE]
385385

386386
This will delete history cache for a project.
387+
Return list of repository paths for which the cache was successfully deleted.
387388

388-
+ Response 204
389+
+ Response 200 (application/json)
390+
+ Body
391+
392+
["/project/repository1","/project/repository2"]
393+
394+
## Project annotation cache management [/projects/{project}/annotationcache]
395+
396+
### delete annotation cache for a project [DELETE]
397+
398+
This will delete annotation cache for a project.
399+
Return list of repository paths for which the cache was successfully deleted.
400+
401+
+ Response 200 (application/json)
402+
+ Body
403+
404+
["/project/repository1","/project/repository2"]
389405

390406
## Project metadata management [/projects/{project}/indexed]
391407

opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/AnalyzerGuru.java

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -593,28 +593,12 @@ public void populateDocument(Document doc, File file, String path, AbstractAnaly
593593
new BytesRef(file.getAbsolutePath())));
594594

595595
if (RuntimeEnvironment.getInstance().isHistoryEnabled()) {
596-
try {
597-
HistoryGuru histGuru = HistoryGuru.getInstance();
598-
HistoryReader hr = histGuru.getHistoryReader(file);
599-
if (hr != null) {
600-
doc.add(new TextField(QueryBuilder.HIST, hr));
601-
History history;
602-
if ((history = histGuru.getHistory(file)) != null) {
603-
List<HistoryEntry> historyEntries = history.getHistoryEntries(1, 0);
604-
if (!historyEntries.isEmpty()) {
605-
HistoryEntry histEntry = historyEntries.get(0);
606-
doc.add(new TextField(QueryBuilder.LASTREV, histEntry.getRevision(), Store.YES));
607-
}
608-
}
609-
}
610-
} catch (HistoryException e) {
611-
LOGGER.log(Level.WARNING, "An error occurred while reading history: ", e);
612-
}
596+
populateDocumentHistory(doc, file);
613597
}
614598
doc.add(new Field(QueryBuilder.DATE, date, string_ft_stored_nanalyzed_norms));
615599
doc.add(new SortedDocValuesField(QueryBuilder.DATE, new BytesRef(date)));
616600

617-
// `path' is not null, as it was passed to Util.path2uid() above.
601+
// 'path' is not null, as it was passed to Util.path2uid() above.
618602
doc.add(new TextField(QueryBuilder.PATH, path, Store.YES));
619603
Project project = Project.getProject(path);
620604
if (project != null) {
@@ -630,8 +614,7 @@ public void populateDocument(Document doc, File file, String path, AbstractAnaly
630614
String fileParent = fpath.getParent();
631615
if (fileParent != null && fileParent.length() > 0) {
632616
String normalizedPath = QueryBuilder.normalizeDirPath(fileParent);
633-
StringField npstring = new StringField(QueryBuilder.DIRPATH,
634-
normalizedPath, Store.NO);
617+
StringField npstring = new StringField(QueryBuilder.DIRPATH, normalizedPath, Store.NO);
635618
doc.add(npstring);
636619
}
637620

@@ -647,6 +630,26 @@ public void populateDocument(Document doc, File file, String path, AbstractAnaly
647630
}
648631
}
649632

633+
private static void populateDocumentHistory(Document doc, File file) {
634+
try {
635+
HistoryGuru histGuru = HistoryGuru.getInstance();
636+
HistoryReader hr = histGuru.getHistoryReader(file);
637+
if (hr != null) {
638+
doc.add(new TextField(QueryBuilder.HIST, hr));
639+
History history;
640+
if ((history = histGuru.getHistory(file)) != null) {
641+
List<HistoryEntry> historyEntries = history.getHistoryEntries(1, 0);
642+
if (!historyEntries.isEmpty()) {
643+
HistoryEntry histEntry = historyEntries.get(0);
644+
doc.add(new TextField(QueryBuilder.LASTREV, histEntry.getRevision(), Store.YES));
645+
}
646+
}
647+
}
648+
} catch (HistoryException e) {
649+
LOGGER.log(Level.WARNING, "An error occurred while reading history: ", e);
650+
}
651+
}
652+
650653
/**
651654
* Write a browse-able version of the file.
652655
*

opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Configuration.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved.
2222
* Portions Copyright (c) 2017, 2020, Chris Fraire <[email protected]>.
2323
* Portions Copyright (c) 2020, Aleksandr Kirillov <[email protected]>.
2424
*/
@@ -107,6 +107,10 @@ public final class Configuration {
107107
* Should the history log be cached?
108108
*/
109109
private boolean historyCache;
110+
/**
111+
* Should the latest annotation be cached?
112+
*/
113+
private boolean annotationCacheEnabled;
110114
/**
111115
* flag to generate history. This is bigger hammer than @{code historyCache}
112116
* above. If set to false, no history query will be ever made and the webapp
@@ -517,6 +521,7 @@ public Configuration() {
517521
cmds = new HashMap<>();
518522
setAllowLeadingWildcard(true);
519523
setAllowedSymlinks(new HashSet<>());
524+
setAnnotationCacheEnabled(false);
520525
setAuthenticationTokens(new HashSet<>());
521526
setAuthorizationWatchdogEnabled(false);
522527
//setBugPage("http://bugs.myserver.org/bugdatabase/view_bug.do?bug_id=");
@@ -798,6 +803,25 @@ public boolean isHistoryCache() {
798803
return historyCache;
799804
}
800805

806+
/**
807+
* Should the latest annotation be cached?
808+
*
809+
* @return {@code true} if a {@code AnnotationCache} implementation should be
810+
* used, {@code false} otherwise
811+
*/
812+
public boolean isAnnotationCacheEnabled() {
813+
return annotationCacheEnabled;
814+
}
815+
816+
/**
817+
* Set whether latest annotation should be cached.
818+
*
819+
* @param useCache if {@code true} enable annotation cache
820+
*/
821+
public void setAnnotationCacheEnabled(boolean useCache) {
822+
this.annotationCacheEnabled = useCache;
823+
}
824+
801825
/**
802826
* Set whether history should be cached.
803827
*

opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Project.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ public class Project implements Comparable<Project>, Nameable, Serializable {
8888
*/
8989
private Boolean historyEnabled = null;
9090

91+
/**
92+
* This flag enables/disables per-project annotation cache.
93+
*/
94+
private Boolean annotationCacheEnabled = null;
95+
9196
/**
9297
* This flag enables/disables per project merge commits.
9398
*/
@@ -141,8 +146,7 @@ public Project(String name) {
141146
}
142147

143148
/**
144-
* Create a project with given name and path and default configuration
145-
* values.
149+
* Create a project with given name and path and default configuration values.
146150
*
147151
* @param name the name of the project
148152
* @param path the path of the project relative to the source root
@@ -297,6 +301,20 @@ public void setHistoryEnabled(boolean flag) {
297301
this.historyEnabled = flag;
298302
}
299303

304+
/**
305+
* @return true if this project should have annotation cache.
306+
*/
307+
public boolean isAnnotationCacheEnabled() {
308+
return annotationCacheEnabled != null && annotationCacheEnabled;
309+
}
310+
311+
/**
312+
* @param flag true if project should have annotation cache, false otherwise.
313+
*/
314+
public void setAnnotationCacheEnabled(boolean flag) {
315+
this.annotationCacheEnabled = flag;
316+
}
317+
300318
/**
301319
* @param flag true if project's repositories should deal with merge commits.
302320
*/
@@ -353,6 +371,7 @@ public void clearProperties() {
353371
historyBasedReindex = null;
354372
mergeCommitsEnabled = null;
355373
historyEnabled = null;
374+
annotationCacheEnabled = null;
356375
handleRenamedFiles = null;
357376
}
358377

@@ -480,6 +499,11 @@ public final void completeWithDefaults() {
480499
setHistoryEnabled(env.isHistoryEnabled());
481500
}
482501

502+
// Allow project to override global setting of annotation cache generation.
503+
if (annotationCacheEnabled == null) {
504+
setAnnotationCacheEnabled(env.isAnnotationCacheEnabled());
505+
}
506+
483507
// Allow project to override global setting of navigate window.
484508
if (navigateWindowEnabled == null) {
485509
setNavigateWindowEnabled(env.isNavigateWindowEnabled());

opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/RuntimeEnvironment.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,24 @@ public void setUseHistoryCache(boolean useHistoryCache) {
715715
syncWriteConfiguration(useHistoryCache, Configuration::setHistoryCache);
716716
}
717717

718+
/**
719+
* Is annotation cache currently enabled?
720+
*
721+
* @return whether annotation cache is enabled
722+
*/
723+
public boolean isAnnotationCacheEnabled() {
724+
return syncReadConfiguration(Configuration::isAnnotationCacheEnabled);
725+
}
726+
727+
/**
728+
* Specify whether annotation cache should be generated during indexing.
729+
*
730+
* @param useAnnotationCache set false if you do not want to use annotation cache
731+
*/
732+
public void setAnnotationCacheEnabled(boolean useAnnotationCache) {
733+
syncWriteConfiguration(useAnnotationCache, Configuration::setAnnotationCacheEnabled);
734+
}
735+
718736
/**
719737
* Should we generate HTML or not during the indexing phase.
720738
*
@@ -736,8 +754,7 @@ public void setGenerateHtml(boolean generateHtml) {
736754
/**
737755
* Set if we should compress the xref files or not.
738756
*
739-
* @param compressXref set to true if the generated html files should be
740-
* compressed
757+
* @param compressXref set to true if the generated html files should be compressed
741758
*/
742759
public void setCompressXref(boolean compressXref) {
743760
syncWriteConfiguration(compressXref, Configuration::setCompressXref);
@@ -1456,12 +1473,12 @@ public void setHistoryBasedReindex(boolean flag) {
14561473
syncWriteConfiguration(flag, Configuration::setHistoryBasedReindex);
14571474
}
14581475

1459-
public FileCollector getFileCollector(String name) {
1460-
return fileCollectorMap.get(name);
1476+
public FileCollector getFileCollector(String projectName) {
1477+
return fileCollectorMap.get(projectName);
14611478
}
14621479

1463-
public void setFileCollector(String name, FileCollector fileCollector) {
1464-
fileCollectorMap.put(name, fileCollector);
1480+
public void setFileCollector(String projectName, FileCollector fileCollector) {
1481+
fileCollectorMap.put(projectName, fileCollector);
14651482
}
14661483

14671484
@VisibleForTesting
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* CDDL HEADER START
3+
*
4+
* The contents of this file are subject to the terms of the
5+
* Common Development and Distribution License (the "License").
6+
* You may not use this file except in compliance with the License.
7+
*
8+
* See LICENSE.txt included in this distribution for the specific
9+
* language governing permissions and limitations under the License.
10+
*
11+
* When distributing Covered Code, include this CDDL HEADER in each
12+
* file and include the License file at LICENSE.txt.
13+
* If applicable, add the following below this CDDL HEADER, with the
14+
* fields enclosed by brackets "[]" replaced with your own identifying
15+
* information: Portions Copyright [yyyy] [name of copyright owner]
16+
*
17+
* CDDL HEADER END
18+
*/
19+
20+
/*
21+
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
22+
*/
23+
package org.opengrok.indexer.history;
24+
25+
import org.opengrok.indexer.configuration.RuntimeEnvironment;
26+
import org.opengrok.indexer.util.ForbiddenSymlinkException;
27+
import org.opengrok.indexer.util.TandemPath;
28+
29+
import java.io.File;
30+
import java.io.IOException;
31+
import java.util.ArrayList;
32+
import java.util.Collection;
33+
import java.util.List;
34+
import java.util.logging.Level;
35+
36+
/**
37+
* Class to hold code shared between various cache implementations,
38+
* notably {@link FileHistoryCache} and {@link FileAnnotationCache}.
39+
*/
40+
public abstract class AbstractCache implements Cache {
41+
42+
public boolean hasCacheForFile(File file) throws HistoryException {
43+
try {
44+
return getCachedFile(file).exists();
45+
} catch (ForbiddenSymlinkException ex) {
46+
LOGGER.log(Level.FINER, ex.getMessage());
47+
return false;
48+
}
49+
}
50+
51+
/**
52+
* Get a <code>File</code> object describing the cache file.
53+
*
54+
* @param file the file to find the cache for
55+
* @return file that might contain cached object for <code>file</code>
56+
*/
57+
File getCachedFile(File file) throws HistoryException, ForbiddenSymlinkException {
58+
59+
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
60+
61+
StringBuilder sb = new StringBuilder();
62+
sb.append(env.getDataRootPath());
63+
sb.append(File.separatorChar);
64+
sb.append(getCacheDirName());
65+
66+
try {
67+
String add = env.getPathRelativeToSourceRoot(file);
68+
if (add.length() == 0) {
69+
add = File.separator;
70+
}
71+
sb.append(add);
72+
} catch (IOException e) {
73+
throw new HistoryException("Failed to get path relative to source root for " + file, e);
74+
}
75+
76+
return new File(TandemPath.join(sb.toString(), ".gz"));
77+
}
78+
79+
public List<String> clearCache(Collection<RepositoryInfo> repositories) {
80+
List<String> clearedRepos = new ArrayList<>();
81+
82+
for (RepositoryInfo repo : repositories) {
83+
try {
84+
this.clear(repo);
85+
clearedRepos.add(repo.getDirectoryNameRelative());
86+
LOGGER.log(Level.INFO, "{1} cache for ''{0}'' cleared.",
87+
new Object[]{repo.getDirectoryName(), this.getInfo()});
88+
} catch (HistoryException e) {
89+
LOGGER.log(Level.WARNING,
90+
"Clearing cache for repository {0} failed: {1}",
91+
new Object[]{repo.getDirectoryName(), e.getLocalizedMessage()});
92+
}
93+
}
94+
95+
return clearedRepos;
96+
}
97+
98+
/**
99+
* Attempt to delete file with its parent.
100+
* @param file file to delete
101+
*/
102+
static void clearWithParent(File file) {
103+
File parent = file.getParentFile();
104+
105+
if (!file.delete() && file.exists()) {
106+
LOGGER.log(Level.WARNING, "Failed to remove obsolete cache-file: {0}", file.getAbsolutePath());
107+
}
108+
109+
if (parent.delete()) {
110+
LOGGER.log(Level.FINE, "Removed empty cache dir:{0}", parent.getAbsolutePath());
111+
}
112+
}
113+
114+
public void clearFile(String path) {
115+
File historyFile;
116+
try {
117+
historyFile = getCachedFile(new File(RuntimeEnvironment.getInstance().getSourceRootPath() + path));
118+
} catch (ForbiddenSymlinkException ex) {
119+
LOGGER.log(Level.FINER, ex.getMessage());
120+
return;
121+
} catch (HistoryException ex) {
122+
LOGGER.log(Level.WARNING, String.format("cannot get history file for file %s", path), ex);
123+
return;
124+
}
125+
126+
clearWithParent(historyFile);
127+
}
128+
}

0 commit comments

Comments
 (0)