diff --git a/tdt4140-gr1800/app.core/pom.xml b/tdt4140-gr1800/app.core/pom.xml
index dba0131a6c8e5266fc30c198a251e94d1f823e13..2cf80a0a5c51a17cae683694c8afe1ea722d4f00 100644
--- a/tdt4140-gr1800/app.core/pom.xml
+++ b/tdt4140-gr1800/app.core/pom.xml
@@ -9,33 +9,49 @@
 		<version>0.0.1-SNAPSHOT</version>
 	</parent>
 
+	<properties>
+		<jackson-version>2.9.3</jackson-version>
+	</properties>
+
 	<dependencies>
 		<dependency>
 			<groupId>com.fasterxml.jackson.core</groupId>
 			<artifactId>jackson-core</artifactId>
-			<version>2.9.3</version>
+			<version>${jackson-version}</version>
 		</dependency>
 		<dependency>
 			<groupId>com.fasterxml.jackson.core</groupId>
 			<artifactId>jackson-databind</artifactId>
-			<version>2.9.3</version>
+			<version>${jackson-version}</version>
 		</dependency>
 		<dependency>
 			<groupId>com.fasterxml.jackson.core</groupId>
 			<artifactId>jackson-annotations</artifactId>
-			<version>2.9.3</version>
+			<version>${jackson-version}</version>
 		</dependency>
 		<dependency>
-		    <groupId>com.fasterxml.jackson.dataformat</groupId>
-    			<artifactId>jackson-dataformat-xml</artifactId>
-    			<version>2.9.3</version>
+			<groupId>com.fasterxml.jackson.dataformat</groupId>
+			<artifactId>jackson-dataformat-xml</artifactId>
+			<version>${jackson-version}</version>
 		</dependency>
-		
+
 		<!-- https://mvnrepository.com/artifact/io.jenetics/jpx -->
 		<dependency>
-		    <groupId>io.jenetics</groupId>
-		    <artifactId>jpx</artifactId>
-		    <version>1.2.3</version>
+			<groupId>io.jenetics</groupId>
+			<artifactId>jpx</artifactId>
+			<version>1.2.3</version>
+		</dependency>
+
+		<dependency>
+			<groupId>de.grundid.opendatalab</groupId>
+			<artifactId>geojson-jackson</artifactId>
+			<version>1.8</version>
+		</dependency>
+
+		<dependency>
+		    <groupId>org.hsqldb</groupId>
+		    <artifactId>hsqldb</artifactId>
+		    <version>2.4.0</version>
 		</dependency>
 
 		<dependency>
diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/Person.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/Person.java
new file mode 100644
index 0000000000000000000000000000000000000000..2499a3849839beb453f0c8fd0acac5001954f707
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/Person.java
@@ -0,0 +1,23 @@
+package tdt4140.gr1800.app.core;
+
+public class Person extends GeoLocationsOwner {
+
+	private String name;
+	private String email;
+	
+	public String getName() {
+		return name;
+	}
+	
+	public void setName(String name) {
+		this.name = name;
+	}
+	
+	public String getEmail() {
+		return email;
+	}
+	
+	public void setEmail(String email) {
+		this.email = email;
+	}
+}
diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/AbstractDbAccessImpl.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/AbstractDbAccessImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4e335f698d4a694cf82976e313f4aec6dfaeda9
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/AbstractDbAccessImpl.java
@@ -0,0 +1,120 @@
+package tdt4140.gr1800.app.db;
+
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import tdt4140.gr1800.app.core.GeoLocated;
+import tdt4140.gr1800.app.core.GeoLocation;
+import tdt4140.gr1800.app.core.GeoLocations;
+import tdt4140.gr1800.app.core.Person;
+
+public abstract class AbstractDbAccessImpl implements IDbAccess {
+
+	protected IdMap<Person> personIds = new IdMap<Person>();
+	protected IdMap<GeoLocations> geoLocationsIds = new IdMap<GeoLocations>();
+	protected IdMap<GeoLocation> geoLocationIds = new IdMap<GeoLocation>();
+
+	public int getId(Person person) {
+		return personIds.getId(person);
+	}
+
+	public int getId(GeoLocations geoLocations) {
+		return geoLocationsIds.getId(geoLocations);
+	}
+
+	public int getId(GeoLocation geoLocation) {
+		return geoLocationIds.getId(geoLocation);
+	}
+	
+	@Override
+	public Person createPerson(String name, String email) {
+		Person person = new Person();
+		person.setName(name);
+		person.setEmail(email);
+		return person;
+	}
+
+	@Override
+	public GeoLocations createGeoLocations(Person owner) {
+		GeoLocations geoLocations = new GeoLocations(owner);
+		owner.addGeolocations(geoLocations);
+		return geoLocations;
+	}
+
+	@Override
+	public GeoLocation addGeoLocation(GeoLocations geoLocations, GeoLocated geoLoc, int elevation, LocalTime time) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public Collection<Person> getAllPersons(boolean refresh) {
+		return new ArrayList<Person>(personIds.get());
+	}
+
+	@Override
+	public Person getPerson(int id, boolean refresh) {
+		Person person = personIds.get(id);
+		return person;
+	}
+
+	@Override
+	public Person getPersonByName(String name, boolean refresh) {
+		for (Person person : personIds.get()) {
+			if (name == person.getName() || (name != null && name.equals(person.getName()))) {
+				return person;
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public Person getPersonByEmail(String email, boolean refresh) {
+		for (Person person : personIds.get()) {
+			if (email == person.getEmail() || (email != null && email.equals(person.getEmail()))) {
+				return person;
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public Collection<GeoLocations> getGeoLocations(Person owner, boolean refresh) {
+		Collection<GeoLocations> result = new ArrayList<>();
+		for (GeoLocations geoLocations : geoLocationsIds.get()) {
+			if (geoLocations.getOwner() == owner) {
+				result.add(geoLocations);
+			}
+		}
+		return result;
+	}
+
+	@Override
+	public void updateGeoLocationsData(GeoLocations geoLocations) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	public void updateGeoLocationData(GeoLocations geoLocations, GeoLocations geoLocation) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	public void deletePerson(Person person) {
+		personIds.remove(person);
+	}
+
+	@Override
+	public void deleteGeoLocations(GeoLocations geoLocations) {
+		geoLocations.getOwner().removeGeolocations(geoLocations);
+		geoLocationsIds.remove(geoLocations);
+	}
+
+	@Override
+	public void deleteGeoLocation(GeoLocations geoLocations, GeoLocation geoLocation) {
+		geoLocationIds.remove(geoLocation);
+	}
+}
diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/DbAccessHelper.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/DbAccessHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b6c0542d2852815d7b188651673646c2a2fac60
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/DbAccessHelper.java
@@ -0,0 +1,131 @@
+package tdt4140.gr1800.app.db;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLType;
+import java.sql.Statement;
+import java.sql.Types;
+
+public class DbAccessHelper {
+
+	private Connection dbConnection;
+	private Statement dbStatement;
+	
+	public DbAccessHelper(Connection dbConnection) {
+		this.dbConnection = dbConnection;
+	}
+
+	public DbAccessHelper(String connectionUri, String user, String pass) throws SQLException {
+		this(DriverManager.getConnection(connectionUri, user, pass));
+	}
+
+	public DbAccessHelper(String connectionUri) throws SQLException {
+		this(connectionUri, "SA", "");
+	}
+	
+	public Connection getDbConnection() {
+		return dbConnection;
+	}
+	
+	private Statement getDbStatement() throws SQLException {
+		if (dbStatement == null) {
+			dbStatement = getDbConnection().createStatement();
+		}
+		return dbStatement;
+	}
+	
+	protected void throwException(SQLException e) throws RuntimeException {
+		throw new RuntimeException(e);
+	}
+	
+	protected void executeStatement(String statement) {
+		try {
+			getDbStatement().execute(statement);
+		} catch (SQLException e) {
+			throwException(e);
+		}
+	}
+
+	protected ResultSet executeQuery(String statement) {
+		ResultSet result = null;
+		try {
+			result = getDbStatement().executeQuery(statement);
+		} catch (SQLException e) {
+			throwException(e);
+		}
+		return result;
+	}
+
+	protected PreparedStatement prepareStatement(String statement, Object... args) {
+		PreparedStatement preparedStatement = null;
+		try {
+			preparedStatement = dbConnection.prepareStatement(statement);
+			for (int argNum = 1; argNum <= args.length; argNum++) {
+				Object arg = args[argNum - 1];
+				if (arg == null) {					
+					preparedStatement.setNull(argNum, Types.VARCHAR);
+				} else if (arg instanceof String)  {
+					preparedStatement.setString(argNum, (String) arg);
+				} else if (arg instanceof Double) {
+					preparedStatement.setDouble(argNum, (Double) arg);
+				} else if (arg instanceof Integer) {
+					preparedStatement.setInt(argNum, (Integer) arg);
+				} else if (arg instanceof Boolean) {
+					preparedStatement.setBoolean(argNum, (Boolean) arg);
+				}
+			}
+		} catch (SQLException e) {
+			throwException(e);
+		}
+		return preparedStatement;
+	}
+
+	protected void executeDbStatement(String statement, Object... args) {
+		PreparedStatement preparedStatement = prepareStatement(statement, args);
+		executeStatement(preparedStatement);
+	}
+
+	protected int executeDbInsertGettingIdentity(String statement, Object... args) {
+		executeDbStatement(statement, args);
+		// TODO
+		// https://stackoverflow.com/questions/1915166/how-to-get-the-insert-id-in-jdbc/1915197#1915197
+		ResultSet result = executeQuery(String.format("CALL IDENTITY()"));
+		try {
+			if (result.next()) {
+				int id = result.getInt(1);
+				return id;
+			} else {
+				throw new RuntimeException("Couldn't get id after " + statement);
+			}
+		} catch (SQLException e) {
+			throwException(e);
+		}
+		return -1;
+	}
+
+	protected void executeStatement(PreparedStatement preparedStatement) {
+		try {
+			preparedStatement.executeUpdate();
+		} catch (SQLException e) {
+			throwException(e);
+		}
+	}
+	
+	protected ResultSet executeQuery(PreparedStatement preparedStatement) {
+		ResultSet result = null;
+		try {
+			result = preparedStatement.executeQuery();
+		} catch (SQLException e) {
+			throwException(e);
+		}
+		return result;
+	}
+	
+	protected ResultSet executeQuery(String statement, Object... args) {
+		PreparedStatement preparedStatement = prepareStatement(statement, args);
+		return executeQuery(preparedStatement);
+	}
+}
diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/DbAccessImpl.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/DbAccessImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..88f3b905a2a2dfe33d658b50c7a60049be42f452
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/DbAccessImpl.java
@@ -0,0 +1,237 @@
+package tdt4140.gr1800.app.db;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import tdt4140.gr1800.app.core.GeoLocations;
+import tdt4140.gr1800.app.core.Person;
+
+public class DbAccessImpl extends AbstractDbAccessImpl {
+
+	private final DbAccessHelper helper;
+	
+	private DbAccessImpl(DbAccessHelper helper) {
+		this.helper = helper;
+	}
+
+	public DbAccessImpl(Connection dbConnection) {
+		this(new DbAccessHelper(dbConnection));
+	}
+
+	public DbAccessImpl(String connectionUri, String user, String pass) throws SQLException {
+		this(DriverManager.getConnection(connectionUri, user, pass));
+	}
+
+	public DbAccessImpl(String connectionUri) throws SQLException {
+		this(connectionUri, "SA", "");
+	}
+	
+	//
+	
+	@Override
+	public synchronized Person createPerson(String name, String email) {
+		Person person = super.createPerson(name, email);
+		int id = helper.executeDbInsertGettingIdentity(String.format("INSERT INTO person (name, email) VALUES ('%s', '%s')", name, email));
+		personIds.set(person, id);
+		return person;
+	}
+
+	protected Person createPerson(int id, String name, String email) {
+		Person person = new Person();
+		person.setName(name);
+		person.setEmail(email);
+		personIds.remove(id);
+		personIds.set(person, id);
+		return person;
+	}
+
+	@Override
+	public Collection<Person> getAllPersons(boolean refresh) {
+		if (refresh) {
+			personIds.clear();
+			ResultSet result = helper.executeQuery("SELECT id, name, email FROM person");
+			try {
+				while (result.next()) {
+					int id = result.getInt(1);
+					String name = result.getString(2), email = result.getString(3);
+					createPerson(id, name, email);
+				}
+			} catch (SQLException e) {
+				helper.throwException(e);
+			}
+		}
+		return super.getAllPersons(false);
+	}
+
+	@Override
+	public Person getPerson(int id, boolean refresh) {
+		Person person = null;
+		if (! refresh) {
+			person = super.getPerson(id, false);
+		}
+		if (person == null || refresh) {
+			ResultSet result = helper.executeQuery("SELECT id, name, email FROM person WHERE id = ?", id);
+			try {
+				if (result.next()) {
+					String name = result.getString(2), email = result.getString(3);
+					person = createPerson(id, name, email);
+				}
+			} catch (SQLException e) {
+				helper.throwException(e);
+			}
+		}
+		return person;
+	}
+
+	@Override
+	public Person getPersonByName(String name, boolean refresh) {
+		Person person = null;
+		if (! refresh) {
+			person = super.getPersonByName(name, false);
+		}
+		if (person == null || refresh) {
+			ResultSet result = helper.executeQuery("SELECT id, name, email FROM person WHERE name = ?", name);
+			try {
+				if (result.next()) {
+					int id = result.getInt(1);
+					String dbName = result.getString(2), email = result.getString(3);
+					person = createPerson(id, dbName, email);
+				}
+			} catch (SQLException e) {
+				helper.throwException(e);
+			}
+		}
+		return person;
+	}
+
+	@Override
+	public Person getPersonByEmail(String email, boolean refresh) {
+		Person person = null;
+		if (! refresh) {
+			person = super.getPersonByEmail(email, false);
+		}
+		if (person == null || refresh) {
+			ResultSet result = helper.executeQuery("SELECT id, name, email FROM person WHERE email = ?", email);
+			try {
+				if (result.next()) {
+					int id = result.getInt(1);
+					String name = result.getString(2), dbEmail = result.getString(3);
+					person = createPerson(id, name, dbEmail);
+				}
+			} catch (SQLException e) {
+				helper.throwException(e);
+			}
+		}
+		return person;
+	}
+	
+	@Override
+	public synchronized void updatePersonData(Person person) {
+		helper.executeDbStatement("UPDATE person SET name = ?, email = ? WHERE id = ?", person.getName(), person.getEmail(), getId(person));
+	}
+
+	@Override
+	public synchronized void deletePerson(Person person) {
+		int id = getId(person);
+		super.deletePerson(person);
+		helper.executeDbStatement("DELETE FROM person WHERE id = ?", id);
+		// not needed with ON DELETE CASCADE set on foreign keys
+//		helper.executeDbStatement("DELETE FROM geoLocations WHERE ownerId = ?", person.getId());
+//		helper.executeDbStatement("DELETE FROM geoLocation WHERE ownerId = ?", person.getId());
+	}
+	
+	//
+
+	@Override
+	public GeoLocations createGeoLocations(Person owner) {
+		GeoLocations geoLocations = super.createGeoLocations(owner);
+		int id = helper.executeDbInsertGettingIdentity(String.format("INSERT INTO geoLocations (ownerId) VALUES ('%s')", getId(owner)));
+		geoLocationsIds.set(geoLocations, id);
+		return geoLocations;
+	}
+
+	private static enum TagOwnerType {
+		GLS, // GeoLocations,
+		GL1,  // GeoLocation,
+	}
+
+	@Override
+	public Collection<GeoLocations> getGeoLocations(Person owner, boolean refresh) {
+		Collection<GeoLocations> existingGeoLocations = super.getGeoLocations(owner, false);
+		if (refresh || existingGeoLocations.isEmpty()) {
+			owner.removeGeolocations((String[]) null);
+			geoLocationsIds.removeAll(existingGeoLocations);
+			existingGeoLocations.clear();
+			int ownerId = getId(owner);
+			ResultSet result = helper.executeQuery("SELECT id, path, name, description, date, time FROM geoLocations WHERE ownerId = ?", ownerId);
+			try {
+				while (result.next()) {
+					int id = result.getInt(1);
+					boolean path = result.getBoolean(2);
+					String name = result.getString(3), description = result.getString(3);
+					GeoLocations geoLocations = new GeoLocations(owner);
+					owner.addGeolocations(geoLocations);
+					geoLocations.setPath(path);
+					geoLocations.setName(name);
+					geoLocations.setDescription(description);
+					// TODO: date and time
+					existingGeoLocations.add(geoLocations);
+					geoLocationsIds.set(geoLocations, id);
+					ResultSet tagResults = helper.executeQuery(String.format("SELECT tag FROM tag WHERE ownerId = ? AND ownerType = '%s'", TagOwnerType.GLS), id);
+					while (tagResults.next()) {
+						geoLocations.addTags(tagResults.getString(1));
+					}
+				}
+			} catch (SQLException e) {
+				helper.throwException(e);
+			}
+			ResultSet tagResult = helper.executeQuery(String.format("SELECT geoLocations.id, tag.tag FROM geoLocations, tag WHERE geoLocations.ownerId = ? AND tag.ownerId = geoLocations.id AND ownerType = '%s'", TagOwnerType.GLS), ownerId);
+			try {
+				while (tagResult.next()) {
+					int geoLocationsId = tagResult.getInt(1);
+					String tag = tagResult.getString(2);
+					GeoLocations geoLocations = geoLocationsIds.get(geoLocationsId);
+					if (geoLocations != null) {
+						geoLocations.addTags(tag);
+					}
+				}
+			} catch (SQLException e) {
+				helper.throwException(e);
+			}
+		}
+		return existingGeoLocations;
+	}
+	
+	@Override
+	public void updateGeoLocationsData(GeoLocations geoLocations) {
+		boolean path = geoLocations.isPath();
+		String name = geoLocations.getName(), desc = geoLocations.getDescription();
+		int ownerId = getId(geoLocations);
+		helper.executeDbStatement("UPDATE geoLocations SET path = ?, name = ?, description = ? WHERE id = ?", path, name, desc, ownerId);
+		deleteTags(ownerId, TagOwnerType.GLS);
+		String insertStatement = "INSERT INTO tag (ownerId, ownerType, tag) VALUES ";
+		String[] tags = geoLocations.getTags();
+		for (int i = 0; i < tags.length; i++) {
+			if (i > 0) {
+				insertStatement += ", ";				
+			}
+			insertStatement += String.format("(%s, '%s', '%s')", ownerId, TagOwnerType.GLS, tags[i]);
+		}
+		helper.executeDbStatement(insertStatement);
+	}
+
+	protected void deleteTags(int ownerId, TagOwnerType ownerType) {
+		helper.executeDbStatement(String.format("DELETE FROM tag WHERE ownerId = ? AND ownerType = '%s'", ownerType), ownerId);
+	}
+
+	@Override
+	public void deleteGeoLocations(GeoLocations geoLocations) {
+		int ownerId = getId(geoLocations);
+		super.deleteGeoLocations(geoLocations);
+		helper.executeDbStatement("DELETE FROM geoLocations WHERE id = ?", ownerId);
+		deleteTags(ownerId, TagOwnerType.GLS);
+	}
+}
diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/IDbAccess.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/IDbAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a0be1aba4cac9226b2677f26ba6d33c9290cb1f
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/IDbAccess.java
@@ -0,0 +1,63 @@
+package tdt4140.gr1800.app.db;
+
+import java.time.LocalTime;
+import java.util.Collection;
+
+import tdt4140.gr1800.app.core.GeoLocated;
+import tdt4140.gr1800.app.core.GeoLocation;
+import tdt4140.gr1800.app.core.GeoLocations;
+import tdt4140.gr1800.app.core.GeoLocationsOwner;
+import tdt4140.gr1800.app.core.Person;
+
+/*
+ * CRUD interface for our domain:
+ * 
+ * @startuml
+ * class GeoLocationsOwner {
+ * 	String id
+ * }
+ * class Person {
+ * 	String name
+ * 	String email
+ * }
+ * GeoLocationsOwner <|-- Person
+ * class GeoLocations {
+ * 	String name
+ * 	String description
+ * }
+ * GeoLocationsOwner *-- GeoLocations: owner
+ * class GeoLocation {
+ * 	int elevation
+ * 	LocalTime time
+ * 	String name
+ * 	String description
+ * }
+ * GeoLocations *-- GeoLocation
+ * GeoLocation *-- LatLong
+ * @enduml
+ */
+public interface IDbAccess {
+	
+	// Create
+	public Person createPerson(String name, String email);
+	public GeoLocations createGeoLocations(Person owner);
+	public GeoLocation addGeoLocation(GeoLocations geoLocations, GeoLocated geoLoc, int elevation, LocalTime time);
+
+	// Read
+	public Collection<Person> getAllPersons(boolean refresh);
+	public Person getPerson(int id, boolean refresh);
+	public Person getPersonByName(String name, boolean refresh);
+	public Person getPersonByEmail(String email, boolean refresh);
+	
+	public Collection<GeoLocations> getGeoLocations(Person owner, boolean refresh);
+	
+	// Update
+	public void updatePersonData(Person person);
+	public void updateGeoLocationsData(GeoLocations geoLocations);
+	public void updateGeoLocationData(GeoLocations geoLocations, GeoLocations geoLocation);
+	
+	// Delete
+	public void deletePerson(Person person);
+	public void deleteGeoLocations(GeoLocations geoLocations);
+	public void deleteGeoLocation(GeoLocations geoLocations, GeoLocation geoLocation);
+}
diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/IdMap.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/IdMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..e83e889afdbb13c7aca0142ed333826fd35f53ab
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/db/IdMap.java
@@ -0,0 +1,69 @@
+package tdt4140.gr1800.app.db;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class IdMap<T> {
+
+	protected Map<Integer, T> id2o = new HashMap<Integer, T>();
+	protected Map<T, Integer> o2id = new HashMap<T, Integer>();
+
+	public T get(int id) {
+		return id2o.get(id);
+	}
+
+	public int getId(T o) {
+		Integer id = o2id.get(o);
+		return (id != null ? id : -1);
+	}
+	
+	public Collection<T> get() {
+		return o2id.keySet();
+	}
+	
+	public Collection<Integer> getIds() {
+		return id2o.keySet();
+	}
+
+	//
+	
+	void set(T o, int id) {
+		if (o2id.containsKey(o)) {
+			throw new IllegalStateException(o + " already has the id " + o2id.get(o));
+		}
+		if (id2o.containsKey(id)) {
+			throw new IllegalStateException(id + " already used by " + id2o.get(id));
+		}
+		id2o.put(id, o);
+		o2id.put(o, id);
+	}
+	
+	void clear() {
+		o2id.clear();
+		id2o.clear();		
+	}
+	
+	private void remove(T o, int id) {
+		o2id.remove(o);
+		id2o.remove(id);		
+	}
+
+	void remove(T o) {
+		if (o2id.containsKey(o)) {
+			remove(o, o2id.get(o));
+		}
+	}
+
+	void removeAll(Iterable<T> os) {
+		for (T o : os) {
+			remove(o);
+		}
+	}
+
+	void remove(int id) {
+		if (id2o.containsKey(id)) {
+			remove(id2o.get(id), id);
+		}		
+	}
+}
diff --git a/tdt4140-gr1800/app.core/src/main/resources/tdt4140/gr1800/app/db/schema.sql b/tdt4140-gr1800/app.core/src/main/resources/tdt4140/gr1800/app/db/schema.sql
new file mode 100644
index 0000000000000000000000000000000000000000..321c749e8b043366e55297301b6bc7ce820ac08d
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/resources/tdt4140/gr1800/app/db/schema.sql
@@ -0,0 +1,46 @@
+DROP TABLE person;
+DROP TABLE geoLocations;
+DROP TABLE geoLocation;
+DROP TABLE tag;
+
+CREATE TABLE person (
+	id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1000) PRIMARY KEY,
+
+	name varchar(80) NULL,
+	email varchar(80) NULL
+);
+
+CREATE TABLE geoLocations (
+	id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1000) PRIMARY KEY,
+	ownerId int NOT NULL FOREIGN KEY REFERENCES person(id) ON DELETE CASCADE,
+
+	path boolean NULL,
+
+	name varchar(80) NULL,
+	description varchar(200) NULL,
+
+	date date NULL,
+	time time NULL,
+);
+
+CREATE TABLE geoLocation (
+	id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1000) PRIMARY KEY,
+	ownerId int NOT NULL FOREIGN KEY REFERENCES geoLocations(id) ON DELETE CASCADE,
+
+	name varchar(80) NULL,
+	description varchar(200) NULL,
+	
+	latitude decimal NOT NULL,
+	longitude decimal NOT NULL,
+	elevation int NULL,
+
+	date date NULL,
+	time time NULL,
+);
+
+CREATE TABLE tag (
+	ownerId int NOT NULL,
+	ownerType char(3),
+
+	tag varchar(15) NULL,
+);
diff --git a/tdt4140-gr1800/app.core/src/test/java/tdt4140/gr1800/app/db/HsqldbAccessTest.java b/tdt4140-gr1800/app.core/src/test/java/tdt4140/gr1800/app/db/HsqldbAccessTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..20c0f0b0f0acc5f0b324071d38f1b02bfca5808e
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/test/java/tdt4140/gr1800/app/db/HsqldbAccessTest.java
@@ -0,0 +1,173 @@
+package tdt4140.gr1800.app.db;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collection;
+import java.util.Scanner;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import tdt4140.gr1800.app.core.GeoLocations;
+import tdt4140.gr1800.app.core.Person;
+
+public class HsqldbAccessTest {
+
+	private Connection dbCon;
+	private AbstractDbAccessImpl dbAccess;
+	private static int testNum = 0;
+	
+	@Before
+	public void setUp() throws Exception {
+		Class.forName("org.hsqldb.jdbc.JDBCDriver");
+		dbCon = DriverManager.getConnection("jdbc:hsqldb:mem:" + HsqldbAccessTest.class.getName() + testNum, "SA", "");
+		executeStatements("schema.sql");
+		dbAccess = new DbAccessImpl(dbCon);
+		testNum++;
+	}
+	
+	@After
+	public void tearDown() {
+		if (dbCon != null) {
+			try {
+				dbCon.close();
+			} catch (SQLException e) {
+			}
+		}
+	}
+
+	protected void executeStatements(String path) throws SQLException {
+		Statement dbStatement = dbCon.createStatement();
+		StringBuilder buffer = new StringBuilder();
+		try (Scanner scanner = new Scanner(HsqldbAccessTest.class.getResourceAsStream(path))) {
+			while (scanner.hasNextLine()) {
+				String line = scanner.nextLine();
+				int pos = line.indexOf(";");
+				if (pos >= 0) {
+					buffer.append(line.substring(0, pos + 1));
+					String sql = buffer.toString();
+					buffer.setLength(0);
+					if (pos < line.length()) {
+						buffer.append(line.substring(pos + 1));
+					}
+					if (! sql.startsWith("DROP")) {
+						dbStatement.execute(sql);
+					}
+				} else {
+					buffer.append(line);
+					buffer.append("\n");
+				}
+			}
+		}
+	}
+	
+	protected void checkPersonData(Person hal, Person dbHal) {
+		Assert.assertNotNull(dbHal);
+		Assert.assertEquals(hal.getName(), dbHal.getName());
+		Assert.assertEquals(hal.getEmail(), dbHal.getEmail());
+	}
+
+	@Test
+	public void testCreatePersonGetAllPersons() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		Collection<Person> persons = dbAccess.getAllPersons(false);
+		Assert.assertEquals(1, persons.size());
+		checkPersonData(hal, persons.iterator().next());
+		dbAccess.personIds.clear();
+		Collection<Person> dbPersons = dbAccess.getAllPersons(true);
+		Assert.assertEquals(1, dbPersons.size());
+		checkPersonData(hal, dbPersons.iterator().next());
+	}
+
+	@Test
+	public void testCreatePersonGetPerson() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		int id = dbAccess.getId(hal);
+		Person dbHal = dbAccess.getPerson(id, true);
+		checkPersonData(hal, dbHal);
+		Assert.assertSame(dbHal, dbAccess.getPerson(id, false));
+	}
+	
+	@Test
+	public void testCreatePersonGetPersonByName() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		Person dbHal = dbAccess.getPersonByName(hal.getName(), true);
+		checkPersonData(hal, dbHal);
+	}
+	
+	@Test
+	public void testCreatePersonGetPersonByEmail() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		Person dbHal = dbAccess.getPersonByEmail(hal.getEmail(), true);
+		checkPersonData(hal, dbHal);
+	}
+	
+	@Test
+	public void testCreatePersonUpdatePerson() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		int id = dbAccess.getId(hal);
+		hal.setName("Hallvard");
+		hal.setEmail("hallvard.traetteberg@gmail.com");
+		dbAccess.updatePersonData(hal);
+		Person dbHal = dbAccess.getPerson(id, true);
+		checkPersonData(hal, dbHal);
+	}
+
+	@Test
+	public void testCreatePersonDeletePerson() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		dbAccess.deletePerson(hal);
+		Collection<Person> persons = dbAccess.getAllPersons(true);
+		Assert.assertEquals(0, persons.size());
+	}
+	
+	@Test
+	public void testCreatePersonCreateGeoLocationsGetGeoLocations() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		// no GeoLocations so far
+		Assert.assertEquals(0, dbAccess.getGeoLocations(hal, false).size());
+		GeoLocations geoLocations1 = dbAccess.createGeoLocations(hal);
+		// check we have one locally
+		Assert.assertEquals(1, hal.getGeoLocations((String[]) null).size());
+		Collection<GeoLocations> geoLocations = dbAccess.getGeoLocations(hal, false);
+		Assert.assertEquals(1, hal.getGeoLocations((String[]) null).size());
+		Assert.assertEquals(1, geoLocations.size());
+		Assert.assertSame(geoLocations1, geoLocations.iterator().next());
+		dbAccess.geoLocationsIds.clear();
+		// check we have one in the db
+		Collection<GeoLocations> dbGeoLocations = dbAccess.getGeoLocations(hal, true);
+		Assert.assertEquals(1, hal.getGeoLocations((String[]) null).size()); 
+		Assert.assertEquals(1, dbGeoLocations.size());
+	}
+	
+	@Test
+	public void testCreatePersonCreateUpdateGeoLocationsGetGeoLocations() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		GeoLocations geoLocations1 = dbAccess.createGeoLocations(hal);
+		geoLocations1.setName("geoLocs1");
+		String[] tags = {"tag1", "tag2"}; 
+		geoLocations1.addTags(tags);
+		dbAccess.updateGeoLocationsData(geoLocations1);
+		dbAccess.geoLocationsIds.clear();
+		// check the db
+		Collection<GeoLocations> dbGeoLocations = dbAccess.getGeoLocations(hal, true);
+		Assert.assertEquals(1, hal.getGeoLocations((String[]) null).size()); 
+		Assert.assertEquals(1, dbGeoLocations.size());
+		GeoLocations dbGeoLocations1 = dbGeoLocations.iterator().next();
+		Assert.assertEquals(2, dbGeoLocations1.getTags().length);
+		Assert.assertTrue(dbGeoLocations1.hasTags(tags));
+	}
+	
+	@Test
+	public void testCreatePersonCreateGeoLocationsDeleteGeoLocations() {
+		Person hal = dbAccess.createPerson("hal", "hal@ntnu.no");
+		GeoLocations geoLocations = dbAccess.createGeoLocations(hal);
+		Collection<GeoLocations> dbGeoLocations = dbAccess.getGeoLocations(hal, true);
+		Assert.assertEquals(1, dbGeoLocations.size());		
+		dbAccess.deleteGeoLocations(geoLocations);
+	}
+}