Some refactoring/extensions in the ATDB API + documentation.

Change-Id: I0fd34e37cbcbbe456cfbb8c258ad69ff35a917ec
Signed-off-by: Raphael Weber <raphael.weber@vector.com>
diff --git a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainImporter.java b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainImporter.java
index 62f32a6..fd28fc1 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainImporter.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainImporter.java
@@ -50,7 +50,8 @@
 			final List<AbstractEventChain> allECs = ConstraintsUtil.getAllAbstractEventChains(this.model.getConstraintsModel());
 			final int eventChainCount = allECs.size();
 			final SubMonitor subMon = SubMonitor.convert(progressMonitor, "Importing event chains from AMALTHEA...", eventChainCount);
-			try (final Statement statement = this.con.createStatement()) {
+			try {
+				final Statement statement = this.con.createStatement();
 				statement.addBatch("INSERT OR IGNORE INTO entityType(name) VALUES('EC');");
 				statement.addBatch("INSERT OR IGNORE INTO property(name, type) VALUES('ecItems', 'entityIdRef');");
 				statement.addBatch("INSERT OR IGNORE INTO property(name, type) VALUES('ecStimulus', 'eventIdRef');");
diff --git a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainMetricCalculator.java b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainMetricCalculator.java
index 519c528..7170166 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainMetricCalculator.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainMetricCalculator.java
@@ -32,8 +32,9 @@
 	@Override
 	public void run(IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
 		final SubMonitor subMon = SubMonitor.convert(progressMonitor, "Calculating event chain metrics...", 3);
-		try (final PreparedStatement metricStmt = this.con.prepareStatement("INSERT OR IGNORE INTO metric(name, dimension) VALUES(?, ?);");
-				final Statement mStmt = this.con.createStatement()) {
+		try {
+			final Statement mStmt = this.con.createStatement();
+			final PreparedStatement metricStmt = this.con.getPreparedStatementFor("INSERT OR IGNORE INTO metric(name, dimension) VALUES(?, ?);");
 			// create event chain instances with separate sql program (a sequence of queries)
 			String ecInstCalculationSQL = "";
 			try {
diff --git a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventImporter.java b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventImporter.java
index eb12bbf..69f335e 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventImporter.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventImporter.java
@@ -49,11 +49,12 @@
 		if (this.model.getEventModel() != null) {
 			final int eventCount = this.model.getEventModel().getEvents().size();
 			final SubMonitor subMon = SubMonitor.convert(progressMonitor, "Importing events from AMALTHEA...", eventCount);
-			try (final PreparedStatement entityStmt = this.con.prepareStatement("INSERT OR IGNORE INTO entity(name) VALUES(?);");
-					final PreparedStatement evTypeStmt = this.con.prepareStatement("INSERT OR IGNORE INTO eventType(name) VALUES(?);");
-					final PreparedStatement eventStmt = this.con.prepareStatement("INSERT OR IGNORE INTO event(name, eventTypeId, entityId, sourceEntityId) "//
-							+ "VALUES(?, (SELECT id FROM eventType WHERE name = ?), (SELECT id FROM entity WHERE name = ?), "//
-							+ "(SELECT id FROM entity WHERE name = ?));")) {
+			try {
+				final PreparedStatement entityStmt = this.con.getPreparedStatementFor("INSERT OR IGNORE INTO entity(name) VALUES(?);");
+				final PreparedStatement evTypeStmt = this.con.getPreparedStatementFor("INSERT OR IGNORE INTO eventType(name) VALUES(?);");
+				final PreparedStatement eventStmt = this.con.getPreparedStatementFor("INSERT OR IGNORE INTO event(name, eventTypeId, entityId, sourceEntityId) "//
+						+ "VALUES(?, (SELECT id FROM eventType WHERE name = ?), (SELECT id FROM entity WHERE name = ?), "//
+						+ "(SELECT id FROM entity WHERE name = ?));");
 				final List<Event> events = this.model.getEventModel().getEvents();
 				for(final Event event:events) {
 					// ignore event sets for now
diff --git a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ATDBMetricCalculator.java b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ATDBMetricCalculator.java
index b877fa6..d8df876 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ATDBMetricCalculator.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ATDBMetricCalculator.java
@@ -44,8 +44,9 @@
 	@Override
 	public void run(IProgressMonitor monitor) throws InvocationTargetException {
 		monitor.beginTask("Calculating metrics...", 100);
-		try (final PreparedStatement metricStmt = this.con.prepareStatement("INSERT INTO metric(name, dimension) VALUES(?, ?);");
-				final Statement statement = this.con.createStatement()) {
+		try {
+			final Statement statement = this.con.createStatement();
+			final PreparedStatement metricStmt = this.con.getPreparedStatementFor("INSERT INTO metric(name, dimension) VALUES(?, ?);");
 			final Set<String> entityInstanceTimeMetrics = new LinkedHashSet<>();
 	
 			// for each state: add a '[state]Time' metric signifying how long the entity instance was in that state
diff --git a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/BTFImporter.java b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/BTFImporter.java
index e7b5a6c..a7def00 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/BTFImporter.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/BTFImporter.java
@@ -53,19 +53,19 @@
 		final SubMonitor readingBTFMonitor = subMon.split(5);
 		readingBTFMonitor.beginTask("Reading BTF file...", 10_000);
 		try (final FileInputStream fis = new FileInputStream(this.btfFile);
-				final Scanner sc = new Scanner(fis, "UTF-8");
-				final PreparedStatement metaStmt = this.con.prepareStatement("INSERT INTO metaInformation VALUES(?, ?);");
-				final PreparedStatement entTStmt = this.con.prepareStatement("INSERT INTO entityType(name) VALUES(?);");
-				final PreparedStatement entStmt = this.con.prepareStatement("INSERT INTO entity(name, entityTypeId) VALUES(?,"
-						+ "(SELECT id FROM entityType WHERE name = ?));");
-				final PreparedStatement instStmt = this.con.prepareStatement("INSERT INTO entityInstance VALUES("
-						+ "(SELECT id FROM entity WHERE name = ?), ?);");
-				final PreparedStatement evTStmt = this.con.prepareStatement("INSERT INTO eventType(name) VALUES(?);");
-				final PreparedStatement evStmt = this.con.prepareStatement("INSERT INTO traceEvent VALUES(?, ?,"
-						+ "(SELECT id FROM entity WHERE name = ?), ?,"//
-						+ "(SELECT id FROM entity WHERE name = ?), ?,"//
-						+ "(SELECT id FROM eventType WHERE name = ?), ?)");
-				final Statement propStmt = this.con.createStatement()) {
+				final Scanner sc = new Scanner(fis, "UTF-8")) {
+			final Statement propStmt = this.con.createStatement();
+			final PreparedStatement metaStmt = this.con.getPreparedStatementFor("INSERT INTO metaInformation VALUES(?, ?);");
+			final PreparedStatement entTStmt = this.con.getPreparedStatementFor("INSERT INTO entityType(name) VALUES(?);");
+			final PreparedStatement entStmt = this.con.getPreparedStatementFor("INSERT INTO entity(name, entityTypeId) VALUES(?,"
+					+ "(SELECT id FROM entityType WHERE name = ?));");
+			final PreparedStatement instStmt = this.con.getPreparedStatementFor("INSERT INTO entityInstance VALUES("
+					+ "(SELECT id FROM entity WHERE name = ?), ?);");
+			final PreparedStatement evTStmt = this.con.getPreparedStatementFor("INSERT INTO eventType(name) VALUES(?);");
+			final PreparedStatement evStmt = this.con.getPreparedStatementFor("INSERT INTO traceEvent VALUES(?, ?,"
+					+ "(SELECT id FROM entity WHERE name = ?), ?,"//
+					+ "(SELECT id FROM entity WHERE name = ?), ?,"//
+					+ "(SELECT id FROM eventType WHERE name = ?), ?)");
 			final long fileSize = new File(this.btfFile).length();
 			long currentTimestamp = Long.MIN_VALUE;
 			int currentSQCNR = 0;
diff --git a/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DatabaseAccess.java b/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DatabaseAccess.java
index 7044f1f..010c534 100644
--- a/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DatabaseAccess.java
+++ b/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DatabaseAccess.java
@@ -68,15 +68,7 @@
 	
 	public DatabaseAccess(final IFile databaseFile) throws SQLException {
 		this.traceDbConnection = new ATDBConnection(databaseFile.getLocation().toFile().toString());
-		
-		final String timeUnitSelection = "SELECT value FROM metaInformation WHERE name LIKE 'timeBase';"; //$NON-NLS-1$
-		this.timeBase = this.traceDbConnection.queryAndMapResult(timeUnitSelection, rs -> {
-			if (rs.next()) {
-				return rs.getString(1);
-			} else {
-				return "?";
-			}
-		});
+		this.timeBase = this.traceDbConnection.getTimeBase();
 		this.query2Result = new LinkedHashMap<>();
 	}
 
diff --git a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBBuilder.java b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBBuilder.java
index 922f4a6..dc8114a 100644
--- a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBBuilder.java
+++ b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBBuilder.java
@@ -20,9 +20,9 @@
 
 public class ATDBBuilder {
 
-	private final ATDBConnection connection;
+	private final DBConnection connection;
 	
-	public ATDBBuilder(final ATDBConnection connection) throws SQLException {
+	public ATDBBuilder(final DBConnection connection) throws SQLException {
 		this.connection = connection;
 	}
 	
diff --git a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java
index 9d44d44..502ea8b 100644
--- a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java
+++ b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java
@@ -13,114 +13,166 @@
 
 package org.eclipse.app4mc.atdb;
 
-import java.sql.Connection;
-import java.sql.DriverManager;
 import java.sql.PreparedStatement;
-import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.Optional;
-import java.util.Properties;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-public final class ATDBConnection implements AutoCloseable {
+public class ATDBConnection extends DBConnection {
 	
-	private final Connection connection;
-	private final Optional<Statement> statement;
-	
-	public ATDBConnection(final String atdbFile) throws SQLException {
-		this(atdbFile, false);
-	}
-	
-	public ATDBConnection(final String atdbFile, final boolean writeAccess) throws SQLException {
-		try {
-			Class.forName("org.sqlite.JDBC"); // init JDBC driver
-		} catch (ClassNotFoundException e) {
-			// fail silently. DriverManager will throw an SQL exception complaining about a missing driver
-		}
-		final String dbFileUrl = "jdbc:sqlite:" + atdbFile;
-		if (!writeAccess) {
-			final Properties properties = new Properties();
-			properties.put("open_mode", "1"); //$NON-NLS-1$ //$NON-NLS-2$
-			this.connection = DriverManager.getConnection(dbFileUrl, properties);
-			this.statement = Optional.empty();
-		} else {
-			this.connection = DriverManager.getConnection(dbFileUrl);
-			final Statement tmpStatement = this.connection.createStatement();
-			tmpStatement.setQueryTimeout(30); // set timeout to 30 sec.
-			tmpStatement.executeUpdate("PRAGMA foreign_keys = ON;"); // enable foreign key checks
-			this.statement = Optional.of(tmpStatement);
-		}
+	private static final String METAINFORMATION_QUERY = "SELECT value FROM metaInformation WHERE name = ?;";
+	private static final String ENTITY_BY_TYPE_QUERY = "SELECT name FROM entity WHERE entityTypeId = (SELECT id FROM entityType WHERE entityType.name = ?);";
+	private static final String EVENT_BY_TYPE_QUERY = "SELECT name FROM event WHERE eventTypeId = (SELECT id FROM eventType WHERE eventType.name = ?);";
+	private static final String EVENTCHAIN_EVENTREF_QUERY = "SELECT (SELECT GROUP_CONCAT((SELECT name FROM event WHERE id = value), ', ') "
+			+ "FROM propertyValue WHERE entityId = ecEntity.id AND propertyId = (SELECT id FROM property WHERE name = ?)) AS evName "
+			+ "FROM entity AS ecEntity WHERE ecEntity.name = ? AND entityTypeId = (SELECT id FROM entityType WHERE entityType.name = 'EC');";
+	private static final String EVENTCHAIN_ITEMS_QUERY = "SELECT (SELECT name FROM entity WHERE id = value) AS items "
+			+ "FROM entity AS ecEntity, propertyValue WHERE ecEntity.name = ? AND entityTypeId = (SELECT id FROM entityType WHERE entityType.name = 'EC') "
+			+ "AND propertyValue.entityId = ecEntity.id AND propertyValue.propertyId = (SELECT id FROM property WHERE name = 'ecItems');";
+	private static final String METRICS_QUERY = "SELECT name FROM metric;";
+	private static final String METRICVALUE_QUERY = "SELECT value FROM entityMetricValue WHERE entityId = (SELECT id FROM entity WHERE name = ?) "
+			+ "AND metricId = (SELECT id FROM metric WHERE name = ?);";
+
+	public ATDBConnection(String dbFile) throws SQLException {
+		super(dbFile);
 	}
 
-	@Override
-	public void close() throws SQLException {
-		if (this.statement.isPresent()) {
-			this.statement.get().close();
+	public ATDBConnection(String dbFile, boolean writeAccess) throws SQLException {
+		super(dbFile, writeAccess);
+	}
+	
+	private String getFirstStringFromPreparedQuery(final String query, final List<String> parameters, final String defaultValue) throws SQLException {
+		return getStringStreamFromPreparedQuery(query, parameters).findFirst().orElse(defaultValue);
+	}
+	
+	private Stream<String> getStringStreamFromPreparedQuery(final String query, final List<String> parameters) throws SQLException {
+		final PreparedStatement prepStmt = super.getPrepareQueryFor(query);
+		for(int i = 0; i < parameters.size(); i++) {
+			prepStmt.setString(i + 1, parameters.get(i));
 		}
-		this.connection.close();
+		return super.queryAndMapToStream(prepStmt, rs -> rs.getString(1)).filter(Objects::nonNull);
 	}
 	
-	public boolean executeUpdate(final String query) throws SQLException {
-		if (this.statement.isPresent()) {
-			this.statement.get().executeUpdate(query);
-			return true;
-		} else {
-			return false;
-		}
+	private List<String> getStringListFromPreparedQuery(final String query, final List<String> parameters) throws SQLException {
+		return getStringStreamFromPreparedQuery(query, parameters).collect(Collectors.toList());
 	}
 	
-	@FunctionalInterface
-	public static interface ThrowingConsumer<T, E extends Exception> {
-	    void accept(T t) throws E;
+	private String getMetaInfo(final String metaInfoKey, final String defaultValue) throws SQLException {
+		return getFirstStringFromPreparedQuery(METAINFORMATION_QUERY, Arrays.asList(metaInfoKey), defaultValue);
 	}
 	
-	public void queryAndConsumeResult(final String query, final ThrowingConsumer<ResultSet, SQLException> consumer) throws SQLException {
-		try (final Statement tmpStatement = this.connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
-			final ResultSet resultSet = tmpStatement.executeQuery(query);
-			consumer.accept(resultSet);
-		}
+	/**
+	 * Returns the time base of this ATDB.
+	 * 
+	 * @return The time base unit. If not set, "?" will be returned.
+	 * @throws SQLException
+	 */
+	public String getTimeBase() throws SQLException {
+		return getMetaInfo("timeBase", "?");
 	}
 	
-	@FunctionalInterface
-	public static interface ThrowingFunction<T, R, E extends Exception> {
-	    R apply(T t) throws E;
+	private List<String> getAllEntitiesByType(final String entityTypeName) throws SQLException {
+		return getStringListFromPreparedQuery(ENTITY_BY_TYPE_QUERY, Arrays.asList(entityTypeName));
 	}
 	
-	public <R> R queryAndMapResult(final String query, final ThrowingFunction<ResultSet, R, SQLException> mapper) throws SQLException {
-		try (final Statement tmpStatement = this.connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
-			final ResultSet resultSet = tmpStatement.executeQuery(query);
-			return mapper.apply(resultSet);
-		}
+	/**
+	 * Returns all task names of this ATDB.
+	 * @return A list of all task names.
+	 * @throws SQLException
+	 */
+	public List<String> getAllTasks() throws SQLException {
+		return getAllEntitiesByType("T");
 	}
 	
-	public PreparedStatement prepareStatement(final String query) throws SQLException {
-		return this.connection.prepareStatement(query);
+	/**
+	 * Returns all ISR (Interrupt Service Routine) names of this ATDB.
+	 * @return A list of all ISR names.
+	 * @throws SQLException
+	 */
+	public List<String> getAllISRs() throws SQLException {
+		return getAllEntitiesByType("I");
 	}
 	
-	public Statement createStatement() throws SQLException {
-		return this.connection.createStatement();
+	/**
+	 * Returns all runnable names of this ATDB.
+	 * @return A list of all runnable names.
+	 * @throws SQLException
+	 */
+	public List<String> getAllRunnables() throws SQLException {
+		return getAllEntitiesByType("R");
 	}
 	
-	public void executeBatchStatements(final Statement...statements) throws SQLException {
-		this.connection.setAutoCommit(false);
-		try {
-			for(final Statement stmt:statements) {
-				stmt.executeBatch();
-			}
-		} finally {
-			this.connection.setAutoCommit(true);
-		}
+	/**
+	 * Returns all event chain names of this ATDB.
+	 * @return A list of all event chain names.
+	 * @throws SQLException
+	 */
+	public List<String> getAllEventChains() throws SQLException {
+		return getAllEntitiesByType("EC");
 	}
 	
-	public boolean tableExists(final String...tableName) throws SQLException {
-		try (final PreparedStatement tmpStatement = this.connection.prepareStatement("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (?);",
-				ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
-			tmpStatement.setString(1, Stream.of(tableName).collect(Collectors.joining(", ")));
-			final ResultSet resultSet = tmpStatement.executeQuery();
-			return resultSet.next();
-		}
+	/**
+	 * Returns all event (specification, not from the actual trace!) names of this ATDB of the given event type.
+	 * @param eventTypeName The event type name of which the returned event names shall be.
+	 * @return A list of all event names.
+	 * @throws SQLException
+	 */
+	public List<String> getAllEventsByType(final String eventTypeName) throws SQLException {
+		return getStringListFromPreparedQuery(EVENT_BY_TYPE_QUERY, Arrays.asList(eventTypeName));
+	}
+	
+	/**
+	 * Returns the stimulus event name for the given event chain name. If there are multiple stimulus events, they will be separated by a ', '.
+	 * @param eventChainName The name of the event chain from which we want to know the stimulus event.
+	 * @return The stimulus event name. Empty, if none was found.
+	 * @throws SQLException
+	 */
+	public String getEventChainStimulus(final String eventChainName) throws SQLException {
+		return getFirstStringFromPreparedQuery(EVENTCHAIN_EVENTREF_QUERY, Arrays.asList("ecStimulus", eventChainName), "");
+	}
+	
+	/**
+	 * Returns the response event name for the given event chain name. If there are multiple response events, they will be separated by a ', '.
+	 * @param eventChainName The name of the event chain from which we want to know the response event.
+	 * @return The response event name. Empty, if none was found.
+	 * @throws SQLException
+	 */
+	public String getEventChainResponse(final String eventChainName) throws SQLException {
+		return getFirstStringFromPreparedQuery(EVENTCHAIN_EVENTREF_QUERY, Arrays.asList("ecResponse", eventChainName), "");
+	}
+	
+	/**
+	 * Returns the names of the item event chains for the given event chain name.
+	 * @param eventChainName The name of the event chain from which we want to know the item event chain names.
+	 * @return The item event chain names.
+	 * @throws SQLException
+	 */
+	public List<String> getEventChainItems(final String eventChainName) throws SQLException {
+		return getStringListFromPreparedQuery(EVENTCHAIN_ITEMS_QUERY, Arrays.asList(eventChainName));
+	}
+	
+	/**
+	 * Returns all metric names contained in this ATDB, some of which are specific to certain entity types.
+	 * @return All metric names.
+	 * @throws SQLException
+	 */
+	public List<String> getAllMetrics() throws SQLException {
+		return getStringListFromPreparedQuery(METRICS_QUERY, Collections.emptyList());
+	}
+	
+	/**
+	 * Returns the value of the metric for the given entity and metric names.
+	 * @param entityName The name of the entity from which we want to know the metric value.
+	 * @param metricName The name of the metric from which we want to know the value for the specified entity.
+	 * @return The value for the metric and entity. Empty, if none is found.
+	 * @throws SQLException
+	 */
+	public String getValueForMetricAndEntity(final String entityName, final String metricName) throws SQLException {
+		return getFirstStringFromPreparedQuery(METRICVALUE_QUERY, Arrays.asList(entityName, metricName), "");
 	}
 
 }
diff --git a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/DBConnection.java b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/DBConnection.java
new file mode 100644
index 0000000..2afd5af
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/DBConnection.java
@@ -0,0 +1,233 @@
+/**
+ ********************************************************************************
+ * Copyright (c) 2020 Eclipse APP4MC contributors.
+ * 
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ ********************************************************************************
+ */
+
+package org.eclipse.app4mc.atdb;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.stream.Stream;
+import java.util.stream.Stream.Builder;
+
+public class DBConnection implements AutoCloseable {
+	
+	private static final String TABLE_EXISTS_QUERY = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?;";
+	
+	private final Connection connection;
+	private final Optional<Statement> statement;
+	
+	private final Map<String, PreparedStatement> prepStmts;
+	private final Collection<Statement> stmts;
+	
+	/**
+	 * Creates a connection to the given db file in read-only mode.
+	 * 
+	 * @param dbFile The path to the db file.
+	 * @throws SQLException
+	 */
+	public DBConnection(final String dbFile) throws SQLException {
+		this(dbFile, false);
+	}
+	
+	/**
+	 * Creates a connection to the given db file.
+	 * 
+	 * @param dbFile The path to the db file.
+	 * @param writeAccess Specify <code>TRUE</code> to open the db connection with write access. <code>FALSE</code> for read-only mode.
+	 * @throws SQLException
+	 */
+	public DBConnection(final String dbFile, final boolean writeAccess) throws SQLException {
+		try {
+			Class.forName("org.sqlite.JDBC"); // init JDBC driver
+		} catch (ClassNotFoundException e) {
+			// fail silently. DriverManager will throw an SQL exception complaining about a missing driver
+		}
+		final String dbFileUrl = "jdbc:sqlite:" + dbFile;
+		if (!writeAccess) {
+			final Properties properties = new Properties();
+			properties.put("open_mode", "1"); //$NON-NLS-1$ //$NON-NLS-2$
+			this.connection = DriverManager.getConnection(dbFileUrl, properties);
+			this.statement = Optional.empty();
+		} else {
+			this.connection = DriverManager.getConnection(dbFileUrl);
+			final Statement tmpStatement = this.connection.createStatement();
+			tmpStatement.setQueryTimeout(30); // set timeout to 30 sec.
+			tmpStatement.executeUpdate("PRAGMA foreign_keys = ON;"); // enable foreign key checks
+			this.statement = Optional.of(tmpStatement);
+		}
+		this.prepStmts = new LinkedHashMap<>();
+		this.stmts = new LinkedHashSet<>();
+	}
+
+	/**
+	 * Closes all statements created by this connection. Then closes the connection to the data base itself.
+	 */
+	@Override
+	public void close() throws SQLException {
+		for(final PreparedStatement ps:this.prepStmts.values()) {
+			ps.close();
+		}
+		this.prepStmts.clear();
+		if (this.statement.isPresent()) {
+			this.statement.get().close();
+		}
+		this.connection.close();
+	}
+	
+	/**
+	 * Executes the statement (changing the contents of the db) given in the query.
+	 * 
+	 * @param query The statement that performs the changes in the db.
+	 * @return <code>TRUE</code> if the statement was executed by the db, <code>FALSE</code> otherwise.
+	 * @throws SQLException
+	 */
+	public boolean executeUpdate(final String query) throws SQLException {
+		if (this.statement.isPresent()) {
+			this.statement.get().executeUpdate(query);
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+	/**
+	 * Same as {@link java.util.function.Consumer}, but may throw exception of type E.
+	 * @param <T>
+	 * @param <E>
+	 */
+	@FunctionalInterface
+	public static interface ThrowingConsumer<T, E extends Exception> {
+	    void accept(T t) throws E;
+	}
+	
+	/**
+	 * Executes a read-only query and consumes the result set via the given consumer function.
+	 * 
+	 * @param query The read-only query to execute.
+	 * @param consumer The function that consumes the result set.
+	 * @throws SQLException
+	 */
+	public void queryAndConsumeResult(final String query, final ThrowingConsumer<ResultSet, SQLException> consumer) throws SQLException {
+		try (final Statement tmpStatement = this.connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
+			final ResultSet resultSet = tmpStatement.executeQuery(query);
+			consumer.accept(resultSet);
+		}
+	}
+	
+	/**
+	 * Same as {@link java.util.function.Function}, but may throw exception of type E.
+	 * @param <T>
+	 * @param <R>
+	 * @param <E>
+	 */
+	@FunctionalInterface
+	public static interface ThrowingFunction<T, R, E extends Exception> {
+	    R apply(T t) throws E;
+	}
+	
+	protected <R> Stream<R> queryAndMapToStream(final PreparedStatement query, final ThrowingFunction<ResultSet, R, SQLException> rowMapper) throws SQLException {
+		final ResultSet resultSet = query.executeQuery();
+		final Builder<R> result = Stream.builder();
+		while (resultSet.next()) {
+			result.accept(rowMapper.apply(resultSet));
+		}
+		return result.build();
+	}
+	
+	/**
+	 * Returns a prepared statement for the the given query. Will return an existing one if the query was already created as a prepared statement.
+	 * Since the prepared statement is internally cached, this DBConnection will also close it, when the connection is closed. Thus, the user must not
+	 * close the prepared statement.
+	 * 
+	 * @param query The query to put into the prepared statement.
+	 * @return The prepared statement for the given query.
+	 * @throws SQLException
+	 */
+	public PreparedStatement getPreparedStatementFor(final String query) throws SQLException {
+		if (!this.prepStmts.containsKey(query)) {
+			this.prepStmts.put(query, this.connection.prepareStatement(query));
+		}
+		return this.prepStmts.get(query);
+	}
+	
+	/**
+	 * Returns a prepared query for the given query string. This query is only allowed read access to the data base. Will return an existing one if the query
+	 * was already created as a prepared query. Since the prepared query is internally cached, this DBConnection will also close it, when the connection is
+	 * closed. Thus, the user must not close the prepared query.
+	 * 
+	 * @param query The query string to put into the prepared query.
+	 * @return The prepared query for the given query string.
+	 * @throws SQLException
+	 */
+	public PreparedStatement getPrepareQueryFor(final String query) throws SQLException {
+		if (!this.prepStmts.containsKey(query)) {
+			this.prepStmts.put(query, this.connection.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));
+		}
+		return this.prepStmts.get(query);
+	}
+	
+	/**
+	 * Creates and returns a new statement. This DBConection will also close the statement, when the connection is closed. Thus, the user must not close the
+	 * statement.
+	 * 
+	 * @return A newly created statement.
+	 * @throws SQLException
+	 */
+	public Statement createStatement() throws SQLException {
+		final Statement stmt = this.connection.createStatement();
+		this.stmts.add(stmt);
+		return stmt;
+	}
+	
+	/**
+	 * Executes the given statement batches (calls {@link Statement#executeBatch()} on each provided statement).
+	 * 
+	 * @param statements Batches of statements to be executed.
+	 * @throws SQLException
+	 */
+	public void executeBatchStatements(final Statement...statements) throws SQLException {
+		final boolean oldAutoCommit = this.connection.getAutoCommit();
+		if (oldAutoCommit) this.connection.setAutoCommit(false);
+		try {
+			for(final Statement stmt:statements) {
+				stmt.executeBatch();
+			}
+		} finally {
+			this.connection.setAutoCommit(oldAutoCommit);
+		}
+	}
+	
+	/**
+	 * Checks whether a table with the given tableName exists in the data base.
+	 * 
+	 * @param tableName The name of the table to check for existence.
+	 * @return <code>TRUE</code> if a table with the specified name exists, <code>FALSE</code> otherwise.
+	 * @throws SQLException
+	 */
+	public boolean tableExists(final String tableName) throws SQLException {
+		final PreparedStatement prepStmt = getPrepareQueryFor(TABLE_EXISTS_QUERY);
+		prepStmt.setString(1, tableName);
+		final ResultSet resultSet = prepStmt.executeQuery();
+		return resultSet.next();
+	}
+
+}