Transport details for contacts can be stored in the database (these are arbitrary key/value pairs that describe how to reach the contact using a particular transport). Moved the generic ContactId and Rating classes out of the database package of the API.

This commit is contained in:
akwizgran
2011-07-06 16:50:01 +01:00
parent 9fbf0f21de
commit b548820f77
10 changed files with 400 additions and 141 deletions

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api.db; package net.sf.briar.api;
/** Type-safe wrapper for an integer that uniquely identifies a contact. */ /** Type-safe wrapper for an integer that uniquely identifies a contact. */
public class ContactId { public class ContactId {

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api.db; package net.sf.briar.api;
/** The ratings that may be applied to an author in peer moderation. */ /** The ratings that may be applied to an author in peer moderation. */
public enum Rating { public enum Rating {

View File

@@ -1,7 +1,10 @@
package net.sf.briar.api.db; package net.sf.briar.api.db;
import java.util.Map;
import java.util.Set; import java.util.Set;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Bundle; import net.sf.briar.api.protocol.Bundle;
import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.GroupId;
@@ -32,8 +35,11 @@ public interface DatabaseComponent {
/** Waits for any open transactions to finish and closes the database. */ /** Waits for any open transactions to finish and closes the database. */
void close() throws DbException; void close() throws DbException;
/** Adds a new contact to the database and returns an ID for the contact. */ /**
ContactId addContact() throws DbException; * Adds a new contact to the database with the given transport details and
* returns an ID for the contact.
*/
ContactId addContact(Map<String, String> transports) throws DbException;
/** Adds a locally generated message to the database. */ /** Adds a locally generated message to the database. */
void addLocallyGeneratedMessage(Message m) throws DbException; void addLocallyGeneratedMessage(Message m) throws DbException;
@@ -53,6 +59,9 @@ public interface DatabaseComponent {
/** Returns the set of groups to which the user subscribes. */ /** Returns the set of groups to which the user subscribes. */
Set<GroupId> getSubscriptions() throws DbException; Set<GroupId> getSubscriptions() throws DbException;
/** Returns the transport details for the given contact. */
Map<String, String> getTransports(ContactId c) throws DbException;
/** /**
* Processes a bundle of acknowledgements, subscriptions, and batches of * Processes a bundle of acknowledgements, subscriptions, and batches of
* messages received from the given contact. Some or all of the messages * messages received from the given contact. Some or all of the messages
@@ -66,6 +75,12 @@ public interface DatabaseComponent {
/** Records the user's rating for the given author. */ /** Records the user's rating for the given author. */
void setRating(AuthorId a, Rating r) throws DbException; void setRating(AuthorId a, Rating r) throws DbException;
/**
* Records the transport details for the given contact, replacing any
* existing transport details.
*/
void setTransports(ContactId c, Map<String, String> transports) throws DbException;
/** Subscribes to the given group. */ /** Subscribes to the given group. */
void subscribe(GroupId g) throws DbException; void subscribe(GroupId g) throws DbException;

View File

@@ -1,10 +1,11 @@
package net.sf.briar.db; package net.sf.briar.db;
import java.util.Map;
import java.util.Set; import java.util.Set;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.ContactId;
import net.sf.briar.api.db.Rating;
import net.sf.briar.api.db.Status; import net.sf.briar.api.db.Status;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.BatchId;
@@ -28,6 +29,7 @@ import net.sf.briar.api.protocol.MessageId;
* <li> messageStatuses * <li> messageStatuses
* <li> ratings * <li> ratings
* <li> subscriptions * <li> subscriptions
* <li> transports
* </ul> * </ul>
*/ */
interface Database<T> { interface Database<T> {
@@ -49,7 +51,11 @@ interface Database<T> {
*/ */
void open(boolean resume) throws DbException; void open(boolean resume) throws DbException;
/** Waits for all open transactions to finish and closes the database. */ /**
* Waits for all open transactions to finish and closes the database.
* <p>
* Locking: all locks write.
*/
void close() throws DbException; void close() throws DbException;
/** Starts a new transaction and returns an object representing it. */ /** Starts a new transaction and returns an object representing it. */
@@ -75,11 +81,12 @@ interface Database<T> {
void addBatchToAck(T txn, ContactId c, BatchId b) throws DbException; void addBatchToAck(T txn, ContactId c, BatchId b) throws DbException;
/** /**
* Adds a new contact to the database and returns an ID for the contact. * Adds a new contact to the database with the given transport details and
* returns an ID for the contact.
* <p> * <p>
* Locking: contacts write, messageStatuses write. * Locking: contacts write, transports write.
*/ */
ContactId addContact(T txn) throws DbException; ContactId addContact(T txn, Map<String, String> transports) throws DbException;
/** /**
* Returns false if the given message is already in the database. Otherwise * Returns false if the given message is already in the database. Otherwise
@@ -115,14 +122,14 @@ interface Database<T> {
/** /**
* Records a contact's subscription to a group. * Records a contact's subscription to a group.
* <p> * <p>
* Locking: contacts read, messageStatuses write. * Locking: contacts read, subscriptions write.
*/ */
void addSubscription(T txn, ContactId c, GroupId g) throws DbException; void addSubscription(T txn, ContactId c, GroupId g) throws DbException;
/** /**
* Removes all recorded subscriptions for the given contact. * Removes all recorded subscriptions for the given contact.
* <p> * <p>
* Locking: contacts read, messageStatuses write. * Locking: contacts read, subscriptions write.
*/ */
void clearSubscriptions(T txn, ContactId c) throws DbException; void clearSubscriptions(T txn, ContactId c) throws DbException;
@@ -150,7 +157,7 @@ interface Database<T> {
/** /**
* Returns the IDs of all contacts. * Returns the IDs of all contacts.
* <p> * <p>
* Locking: contacts read, messageStatuses read. * Locking: contacts read.
*/ */
Set<ContactId> getContacts(T txn) throws DbException; Set<ContactId> getContacts(T txn) throws DbException;
@@ -238,6 +245,13 @@ interface Database<T> {
*/ */
Set<GroupId> getSubscriptions(T txn) throws DbException; Set<GroupId> getSubscriptions(T txn) throws DbException;
/**
* Returns the transport details for the given contact.
* <p>
* Locking: contacts read, transports read.
*/
Map<String, String> getTransports(T txn, ContactId c) throws DbException;
/** /**
* Removes an outstanding batch that has been acknowledged. Any messages in * Removes an outstanding batch that has been acknowledged. Any messages in
* the batch that are still considered outstanding (Status.SENT) with * the batch that are still considered outstanding (Status.SENT) with
@@ -258,7 +272,8 @@ interface Database<T> {
/** /**
* Removes a contact (and all associated state) from the database. * Removes a contact (and all associated state) from the database.
* <p> * <p>
* Locking: contacts write, messageStatuses write. * Locking: contacts write, messageStatuses write, subscriptions write,
* transports write.
*/ */
void removeContact(T txn, ContactId c) throws DbException; void removeContact(T txn, ContactId c) throws DbException;
@@ -282,20 +297,20 @@ interface Database<T> {
* Unsubscribes from the given group. Any messages belonging to the group * Unsubscribes from the given group. Any messages belonging to the group
* are deleted from the database. * are deleted from the database.
* <p> * <p>
* Locking: contacts read, subscriptions write, messages write, * Locking: contacts read, messages write, messageStatuses write,
* messageStatuses write. * subscriptions write.
*/ */
void removeSubscription(T txn, GroupId g) throws DbException; void removeSubscription(T txn, GroupId g) throws DbException;
/** /**
* Records the user's rating for the given author. * Sets the user's rating for the given author.
* <p> * <p>
* Locking: ratings write. * Locking: ratings write.
*/ */
Rating setRating(T txn, AuthorId a, Rating r) throws DbException; Rating setRating(T txn, AuthorId a, Rating r) throws DbException;
/** /**
* Records the sendability score of the given message. * Sets the sendability score of the given message.
* <p> * <p>
* Locking: messages write. * Locking: messages write.
*/ */
@@ -307,4 +322,12 @@ interface Database<T> {
* Locking: contacts read, messages read, messageStatuses write. * Locking: contacts read, messages read, messageStatuses write.
*/ */
void setStatus(T txn, ContactId c, MessageId m, Status s) throws DbException; void setStatus(T txn, ContactId c, MessageId m, Status s) throws DbException;
/**
* Sets the transport details for the given contact, replacing any existing
* transport details.
* <p>
* Locking: contacts read, transports write.
*/
void setTransports(T txn, ContactId c, Map<String, String> transports) throws DbException;
} }

View File

@@ -3,10 +3,10 @@ package net.sf.briar.db;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.briar.api.db.ContactId; import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.Rating;
import net.sf.briar.api.db.Status; import net.sf.briar.api.db.Status;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;

View File

@@ -13,13 +13,16 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.briar.api.db.ContactId; import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.Rating;
import net.sf.briar.api.db.Status; import net.sf.briar.api.db.Status;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.BatchId;
@@ -147,6 +150,15 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String INDEX_STATUSES_BY_CONTACT = private static final String INDEX_STATUSES_BY_CONTACT =
"CREATE INDEX statusesByContact ON statuses (contactId)"; "CREATE INDEX statusesByContact ON statuses (contactId)";
private static final String CREATE_TRANSPORTS =
"CREATE TABLE transports"
+ " (contactId INT NOT NULL,"
+ " detailKey VARCHAR NOT NULL,"
+ " detailValue VARCHAR NOT NULL,"
+ " PRIMARY KEY (contactId, detailKey),"
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE)";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(JdbcDatabase.class.getName()); Logger.getLogger(JdbcDatabase.class.getName());
@@ -236,6 +248,9 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(insertHashType(CREATE_STATUSES)); s.executeUpdate(insertHashType(CREATE_STATUSES));
s.executeUpdate(INDEX_STATUSES_BY_MESSAGE); s.executeUpdate(INDEX_STATUSES_BY_MESSAGE);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT); s.executeUpdate(INDEX_STATUSES_BY_CONTACT);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Creating transports table");
s.executeUpdate(insertHashType(CREATE_TRANSPORTS));
s.close(); s.close();
} catch(SQLException e) { } catch(SQLException e) {
tryToClose(s); tryToClose(s);
@@ -363,7 +378,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public ContactId addContact(Connection txn) throws DbException { public ContactId addContact(Connection txn, Map<String, String> transports)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -400,6 +416,25 @@ abstract class JdbcDatabase implements Database<Connection> {
rowsAffected = ps.executeUpdate(); rowsAffected = ps.executeUpdate();
assert rowsAffected == 1; assert rowsAffected == 1;
ps.close(); ps.close();
// Store the contact's transport details
if(transports != null) {
sql = "INSERT INTO transports"
+ " (contactId, detailKey, detailValue)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
for(Entry<String, String> e : transports.entrySet()) {
ps.setString(2, e.getKey());
ps.setString(3, e.getValue());
ps.addBatch();
}
int[] rowsAffectedArray = ps.executeBatch();
assert rowsAffectedArray.length == transports.size();
for(int i = 0; i < rowsAffectedArray.length; i++) {
assert rowsAffectedArray[i] == 1;
}
ps.close();
}
return c; return c;
} catch(SQLException e) { } catch(SQLException e) {
tryToClose(ps); tryToClose(ps);
@@ -476,10 +511,10 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(3, m.getBytes()); ps.setBytes(3, m.getBytes());
ps.addBatch(); ps.addBatch();
} }
int[] rowsAffected1 = ps.executeBatch(); int[] rowsAffectedArray = ps.executeBatch();
assert rowsAffected1.length == sent.size(); assert rowsAffectedArray.length == sent.size();
for(int i = 0; i < rowsAffected1.length; i++) { for(int i = 0; i < rowsAffectedArray.length; i++) {
assert rowsAffected1[i] == 1; assert rowsAffectedArray[i] == 1;
} }
ps.close(); ps.close();
// Set the status of each message in the batch to SENT // Set the status of each message in the batch to SENT
@@ -493,10 +528,10 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(2, m.getBytes()); ps.setBytes(2, m.getBytes());
ps.addBatch(); ps.addBatch();
} }
rowsAffected1 = ps.executeBatch(); rowsAffectedArray = ps.executeBatch();
assert rowsAffected1.length == sent.size(); assert rowsAffectedArray.length == sent.size();
for(int i = 0; i < rowsAffected1.length; i++) { for(int i = 0; i < rowsAffectedArray.length; i++) {
assert rowsAffected1[i] <= 1; assert rowsAffectedArray[i] <= 1;
} }
ps.close(); ps.close();
} catch(SQLException e) { } catch(SQLException e) {
@@ -1067,6 +1102,29 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public Map<String, String> getTransports(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT detailKey, detailValue FROM transports"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
Map<String, String> transports = new TreeMap<String, String>();
while(rs.next()) transports.put(rs.getString(1), rs.getString(2));
rs.close();
ps.close();
return transports;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(txn);
throw new DbException(e);
}
}
public void removeAckedBatch(Connection txn, ContactId c, BatchId b) public void removeAckedBatch(Connection txn, ContactId c, BatchId b)
throws DbException { throws DbException {
removeBatch(txn, c, b, Status.SEEN); removeBatch(txn, c, b, Status.SEEN);
@@ -1097,18 +1155,18 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
rs.close(); rs.close();
ps.close(); ps.close();
int[] rowsAffected = ps1.executeBatch(); int[] rowsAffectedArray = ps1.executeBatch();
assert rowsAffected.length == messages; assert rowsAffectedArray.length == messages;
for(int i = 0; i < rowsAffected.length; i++) { for(int i = 0; i < rowsAffectedArray.length; i++) {
assert rowsAffected[i] <= 1; assert rowsAffectedArray[i] <= 1;
} }
ps1.close(); ps1.close();
// Cascade on delete // Cascade on delete
sql = "DELETE FROM outstandingBatches WHERE batchId = ?"; sql = "DELETE FROM outstandingBatches WHERE batchId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, b.getBytes()); ps.setBytes(1, b.getBytes());
int rowsAffected1 = ps.executeUpdate(); int rowsAffected = ps.executeUpdate();
assert rowsAffected1 <= 1; assert rowsAffected <= 1;
ps.close(); ps.close();
} catch(SQLException e) { } catch(SQLException e) {
tryToClose(rs); tryToClose(rs);
@@ -1314,4 +1372,40 @@ abstract class JdbcDatabase implements Database<Connection> {
throw new DbException(e); throw new DbException(e);
} }
} }
public void setTransports(Connection txn, ContactId c,
Map<String, String> transports) throws DbException {
PreparedStatement ps = null;
try {
// Delete any existing transports
String sql = "DELETE FROM transports WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.executeUpdate();
ps.close();
// Store the new transports
if(transports != null) {
sql = "INSERT INTO transports"
+ " (contactId, detailKey, detailValue)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
for(Entry<String, String> e : transports.entrySet()) {
ps.setString(2, e.getKey());
ps.setString(3, e.getValue());
ps.addBatch();
}
int[] rowsAffectedArray = ps.executeBatch();
assert rowsAffectedArray.length == transports.size();
for(int i = 0; i < rowsAffectedArray.length; i++) {
assert rowsAffectedArray[i] == 1;
}
ps.close();
}
} catch(SQLException e) {
tryToClose(ps);
tryToClose(txn);
throw new DbException(e);
}
}
} }

View File

@@ -2,15 +2,16 @@ package net.sf.briar.db;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.briar.api.db.ContactId; import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.Rating;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.BatchId;
@@ -46,6 +47,8 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
new ReentrantReadWriteLock(true); new ReentrantReadWriteLock(true);
private final ReentrantReadWriteLock subscriptionLock = private final ReentrantReadWriteLock subscriptionLock =
new ReentrantReadWriteLock(true); new ReentrantReadWriteLock(true);
private final ReentrantReadWriteLock transportLock =
new ReentrantReadWriteLock(true);
@Inject @Inject
ReadWriteLockDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner, ReadWriteLockDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner,
@@ -83,15 +86,16 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
public ContactId addContact() throws DbException { public ContactId addContact(Map<String, String> transports)
throws DbException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact"); if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
contactLock.writeLock().lock(); contactLock.writeLock().lock();
try { try {
messageStatusLock.writeLock().lock(); transportLock.writeLock().lock();
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
ContactId c = db.addContact(txn); ContactId c = db.addContact(txn, transports);
db.commitTransaction(txn); db.commitTransaction(txn);
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Added contact " + c); LOG.fine("Added contact " + c);
@@ -101,7 +105,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
throw e; throw e;
} }
} finally { } finally {
messageStatusLock.writeLock().unlock(); transportLock.writeLock().unlock();
} }
} finally { } finally {
contactLock.writeLock().unlock(); contactLock.writeLock().unlock();
@@ -313,19 +317,14 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
public Set<ContactId> getContacts() throws DbException { public Set<ContactId> getContacts() throws DbException {
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
messageStatusLock.readLock().lock(); Txn txn = db.startTransaction();
try { try {
Txn txn = db.startTransaction(); Set<ContactId> contacts = db.getContacts(txn);
try { db.commitTransaction(txn);
Set<ContactId> contacts = db.getContacts(txn); return contacts;
db.commitTransaction(txn); } catch(DbException e) {
return contacts; db.abortTransaction(txn);
} catch(DbException e) { throw e;
db.abortTransaction(txn);
throw e;
}
} finally {
messageStatusLock.readLock().unlock();
} }
} finally { } finally {
contactLock.readLock().unlock(); contactLock.readLock().unlock();
@@ -366,6 +365,29 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
public Map<String, String> getTransports(ContactId c) throws DbException {
contactLock.readLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
transportLock.readLock().lock();
try {
Txn txn = db.startTransaction();
try {
Map<String, String> transports = db.getTransports(txn, c);
db.commitTransaction(txn);
return transports;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.readLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
public void receiveBundle(ContactId c, Bundle b) throws DbException { public void receiveBundle(ContactId c, Bundle b) throws DbException {
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Received bundle from " + c + ", " LOG.fine("Received bundle from " + c + ", "
@@ -405,7 +427,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
messageStatusLock.writeLock().lock(); subscriptionLock.writeLock().lock();
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
@@ -423,7 +445,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
throw e; throw e;
} }
} finally { } finally {
messageStatusLock.writeLock().unlock(); subscriptionLock.writeLock().unlock();
} }
} finally { } finally {
contactLock.readLock().lock(); contactLock.readLock().lock();
@@ -539,13 +561,23 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try { try {
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();
try { try {
Txn txn = db.startTransaction(); subscriptionLock.writeLock().lock();
try { try {
db.removeContact(txn, c); transportLock.writeLock().lock();
db.commitTransaction(txn); try {
} catch(DbException e) { Txn txn = db.startTransaction();
db.abortTransaction(txn); try {
throw e; db.removeContact(txn, c);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
} finally {
subscriptionLock.writeLock().unlock();
} }
} finally { } finally {
messageStatusLock.writeLock().unlock(); messageStatusLock.writeLock().unlock();
@@ -581,6 +613,29 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
public void setTransports(ContactId c, Map<String, String> transports)
throws DbException {
contactLock.readLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
transportLock.writeLock().lock();
try {
Txn txn = db.startTransaction();
try {
db.setTransports(txn, c, transports);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
public void subscribe(GroupId g) throws DbException { public void subscribe(GroupId g) throws DbException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g); if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g);
subscriptionLock.writeLock().lock(); subscriptionLock.writeLock().lock();

View File

@@ -2,14 +2,15 @@ package net.sf.briar.db;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.ContactId;
import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.Rating;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.BatchId;
@@ -40,6 +41,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
private final Object messageStatusLock = new Object(); private final Object messageStatusLock = new Object();
private final Object ratingLock = new Object(); private final Object ratingLock = new Object();
private final Object subscriptionLock = new Object(); private final Object subscriptionLock = new Object();
private final Object transportLock = new Object();
@Inject @Inject
SynchronizedDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner, SynchronizedDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner,
@@ -54,7 +56,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
synchronized(ratingLock) { synchronized(ratingLock) {
synchronized(subscriptionLock) { synchronized(subscriptionLock) {
db.close(); synchronized(transportLock) {
db.close();
}
} }
} }
} }
@@ -62,13 +66,14 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
public ContactId addContact() throws DbException { public ContactId addContact(Map<String, String> transports)
throws DbException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact"); if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
synchronized(contactLock) { synchronized(contactLock) {
synchronized(messageStatusLock) { synchronized(transportLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
ContactId c = db.addContact(txn); ContactId c = db.addContact(txn, transports);
db.commitTransaction(txn); db.commitTransaction(txn);
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Added contact " + c); LOG.fine("Added contact " + c);
@@ -229,16 +234,14 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
public Set<ContactId> getContacts() throws DbException { public Set<ContactId> getContacts() throws DbException {
synchronized(contactLock) { synchronized(contactLock) {
synchronized(messageStatusLock) { Txn txn = db.startTransaction();
Txn txn = db.startTransaction(); try {
try { Set<ContactId> contacts = db.getContacts(txn);
Set<ContactId> contacts = db.getContacts(txn); db.commitTransaction(txn);
db.commitTransaction(txn); return contacts;
return contacts; } catch(DbException e) {
} catch(DbException e) { db.abortTransaction(txn);
db.abortTransaction(txn); throw e;
throw e;
}
} }
} }
} }
@@ -271,6 +274,23 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
public Map<String, String> getTransports(ContactId c) throws DbException {
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
synchronized(transportLock) {
Txn txn = db.startTransaction();
try {
Map<String, String> transports = db.getTransports(txn, c);
db.commitTransaction(txn);
return transports;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
}
}
public void receiveBundle(ContactId c, Bundle b) throws DbException { public void receiveBundle(ContactId c, Bundle b) throws DbException {
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Received bundle from " + c + ", " LOG.fine("Received bundle from " + c + ", "
@@ -300,7 +320,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
// Update the contact's subscriptions // Update the contact's subscriptions
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageStatusLock) { synchronized(subscriptionLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
db.clearSubscriptions(txn, c); db.clearSubscriptions(txn, c);
@@ -397,13 +417,17 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Removing contact " + c); if(LOG.isLoggable(Level.FINE)) LOG.fine("Removing contact " + c);
synchronized(contactLock) { synchronized(contactLock) {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); synchronized(subscriptionLock) {
try { synchronized(transportLock) {
db.removeContact(txn, c); Txn txn = db.startTransaction();
db.commitTransaction(txn); try {
} catch(DbException e) { db.removeContact(txn, c);
db.abortTransaction(txn); db.commitTransaction(txn);
throw e; } catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
} }
} }
} }
@@ -429,6 +453,23 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
public void setTransports(ContactId c, Map<String, String> transports)
throws DbException {
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
synchronized(transportLock) {
Txn txn = db.startTransaction();
try {
db.setTransports(txn, c, transports);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
}
}
public void subscribe(GroupId g) throws DbException { public void subscribe(GroupId g) throws DbException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g); if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g);
synchronized(subscriptionLock) { synchronized(subscriptionLock) {

View File

@@ -1,15 +1,16 @@
package net.sf.briar.db; package net.sf.briar.db;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.Set; import java.util.Set;
import junit.framework.TestCase; import junit.framework.TestCase;
import net.sf.briar.TestUtils; import net.sf.briar.TestUtils;
import net.sf.briar.api.db.ContactId; import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.Rating;
import net.sf.briar.api.db.Status; import net.sf.briar.api.db.Status;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;
@@ -65,6 +66,10 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test @Test
public void testSimpleCalls() throws DbException { public void testSimpleCalls() throws DbException {
final Map<String, String> transports =
Collections.singletonMap("foo", "bar");
final Map<String, String> transports1 =
Collections.singletonMap("foo", "bar baz");
final Set<GroupId> subs = Collections.singleton(groupId); final Set<GroupId> subs = Collections.singleton(groupId);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -82,12 +87,21 @@ public abstract class DatabaseComponentTest extends TestCase {
// getRating(authorId) // getRating(authorId)
oneOf(database).getRating(txn, authorId); oneOf(database).getRating(txn, authorId);
will(returnValue(Rating.UNRATED)); will(returnValue(Rating.UNRATED));
// addContact(contactId) // addContact(transports)
oneOf(database).addContact(txn); oneOf(database).addContact(txn, transports);
will(returnValue(contactId)); will(returnValue(contactId));
// getContacts() // getContacts()
oneOf(database).getContacts(txn); oneOf(database).getContacts(txn);
will(returnValue(Collections.singleton(contactId))); will(returnValue(Collections.singleton(contactId)));
// getTransports(contactId)
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).getTransports(txn, contactId);
will(returnValue(transports));
// setTransports(contactId, transports1)
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).setTransports(txn, contactId, transports1);
// subscribe(groupId) // subscribe(groupId)
oneOf(database).addSubscription(txn, groupId); oneOf(database).addSubscription(txn, groupId);
// getSubscriptions() // getSubscriptions()
@@ -106,8 +120,10 @@ public abstract class DatabaseComponentTest extends TestCase {
db.open(false); db.open(false);
assertEquals(Rating.UNRATED, db.getRating(authorId)); assertEquals(Rating.UNRATED, db.getRating(authorId));
assertEquals(contactId, db.addContact()); assertEquals(contactId, db.addContact(transports));
assertEquals(Collections.singleton(contactId), db.getContacts()); assertEquals(Collections.singleton(contactId), db.getContacts());
assertEquals(transports, db.getTransports(contactId));
db.setTransports(contactId, transports1);
db.subscribe(groupId); db.subscribe(groupId);
assertEquals(Collections.singleton(groupId), db.getSubscriptions()); assertEquals(Collections.singleton(groupId), db.getSubscriptions());
db.unsubscribe(groupId); db.unsubscribe(groupId);

View File

@@ -6,16 +6,18 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.TestCase; import junit.framework.TestCase;
import net.sf.briar.TestUtils; import net.sf.briar.TestUtils;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.crypto.Password; import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.ContactId;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.Rating;
import net.sf.briar.api.db.Status; import net.sf.briar.api.db.Status;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.BatchId;
@@ -80,7 +82,8 @@ public class H2DatabaseTest extends TestCase {
// Store some records // Store some records
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId));
assertEquals(contactId, db.addContact(txn)); Map<String, String> transports = Collections.singletonMap("foo", "bar");
assertEquals(contactId, db.addContact(txn, transports));
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
assertFalse(db.containsSubscription(txn, groupId)); assertFalse(db.containsSubscription(txn, groupId));
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
@@ -96,6 +99,8 @@ public class H2DatabaseTest extends TestCase {
// Check that the records are still there // Check that the records are still there
txn = db.startTransaction(); txn = db.startTransaction();
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
transports = db.getTransports(txn, contactId);
assertEquals(Collections.singletonMap("foo", "bar"), transports);
assertTrue(db.containsSubscription(txn, groupId)); assertTrue(db.containsSubscription(txn, groupId));
assertTrue(db.containsMessage(txn, messageId)); assertTrue(db.containsMessage(txn, messageId));
Message m1 = db.getMessage(txn, messageId); Message m1 = db.getMessage(txn, messageId);
@@ -118,6 +123,7 @@ public class H2DatabaseTest extends TestCase {
// Check that the records are gone // Check that the records are gone
txn = db.startTransaction(); txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId));
assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId));
assertFalse(db.containsSubscription(txn, groupId)); assertFalse(db.containsSubscription(txn, groupId));
assertFalse(db.containsMessage(txn, messageId)); assertFalse(db.containsMessage(txn, messageId));
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -130,28 +136,25 @@ public class H2DatabaseTest extends TestCase {
ContactId contactId2 = new ContactId(3); ContactId contactId2 = new ContactId(3);
ContactId contactId3 = new ContactId(4); ContactId contactId3 = new ContactId(4);
MessageFactory messageFactory = new TestMessageFactory(); MessageFactory messageFactory = new TestMessageFactory();
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Create three contacts // Create three contacts
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId));
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
assertFalse(db.containsContact(txn, contactId1)); assertFalse(db.containsContact(txn, contactId1));
assertEquals(contactId1, db.addContact(txn)); assertEquals(contactId1, db.addContact(txn, null));
assertTrue(db.containsContact(txn, contactId1)); assertTrue(db.containsContact(txn, contactId1));
assertFalse(db.containsContact(txn, contactId2)); assertFalse(db.containsContact(txn, contactId2));
assertEquals(contactId2, db.addContact(txn)); assertEquals(contactId2, db.addContact(txn, null));
assertTrue(db.containsContact(txn, contactId2)); assertTrue(db.containsContact(txn, contactId2));
/*
// Delete one of the contacts // Delete one of the contacts
db.removeContact(txn, contactId1); db.removeContact(txn, contactId1);
assertFalse(db.containsContact(txn, contactId1)); assertFalse(db.containsContact(txn, contactId1));
*/
// Add another contact - a new ID should be created // Add another contact - a new ID should be created
assertFalse(db.containsContact(txn, contactId3)); assertFalse(db.containsContact(txn, contactId3));
assertEquals(contactId3, db.addContact(txn)); assertEquals(contactId3, db.addContact(txn, null));
assertTrue(db.containsContact(txn, contactId3)); assertTrue(db.containsContact(txn, contactId3));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -180,9 +183,8 @@ public class H2DatabaseTest extends TestCase {
public void testUnsubscribingRemovesMessage() throws DbException { public void testUnsubscribingRemovesMessage() throws DbException {
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Subscribe to a group and store a message // Subscribe to a group and store a message
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
@@ -204,12 +206,11 @@ public class H2DatabaseTest extends TestCase {
public void testSendableMessagesMustBeSendable() throws DbException { public void testSendableMessagesMustBeSendable() throws DbException {
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Add a contact, subscribe to a group and store a message // Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
db.addSubscription(txn, contactId, groupId); db.addSubscription(txn, contactId, groupId);
db.addMessage(txn, message); db.addMessage(txn, message);
@@ -247,12 +248,11 @@ public class H2DatabaseTest extends TestCase {
public void testSendableMessagesMustBeNew() throws DbException { public void testSendableMessagesMustBeNew() throws DbException {
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Add a contact, subscribe to a group and store a message // Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
db.addSubscription(txn, contactId, groupId); db.addSubscription(txn, contactId, groupId);
db.addMessage(txn, message); db.addMessage(txn, message);
@@ -296,12 +296,11 @@ public class H2DatabaseTest extends TestCase {
public void testSendableMessagesMustBeSubscribed() throws DbException { public void testSendableMessagesMustBeSubscribed() throws DbException {
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Add a contact, subscribe to a group and store a message // Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
db.addMessage(txn, message); db.addMessage(txn, message);
db.setSendability(txn, messageId, 1); db.setSendability(txn, messageId, 1);
@@ -338,12 +337,11 @@ public class H2DatabaseTest extends TestCase {
public void testSendableMessagesMustFitCapacity() throws DbException { public void testSendableMessagesMustFitCapacity() throws DbException {
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Add a contact, subscribe to a group and store a message // Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
db.addSubscription(txn, contactId, groupId); db.addSubscription(txn, contactId, groupId);
db.addMessage(txn, message); db.addMessage(txn, message);
@@ -374,12 +372,11 @@ public class H2DatabaseTest extends TestCase {
BatchId batchId1 = new BatchId(TestUtils.getRandomId()); BatchId batchId1 = new BatchId(TestUtils.getRandomId());
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Add a contact and some batches to ack // Add a contact and some batches to ack
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
db.addBatchToAck(txn, contactId, batchId); db.addBatchToAck(txn, contactId, batchId);
db.addBatchToAck(txn, contactId, batchId1); db.addBatchToAck(txn, contactId, batchId1);
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -406,12 +403,11 @@ public class H2DatabaseTest extends TestCase {
public void testRemoveAckedBatch() throws DbException { public void testRemoveAckedBatch() throws DbException {
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Add a contact, subscribe to a group and store a message // Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
db.addSubscription(txn, contactId, groupId); db.addSubscription(txn, contactId, groupId);
db.addMessage(txn, message); db.addMessage(txn, message);
@@ -450,12 +446,11 @@ public class H2DatabaseTest extends TestCase {
public void testRemoveLostBatch() throws DbException { public void testRemoveLostBatch() throws DbException {
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Add a contact, subscribe to a group and store a message // Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
db.addSubscription(txn, contactId, groupId); db.addSubscription(txn, contactId, groupId);
db.addMessage(txn, message); db.addMessage(txn, message);
@@ -504,12 +499,11 @@ public class H2DatabaseTest extends TestCase {
Set<MessageId> empty = Collections.emptySet(); Set<MessageId> empty = Collections.emptySet();
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Add a contact // Add a contact
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn)); assertEquals(contactId, db.addContact(txn, null));
// Add an oustanding batch (associated with BundleId.NONE) // Add an oustanding batch (associated with BundleId.NONE)
db.addOutstandingBatch(txn, contactId, batchId, empty); db.addOutstandingBatch(txn, contactId, batchId, empty);
// Receive a bundle // Receive a bundle
@@ -535,6 +529,7 @@ public class H2DatabaseTest extends TestCase {
lost = db.addReceivedBundle(txn, contactId, bundleId4); lost = db.addReceivedBundle(txn, contactId, bundleId4);
assertTrue(lost.isEmpty()); assertTrue(lost.isEmpty());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -547,9 +542,8 @@ public class H2DatabaseTest extends TestCase {
authorId1, timestamp, body); authorId1, timestamp, body);
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Subscribe to a group and store two messages // Subscribe to a group and store two messages
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
@@ -589,9 +583,8 @@ public class H2DatabaseTest extends TestCase {
authorId, timestamp, body); authorId, timestamp, body);
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Subscribe to the groups and store the messages // Subscribe to the groups and store the messages
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
@@ -626,9 +619,8 @@ public class H2DatabaseTest extends TestCase {
authorId, timestamp + 1000, body); authorId, timestamp + 1000, body);
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Subscribe to a group and store two messages // Subscribe to a group and store two messages
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.addSubscription(txn, groupId); db.addSubscription(txn, groupId);
@@ -665,9 +657,8 @@ public class H2DatabaseTest extends TestCase {
authorId, timestamp, largeBody); authorId, timestamp, largeBody);
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
// Create a new database
Database<Connection> db = open(false, messageFactory); Database<Connection> db = open(false, messageFactory);
// Sanity check: there should be enough space on disk for this test // Sanity check: there should be enough space on disk for this test
assertTrue(testDir.getFreeSpace() > MAX_SIZE); assertTrue(testDir.getFreeSpace() > MAX_SIZE);
// The free space should not be more than the allowed maximum size // The free space should not be more than the allowed maximum size
@@ -692,9 +683,8 @@ public class H2DatabaseTest extends TestCase {
final AtomicBoolean transactionFinished = new AtomicBoolean(false); final AtomicBoolean transactionFinished = new AtomicBoolean(false);
final AtomicBoolean closed = new AtomicBoolean(false); final AtomicBoolean closed = new AtomicBoolean(false);
final AtomicBoolean error = new AtomicBoolean(false); final AtomicBoolean error = new AtomicBoolean(false);
// Create a new database
final Database<Connection> db = open(false, messageFactory); final Database<Connection> db = open(false, messageFactory);
// Start a transaction // Start a transaction
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// In another thread, close the database // In another thread, close the database
@@ -733,9 +723,8 @@ public class H2DatabaseTest extends TestCase {
final AtomicBoolean transactionFinished = new AtomicBoolean(false); final AtomicBoolean transactionFinished = new AtomicBoolean(false);
final AtomicBoolean closed = new AtomicBoolean(false); final AtomicBoolean closed = new AtomicBoolean(false);
final AtomicBoolean error = new AtomicBoolean(false); final AtomicBoolean error = new AtomicBoolean(false);
// Create a new database
final Database<Connection> db = open(false, messageFactory); final Database<Connection> db = open(false, messageFactory);
// Start a transaction // Start a transaction
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// In another thread, close the database // In another thread, close the database
@@ -767,6 +756,32 @@ public class H2DatabaseTest extends TestCase {
assertFalse(error.get()); assertFalse(error.get());
} }
@Test
public void testUpdateTransports() throws DbException {
Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class);
Database<Connection> db = open(false, messageFactory);
// Add a contact with some transport details
Connection txn = db.startTransaction();
Map<String, String> transports = Collections.singletonMap("foo", "bar");
assertEquals(contactId, db.addContact(txn, transports));
assertEquals(transports, db.getTransports(txn, contactId));
// Replace the transport details
transports = new TreeMap<String, String>();
transports.put("foo", "bar baz");
transports.put("bar", "baz quux");
db.setTransports(txn, contactId, transports);
assertEquals(transports, db.getTransports(txn, contactId));
// Remove the transport details
db.setTransports(txn, contactId, null);
assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId));
db.commitTransaction(txn);
db.close();
context.assertIsSatisfied();
}
private Database<Connection> open(boolean resume, private Database<Connection> open(boolean resume,
MessageFactory messageFactory) throws DbException { MessageFactory messageFactory) throws DbException {
final char[] passwordArray = passwordString.toCharArray(); final char[] passwordArray = passwordString.toCharArray();