Small improvements to DB interface.

This commit is contained in:
akwizgran
2016-01-19 10:55:46 +00:00
parent 5355951466
commit 77054cbae7
9 changed files with 99 additions and 79 deletions

View File

@@ -46,7 +46,7 @@ public interface DatabaseComponent {
ContactId addContact(Author remote, AuthorId local) throws DbException; ContactId addContact(Author remote, AuthorId local) throws DbException;
/** Adds a group to the given contact's subscriptions. */ /** Adds a group to the given contact's subscriptions. */
void addGroup(ContactId c, Group g) throws DbException; void addContactGroup(ContactId c, Group g) throws DbException;
/** /**
* Subscribes to a group, or returns false if the user already has the * Subscribes to a group, or returns false if the user already has the
@@ -140,8 +140,11 @@ public interface DatabaseComponent {
Collection<TransportUpdate> generateTransportUpdates(ContactId c, Collection<TransportUpdate> generateTransportUpdates(ContactId c,
int maxLatency) throws DbException; int maxLatency) throws DbException;
/** Returns all groups to which the user could subscribe. */ /**
Collection<Group> getAvailableGroups() throws DbException; * Returns all groups belonging to the given client to which the user could
* subscribe.
*/
Collection<Group> getAvailableGroups(ClientId c) throws DbException;
/** Returns the contact with the given ID. */ /** Returns the contact with the given ID. */
Contact getContact(ContactId c) throws DbException; Contact getContact(ContactId c) throws DbException;
@@ -152,8 +155,11 @@ public interface DatabaseComponent {
/** Returns the group with the given ID, if the user subscribes to it. */ /** Returns the group with the given ID, if the user subscribes to it. */
Group getGroup(GroupId g) throws DbException; Group getGroup(GroupId g) throws DbException;
/** Returns all groups to which the user subscribes. */ /**
Collection<Group> getGroups() throws DbException; * Returns all groups belonging to the given client to which the user
* subscribes.
*/
Collection<Group> getGroups(ClientId c) throws DbException;
/** Returns the local pseudonym with the given ID. */ /** Returns the local pseudonym with the given ID. */
LocalAuthor getLocalAuthor(AuthorId a) throws DbException; LocalAuthor getLocalAuthor(AuthorId a) throws DbException;

View File

@@ -0,0 +1,8 @@
package org.briarproject.api.db;
/**
* Thrown when a duplicate message is added to the database. This exception may
* occur due to concurrent updates and does not indicate a database error.
*/
public class MessageExistsException extends DbException {
}

View File

@@ -92,7 +92,7 @@ interface Database<T> {
* <p> * <p>
* Locking: write. * Locking: write.
*/ */
void addGroup(T txn, ContactId c, Group g) throws DbException; void addContactGroup(T txn, ContactId c, Group g) throws DbException;
/** /**
* Subscribes to a group, or returns false if the user already has the * Subscribes to a group, or returns false if the user already has the
@@ -225,11 +225,12 @@ interface Database<T> {
int countOfferedMessages(T txn, ContactId c) throws DbException; int countOfferedMessages(T txn, ContactId c) throws DbException;
/** /**
* Returns all groups to which the user could subscribe. * Returns all groups belonging to the given client to which the user could
* subscribe.
* <p> * <p>
* Locking: read. * Locking: read.
*/ */
Collection<Group> getAvailableGroups(T txn) throws DbException; Collection<Group> getAvailableGroups(T txn, ClientId c) throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given ID.
@@ -274,11 +275,12 @@ interface Database<T> {
Group getGroup(T txn, GroupId g) throws DbException; Group getGroup(T txn, GroupId g) throws DbException;
/** /**
* Returns all groups to which the user subscribes. * Returns all groups belonging to the given client to which the user
* subscribes.
* <p> * <p>
* Locking: read. * Locking: read.
*/ */
Collection<Group> getGroups(T txn) throws DbException; Collection<Group> getGroups(T txn, ClientId c) throws DbException;
/** /**
* Returns the local pseudonym with the given ID. * Returns the local pseudonym with the given ID.

View File

@@ -9,6 +9,7 @@ import org.briarproject.api.db.ContactExistsException;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.LocalAuthorExistsException; import org.briarproject.api.db.LocalAuthorExistsException;
import org.briarproject.api.db.MessageExistsException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.db.NoSuchLocalAuthorException; import org.briarproject.api.db.NoSuchLocalAuthorException;
@@ -168,12 +169,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return c; return c;
} }
public void addGroup(ContactId c, Group g) throws DbException { public void addContactGroup(ContactId c, Group g) throws DbException {
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
T txn = db.startTransaction(); T txn = db.startTransaction();
try { try {
db.addGroup(txn, c, g); db.addContactGroup(txn, c, g);
db.commitTransaction(txn); db.commitTransaction(txn);
} catch (DbException e) { } catch (DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -225,17 +226,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
public void addLocalMessage(Message m, ClientId c, Metadata meta) public void addLocalMessage(Message m, ClientId c, Metadata meta)
throws DbException { throws DbException {
boolean duplicate, subscribed;
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
T txn = db.startTransaction(); T txn = db.startTransaction();
try { try {
duplicate = db.containsMessage(txn, m.getId()); if (db.containsMessage(txn, m.getId()))
subscribed = db.containsGroup(txn, m.getGroupId()); throw new MessageExistsException();
if (!duplicate && subscribed) { if (!db.containsGroup(txn, m.getGroupId()))
addMessage(txn, m, null); throw new NoSuchSubscriptionException();
db.mergeMessageMetadata(txn, m.getId(), meta); addMessage(txn, m, null);
} db.mergeMessageMetadata(txn, m.getId(), meta);
db.commitTransaction(txn); db.commitTransaction(txn);
} catch (DbException e) { } catch (DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -244,10 +244,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
} }
if (!duplicate && subscribed) { eventBus.broadcast(new MessageAddedEvent(m, null));
eventBus.broadcast(new MessageAddedEvent(m, null)); eventBus.broadcast(new MessageValidatedEvent(m, c, true, true));
eventBus.broadcast(new MessageValidatedEvent(m, c, true, true));
}
} }
/** /**
@@ -524,12 +522,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
} }
public Collection<Group> getAvailableGroups() throws DbException { public Collection<Group> getAvailableGroups(ClientId c) throws DbException {
lock.readLock().lock(); lock.readLock().lock();
try { try {
T txn = db.startTransaction(); T txn = db.startTransaction();
try { try {
Collection<Group> groups = db.getAvailableGroups(txn); Collection<Group> groups = db.getAvailableGroups(txn, c);
db.commitTransaction(txn); db.commitTransaction(txn);
return groups; return groups;
} catch (DbException e) { } catch (DbException e) {
@@ -596,12 +594,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
} }
public Collection<Group> getGroups() throws DbException { public Collection<Group> getGroups(ClientId c) throws DbException {
lock.readLock().lock(); lock.readLock().lock();
try { try {
T txn = db.startTransaction(); T txn = db.startTransaction();
try { try {
Collection<Group> groups = db.getGroups(txn); Collection<Group> groups = db.getGroups(txn, c);
db.commitTransaction(txn); db.commitTransaction(txn);
return groups; return groups;
} catch (DbException e) { } catch (DbException e) {

View File

@@ -647,7 +647,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public void addGroup(Connection txn, ContactId c, Group g) public void addContactGroup(Connection txn, ContactId c, Group g)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -1155,28 +1155,28 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public Collection<Group> getAvailableGroups(Connection txn) public Collection<Group> getAvailableGroups(Connection txn, ClientId c)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT DISTINCT" String sql = "SELECT DISTINCT cg.groupId, cg.descriptor"
+ " cg.groupId, cg.clientId, cg.descriptor"
+ " FROM contactGroups AS cg" + " FROM contactGroups AS cg"
+ " LEFT OUTER JOIN groups AS g" + " LEFT OUTER JOIN groups AS g"
+ " ON cg.groupId = g.groupId" + " ON cg.groupId = g.groupId"
+ " WHERE g.groupId IS NULL" + " WHERE cg.clientId = ?"
+ " AND g.groupId IS NULL"
+ " GROUP BY cg.groupId"; + " GROUP BY cg.groupId";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, c.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<Group> groups = new ArrayList<Group>(); List<Group> groups = new ArrayList<Group>();
Set<GroupId> ids = new HashSet<GroupId>(); Set<GroupId> ids = new HashSet<GroupId>();
while (rs.next()) { while (rs.next()) {
GroupId id = new GroupId(rs.getBytes(1)); GroupId id = new GroupId(rs.getBytes(1));
if (!ids.add(id)) throw new DbStateException(); if (!ids.add(id)) throw new DbStateException();
ClientId clientId = new ClientId(rs.getBytes(2)); byte[] descriptor = rs.getBytes(2);
byte[] descriptor = rs.getBytes(3); groups.add(new Group(id, c, descriptor));
groups.add(new Group(id, clientId, descriptor));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1308,19 +1308,21 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public Collection<Group> getGroups(Connection txn) throws DbException { public Collection<Group> getGroups(Connection txn, ClientId c)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT groupId, clientId, descriptor FROM groups"; String sql = "SELECT groupId, descriptor FROM groups"
+ " WHERE clientId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, c.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<Group> groups = new ArrayList<Group>(); List<Group> groups = new ArrayList<Group>();
while (rs.next()) { while (rs.next()) {
GroupId id = new GroupId(rs.getBytes(1)); GroupId id = new GroupId(rs.getBytes(1));
ClientId clientId = new ClientId(rs.getBytes(2)); byte[] descriptor = rs.getBytes(2);
byte[] descriptor = rs.getBytes(3); groups.add(new Group(id, c, descriptor));
groups.add(new Group(id, clientId, descriptor));
} }
rs.close(); rs.close();
ps.close(); ps.close();

View File

@@ -143,17 +143,13 @@ class ForumManagerImpl implements ForumManager {
@Override @Override
public Collection<Forum> getAvailableForums() throws DbException { public Collection<Forum> getAvailableForums() throws DbException {
// TODO: Get groups by client ID Collection<Group> groups = db.getAvailableGroups(CLIENT_ID);
Collection<Group> groups = db.getAvailableGroups();
List<Forum> forums = new ArrayList<Forum>(groups.size()); List<Forum> forums = new ArrayList<Forum>(groups.size());
for (Group g : groups) { for (Group g : groups) {
if (g.getClientId().equals(CLIENT_ID)) { try {
try { forums.add(parseForum(g));
forums.add(parseForum(g)); } catch (FormatException e) {
} catch (FormatException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
} }
return Collections.unmodifiableList(forums); return Collections.unmodifiableList(forums);
@@ -193,17 +189,13 @@ class ForumManagerImpl implements ForumManager {
@Override @Override
public Collection<Forum> getForums() throws DbException { public Collection<Forum> getForums() throws DbException {
// TODO: Get groups by client ID Collection<Group> groups = db.getGroups(CLIENT_ID);
Collection<Group> groups = db.getGroups();
List<Forum> forums = new ArrayList<Forum>(groups.size()); List<Forum> forums = new ArrayList<Forum>(groups.size());
for (Group g : groups) { for (Group g : groups) {
if (g.getClientId().equals(CLIENT_ID)) { try {
try { forums.add(parseForum(g));
forums.add(parseForum(g)); } catch (FormatException e) {
} catch (FormatException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
} }
return Collections.unmodifiableList(forums); return Collections.unmodifiableList(forums);

View File

@@ -82,7 +82,7 @@ class MessagingManagerImpl implements MessagingManager {
Group conversation = createConversationGroup(db.getContact(c)); Group conversation = createConversationGroup(db.getContact(c));
// Subscribe to the group and share it with the contact // Subscribe to the group and share it with the contact
db.addGroup(conversation); db.addGroup(conversation);
db.addGroup(c, conversation); db.addContactGroup(c, conversation);
db.setVisibility(conversation.getId(), Collections.singletonList(c)); db.setVisibility(conversation.getId(), Collections.singletonList(c));
} }
@@ -141,7 +141,6 @@ class MessagingManagerImpl implements MessagingManager {
@Override @Override
public GroupId getConversationId(ContactId c) throws DbException { public GroupId getConversationId(ContactId c) throws DbException {
// TODO: Make this more efficient
return createConversationGroup(db.getContact(c)).getId(); return createConversationGroup(db.getContact(c)).getId();
} }

View File

@@ -9,6 +9,7 @@ import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.MessageExistsException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.db.NoSuchLocalAuthorException; import org.briarproject.api.db.NoSuchLocalAuthorException;
@@ -166,7 +167,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
oneOf(database).containsGroup(txn, groupId); oneOf(database).containsGroup(txn, groupId);
will(returnValue(true)); will(returnValue(true));
// getGroups() // getGroups()
oneOf(database).getGroups(txn); oneOf(database).getGroups(txn, clientId);
will(returnValue(Collections.singletonList(group))); will(returnValue(Collections.singletonList(group)));
// removeGroup() // removeGroup()
oneOf(database).containsGroup(txn, groupId); oneOf(database).containsGroup(txn, groupId);
@@ -205,7 +206,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
db.getRemoteProperties(transportId)); db.getRemoteProperties(transportId));
db.addGroup(group); // First time - listeners called db.addGroup(group); // First time - listeners called
db.addGroup(group); // Second time - not called db.addGroup(group); // Second time - not called
assertEquals(Collections.singletonList(group), db.getGroups()); assertEquals(Collections.singletonList(group), db.getGroups(clientId));
db.removeGroup(group); db.removeGroup(group);
db.removeContact(contactId); db.removeContact(contactId);
db.removeLocalAuthor(localAuthorId); db.removeLocalAuthor(localAuthorId);
@@ -226,14 +227,17 @@ public class DatabaseComponentImplTest extends BriarTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).containsGroup(txn, groupId); oneOf(database).abortTransaction(txn);
will(returnValue(true));
oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.addLocalMessage(message, clientId, metadata); try {
db.addLocalMessage(message, clientId, metadata);
fail();
} catch (MessageExistsException expected) {
// Expected
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -253,12 +257,17 @@ public class DatabaseComponentImplTest extends BriarTestCase {
will(returnValue(false)); will(returnValue(false));
oneOf(database).containsGroup(txn, groupId); oneOf(database).containsGroup(txn, groupId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).commitTransaction(txn); oneOf(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.addLocalMessage(message, clientId, metadata); try {
db.addLocalMessage(message, clientId, metadata);
fail();
} catch (NoSuchSubscriptionException expected) {
// Expected
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }

View File

@@ -58,6 +58,7 @@ public class H2DatabaseTest extends BriarTestCase {
private final File testDir = TestUtils.getTestDirectory(); private final File testDir = TestUtils.getTestDirectory();
private final Random random = new Random(); private final Random random = new Random();
private final ClientId clientId;
private final GroupId groupId; private final GroupId groupId;
private final Group group; private final Group group;
private final Author author; private final Author author;
@@ -72,8 +73,8 @@ public class H2DatabaseTest extends BriarTestCase {
private final ContactId contactId; private final ContactId contactId;
public H2DatabaseTest() throws Exception { public H2DatabaseTest() throws Exception {
clientId = new ClientId(TestUtils.getRandomId());
groupId = new GroupId(TestUtils.getRandomId()); groupId = new GroupId(TestUtils.getRandomId());
ClientId clientId = new ClientId(TestUtils.getRandomId());
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH]; byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
group = new Group(groupId, clientId, descriptor); group = new Group(groupId, clientId, descriptor);
AuthorId authorId = new AuthorId(TestUtils.getRandomId()); AuthorId authorId = new AuthorId(TestUtils.getRandomId());
@@ -901,31 +902,34 @@ public class H2DatabaseTest extends BriarTestCase {
db.setGroups(txn, contactId1, Collections.singletonList(group), 1); db.setGroups(txn, contactId1, Collections.singletonList(group), 1);
// The group should be available // The group should be available
assertEquals(Collections.emptyList(), db.getGroups(txn)); assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
assertEquals(Collections.singletonList(group), assertEquals(Collections.singletonList(group),
db.getAvailableGroups(txn)); db.getAvailableGroups(txn, clientId));
// Subscribe to the group - it should no longer be available // Subscribe to the group - it should no longer be available
db.addGroup(txn, group); db.addGroup(txn, group);
assertEquals(Collections.singletonList(group), db.getGroups(txn)); assertEquals(Collections.singletonList(group),
assertEquals(Collections.emptyList(), db.getAvailableGroups(txn)); db.getGroups(txn, clientId));
assertEquals(Collections.emptyList(),
db.getAvailableGroups(txn, clientId));
// Unsubscribe from the group - it should be available again // Unsubscribe from the group - it should be available again
db.removeGroup(txn, groupId); db.removeGroup(txn, groupId);
assertEquals(Collections.emptyList(), db.getGroups(txn)); assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
assertEquals(Collections.singletonList(group), assertEquals(Collections.singletonList(group),
db.getAvailableGroups(txn)); db.getAvailableGroups(txn, clientId));
// The first contact unsubscribes - it should still be available // The first contact unsubscribes - it should still be available
db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2); db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
assertEquals(Collections.emptyList(), db.getGroups(txn)); assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
assertEquals(Collections.singletonList(group), assertEquals(Collections.singletonList(group),
db.getAvailableGroups(txn)); db.getAvailableGroups(txn, clientId));
// The second contact unsubscribes - it should no longer be available // The second contact unsubscribes - it should no longer be available
db.setGroups(txn, contactId1, Collections.<Group>emptyList(), 2); db.setGroups(txn, contactId1, Collections.<Group>emptyList(), 2);
assertEquals(Collections.emptyList(), db.getGroups(txn)); assertEquals(Collections.emptyList(), db.getGroups(txn, clientId));
assertEquals(Collections.emptyList(), db.getAvailableGroups(txn)); assertEquals(Collections.emptyList(),
db.getAvailableGroups(txn, clientId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();