Unified ATDB access for viewer and importer.

Change-Id: Ic661d039f433a25d84ded62cf3e129291874d420
Signed-off-by: Raphael Weber <raphael.weber@vector.com>
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 7d63809..1988aea 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
@@ -14,7 +14,6 @@
 package org.eclipse.app4mc.atdb._import.btf;
 
 import java.lang.reflect.InvocationTargetException;
-import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -25,6 +24,7 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.eclipse.app4mc.atdb.ATDBConnection;
 import org.eclipse.app4mc.atdb._import.btf.model.BTFCombiState;
 import org.eclipse.app4mc.atdb._import.btf.model.BTFCountMetric;
 import org.eclipse.app4mc.atdb._import.btf.model.BTFEntityState;
@@ -35,9 +35,9 @@
 
 public class ATDBMetricCalculator implements IRunnableWithProgress {
 	
-	private final Connection con;
+	private final ATDBConnection con;
 	
-	public ATDBMetricCalculator(final Connection con) {
+	public ATDBMetricCalculator(final ATDBConnection con) {
 		this.con = con;
 	}
 
@@ -170,12 +170,10 @@
 			}
 	
 			// execute all statements
-			this.con.setAutoCommit(false);
-			metricStmt.executeBatch();
+			this.con.executeBatchStatements(metricStmt);
 			monitor.worked(70);
-			statement.executeBatch();
+			this.con.executeBatchStatements(statement);
 			monitor.worked(30);
-			this.con.setAutoCommit(true);
 		} catch (SQLException e) {
 			throw new InvocationTargetException(e);
 		}
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 2121bd5..cd6bac9 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
@@ -17,7 +17,6 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
-import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -32,6 +31,7 @@
 import java.util.Scanner;
 import java.util.Set;
 
+import org.eclipse.app4mc.atdb.ATDBConnection;
 import org.eclipse.app4mc.atdb._import.btf.model.BTFEntityType;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.SubMonitor;
@@ -40,9 +40,9 @@
 public class BTFImporter implements IRunnableWithProgress {
 	
 	private final String btfFile;
-	private final Connection con;
+	private final ATDBConnection con;
 	
-	public BTFImporter(final Connection con, final String btfFile) {
+	public BTFImporter(final ATDBConnection con, final String btfFile) {
 		this.con = con;
 		this.btfFile = btfFile;
 	}
@@ -64,7 +64,8 @@
 				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 = ?), ?)")) {
+						+ "(SELECT id FROM eventType WHERE name = ?), ?)");
+				final Statement propStmt = this.con.createStatement()) {
 			final long fileSize = new File(this.btfFile).length();
 			long currentTimestamp = Long.MIN_VALUE;
 			int currentSQCNR = 0;
@@ -188,26 +189,16 @@
 					}
 				}
 			}
+			executeEntityInsertStatements(entStmt);
+			executePropertyInsertStatements(propStmt);
 			readingBTFMonitor.done();
+			
 			final SubMonitor writeEventsMonitor = subMon.split(1);
 			writeEventsMonitor.beginTask("Writing events to data base...", 1);
-			this.con.setAutoCommit(false);
-			metaStmt.executeBatch();
-			entTStmt.executeBatch();
-			executeEntityInsertStatements(entStmt);
-			instStmt.executeBatch();
-			evTStmt.executeBatch();
-			evStmt.executeBatch();
-			executePropertyInsertStatements(this.con.createStatement());
+			this.con.executeBatchStatements(metaStmt, entTStmt, entStmt, instStmt, evTStmt, evStmt, propStmt);
 			writeEventsMonitor.done();
 		} catch (final IOException | SQLException e) {
 			throw new InvocationTargetException(e);
-		} finally {
-			try {
-				this.con.setAutoCommit(true);
-			} catch (SQLException e) {
-				throw new InvocationTargetException(e);
-			}
 		}
 	}
 
@@ -230,7 +221,6 @@
 			eStmt.setString(2, entityType);
 			eStmt.addBatch();
 		}
-		eStmt.executeBatch();
 	}
 
 	private final Set<String> instNames = new HashSet<>();
@@ -302,7 +292,6 @@
 		for (final String st : statements) {
 			stmt.addBatch(st);
 		}
-		stmt.executeBatch();
 	}
 
 }
diff --git a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ImportTransformation.java b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ImportTransformation.java
index 4c57de3..04dcdc7 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ImportTransformation.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ImportTransformation.java
@@ -16,10 +16,10 @@
 package org.eclipse.app4mc.atdb._import.btf;
 
 import java.lang.reflect.InvocationTargetException;
