Don't store subscription or transport updates that are older than those already received. Also some small changes to DatabaseComponent impls for readability.

This commit is contained in:
akwizgran
2011-07-14 13:53:13 +01:00
parent 836d30f6df
commit fcedc34d10
10 changed files with 397 additions and 216 deletions

View File

@@ -51,7 +51,8 @@ public interface DatabaseComponent {
* Generates a bundle of acknowledgements, subscriptions, and batches of
* messages for the given contact.
*/
void generateBundle(ContactId c, BundleWriter bundleBuilder) throws DbException, IOException, GeneralSecurityException;
void generateBundle(ContactId c, BundleWriter bundleBuilder)
throws DbException, IOException, GeneralSecurityException;
/** Returns the IDs of all contacts. */
Set<ContactId> getContacts() throws DbException;
@@ -73,7 +74,8 @@ public interface DatabaseComponent {
* messages received from the given contact. Some or all of the messages
* in the bundle may be stored.
*/
void receiveBundle(ContactId c, BundleReader b) throws DbException, IOException, GeneralSecurityException;
void receiveBundle(ContactId c, BundleReader b) throws DbException,
IOException, GeneralSecurityException;
/** Removes a contact (and all associated state) from the database. */
void removeContact(ContactId c) throws DbException;
@@ -81,12 +83,6 @@ public interface DatabaseComponent {
/** Records the user's rating for the given author. */
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. */
void subscribe(GroupId g) throws DbException;

View File

@@ -5,5 +5,6 @@ import java.security.GeneralSecurityException;
public interface MessageParser {
Message parseMessage(byte[] raw) throws IOException, GeneralSecurityException;
Message parseMessage(byte[] raw) throws IOException,
GeneralSecurityException;
}

View File

@@ -81,7 +81,8 @@ interface Database<T> {
* <p>
* Locking: contacts write, transports write.
*/
ContactId addContact(T txn, Map<String, String> transports) throws DbException;
ContactId addContact(T txn, Map<String, String> transports)
throws DbException;
/**
* Returns false if the given message is already in the database. Otherwise
@@ -96,7 +97,8 @@ interface Database<T> {
* <p>
* Locking: contacts read, messages read, messageStatuses write.
*/
void addOutstandingBatch(T txn, ContactId c, BatchId b, Set<MessageId> sent) throws DbException;
void addOutstandingBatch(T txn, ContactId c, BatchId b, Set<MessageId> sent)
throws DbException;
/**
* Subscribes to the given group.
@@ -169,7 +171,8 @@ interface Database<T> {
* <p>
* Locking: messages read.
*/
Iterable<MessageId> getMessagesByAuthor(T txn, AuthorId a) throws DbException;
Iterable<MessageId> getMessagesByAuthor(T txn, AuthorId a)
throws DbException;
/**
* Returns the number of children of the message identified by the given
@@ -216,7 +219,8 @@ interface Database<T> {
* <p>
* Locking: contacts read, messages read, messageStatuses read.
*/
Iterable<MessageId> getSendableMessages(T txn, ContactId c, long capacity) throws DbException;
Iterable<MessageId> getSendableMessages(T txn, ContactId c, long capacity)
throws DbException;
/**
* Returns the groups to which the user subscribes.
@@ -225,6 +229,13 @@ interface Database<T> {
*/
Set<GroupId> getSubscriptions(T txn) throws DbException;
/**
* Returns the groups to which the given contact subscribes.
* <p>
* Locking: contacts read, subscriptions read.
*/
Set<GroupId> getSubscriptions(T txn, ContactId c) throws DbException;
/**
* Returns the local transport details.
* <p>
@@ -308,15 +319,17 @@ interface Database<T> {
* <p>
* 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 subscriptions for the given contact, replacing any existing
* subscriptions.
* subscriptions unless the existing subscriptions have a newer timestamp.
* <p>
* Locking: contacts read, subscriptions write.
* Locking: contacts write, subscriptions write.
*/
void setSubscriptions(T txn, ContactId c, Set<GroupId> subs) throws DbException;
void setSubscriptions(T txn, ContactId c, Set<GroupId> subs, long timestamp)
throws DbException;
/**
* Sets the local transport details, replacing any existing transport
@@ -324,13 +337,15 @@ interface Database<T> {
* <p>
* Locking: transports write.
*/
void setTransports(T txn, Map<String, String> transports) throws DbException;
void setTransports(T txn, Map<String, String> transports)
throws DbException;
/**
* Sets the transport details for the given contact, replacing any existing
* transport details.
* transport details unless the existing details have a newer timestamp.
* <p>
* Locking: contacts read, transports write.
* Locking: contacts write, transports write.
*/
void setTransports(T txn, ContactId c, Map<String, String> transports) throws DbException;
void setTransports(T txn, ContactId c, Map<String, String> transports,
long timestamp) throws DbException;
}

View File

@@ -12,8 +12,10 @@ public class DatabaseModule extends AbstractModule {
@Override
protected void configure() {
bind(Database.class).to(H2Database.class);
bind(DatabaseComponent.class).to(ReadWriteLockDatabaseComponent.class).in(Singleton.class);
bind(Password.class).annotatedWith(DatabasePassword.class).toInstance(new Password() {
bind(DatabaseComponent.class).to(
ReadWriteLockDatabaseComponent.class).in(Singleton.class);
bind(Password.class).annotatedWith(DatabasePassword.class).toInstance(
new Password() {
public char[] getPassword() {
return "fixme fixme".toCharArray();
}

View File

@@ -38,15 +38,15 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_LOCAL_SUBSCRIPTIONS =
"CREATE TABLE localSubscriptions"
+ " (groupId XXXX NOT NULL,"
+ " (groupId HASH NOT NULL,"
+ " PRIMARY KEY (groupId))";
private static final String CREATE_MESSAGES =
"CREATE TABLE messages"
+ " (messageId XXXX NOT NULL,"
+ " parentId XXXX NOT NULL,"
+ " groupId XXXX NOT NULL,"
+ " authorId XXXX NOT NULL,"
+ " (messageId HASH NOT NULL,"
+ " parentId HASH NOT NULL,"
+ " groupId HASH NOT NULL,"
+ " authorId HASH NOT NULL,"
+ " timestamp BIGINT NOT NULL,"
+ " size INT NOT NULL,"
+ " raw BLOB NOT NULL,"
@@ -70,11 +70,13 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_CONTACTS =
"CREATE TABLE contacts"
+ " (contactId INT NOT NULL,"
+ " subscriptionsTimestamp TIMESTAMP NOT NULL,"
+ " transportsTimestamp TIMESTAMP NOT NULL,"
+ " PRIMARY KEY (contactId))";
private static final String CREATE_BATCHES_TO_ACK =
"CREATE TABLE batchesToAck"
+ " (batchId XXXX NOT NULL,"
+ " (batchId HASH NOT NULL,"
+ " contactId INT NOT NULL,"
+ " PRIMARY KEY (batchId),"
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
@@ -83,16 +85,16 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_CONTACT_SUBSCRIPTIONS =
"CREATE TABLE contactSubscriptions"
+ " (contactId INT NOT NULL,"
+ " groupId XXXX NOT NULL,"
+ " groupId HASH NOT NULL,"
+ " PRIMARY KEY (contactId, groupId),"
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE)";
private static final String CREATE_OUTSTANDING_BATCHES =
"CREATE TABLE outstandingBatches"
+ " (batchId XXXX NOT NULL,"
+ " (batchId HASH NOT NULL,"
+ " contactId INT NOT NULL,"
+ " timestamp YYYY NOT NULL,"
+ " timestamp TIMESTAMP NOT NULL,"
+ " passover INT NOT NULL,"
+ " PRIMARY KEY (batchId),"
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
@@ -100,9 +102,9 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_OUTSTANDING_MESSAGES =
"CREATE TABLE outstandingMessages"
+ " (batchId XXXX NOT NULL,"
+ " (batchId HASH NOT NULL,"
+ " contactId INT NOT NULL,"
+ " messageId XXXX NOT NULL,"
+ " messageId HASH NOT NULL,"
+ " PRIMARY KEY (batchId, messageId),"
+ " FOREIGN KEY (batchId) REFERENCES outstandingBatches (batchId)"
+ " ON DELETE CASCADE,"
@@ -117,13 +119,13 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_RATINGS =
"CREATE TABLE ratings"
+ " (authorId XXXX NOT NULL,"
+ " (authorId HASH NOT NULL,"
+ " rating SMALLINT NOT NULL,"
+ " PRIMARY KEY (authorId))";
private static final String CREATE_STATUSES =
"CREATE TABLE statuses"
+ " (messageId XXXX NOT NULL,"
+ " (messageId HASH NOT NULL,"
+ " contactId INT NOT NULL,"
+ " status SMALLINT NOT NULL,"
+ " PRIMARY KEY (messageId, contactId),"
@@ -157,7 +159,7 @@ abstract class JdbcDatabase implements Database<Connection> {
Logger.getLogger(JdbcDatabase.class.getName());
// Different database libraries use different names for certain types
private final String hashType, bigIntType;
private final String hashType, timestampType;
private final LinkedList<Connection> connections =
new LinkedList<Connection>(); // Locking: self
@@ -166,9 +168,9 @@ abstract class JdbcDatabase implements Database<Connection> {
protected abstract Connection createConnection() throws SQLException;
JdbcDatabase(String hashType, String bigIntType) {
JdbcDatabase(String hashType, String timestampType) {
this.hashType = hashType;
this.bigIntType = bigIntType;
this.timestampType = timestampType;
}
protected void open(boolean resume, File dir, String driverClass)
@@ -254,7 +256,8 @@ abstract class JdbcDatabase implements Database<Connection> {
}
private String insertTypeNames(String s) {
return s.replaceAll("XXXX", hashType).replaceAll("YYYY", bigIntType);
s = s.replaceAll("HASH", hashType);
return s.replaceAll("TIMESTAMP", timestampType);
}
private void tryToClose(Connection c) {
@@ -388,9 +391,13 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close();
ps.close();
// Create a new contact row
sql = "INSERT INTO contacts (contactId) VALUES (?)";
sql = "INSERT INTO contacts"
+ " (contactId, subscriptionsTimestamp, transportsTimestamp)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setLong(2, 0L);
ps.setLong(3, 0L);
int rowsAffected = ps.executeUpdate();
assert rowsAffected == 1;
ps.close();
@@ -955,6 +962,29 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Set<GroupId> getSubscriptions(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT groupId FROM contactSubscriptions"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
Set<GroupId> ids = new HashSet<GroupId>();
while(rs.next()) ids.add(new GroupId(rs.getBytes(1)));
rs.close();
ps.close();
return ids;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(txn);
throw new DbException(e);
}
}
public Map<String, String> getTransports(Connection txn)
throws DbException {
PreparedStatement ps = null;
@@ -1279,12 +1309,27 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void setSubscriptions(Connection txn, ContactId c, Set<GroupId> subs)
throws DbException {
public void setSubscriptions(Connection txn, ContactId c, Set<GroupId> subs,
long timestamp) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Return if the timestamp isn't fresh
String sql = "SELECT subscriptionsTimestamp FROM contacts"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
boolean found = rs.next();
assert found;
long lastTimestamp = rs.getLong(1);
boolean more = rs.next();
assert !more;
rs.close();
ps.close();
if(lastTimestamp >= timestamp) return;
// Delete any existing subscriptions
String sql = "DELETE FROM contactSubscriptions WHERE contactId = ?";
sql = "DELETE FROM contactSubscriptions WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.executeUpdate();
@@ -1304,6 +1349,15 @@ abstract class JdbcDatabase implements Database<Connection> {
assert rowsAffectedArray[i] == 1;
}
ps.close();
// Update the timestamp
sql = "UPDATE contacts SET subscriptionsTimestamp = ?"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setLong(1, timestamp);
ps.setInt(2, c.getInt());
int rowsAffected = ps.executeUpdate();
assert rowsAffected == 1;
ps.close();
} catch(SQLException e) {
tryToClose(ps);
tryToClose(txn);
@@ -1345,11 +1399,26 @@ abstract class JdbcDatabase implements Database<Connection> {
}
public void setTransports(Connection txn, ContactId c,
Map<String, String> transports) throws DbException {
Map<String, String> transports, long timestamp) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Return if the timestamp isn't fresh
String sql = "SELECT transportsTimestamp FROM contacts"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
boolean found = rs.next();
assert found;
long lastTimestamp = rs.getLong(1);
boolean more = rs.next();
assert !more;
rs.close();
ps.close();
if(lastTimestamp >= timestamp) return;
// Delete any existing transports
String sql = "DELETE FROM contactTransports WHERE contactId = ?";
sql = "DELETE FROM contactTransports WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.executeUpdate();
@@ -1372,6 +1441,15 @@ abstract class JdbcDatabase implements Database<Connection> {
}
ps.close();
}
// Update the timestamp
sql = "UPDATE contacts SET transportsTimestamp = ?"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setLong(1, timestamp);
ps.setInt(2, c.getInt());
int rowsAffected = ps.executeUpdate();
assert rowsAffected == 1;
ps.close();
} catch(SQLException e) {
tryToClose(ps);
tryToClose(txn);

View File

@@ -2,6 +2,7 @@ package net.sf.briar.db;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
@@ -192,10 +193,19 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
public void generateBundle(ContactId c, BundleWriter b) throws DbException,
IOException, GeneralSecurityException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c);
Set<BatchId> acks;
Set<GroupId> subs;
Map<String, String> transports;
// Add acks
Set<BatchId> acks = generateAcks(c);
Set<GroupId> subs = generateSubscriptions(c);
Map<String, String> transports = generateTransports(c);
// Add the header to the bundle
b.addHeader(acks, subs, transports);
// Add as many messages as possible to the bundle
while(generateBatch(c, b));
b.finish();
if(LOG.isLoggable(Level.FINE)) LOG.fine("Bundle generated");
System.gc();
}
private Set<BatchId> generateAcks(ContactId c) throws DbException {
contactLock.readLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
@@ -203,10 +213,11 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try {
Txn txn = db.startTransaction();
try {
acks = db.removeBatchesToAck(txn, c);
Set<BatchId> acks = db.removeBatchesToAck(txn, c);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + acks.size() + " acks");
db.commitTransaction(txn);
return acks;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
@@ -217,7 +228,9 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} finally {
contactLock.readLock().unlock();
}
// Add subscriptions
}
private Set<GroupId> generateSubscriptions(ContactId c) throws DbException {
contactLock.readLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
@@ -225,10 +238,11 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try {
Txn txn = db.startTransaction();
try {
subs = db.getSubscriptions(txn);
Set<GroupId> subs = db.getSubscriptions(txn);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + subs.size() + " subscriptions");
db.commitTransaction(txn);
return subs;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
@@ -239,7 +253,10 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} finally {
contactLock.readLock().unlock();
}
// Add transport details
}
private Map<String, String> generateTransports(ContactId c)
throws DbException {
contactLock.readLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
@@ -247,10 +264,11 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try {
Txn txn = db.startTransaction();
try {
transports = db.getTransports(txn);
Map<String, String> transports = db.getTransports(txn);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + transports.size() + " transports");
db.commitTransaction(txn);
return transports;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
@@ -261,17 +279,10 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} finally {
contactLock.readLock().unlock();
}
// Add the header to the bundle
b.addHeader(acks, subs, transports);
// Add as many messages as possible to the bundle
while(fillBatch(c, b));
b.finish();
if(LOG.isLoggable(Level.FINE)) LOG.fine("Bundle generated");
System.gc();
}
private boolean fillBatch(ContactId c, BundleWriter b) throws DbException,
IOException, GeneralSecurityException {
private boolean generateBatch(ContactId c, BundleWriter b)
throws DbException, IOException, GeneralSecurityException {
contactLock.readLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
@@ -309,7 +320,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} catch(IOException e) {
db.abortTransaction(txn);
throw e;
} catch(GeneralSecurityException e) {
} catch(SignatureException e) {
db.abortTransaction(txn);
throw e;
}
@@ -435,12 +446,29 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
public void receiveBundle(ContactId c, BundleReader b) throws DbException,
IOException, GeneralSecurityException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Received bundle from " + c);
Header h;
Header h = b.getHeader();
receiveAcks(c, h);
receiveSubscriptions(c, h);
receiveTransports(c, h);
// Store the messages
int batches = 0;
Batch batch = null;
while((batch = b.getNextBatch()) != null) {
receiveBatch(c, batch);
batches++;
}
if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + batches + " batches");
b.finish();
findLostBatches(c);
System.gc();
}
private void receiveAcks(ContactId c, Header h) throws DbException {
// Mark all messages in acked batches as seen
contactLock.readLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
h = b.getHeader();
messageLock.readLock().lock();
try {
messageStatusLock.writeLock().lock();
@@ -467,8 +495,12 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} finally {
contactLock.readLock().unlock();
}
}
private void receiveSubscriptions(ContactId c, Header h)
throws DbException {
// Update the contact's subscriptions
contactLock.readLock().lock();
contactLock.writeLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
subscriptionLock.writeLock().lock();
@@ -476,7 +508,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
Txn txn = db.startTransaction();
try {
Set<GroupId> subs = h.getSubscriptions();
db.setSubscriptions(txn, c, subs);
db.setSubscriptions(txn, c, subs, h.getTimestamp());
if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + subs.size() + " subscriptions");
db.commitTransaction(txn);
@@ -488,10 +520,13 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
subscriptionLock.writeLock().unlock();
}
} finally {
contactLock.readLock().unlock();
contactLock.writeLock().unlock();
}
}
private void receiveTransports(ContactId c, Header h) throws DbException {
// Update the contact's transport details
contactLock.readLock().lock();
contactLock.writeLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
transportLock.writeLock().lock();
@@ -499,7 +534,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
Txn txn = db.startTransaction();
try {
Map<String, String> transports = h.getTransports();
db.setTransports(txn, c, transports);
db.setTransports(txn, c, transports, h.getTimestamp());
if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + transports.size()
+ " transports");
@@ -512,23 +547,11 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
transportLock.writeLock().unlock();
}
} finally {
contactLock.readLock().unlock();
contactLock.writeLock().unlock();
}
// Store the messages
int batches = 0;
Batch batch = null;
while((batch = b.getNextBatch()) != null) {
storeBatch(c, batch);
batches++;
}
if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + batches + " batches");
b.finish();
findLostBatches(c);
System.gc();
}
private void storeBatch(ContactId c, Batch b) throws DbException {
private void receiveBatch(ContactId c, Batch b) throws DbException {
waitForPermissionToWrite();
contactLock.readLock().lock();
try {
@@ -687,29 +710,6 @@ 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 {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g);
subscriptionLock.writeLock().lock();

View File

@@ -146,68 +146,78 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
public void generateBundle(ContactId c, BundleWriter b) throws DbException,
IOException, GeneralSecurityException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c);
Set<BatchId> acks;
Set<GroupId> subs;
Map<String, String> transports;
// Add acks
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageStatusLock) {
Txn txn = db.startTransaction();
try {
acks = db.removeBatchesToAck(txn, c);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + acks.size() + " acks");
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
}
// Add subscriptions
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
synchronized(subscriptionLock) {
Txn txn = db.startTransaction();
try {
subs = db.getSubscriptions(txn);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + subs.size() + " subscriptions");
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
}
// Add transport details
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
synchronized(transportLock) {
Txn txn = db.startTransaction();
try {
transports = db.getTransports(txn);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + transports.size() + " transports");
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
}
Set<BatchId> acks = generateAcks(c);
Set<GroupId> subs = generateSubscriptions(c);
Map<String, String> transports = generateTransports(c);
// Add the header to the bundle
b.addHeader(acks, subs, transports);
// Add as many messages as possible to the bundle
while(fillBatch(c, b));
while(generateBatch(c, b));
b.finish();
if(LOG.isLoggable(Level.FINE)) LOG.fine("Bundle generated");
System.gc();
}
private boolean fillBatch(ContactId c, BundleWriter b) throws DbException,
IOException, GeneralSecurityException {
private Set<BatchId> generateAcks(ContactId c) throws DbException {
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageStatusLock) {
Txn txn = db.startTransaction();
try {
Set<BatchId> acks = db.removeBatchesToAck(txn, c);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + acks.size() + " acks");
db.commitTransaction(txn);
return acks;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
}
}
private Set<GroupId> generateSubscriptions(ContactId c) throws DbException {
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
synchronized(subscriptionLock) {
Txn txn = db.startTransaction();
try {
Set<GroupId> subs = db.getSubscriptions(txn);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + subs.size() + " subscriptions");
db.commitTransaction(txn);
return subs;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
}
}
private Map<String, String> generateTransports(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);
if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + transports.size() + " transports");
db.commitTransaction(txn);
return transports;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
}
}
}
private boolean generateBatch(ContactId c, BundleWriter b)
throws DbException, IOException, GeneralSecurityException {
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageLock) {
@@ -242,6 +252,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
} catch(IOException e) {
db.abortTransaction(txn);
throw e;
} catch(SignatureException e) {
db.abortTransaction(txn);
throw e;
@@ -327,11 +340,28 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
public void receiveBundle(ContactId c, BundleReader b) throws DbException,
IOException, GeneralSecurityException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Received bundle from " + c);
Header h;
Header h = b.getHeader();
receiveAcks(c, h);
receiveSubscriptions(c, h);
receiveTransports(c, h);
// Store the messages
int batches = 0;
Batch batch = null;
while((batch = b.getNextBatch()) != null) {
receiveBatch(c, batch);
batches++;
}
if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + batches + " batches");
b.finish();
findLostBatches(c);
System.gc();
}
private void receiveAcks(ContactId c, Header h) throws DbException {
// Mark all messages in acked batches as seen
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
h = b.getHeader();
synchronized(messageLock) {
synchronized(messageStatusLock) {
Set<BatchId> acks = h.getAcks();
@@ -350,6 +380,10 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
}
}
}
}
private void receiveSubscriptions(ContactId c, Header h)
throws DbException {
// Update the contact's subscriptions
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
@@ -357,7 +391,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
Txn txn = db.startTransaction();
try {
Set<GroupId> subs = h.getSubscriptions();
db.setSubscriptions(txn, c, subs);
db.setSubscriptions(txn, c, subs, h.getTimestamp());
if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + subs.size() + " subscriptions");
db.commitTransaction(txn);
@@ -367,6 +401,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
}
}
}
}
private void receiveTransports(ContactId c, Header h) throws DbException {
// Update the contact's transport details
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
@@ -374,7 +411,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
Txn txn = db.startTransaction();
try {
Map<String, String> transports = h.getTransports();
db.setTransports(txn, c, transports);
db.setTransports(txn, c, transports, h.getTimestamp());
if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + transports.size()
+ " transports");
@@ -385,21 +422,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
}
}
}
// Store the messages
int batches = 0;
Batch batch = null;
while((batch = b.getNextBatch()) != null) {
storeBatch(c, batch);
batches++;
}
if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + batches + " batches");
b.finish();
findLostBatches(c);
System.gc();
}
private void storeBatch(ContactId c, Batch b) throws DbException {
private void receiveBatch(ContactId c, Batch b) throws DbException {
waitForPermissionToWrite();
synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException();
@@ -511,23 +536,6 @@ 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 {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g);
synchronized(subscriptionLock) {

View File

@@ -8,6 +8,7 @@ public class InvitationModule extends AbstractModule {
@Override
protected void configure() {
bind(InvitationWorkerFactory.class).to(InvitationWorkerFactoryImpl.class);
bind(InvitationWorkerFactory.class).to(
InvitationWorkerFactoryImpl.class);
}
}

View File

@@ -71,8 +71,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test
public void testSimpleCalls() throws DbException {
final Map<String, String> transports1 =
Collections.singletonMap("foo", "bar baz");
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
@@ -98,10 +96,6 @@ public abstract class DatabaseComponentTest extends TestCase {
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)
oneOf(database).addSubscription(txn, groupId);
// getSubscriptions()
@@ -122,7 +116,6 @@ public abstract class DatabaseComponentTest extends TestCase {
assertEquals(contactId, db.addContact(transports));
assertEquals(contacts, db.getContacts());
assertEquals(transports, db.getTransports(contactId));
db.setTransports(contactId, transports1);
db.subscribe(groupId);
assertEquals(subs, db.getSubscriptions());
db.unsubscribe(groupId);
@@ -509,7 +502,10 @@ public abstract class DatabaseComponentTest extends TestCase {
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final BundleReader bundleReader = context.mock(BundleReader.class);
final Header header = context.mock(Header.class);
context.checking(new Expectations() {{
oneOf(bundleReader).getHeader();
will(returnValue(header));
// Check that the contact is still in the DB
oneOf(database).startTransaction();
will(returnValue(txn));
@@ -552,11 +548,16 @@ public abstract class DatabaseComponentTest extends TestCase {
// Subscriptions
oneOf(header).getSubscriptions();
will(returnValue(subs));
oneOf(database).setSubscriptions(txn, contactId, subs);
oneOf(header).getTimestamp();
will(returnValue(timestamp));
oneOf(database).setSubscriptions(txn, contactId, subs, timestamp);
// Transports
oneOf(header).getTransports();
will(returnValue(transports));
oneOf(database).setTransports(txn, contactId, transports);
oneOf(header).getTimestamp();
will(returnValue(timestamp));
oneOf(database).setTransports(txn, contactId, transports,
timestamp);
// Batches
oneOf(bundleReader).getNextBatch();
will(returnValue(batch));

View File

@@ -195,7 +195,7 @@ public class H2DatabaseTest extends TestCase {
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId));
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setStatus(txn, contactId, messageId, Status.NEW);
db.commitTransaction(txn);
@@ -234,7 +234,7 @@ public class H2DatabaseTest extends TestCase {
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId));
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.commitTransaction(txn);
@@ -293,7 +293,7 @@ public class H2DatabaseTest extends TestCase {
// The contact subscribing should make the message sendable
txn = db.startTransaction();
db.setSubscriptions(txn, contactId, Collections.singleton(groupId));
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertTrue(it.hasNext());
assertEquals(messageId, it.next());
@@ -301,7 +301,7 @@ public class H2DatabaseTest extends TestCase {
// The contact unsubscribing should make the message unsendable
txn = db.startTransaction();
db.setSubscriptions(txn, contactId, Collections.<GroupId>emptySet());
db.setSubscriptions(txn, contactId, Collections.<GroupId>emptySet(), 2);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
@@ -317,7 +317,7 @@ public class H2DatabaseTest extends TestCase {
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId));
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -377,7 +377,7 @@ public class H2DatabaseTest extends TestCase {
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId));
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -417,7 +417,7 @@ public class H2DatabaseTest extends TestCase {
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId));
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -740,10 +740,10 @@ public class H2DatabaseTest extends TestCase {
transports = new TreeMap<String, String>();
transports.put("foo", "bar baz");
transports.put("bar", "baz quux");
db.setTransports(txn, contactId, transports);
db.setTransports(txn, contactId, transports, 1);
assertEquals(transports, db.getTransports(txn, contactId));
// Remove the transport details
db.setTransports(txn, contactId, null);
db.setTransports(txn, contactId, null, 2);
assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId));
// Set the local transport details
db.setTransports(txn, transports);
@@ -756,6 +756,85 @@ public class H2DatabaseTest extends TestCase {
db.close();
}
@Test
public void testTransportsNotUpdatedIfTimestampIsOld() throws DbException {
Database<Connection> db = open(false);
// 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 using a timestamp of 2
Map<String, String> transports1 = new TreeMap<String, String>();
transports1.put("foo", "bar baz");
transports1.put("bar", "baz quux");
db.setTransports(txn, contactId, transports1, 2);
assertEquals(transports1, db.getTransports(txn, contactId));
// Try to replace the transport details using a timestamp of 1
Map<String, String> transports2 = new TreeMap<String, String>();
transports2.put("bar", "baz");
transports2.put("quux", "fnord");
db.setTransports(txn, contactId, transports2, 1);
// The old transports should still be there
assertEquals(transports1, db.getTransports(txn, contactId));
db.commitTransaction(txn);
db.close();
}
@Test
public void testUpdateSubscriptions() throws DbException {
Database<Connection> db = open(false);
// Add a contact
Connection txn = db.startTransaction();
Map<String, String> transports = Collections.emptyMap();
assertEquals(contactId, db.addContact(txn, transports));
// Add some subscriptions
Set<GroupId> subs = new HashSet<GroupId>();
subs.add(new GroupId(TestUtils.getRandomId()));
subs.add(new GroupId(TestUtils.getRandomId()));
db.setSubscriptions(txn, contactId, subs, 1);
assertEquals(subs, db.getSubscriptions(txn, contactId));
// Update the subscriptions
Set<GroupId> subs1 = new HashSet<GroupId>();
subs1.add(new GroupId(TestUtils.getRandomId()));
subs1.add(new GroupId(TestUtils.getRandomId()));
db.setSubscriptions(txn, contactId, subs1, 2);
assertEquals(subs1, db.getSubscriptions(txn, contactId));
db.commitTransaction(txn);
db.close();
}
@Test
public void testSubscriptionsNotUpdatedIfTimestampIsOld()
throws DbException {
Database<Connection> db = open(false);
// Add a contact
Connection txn = db.startTransaction();
Map<String, String> transports = Collections.emptyMap();
assertEquals(contactId, db.addContact(txn, transports));
// Add some subscriptions
Set<GroupId> subs = new HashSet<GroupId>();
subs.add(new GroupId(TestUtils.getRandomId()));
subs.add(new GroupId(TestUtils.getRandomId()));
db.setSubscriptions(txn, contactId, subs, 2);
assertEquals(subs, db.getSubscriptions(txn, contactId));
// Try to update the subscriptions using a timestamp of 1
Set<GroupId> subs1 = new HashSet<GroupId>();
subs1.add(new GroupId(TestUtils.getRandomId()));
subs1.add(new GroupId(TestUtils.getRandomId()));
db.setSubscriptions(txn, contactId, subs1, 1);
// The old subscriptions should still be there
assertEquals(subs, db.getSubscriptions(txn, contactId));
db.commitTransaction(txn);
db.close();
}
private Database<Connection> open(boolean resume) throws DbException {
final char[] passwordArray = passwordString.toCharArray();
Mockery context = new Mockery();