From 7fb589075d6d740f7aecf5328eba96607e58ab26 Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Wed, 6 Jul 2011 19:07:10 +0100 Subject: [PATCH] Added support for local transport details. Each bundle contains the sender's latest transport details. --- api/net/sf/briar/api/protocol/Bundle.java | 8 ++ components/net/sf/briar/db/Database.java | 27 +++--- components/net/sf/briar/db/JdbcDatabase.java | 88 ++++++++++++++++--- .../db/ReadWriteLockDatabaseComponent.java | 50 ++++++++++- .../db/SynchronizedDatabaseComponent.java | 36 ++++++++ .../sf/briar/db/DatabaseComponentTest.java | 10 +++ test/net/sf/briar/db/H2DatabaseTest.java | 6 ++ 7 files changed, 200 insertions(+), 25 deletions(-) diff --git a/api/net/sf/briar/api/protocol/Bundle.java b/api/net/sf/briar/api/protocol/Bundle.java index e027825fba..8dc8c806ba 100644 --- a/api/net/sf/briar/api/protocol/Bundle.java +++ b/api/net/sf/briar/api/protocol/Bundle.java @@ -1,5 +1,7 @@ package net.sf.briar.api.protocol; +import java.util.Map; + /** A bundle of acknowledgements, subscriptions, and batches of messages. */ public interface Bundle { @@ -29,6 +31,12 @@ public interface Bundle { /** Adds a subscription to the bundle. Cannot be called after seal(). */ void addSubscription(GroupId g); + /** Returns the transport details contained in the bundle. */ + Map<String, String> getTransports(); + + /** Adds a transport detail to the bundle. Cannot be called after seal(). */ + void addTransport(String key, String value); + /** Returns the batches of messages contained in the bundle. */ Iterable<Batch> getBatches(); diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index ca2cdb0b7b..ff50293bdb 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -119,18 +119,8 @@ interface Database<T> { */ void addSubscription(T txn, GroupId g) throws DbException; - /** - * Records a contact's subscription to a group. - * <p> - * Locking: contacts read, subscriptions write. - */ + // FIXME: Replace these two methods with a setSubscriptions() method void addSubscription(T txn, ContactId c, GroupId g) throws DbException; - - /** - * Removes all recorded subscriptions for the given contact. - * <p> - * Locking: contacts read, subscriptions write. - */ void clearSubscriptions(T txn, ContactId c) throws DbException; /** @@ -245,6 +235,13 @@ interface Database<T> { */ Set<GroupId> getSubscriptions(T txn) throws DbException; + /** + * Returns the local transport details. + * <p> + * Locking: transports read. + */ + Map<String, String> getTransports(T txn) throws DbException; + /** * Returns the transport details for the given contact. * <p> @@ -323,6 +320,14 @@ interface Database<T> { */ void setStatus(T txn, ContactId c, MessageId m, Status s) throws DbException; + /** + * Sets the local transport details, replacing any existing transport + * details. + * <p> + * Locking: transports write. + */ + void setTransports(T txn, Map<String, String> transports) throws DbException; + /** * Sets the transport details for the given contact, replacing any existing * transport details. diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index dc6b73259f..b81340fb4a 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -150,15 +150,21 @@ abstract class JdbcDatabase implements Database<Connection> { private static final String INDEX_STATUSES_BY_CONTACT = "CREATE INDEX statusesByContact ON statuses (contactId)"; - private static final String CREATE_TRANSPORTS = - "CREATE TABLE transports" + private static final String CREATE_CONTACT_TRANSPORTS = + "CREATE TABLE contactTransports" + " (contactId INT NOT NULL," - + " detailKey VARCHAR NOT NULL," - + " detailValue VARCHAR NOT NULL," - + " PRIMARY KEY (contactId, detailKey)," + + " key VARCHAR NOT NULL," + + " value VARCHAR NOT NULL," + + " PRIMARY KEY (contactId, key)," + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; + private static final String CREATE_LOCAL_TRANSPORTS = + "CREATE TABLE localTransports" + + " (key VARCHAR NOT NULL," + + " value VARCHAR NOT NULL," + + " PRIMARY KEY (key))"; + private static final Logger LOG = Logger.getLogger(JdbcDatabase.class.getName()); @@ -249,8 +255,11 @@ abstract class JdbcDatabase implements Database<Connection> { s.executeUpdate(INDEX_STATUSES_BY_MESSAGE); s.executeUpdate(INDEX_STATUSES_BY_CONTACT); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Creating transports table"); - s.executeUpdate(insertHashType(CREATE_TRANSPORTS)); + LOG.fine("Creating contact transports table"); + s.executeUpdate(insertHashType(CREATE_CONTACT_TRANSPORTS)); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Creating local transports table"); + s.executeUpdate(insertHashType(CREATE_LOCAL_TRANSPORTS)); s.close(); } catch(SQLException e) { tryToClose(s); @@ -418,8 +427,8 @@ abstract class JdbcDatabase implements Database<Connection> { ps.close(); // Store the contact's transport details if(transports != null) { - sql = "INSERT INTO transports" - + " (contactId, detailKey, detailValue)" + sql = "INSERT INTO contactTransports" + + " (contactId, key, value)" + " VALUES (?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); @@ -1102,12 +1111,33 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public Map<String, String> getTransports(Connection txn) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT key, value FROM localTransports"; + ps = txn.prepareStatement(sql); + 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 Map<String, String> getTransports(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT detailKey, detailValue FROM transports" + String sql = "SELECT key, value FROM contactTransports" + " WHERE contactId = ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); @@ -1373,20 +1403,52 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public void setTransports(Connection txn, Map<String, String> transports) + throws DbException { + PreparedStatement ps = null; + try { + // Delete any existing transports + String sql = "DELETE FROM localTransports"; + ps = txn.prepareStatement(sql); + ps.executeUpdate(); + ps.close(); + // Store the new transports + if(transports != null) { + sql = "INSERT INTO localTransports (key, value)" + + " VALUES (?, ?)"; + ps = txn.prepareStatement(sql); + for(Entry<String, String> e : transports.entrySet()) { + ps.setString(1, e.getKey()); + ps.setString(2, 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); + } + } + 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 = ?"; + String sql = "DELETE FROM contactTransports 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)" + sql = "INSERT INTO contactTransports (contactId, key, value)" + " VALUES (?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java index 93c24ab6d4..d815271f37 100644 --- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java +++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java @@ -3,6 +3,7 @@ package net.sf.briar.db; import java.util.HashSet; import java.util.Iterator; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; @@ -237,6 +238,33 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } finally { contactLock.readLock().unlock(); } + // Add transport details + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + transportLock.readLock().lock(); + try { + Txn txn = db.startTransaction(); + try { + int numTransports = 0; + Map<String, String> transports = db.getTransports(txn); + for(Entry<String, String> e : transports.entrySet()) { + b.addTransport(e.getKey(), e.getValue()); + numTransports++; + } + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Added " + numTransports + " transports"); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } // Add as many messages as possible to the bundle long capacity = b.getCapacity(); while(true) { @@ -448,7 +476,27 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { subscriptionLock.writeLock().unlock(); } } finally { - contactLock.readLock().lock(); + contactLock.readLock().unlock(); + } + // Update the contact's transport details + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + transportLock.writeLock().lock(); + try { + Txn txn = db.startTransaction(); + try { + db.setTransports(txn, c, b.getTransports()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); } // Store the messages int batches = 0; diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java index 55900ec225..c5804db21a 100644 --- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java +++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java @@ -4,6 +4,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; @@ -178,6 +179,27 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } } + // Add transport details + synchronized(contactLock) { + if(!containsContact(c)) throw new NoSuchContactException(); + synchronized(transportLock) { + Txn txn = db.startTransaction(); + try { + int numTransports = 0; + Map<String, String> transports = db.getTransports(txn); + for(Entry<String, String> e : transports.entrySet()) { + b.addTransport(e.getKey(), e.getValue()); + numTransports++; + } + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Added " + numTransports + " transports"); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } + } // Add as many messages as possible to the bundle long capacity = b.getCapacity(); while(true) { @@ -338,6 +360,20 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } } + // Update the contact's transport details + synchronized(contactLock) { + if(!containsContact(c)) throw new NoSuchContactException(); + synchronized(transportLock) { + Txn txn = db.startTransaction(); + try { + db.setTransports(txn, c, b.getTransports()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } + } // Store the messages int batches = 0; for(Batch batch : b.getBatches()) { diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index ba6c808e13..120610c15b 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -511,6 +511,10 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).getSubscriptions(txn); will(returnValue(Collections.singleton(groupId))); oneOf(bundle).addSubscription(groupId); + // Add transports to the bundle + oneOf(database).getTransports(txn); + will(returnValue(Collections.singletonMap("foo", "bar"))); + oneOf(bundle).addTransport("foo", "bar"); // Prepare to add batches to the bundle oneOf(bundle).getCapacity(); will(returnValue((long) ONE_MEGABYTE)); @@ -575,6 +579,8 @@ public abstract class DatabaseComponentTest extends TestCase { @Test public void testReceivedBundle() throws DbException { + final Map<String, String> transports = + Collections.singletonMap("foo", "bar"); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); @@ -598,6 +604,10 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(bundle).getSubscriptions(); will(returnValue(Collections.singleton(groupId))); oneOf(database).addSubscription(txn, contactId, groupId); + // Transports + oneOf(bundle).getTransports(); + will(returnValue(transports)); + oneOf(database).setTransports(txn, contactId, transports); // Batches oneOf(bundle).getBatches(); will(returnValue(Collections.singleton(batch))); diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java index 7e499e1ada..f89dbc974f 100644 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -776,6 +776,12 @@ public class H2DatabaseTest extends TestCase { // Remove the transport details db.setTransports(txn, contactId, null); assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId)); + // Set the local transport details + db.setTransports(txn, transports); + assertEquals(transports, db.getTransports(txn)); + // Remove the local transport details + db.setTransports(txn, null); + assertEquals(Collections.emptyMap(), db.getTransports(txn)); db.commitTransaction(txn); db.close(); -- GitLab