-import java.sql.Connection;
 import java.sql.SQLException;
 
 import org.eclipse.app4mc.atdb.ATDBBuilder;
+import org.eclipse.app4mc.atdb.ATDBConnection;
 import org.eclipse.app4mc.atdb._import.btf.model.BTFEntityType;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.SubMonitor;
@@ -47,17 +47,16 @@
 	public void run(final IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
 		if (!this.btfFile.isEmpty() && !this.atdbFile.isEmpty()) {
 			final SubMonitor subMon = SubMonitor.convert(progressMonitor, "Converting BTF trace to ATDB...", 100);
-			try (final ATDBBuilder atdbBuilder = new ATDBBuilder(this.atdbFile)) {
+			try (final ATDBConnection con = new ATDBConnection(atdbFile, true)) {
 				final SubMonitor createATDBMonitor = subMon.split(1);
 				createATDBMonitor.beginTask("Creating empty ATDB...", 1);
-				atdbBuilder.createBasicDBStructure().createBasicViews()
+				final ATDBBuilder atdbBuilder = new ATDBBuilder(con).createBasicDBStructure().createBasicViews()
 						.createOptionalAndTemporaryTables(BTFEntityType.literals, this.persistTraceEvents);
 				if (this.persistTraceEvents)
 					atdbBuilder.createOptionalViews(BTFEntityType.literals);
 				createATDBMonitor.done();
 				
 				final SubMonitor btfImportMonitor = subMon.split(69);
-				final Connection con = atdbBuilder.getCurrentConnection();
 				final IRunnableWithProgress btfImporter = new BTFImporter(con, this.btfFile);
 				btfImporter.run(btfImportMonitor);
 				btfImportMonitor.done();
diff --git a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/model/BTFEntityType.java b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/model/BTFEntityType.java
index 3a481fa..73d98a5 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/model/BTFEntityType.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/model/BTFEntityType.java
@@ -37,7 +37,7 @@
 	private final String entityTypeName;
 
 	private BTFEntityType(final Class<? extends Enum<?>> eventTypeEnum, final String... traceAlias) {
-		this.traceAliases = Arrays.asList(traceAlias);
+		this.traceAliases = Collections.unmodifiableList(Arrays.asList(traceAlias));
 		String entityTypeName = eventTypeEnum.getSimpleName();
 		entityTypeName = entityTypeName.substring(0, entityTypeName.indexOf("EventType")).toLowerCase();
 		this.entityTypeName = entityTypeName;
@@ -54,8 +54,8 @@
 	}
 	
 	@Override
-	public String getTraceAliases() {
-		return this.traceAliases.stream().map(ta -> "'" + ta + "'").collect(Collectors.joining(", "));
+	public List<String> getTraceAliases() {
+		return this.traceAliases;
 	}
 	
 	public static BTFEntityType getForName(final String name) {
@@ -63,7 +63,7 @@
 	}
 	
 	@Override
-	public Set<? extends Enum<?>> getPossibleEvents() {
+	public Set<Enum<?>> getPossibleEvents() {
 		return BTFEntityState.getPossibleEventsFor(this);
 	}
 
diff --git a/plugins/org.eclipse.app4mc.atdb.metrics/META-INF/MANIFEST.MF b/plugins/org.eclipse.app4mc.atdb.metrics/META-INF/MANIFEST.MF
index af0d7c0..883dcde 100644
--- a/plugins/org.eclipse.app4mc.atdb.metrics/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.app4mc.atdb.metrics/META-INF/MANIFEST.MF
@@ -8,9 +8,8 @@
  org.eclipse.ui,
  org.eclipse.ui.ide,
  org.eclipse.nebula.widgets.nattable.core,
- org.sqlite.jdbc,
- com.google.guava,
- org.eclipse.ui.views.properties.tabbed
+ org.eclipse.app4mc.atdb,
+ com.google.guava
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-ActivationPolicy: lazy
 Bundle-Vendor: Eclipse APP4MC
diff --git a/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DBViewer.java b/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DBViewer.java
index 0716e87..e10b71d 100644
--- a/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DBViewer.java
+++ b/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DBViewer.java
@@ -13,7 +13,6 @@
 
 package org.eclipse.app4mc.atdb.metrics;
 
-import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -59,8 +58,6 @@
 
 public class DBViewer extends EditorPart implements IEditorPart {
 
-	private DatabaseAccess dbAccess = null;
-
 	private Map<String, DBResultRowDataProvider> view2ResultRowDataProvider = new LinkedHashMap<>();
 	private CTabFolder cTabFolder = null;
 
@@ -97,12 +94,11 @@
 		if (input instanceof FileEditorInput) {
 			final FileEditorInput fei = (FileEditorInput) input;
 			final IFile dbFile = fei.getFile();
-			try {
-				this.dbAccess = new DatabaseAccess(dbFile);
+			try (final DatabaseAccess dbAccess = new DatabaseAccess(dbFile)){
 				this.view2ResultRowDataProvider.put(Messages.DBViewer_processTimeMetricsTitle,
-						this.dbAccess.getMinAvgMaxProcessMetricValues(Arrays.asList("time"), true)); //$NON-NLS-1$
+						dbAccess.getMinAvgMaxProcessMetricValues(Arrays.asList("time"), true)); //$NON-NLS-1$
 				this.view2ResultRowDataProvider.put(Messages.DBViewer_runnableTimeMetricsTitle,
-						this.dbAccess.getMinAvgMaxRunnableMetricValues(Arrays.asList("time"), true, true)); //$NON-NLS-1$
+						dbAccess.getMinAvgMaxRunnableMetricValues(Arrays.asList("time"), true, true)); //$NON-NLS-1$
 			}
 			catch (final Exception e) {
 				MessageDialog.openError(site.getShell(), Messages.DBViewer_fileErrorTitle,
@@ -192,17 +188,4 @@
 			this.cTabFolder.forceFocus();
 		}
 	}
-
-	@Override
-	public void dispose() {
-		if (this.dbAccess != null) {
-			try {
-				this.dbAccess.closeDatabaseConnection();
-			}
-			catch (final SQLException e) {
-				throw new RuntimeException(e);
-			}
-		}
-		super.dispose();
-	}
 }
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 f0a1025..b4689a3 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
@@ -13,25 +13,21 @@
 
 package org.eclipse.app4mc.atdb.metrics;
 
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.stream.Collectors;
 
+import org.eclipse.app4mc.atdb.ATDBConnection;
 import org.eclipse.core.resources.IFile;
 
-public class DatabaseAccess {
+public class DatabaseAccess implements AutoCloseable {
 
-	private final Connection traceDbConnection;
+	private final ATDBConnection traceDbConnection;
 	private final String timeBase;
 	private final Map<String, DBResultRowDataProvider> query2Result;
 	
@@ -68,27 +64,22 @@
 		return prependedColumns.isEmpty() ? "" : indent + prependedColumns.stream().map(c -> c.entityId2Name).collect(Collectors.joining(",\n" + indent)) + ",\n";
 	}
 	
-	public DatabaseAccess(final IFile databaseFile) throws SQLException, ClassNotFoundException {
-		Class.forName("org.sqlite.JDBC"); //$NON-NLS-1$
-		String connectionStr = "jdbc:sqlite:"; //$NON-NLS-1$
-		connectionStr += databaseFile.getLocation().toFile().toString();
-		final Properties properties = new Properties();
-		properties.put("open_mode", "1"); //$NON-NLS-1$ //$NON-NLS-2$
-		this.traceDbConnection = DriverManager.getConnection(connectionStr, properties);
+	public DatabaseAccess(final IFile databaseFile) throws SQLException {
+		this.traceDbConnection = new ATDBConnection(databaseFile.getLocation().toFile().toString());
 		
-		String timeBase = "?";
 		final String timeUnitSelection = "SELECT value FROM metaInformation WHERE name LIKE 'timeBase';"; //$NON-NLS-1$
-		try (final Statement statement = this.traceDbConnection.createStatement();
-				final ResultSet resultSet = statement.executeQuery(timeUnitSelection)) {
-			if (resultSet.next()) {
-				timeBase = resultSet.getString(1);
+		this.timeBase = this.traceDbConnection.queryAndMapResult(timeUnitSelection, rs -> {
+			if (rs.next()) {
+				return rs.getString(1);
+			} else {
+				return "?";
 			}
-		}
-		this.timeBase = timeBase;
+		});
 		this.query2Result = new LinkedHashMap<>();
 	}
 
-	public void closeDatabaseConnection() throws SQLException {
+	@Override
+	public void close() throws SQLException {
 		this.query2Result.clear();
 		this.traceDbConnection.close();
 	}
@@ -151,9 +142,8 @@
 				+ "FROM precalculated\n"
 				+ "ORDER BY " + getPrependedColumns2Order(prependColumns) + entityTypeLabel.toLowerCase() + "Id, metricId;"; //$NON-NLS-1$
 		if (!this.query2Result.containsKey(queryString)) {
-			try (final Statement statement = this.traceDbConnection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
-				this.query2Result.put(queryString, DBResultRowDataProvider.of(statement.executeQuery(queryString), groupColumnIndices));
-			}
+			this.traceDbConnection.queryAndConsumeResult(queryString, rs ->
+				this.query2Result.put(queryString, DBResultRowDataProvider.of(rs, groupColumnIndices)));
 		}
 		return this.query2Result.get(queryString);
 	}
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 48fedd6..03c48c1 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
@@ -13,125 +13,104 @@
 
 package org.eclipse.app4mc.atdb;
 
-import java.sql.Connection;
-import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.sql.Statement;
 import java.util.Collection;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-public class ATDBBuilder implements AutoCloseable {
+public class ATDBBuilder {
 
-	private final Connection connection;
-	private final Statement statement;
+	private final ATDBConnection connection;
 	
-	public ATDBBuilder(final String atdbFile) 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
-		}
-		this.connection = DriverManager.getConnection("jdbc:sqlite:" + atdbFile);
-		this.statement = connection.createStatement();
-		this.statement.setQueryTimeout(30); // set timeout to 30 sec.
-		this.statement.executeUpdate("PRAGMA foreign_keys = ON;"); // enable foreign key checks
-	}
-	
-	public Connection getCurrentConnection() {
-		return this.connection;
-	}
-
-	@Override
-	public void close() throws SQLException {
-		this.connection.close();
+	public ATDBBuilder(final ATDBConnection connection) throws SQLException {
+		this.connection = connection;
 	}
 	
 	public ATDBBuilder createBasicDBStructure() throws SQLException {
 		// create meta info table
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS metaInformation (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS metaInformation (\n"
 				+ "  name  TEXT PRIMARY KEY,\n"
 				+ "  value TEXT\n"
 				+ ");");
 
 		// create entity type table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS entityType (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityType (\n"
 				+ "  id   INTEGER PRIMARY KEY,\n"
 				+ "  name TEXT NOT NULL UNIQUE\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS entityTypeIndex ON entityType(name);");
+		this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS entityTypeIndex ON entityType(name);");
 
 		// create entity table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS entity (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entity (\n"
 				+ "  id           INTEGER PRIMARY KEY,\n"
 				+ "  name         TEXT NOT NULL UNIQUE,\n"
 				+ "  entityTypeId REFERENCES entityType(id)\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS entityIndex ON entity(name);");
+		this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS entityIndex ON entity(name);");
 		
 		// create entity instance table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS entityInstance (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityInstance (\n"
 				+ "  entityId REFERENCES entity(id),\n"
 				+ "  sqcnr    INTEGER,\n"
 				+ "  PRIMARY KEY(entityId, sqcnr)\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE INDEX IF NOT EXISTS entityInstanceIndex ON entityInstance(entityId, sqcnr);");
+		this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS entityInstanceIndex ON entityInstance(entityId, sqcnr);");
 
 		// create event type table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS eventType (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS eventType (\n"
 				+ "  id   INTEGER PRIMARY KEY,\n"
 				+ "  name TEXT NOT NULL UNIQUE\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS eventTypeIndex ON eventType(name);");
+		this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS eventTypeIndex ON eventType(name);");
 
 		// create property table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS property (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS property (\n"
 				+ "  id   INTEGER PRIMARY KEY,\n"
 				+ "  name TEXT NOT NULL UNIQUE,\n"
 				+ "  type TEXT"//
 				+ ");");
-		this.statement.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS propertyIndex ON property(name);");
+		this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS propertyIndex ON property(name);");
 
 		// create property value table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS propertyValue (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS propertyValue (\n"
 				+ "  entityId   REFERENCES entity(id),\n"
 				+ "  propertyId REFERENCES property(id),\n"
 				+ "  sqcnr      INTEGER DEFAULT 0,\n"
 				+ "  value      TEXT,\n"
 				+ "  PRIMARY KEY(entityId, propertyId, sqcnr)\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE INDEX IF NOT EXISTS propertyValueIndex ON propertyValue(entityId, propertyId);");
+		this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS propertyValueIndex ON propertyValue(entityId, propertyId);");
 		
 		// create event table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS event (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS event (\n"
 				+ "  id             INTEGER PRIMARY KEY,\n"
 				+ "  name           TEXT NOT NULL UNIQUE,\n"
 				+ "  eventTypeId    REFERENCES eventType,\n"
 				+ "  entityId       REFERENCES entity,\n"
 				+ "  sourceEntityId REFERENCES entity DEFAULT NULL\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS eventIndex ON event(name);");
+		this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS eventIndex ON event(name);");
 		
 		// create metric table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS metric (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS metric (\n"
 				+ "  id        INTEGER PRIMARY KEY,\n"
 				+ "  name      TEXT NOT NULL UNIQUE,\n"
 				+ "  dimension TEXT NOT NULL\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS metricIndex ON metric(name);");
+		this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS metricIndex ON metric(name);");
 
 		// create entity metric instance value table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS entityMetricInstanceValue (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityMetricInstanceValue (\n"
 				+ "  entityId       REFERENCES entity(id),\n"
 				+ "  metricId       REFERENCES metric(id),\n"
 				+ "  sqcnr          INTEGER,\n"
 				+ "  value          TEXT,\n"
 				+ "  PRIMARY KEY(entityId, metricId, sqcnr)\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE INDEX IF NOT EXISTS entityMetricInstanceValueIndex ON entityMetricInstanceValue(entityId, metricId, sqcnr);");
+		this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS entityMetricInstanceValueIndex ON entityMetricInstanceValue(entityId, metricId, sqcnr);");
 		
 		// create entity instance metric value table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS entityInstanceMetricValue (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityInstanceMetricValue (\n"
 				+ "  entityId       ,\n"
 				+ "  entityInstance ,\n"
 				+ "  metricId       REFERENCES metric(id),\n"
@@ -139,16 +118,16 @@
 				+ "  PRIMARY KEY(entityId, entityInstance, metricId),\n"
 				+ "  FOREIGN KEY(entityId, entityInstance) REFERENCES entityInstance\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE INDEX IF NOT EXISTS entityInstanceMetricValueIndex ON entityInstanceMetricValue(entityId, entityInstance, metricId);");
+		this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS entityInstanceMetricValueIndex ON entityInstanceMetricValue(entityId, entityInstance, metricId);");
 		
 		// create entity metric value table and index
-		this.statement.executeUpdate("CREATE TABLE IF NOT EXISTS entityMetricValue (\n"
+		this.connection.executeUpdate("CREATE TABLE IF NOT EXISTS entityMetricValue (\n"
 				+ "  entityId REFERENCES entity(id),\n"
 				+ "  metricId REFERENCES metric(id),\n"
 				+ "  value    TEXT,\n"
 				+ "  PRIMARY KEY(entityId, metricId)\n"
 				+ ");");
-		this.statement.executeUpdate("CREATE INDEX IF NOT EXISTS entityMetricValueIndex ON entityMetricValue(entityId, metricId);");
+		this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS entityMetricValueIndex ON entityMetricValue(entityId, metricId);");
 		
 		return this;
 	}
@@ -158,7 +137,7 @@
 				+ "      (SELECT name FROM %1$s WHERE id = %2$s.value)\n";
 
 		// human readable entity property values
-		this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS vPropertyValue AS SELECT\n"
+		this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vPropertyValue AS SELECT\n"
 				+ "  (SELECT name FROM entity WHERE id = propertyValue.entityId) AS entityName,\n"
 				+ "  (SELECT name FROM entityType WHERE id = "
 				+ "(SELECT entityTypeId FROM entity WHERE id = propertyValue.entityId)"
@@ -174,7 +153,7 @@
 				+ "FROM propertyValue GROUP BY entityId, propertyId ORDER BY entityId, propertyId;");
 		
 		// human readable observable events
-		this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS vEvent AS SELECT\n"
+		this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEvent AS SELECT\n"
 				+ "  name,\n"
 				+ "  (SELECT name FROM eventType WHERE id = eventTypeId) AS eventType,\n"
 				+ "  (SELECT name FROM entity WHERE id = entityId) AS entityName,\n"
@@ -188,7 +167,7 @@
 				+ "FROM event;");
 		
 		// human readable event chain table (extract from entity)
