diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java index 7b42d82c3..4fe5ad951 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java @@ -24,6 +24,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; @@ -36,6 +37,7 @@ import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import javax.annotation.concurrent.ThreadSafe; +import org.apache.commons.collections.map.ReferenceMap; import org.owasp.dependencycheck.data.cwe.CweDB; import org.owasp.dependencycheck.dependency.Reference; import org.owasp.dependencycheck.dependency.Vulnerability; @@ -48,6 +50,8 @@ import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.commons.collections.map.AbstractReferenceMap.HARD; +import static org.apache.commons.collections.map.AbstractReferenceMap.SOFT; import static org.owasp.dependencycheck.data.nvdcve.CveDB.PreparedStatementCveDb.*; /** @@ -91,6 +95,9 @@ public final class CveDB implements AutoCloseable { */ private final EnumMap preparedStatements = new EnumMap<>(PreparedStatementCveDb.class); + @SuppressWarnings("unchecked") + private final Map> vulnerabilitiesForCpeCache = Collections.synchronizedMap(new ReferenceMap(HARD, SOFT)); + /** * The enum value names must match the keys of the statements in the * statement bundles "dbStatements*.properties". @@ -269,6 +276,7 @@ public final class CveDB implements AutoCloseable { instance.usageCount -= 1; if (instance.usageCount <= 0 && instance.isOpen()) { instance.usageCount = 0; + clearCache(); instance.closeStatements(); try { instance.connection.close(); @@ -474,6 +482,7 @@ public final class CveDB implements AutoCloseable { * @param value the property value */ public synchronized void saveProperty(String key, String value) { + clearCache(); try { try { final PreparedStatement mergeProperty = getPreparedStatement(MERGE_PROPERTY); @@ -498,6 +507,17 @@ public final class CveDB implements AutoCloseable { } } + /** + * Clears cache. Should be called whenever something is modified. While this is not the optimal cache eviction + * strategy, this is good enough for typical usage (update DB and then only read) and it is easier to maintain + * the code. + * + * It should be also called when DB is closed. + */ + private void clearCache() { + vulnerabilitiesForCpeCache.clear(); + } + /** * Retrieves the vulnerabilities associated with the specified CPE. * @@ -506,6 +526,13 @@ public final class CveDB implements AutoCloseable { * @throws DatabaseException thrown if there is an exception retrieving data */ public synchronized List getVulnerabilities(String cpeStr) throws DatabaseException { + final List cachedVulnerabilities = vulnerabilitiesForCpeCache.get(cpeStr); + if (cachedVulnerabilities != null) { + LOGGER.debug("Cache hit for {}", cpeStr); + return cachedVulnerabilities; + } else { + LOGGER.debug("Cache miss for {}", cpeStr); + } final VulnerableSoftware cpe = new VulnerableSoftware(); try { cpe.parseName(cpeStr); @@ -554,6 +581,7 @@ public final class CveDB implements AutoCloseable { } finally { DBUtils.closeResultSet(rs); } + vulnerabilitiesForCpeCache.put(cpeStr, vulnerabilities); return vulnerabilities; } @@ -633,6 +661,7 @@ public final class CveDB implements AutoCloseable { * @throws DatabaseException is thrown if the database */ public synchronized void updateVulnerability(Vulnerability vuln) throws DatabaseException { + clearCache(); try { int vulnerabilityId = 0; final PreparedStatement selectVulnerabilityId = getPreparedStatement(SELECT_VULNERABILITY_ID); @@ -799,6 +828,7 @@ public final class CveDB implements AutoCloseable { * ensure orphan entries are removed. */ public synchronized void cleanupDatabase() { + clearCache(); try { final PreparedStatement ps = getPreparedStatement(CLEANUP_ORPHANS); if (ps != null) { @@ -934,6 +964,7 @@ public final class CveDB implements AutoCloseable { * Deletes unused dictionary entries from the database. */ public synchronized void deleteUnusedCpe() { + clearCache(); PreparedStatement ps = null; try { ps = connection.prepareStatement(statementBundle.getString("DELETE_UNUSED_DICT_CPE")); @@ -956,6 +987,7 @@ public final class CveDB implements AutoCloseable { * @param product the CPE product */ public synchronized void addCpe(String cpe, String vendor, String product) { + clearCache(); PreparedStatement ps = null; try { ps = connection.prepareStatement(statementBundle.getString("ADD_DICT_CPE"));