-		this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS vEventChainEntity AS SELECT\n"
+		this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEventChainEntity AS SELECT\n"
 				+ "  name AS eventChainName,\n"
 				+ "  (SELECT GROUP_CONCAT(name, ', ') FROM event WHERE id IN (SELECT value FROM propertyValue WHERE"
 				+ "    entityId = ecEntity.id AND propertyId = (SELECT id FROM property WHERE name = 'ecStimulus'))) AS stimulus,\n"
@@ -203,7 +182,7 @@
 				+ "FROM entity AS ecEntity WHERE entityTypeId = (SELECT id FROM entityType WHERE entityType.name = 'EC');");
 		
 		// human readable entity specific metric instances
-		this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityMetricInstanceValue AS SELECT\n"
+		this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityMetricInstanceValue AS SELECT\n"
 				+ "  (SELECT name FROM entity WHERE id = entityMetricInstanceValue.entityId) AS entityName,\n"
 				+ "  (SELECT name FROM entityType WHERE id =\n"
 				+ "    (SELECT entityTypeId FROM entity WHERE id = entityMetricInstanceValue.entityId)\n"
@@ -215,7 +194,7 @@
 				+ "ORDER BY entityId, metricId, sqcnr;");
 		
 		// human readable entity instance specific metrics
-		this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityInstanceMetricValue AS SELECT\n"
+		this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityInstanceMetricValue AS SELECT\n"
 				+ "  (SELECT name FROM entity WHERE id = entityInstanceMetricValue.entityId) AS entityName,\n"
 				+ "  (SELECT name FROM entityType WHERE id =\n"
 				+ "    (SELECT entityTypeId FROM entity WHERE id = entityInstanceMetricValue.entityId)\n"
@@ -227,7 +206,7 @@
 				+ "ORDER BY entityId, entityInstance, metricId;");
 		
 		// human readable entity specific metrics
-		this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityMetricValue AS SELECT\n"
+		this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEntityMetricValue AS SELECT\n"
 				+ "  (SELECT name FROM entity WHERE id = entityMetricValue.entityId) AS entityName,\n"
 				+ "  (SELECT name FROM entityType WHERE id =\n"
 				+ "    (SELECT entityTypeId FROM entity WHERE id = entityMetricValue.entityId)\n"
@@ -246,11 +225,12 @@
 	private static String getInstRuntimeEventsQuery(final EntityType entityType) {
 		final String events = entityType.getPossibleEvents().stream().map(e -> "'" + e.toString().toLowerCase() + "'")
 				.collect(Collectors.joining(", "));
+		final String traceAliases = entityType.getTraceAliases().stream().map(ta -> "'" + ta + "'").collect(Collectors.joining(", "));
 		return "  SELECT timestamp, sqcnr, entityId, entityInstance, sourceEntityId, sourceEntityInstance, eventTypeId\n"//
 				+ "  FROM traceEvent WHERE\n"//
 				+ "    eventTypeId IN (SELECT id FROM eventType WHERE name IN (" + events + ")) AND\n"//
 				+ "    entityId IN (SELECT id FROM entity WHERE entityTypeId IN\n"//
-				+ "      (SELECT id FROM entityType WHERE name IN (" + entityType.getTraceAliases() + "))\n"//
+				+ "      (SELECT id FROM entityType WHERE name IN (" + traceAliases + "))\n"//
 				+ "    )\n"//
 				+ "  GROUP BY entityId, entityInstance, timestamp, sqcnr";
 	}
@@ -269,7 +249,7 @@
 	public ATDBBuilder createOptionalAndTemporaryTables(final Collection<? extends EntityType> entityTypes, final boolean persistOptionalTables) throws SQLException {
 		final String persistableTablePref = getPersistableTablePrefix(persistOptionalTables);
 		// create traceEvent table and indices
-		this.statement.executeUpdate(persistableTablePref + "traceEvent (\n"
+		this.connection.executeUpdate(persistableTablePref + "traceEvent (\n"
 				+ "  timestamp            INTEGER,\n"
 				+ "  sqcnr                INTEGER,\n"
 				+ "  entityId             INTEGER,\n"
@@ -283,14 +263,14 @@
 						+ ",\n  FOREIGN KEY(sourceEntityId, sourceEntityInstance) REFERENCES entityInstance"
 						+ ",\n  FOREIGN KEY(eventTypeId) REFERENCES eventType\n" : "\n")
 				+ ");");
-		this.statement.executeUpdate("CREATE INDEX IF NOT EXISTS traceEventIndex ON traceEvent(timestamp, sqcnr);");
-		this.statement.executeUpdate("CREATE INDEX IF NOT EXISTS traceEventIndexForECs ON traceEvent(entityId, eventTypeId);");
+		this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS traceEventIndex ON traceEvent(timestamp, sqcnr);");
+		this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS traceEventIndexForECs ON traceEvent(entityId, eventTypeId);");
 		
 		createTemporaryEntityFilteredTraceEventTables(entityTypes);
 		
 		for (final EntityType et : entityTypes) {
 			// extract runtime relevant traceEvent counts per entity instance
-			this.statement.executeUpdate(persistableTablePref + et.getName() + "InstanceTraceInfo (\n"
+			this.connection.executeUpdate(persistableTablePref + et.getName() + "InstanceTraceInfo (\n"
 					+ "  entityId             INTEGER,\n"
 					+ "  entityInstance       INTEGER,\n"
 					+ et.getPossibleEvents().stream().map(e -> e.toString().toLowerCase() + "EventCount  INTEGER,\n").collect(Collectors.joining())
@@ -298,12 +278,12 @@
 					+ "  PRIMARY KEY(entityId, entityInstance)\n"
 					+ ");");
 			// create index for better performance on trace events
-			this.statement.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS " + et.getName() + "InstanceTraceInfoIndex ON " + et.getName()
+			this.connection.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS " + et.getName() + "InstanceTraceInfoIndex ON " + et.getName()
 					+ "InstanceTraceInfo(entityId, entityInstance);");
 		}
 
 		// create event chain info table
-		this.statement.executeUpdate(persistableTablePref + "eventChainInstanceInfo (\n"//
+		this.connection.executeUpdate(persistableTablePref + "eventChainInstanceInfo (\n"//
 				+ "  entityId          INTEGER,\n"//
 				+ "  entityInstance    INTEGER,\n"//
 				+ "  stimulusTimestamp INTEGER,\n"//
@@ -324,7 +304,7 @@
 	public ATDBBuilder createTemporaryEntityFilteredTraceEventTables(final Collection<? extends EntityType> entityTypes) throws SQLException {
 		for (final EntityType et : entityTypes) {
 			// collect/filter runtime relevant events per entity type
-			this.statement.executeUpdate("CREATE TEMPORARY TABLE IF NOT EXISTS " + et.getName() + "InstanceRuntimeTraceEvent (\n"
+			this.connection.executeUpdate("CREATE TEMPORARY TABLE IF NOT EXISTS " + et.getName() + "InstanceRuntimeTraceEvent (\n"
 					+ "  timestamp            INTEGER,\n"
 					+ "  sqcnr                INTEGER,\n"
 					+ "  entityId             INTEGER,\n"
@@ -335,7 +315,7 @@
 					+ "  PRIMARY KEY(timestamp, sqcnr)\n"
 					+ ");");
 			// create index for better performance on trace events
-			this.statement.executeUpdate("CREATE INDEX IF NOT EXISTS " + et.getName() + "InstanceRuntimeTraceEventIndex ON "
+			this.connection.executeUpdate("CREATE INDEX IF NOT EXISTS " + et.getName() + "InstanceRuntimeTraceEventIndex ON "
 					+ et.getName() + "InstanceRuntimeTraceEvent(entityId, entityInstance, eventTypeId);");
 		}
 		return this;
@@ -348,7 +328,7 @@
 	 */
 	public ATDBBuilder createOptionalViews(final Collection<? extends EntityType> entityTypes) throws SQLException {
 		// human readable event table
-		this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS vTraceEvent AS SELECT\n"//
+		this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vTraceEvent AS SELECT\n"//
 				+ "  traceEvent.timestamp,\n"//
 				+ "  traceEvent.sqcnr,\n"//
 				+ "  (SELECT name FROM entity WHERE id = traceEvent.entityId) AS entityName,\n"//
@@ -368,7 +348,7 @@
 		for (final EntityType et : entityTypes) {
 			final String reDBName = et.getName() + "InstanceRuntimeTraceEvent";
 			// human readable intermediate view for all runtime relevant events per entity instance
-			this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS v" + reDBName + " AS SELECT\n"//
+			this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS v" + reDBName + " AS SELECT\n"//
 					+ "  timestamp,\n"//
 					+ "  sqcnr,\n"//
 					+ "  (SELECT name FROM entity WHERE id = entityId) AS " + et.getUCName() + "Name,\n"//
@@ -381,7 +361,7 @@
 			final String eiDBName = et.getName() + "InstanceTraceInfo";
 			final String eventCountColumns = et.getPossibleEvents().stream()
 					.map(ev -> eiDBName + "." + ev.toString().toLowerCase() + "EventCount").collect(Collectors.joining(",\n  "));
-			this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS v" + eiDBName + " AS SELECT\n"//
+			this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS v" + eiDBName + " AS SELECT\n"//
 					+ "  (SELECT name FROM entity WHERE id = " + eiDBName + ".entityId) AS " + et.getUCName() + "Name,\n"//
 					+ "  " + eiDBName + ".entityInstance,\n"//
 					+ "  " + eventCountColumns + ",\n"//
@@ -390,7 +370,7 @@
 		}
 
 		// human readable event chain instance info table
-		this.statement.executeUpdate("CREATE VIEW IF NOT EXISTS vEventChainInstanceInfo AS SELECT\n"//
+		this.connection.executeUpdate("CREATE VIEW IF NOT EXISTS vEventChainInstanceInfo AS SELECT\n"//
 				+ "  (SELECT name FROM entity WHERE id = entityId) AS eventChainName,\n"//
 				+ "  entityInstance AS ecInstance,\n"//
 				+ "  stimulusTimestamp,\n"//
@@ -416,9 +396,9 @@
 	public void autoPopulateEntityFilteredTraceEventTables(final Collection<? extends EntityType> entityTypes) throws SQLException {
 		for (final EntityType et : entityTypes) {
 			// collect/filter runtime relevant events per entity instance
-			this.statement.executeUpdate("INSERT INTO " + et.getName() + "InstanceRuntimeTraceEvent\n" + getInstRuntimeEventsQuery(et) + ";");
+			this.connection.executeUpdate("INSERT INTO " + et.getName() + "InstanceRuntimeTraceEvent\n" + getInstRuntimeEventsQuery(et) + ";");
 			// extract runtime relevant traceEvent counts per entity instance
-			this.statement.executeUpdate("INSERT INTO " + et.getName() + "InstanceTraceInfo\n" + getInstEventInfosQuery(et) + ";");
+			this.connection.executeUpdate("INSERT INTO " + et.getName() + "InstanceTraceInfo\n" + getInstEventInfosQuery(et) + ";");
 		}
 	}
 	
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
new file mode 100644
index 0000000..c0dfcdd
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java
@@ -0,0 +1,102 @@
+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;
+
+public final class ATDBConnection implements AutoCloseable {
+	
+	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 statement = this.connection.createStatement();
+			statement.setQueryTimeout(30); // set timeout to 30 sec.
+			statement.executeUpdate("PRAGMA foreign_keys = ON;"); // enable foreign key checks
+			this.statement = Optional.of(statement);
+		}
+	}
+
+	@Override
+	public void close() throws SQLException {
+		if (this.statement.isPresent()) {
+			this.statement.get().close();
+		}
+		this.connection.close();
+	}
+	
+	public boolean executeUpdate(final String query) throws SQLException {
+		if (this.statement.isPresent()) {
+			this.statement.get().execute(query);
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+	@FunctionalInterface
+	public static interface ThrowingConsumer<T, E extends Exception> {
+	    void accept(T t) throws E;
+	}
+	
+	public void queryAndConsumeResult(final String query, final ThrowingConsumer<ResultSet, SQLException> consumer) throws SQLException {
+		try (final Statement statement = this.connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
+			final ResultSet resultSet = statement.executeQuery(query);
+			consumer.accept(resultSet);
+		}
+	}
+	
+	@FunctionalInterface
+	public static interface ThrowingFunction<T, R, E extends Exception> {
+	    R apply(T t) throws E;
+	}
+	
+	public <R> R queryAndMapResult(final String query, final ThrowingFunction<ResultSet, R, SQLException> mapper) throws SQLException {
+		try (final Statement statement = this.connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
+			final ResultSet resultSet = statement.executeQuery(query);
+			return mapper.apply(resultSet);
+		}
+	}
+	
+	public PreparedStatement prepareStatement(final String query) throws SQLException {
+		return this.connection.prepareStatement(query);
+	}
+	
+	public Statement createStatement() throws SQLException {
+		return this.connection.createStatement();
+	}
+	
+	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);
+		}
+	}
+
+}
diff --git a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/EntityType.java b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/EntityType.java
index 7ff00da..059bbd7 100644
--- a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/EntityType.java
+++ b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/EntityType.java
@@ -13,9 +13,9 @@
 		return ucName;
 	}
 	
-	String getTraceAliases();
+	List<String> getTraceAliases();
 	
-	Set<? extends Enum<?>> getPossibleEvents();
+	Set<Enum<?>> getPossibleEvents();
 	
 	List<String> getValidityConstraints();
 }