diff --git a/briar-api/src/org/briarproject/api/contact/ContactManager.java b/briar-api/src/org/briarproject/api/contact/ContactManager.java index 3b311d66d58146de84e2d7dc9932d7047483c6aa..ffc493477dca7244b785414726b819cb50c3c2e7 100644 --- a/briar-api/src/org/briarproject/api/contact/ContactManager.java +++ b/briar-api/src/org/briarproject/api/contact/ContactManager.java @@ -1,6 +1,7 @@ package org.briarproject.api.contact; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.AuthorId; @@ -30,10 +31,10 @@ public interface ContactManager { void removeContact(ContactId c) throws DbException; interface AddContactHook { - void addingContact(Contact c); + void addingContact(Transaction txn, Contact c) throws DbException; } interface RemoveContactHook { - void removingContact(Contact c); + void removingContact(Transaction txn, Contact c) throws DbException; } } diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java index 435d1025b159e561838478506c42fe82240a5a29..d38dc08d686a20c2428404d5593302e675be95dc 100644 --- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java +++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java @@ -26,72 +26,85 @@ import java.util.Map; /** * Encapsulates the database implementation and exposes high-level operations * to other components. - * <p> + * <p/> * This interface's methods are blocking, but they do not call out into other * components except to broadcast {@link org.briarproject.api.event.Event * Events}, so they can safely be called while holding locks. */ public interface DatabaseComponent { - /** Opens the database and returns true if the database already existed. */ + /** + * Opens the database and returns true if the database already existed. + */ boolean open() throws DbException; - /** 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, IOException; - /** Starts a new transaction and returns an object representing it. */ - Transaction startTransaction() throws DbException; - /** - * Aborts the given transaction - no changes made during the transaction - * will be applied to the database. + * Starts a new transaction and returns an object representing it. */ - void abortTransaction(Transaction txn); + Transaction startTransaction() throws DbException; /** - * Commits the given transaction - all changes made during the transaction - * will be applied to the database. + * Ends a transaction. If the transaction's + * {@link Transaction#setComplete() commit} flag is set, the + * transaction is committed, otherwise it is aborted. */ - void commitTransaction(Transaction txn) throws DbException; + void endTransaction(Transaction txn) throws DbException; /** * Stores a contact associated with the given local and remote pseudonyms, * and returns an ID for the contact. */ - ContactId addContact(Author remote, AuthorId local) throws DbException; + ContactId addContact(Transaction txn, Author remote, AuthorId local) + throws DbException; - /** Stores a group. */ - void addGroup(Group g) throws DbException; + /** + * Stores a group. + */ + void addGroup(Transaction txn, Group g) throws DbException; - /** Stores a local pseudonym. */ - void addLocalAuthor(LocalAuthor a) throws DbException; + /** + * Stores a local pseudonym. + */ + void addLocalAuthor(Transaction txn, LocalAuthor a) throws DbException; - /** Stores a local message. */ - void addLocalMessage(Message m, ClientId c, Metadata meta, boolean shared) - throws DbException; + /** + * Stores a local message. + */ + void addLocalMessage(Transaction txn, Message m, ClientId c, Metadata meta, + boolean shared) throws DbException; - /** Stores a transport. */ - void addTransport(TransportId t, int maxLatency) throws DbException; + /** + * Stores a transport. + */ + void addTransport(Transaction txn, TransportId t, int maxLatency) + throws DbException; /** * Stores transport keys for a newly added contact. */ - void addTransportKeys(ContactId c, TransportKeys k) throws DbException; + void addTransportKeys(Transaction txn, ContactId c, TransportKeys k) + throws DbException; /** * Deletes the message with the given ID. The message ID and any other * associated data are not deleted. */ - void deleteMessage(MessageId m) throws DbException; + void deleteMessage(Transaction txn, MessageId m) throws DbException; /** Deletes any metadata associated with the given message. */ - void deleteMessageMetadata(MessageId m) throws DbException; + void deleteMessageMetadata(Transaction txn, MessageId m) throws DbException; /** * Returns an acknowledgement for the given contact, or null if there are * no messages to acknowledge. */ - Ack generateAck(ContactId c, int maxMessages) throws DbException; + Ack generateAck(Transaction txn, ContactId c, int maxMessages) + throws DbException; /** * Returns a batch of raw messages for the given contact, with a total @@ -99,22 +112,23 @@ public interface DatabaseComponent { * transport with the given maximum latency. Returns null if there are no * sendable messages that fit in the given length. */ - Collection<byte[]> generateBatch(ContactId c, int maxLength, - int maxLatency) throws DbException; + Collection<byte[]> generateBatch(Transaction txn, ContactId c, + int maxLength, int maxLatency) throws DbException; /** * Returns an offer for the given contact for transmission over a * transport with the given maximum latency, or null if there are no * messages to offer. */ - Offer generateOffer(ContactId c, int maxMessages, int maxLatency) - throws DbException; + Offer generateOffer(Transaction txn, ContactId c, int maxMessages, + int maxLatency) throws DbException; /** * Returns a request for the given contact, or null if there are no * messages to request. */ - Request generateRequest(ContactId c, int maxMessages) throws DbException; + Request generateRequest(Transaction txn, ContactId c, int maxMessages) + throws DbException; /** * Returns a batch of raw messages for the given contact, with a total @@ -123,158 +137,226 @@ public interface DatabaseComponent { * requested by the contact are returned. Returns null if there are no * sendable messages that fit in the given length. */ - Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength, - int maxLatency) throws DbException; + Collection<byte[]> generateRequestedBatch(Transaction txn, ContactId c, + int maxLength, int maxLatency) throws DbException; - /** Returns the contact with the given ID. */ - Contact getContact(ContactId c) throws DbException; + /** + * Returns the contact with the given ID. + */ + Contact getContact(Transaction txn, ContactId c) throws DbException; - /** Returns all contacts. */ - Collection<Contact> getContacts() throws DbException; + /** + * Returns all contacts. + */ + Collection<Contact> getContacts(Transaction txn) throws DbException; - /** Returns all contacts associated with the given local pseudonym. */ - Collection<ContactId> getContacts(AuthorId a) throws DbException; + /** + * Returns all contacts associated with the given local pseudonym. + */ + Collection<ContactId> getContacts(Transaction txn, AuthorId a) + throws DbException; - /** Returns the unique ID for this device. */ - DeviceId getDeviceId() throws DbException; + /** + * Returns the unique ID for this device. + */ + DeviceId getDeviceId(Transaction txn) throws DbException; - /** Returns the group with the given ID. */ - Group getGroup(GroupId g) throws DbException; + /** + * Returns the group with the given ID. + */ + Group getGroup(Transaction txn, GroupId g) throws DbException; - /** Returns the metadata for the given group. */ - Metadata getGroupMetadata(GroupId g) throws DbException; + /** + * Returns the metadata for the given group. + */ + Metadata getGroupMetadata(Transaction txn, GroupId g) throws DbException; - /** Returns all groups belonging to the given client. */ - Collection<Group> getGroups(ClientId c) throws DbException; + /** + * Returns all groups belonging to the given client. + */ + Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException; - /** Returns the local pseudonym with the given ID. */ - LocalAuthor getLocalAuthor(AuthorId a) throws DbException; + /** + * Returns the local pseudonym with the given ID. + */ + LocalAuthor getLocalAuthor(Transaction txn, AuthorId a) throws DbException; - /** Returns all local pseudonyms. */ - Collection<LocalAuthor> getLocalAuthors() throws DbException; + /** + * Returns all local pseudonyms. + */ + Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException; /** * Returns the IDs of any messages that need to be validated by the given * client. */ - Collection<MessageId> getMessagesToValidate(ClientId c) throws DbException; + Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c) + throws DbException; - /** Returns the message with the given ID, in serialised form. */ - byte[] getRawMessage(MessageId m) throws DbException; + /** + * Returns the message with the given ID, in serialised form. + */ + byte[] getRawMessage(Transaction txn, MessageId m) throws DbException; - /** Returns the metadata for all messages in the given group. */ - Map<MessageId, Metadata> getMessageMetadata(GroupId g) + /** + * Returns the metadata for all messages in the given group. + */ + Map<MessageId, Metadata> getMessageMetadata(Transaction txn, GroupId g) throws DbException; - /** Returns the metadata for the given message. */ - Metadata getMessageMetadata(MessageId m) throws DbException; + /** + * Returns the metadata for the given message. + */ + Metadata getMessageMetadata(Transaction txn, MessageId m) + throws DbException; /** * Returns the status of all messages in the given group with respect to * the given contact. */ - Collection<MessageStatus> getMessageStatus(ContactId c, GroupId g) - throws DbException; + Collection<MessageStatus> getMessageStatus(Transaction txn, ContactId c, + GroupId g) throws DbException; /** * Returns the status of the given message with respect to the given * contact. */ - MessageStatus getMessageStatus(ContactId c, MessageId m) + MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m) throws DbException; - /** Returns all settings in the given namespace. */ - Settings getSettings(String namespace) throws DbException; + /** + * Returns all settings in the given namespace. + */ + Settings getSettings(Transaction txn, String namespace) throws DbException; - /** Returns all transport keys for the given transport. */ - Map<ContactId, TransportKeys> getTransportKeys(TransportId t) - throws DbException; + /** + * Returns all transport keys for the given transport. + */ + Map<ContactId, TransportKeys> getTransportKeys(Transaction txn, + TransportId t) throws DbException; - /** Returns the maximum latencies in milliseconds of all transports. */ - Map<TransportId, Integer> getTransportLatencies() throws DbException; + /** + * Returns the maximum latencies in milliseconds of all transports. + */ + Map<TransportId, Integer> getTransportLatencies(Transaction txn) + throws DbException; /** * Increments the outgoing stream counter for the given contact and * transport in the given rotation period . */ - void incrementStreamCounter(ContactId c, TransportId t, long rotationPeriod) - throws DbException; + void incrementStreamCounter(Transaction txn, ContactId c, TransportId t, + long rotationPeriod) throws DbException; - /** Returns true if the given group is visible to the given contact. */ - boolean isVisibleToContact(ContactId c, GroupId g) throws DbException; + /** + * Returns true if the given group is visible to the given contact. + */ + boolean isVisibleToContact(Transaction txn, ContactId c, GroupId g) + throws DbException; /** * Merges the given metadata with the existing metadata for the given * group. */ - void mergeGroupMetadata(GroupId g, Metadata meta) throws DbException; + void mergeGroupMetadata(Transaction txn, GroupId g, Metadata meta) + throws DbException; /** * Merges the given metadata with the existing metadata for the given * message. */ - void mergeMessageMetadata(MessageId m, Metadata meta) throws DbException; + void mergeMessageMetadata(Transaction txn, MessageId m, Metadata meta) + throws DbException; /** * Merges the given settings with the existing settings in the given * namespace. */ - void mergeSettings(Settings s, String namespace) throws DbException; + void mergeSettings(Transaction txn, Settings s, String namespace) + throws DbException; - /** Processes an ack from the given contact. */ - void receiveAck(ContactId c, Ack a) throws DbException; + /** + * Processes an ack from the given contact. + */ + void receiveAck(Transaction txn, ContactId c, Ack a) throws DbException; - /** Processes a message from the given contact. */ - void receiveMessage(ContactId c, Message m) throws DbException; + /** + * Processes a message from the given contact. + */ + void receiveMessage(Transaction txn, ContactId c, Message m) + throws DbException; - /** Processes an offer from the given contact. */ - void receiveOffer(ContactId c, Offer o) throws DbException; + /** + * Processes an offer from the given contact. + */ + void receiveOffer(Transaction txn, ContactId c, Offer o) throws DbException; - /** Processes a request from the given contact. */ - void receiveRequest(ContactId c, Request r) throws DbException; + /** + * Processes a request from the given contact. + */ + void receiveRequest(Transaction txn, ContactId c, Request r) + throws DbException; - /** Removes a contact (and all associated state) from the database. */ - void removeContact(ContactId c) throws DbException; + /** + * Removes a contact (and all associated state) from the database. + */ + void removeContact(Transaction txn, ContactId c) throws DbException; - /** Removes a group (and all associated state) from the database. */ - void removeGroup(Group g) throws DbException; + /** + * Removes a group (and all associated state) from the database. + */ + void removeGroup(Transaction txn, Group g) throws DbException; /** * Removes a local pseudonym (and all associated state) from the database. */ - void removeLocalAuthor(AuthorId a) throws DbException; + void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException; - /** Removes a transport (and all associated state) from the database. */ - void removeTransport(TransportId t) throws DbException; + /** + * Removes a transport (and all associated state) from the database. + */ + void removeTransport(Transaction txn, TransportId t) throws DbException; - /** Sets the status of the given contact. */ - void setContactStatus(ContactId c, StorageStatus s) throws DbException; + /** + * Sets the status of the given contact. + */ + void setContactStatus(Transaction txn, ContactId c, StorageStatus s) + throws DbException; - /** Sets the status of the given local pseudonym. */ - void setLocalAuthorStatus(AuthorId a, StorageStatus s) - throws DbException; + /** + * Sets the status of the given local pseudonym. + */ + void setLocalAuthorStatus(Transaction txn, AuthorId a, StorageStatus s) + throws DbException; - /** Marks the given message as shared or unshared. */ - void setMessageShared(Message m, boolean shared) throws DbException; + /** + * Marks the given message as shared or unshared. + */ + void setMessageShared(Transaction txn, Message m, boolean shared) + throws DbException; - /** Marks the given message as valid or invalid. */ - void setMessageValid(Message m, ClientId c, boolean valid) + /** + * Marks the given message as valid or invalid. + */ + void setMessageValid(Transaction txn, Message m, ClientId c, boolean valid) throws DbException; /** * Sets the reordering window for the given contact and transport in the * given rotation period. */ - void setReorderingWindow(ContactId c, TransportId t, long rotationPeriod, - long base, byte[] bitmap) throws DbException; + void setReorderingWindow(Transaction txn, ContactId c, TransportId t, + long rotationPeriod, long base, byte[] bitmap) throws DbException; - /** Makes a group visible or invisible to a contact. */ - void setVisibleToContact(ContactId c, GroupId g, boolean visible) - throws DbException; + /** + * Makes a group visible or invisible to a contact. + */ + void setVisibleToContact(Transaction txn, ContactId c, GroupId g, + boolean visible) throws DbException; /** * Stores the given transport keys, deleting any keys they have replaced. */ - void updateTransportKeys(Map<ContactId, TransportKeys> keys) - throws DbException; + void updateTransportKeys(Transaction txn, + Map<ContactId, TransportKeys> keys) throws DbException; } diff --git a/briar-api/src/org/briarproject/api/db/Transaction.java b/briar-api/src/org/briarproject/api/db/Transaction.java index 2ea803b05ef888bc1cfe7602ee4b734c937fe79a..46497a039e8dc8c43a99ca2e11165377030b871b 100644 --- a/briar-api/src/org/briarproject/api/db/Transaction.java +++ b/briar-api/src/org/briarproject/api/db/Transaction.java @@ -1,15 +1,38 @@ package org.briarproject.api.db; -/** A wrapper around a database transaction. */ +/** + * A wrapper around a database transaction. Transactions are not thread-safe. + */ public class Transaction { private final Object txn; + private boolean complete = false; public Transaction(Object txn) { this.txn = txn; } + /** + * Returns the database transaction. The type of the returned object + * depends on the database implementation. + */ public Object unbox() { return txn; } + + /** + * Returns true if the transaction is ready to be committed. + */ + public boolean isComplete() { + return complete; + } + + /** + * Marks the transaction as ready to be committed. This method must not be + * called more than once. + */ + public void setComplete() { + if (complete) throw new IllegalStateException(); + complete = true; + } } diff --git a/briar-api/src/org/briarproject/api/identity/IdentityManager.java b/briar-api/src/org/briarproject/api/identity/IdentityManager.java index e872d845edbfb7ce3b21397791ff13bf74fa5c02..f2a4db2d906f1a6cec2e8292ce7a7437d94ae158 100644 --- a/briar-api/src/org/briarproject/api/identity/IdentityManager.java +++ b/briar-api/src/org/briarproject/api/identity/IdentityManager.java @@ -1,6 +1,7 @@ package org.briarproject.api.identity; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import java.util.Collection; @@ -25,10 +26,11 @@ public interface IdentityManager { void removeLocalAuthor(AuthorId a) throws DbException; interface AddIdentityHook { - void addingIdentity(LocalAuthor a); + void addingIdentity(Transaction txn, LocalAuthor a) throws DbException; } interface RemoveIdentityHook { - void removingIdentity(LocalAuthor a); + void removingIdentity(Transaction txn, LocalAuthor a) + throws DbException; } } diff --git a/briar-api/src/org/briarproject/api/sync/ValidationManager.java b/briar-api/src/org/briarproject/api/sync/ValidationManager.java index ff1e854c33447f06a4826d2f03fe82a0cdc691ae..690bc54e23f9fa110a7721b8e5f6284fad39752f 100644 --- a/briar-api/src/org/briarproject/api/sync/ValidationManager.java +++ b/briar-api/src/org/briarproject/api/sync/ValidationManager.java @@ -1,6 +1,8 @@ package org.briarproject.api.sync; +import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.Transaction; /** * Responsible for managing message validators and passing them messages to @@ -35,6 +37,7 @@ public interface ValidationManager { void registerValidationHook(ValidationHook hook); interface ValidationHook { - void validatingMessage(Message m, ClientId c, Metadata meta); + void validatingMessage(Transaction txn, Message m, ClientId c, + Metadata meta) throws DbException; } } diff --git a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java index b8f9553c84e62362acbfcec5b59f370015769c54..7bdb3bc450357afc1f5b5c215e902a523ac76829 100644 --- a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java +++ b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java @@ -8,6 +8,7 @@ import org.briarproject.api.contact.ContactManager; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchContactException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.ContactAddedEvent; import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.EventBus; @@ -52,19 +53,31 @@ class ContactManagerImpl implements ContactManager, Service, public boolean start() { // Finish adding/removing any partly added/removed contacts try { - for (Contact c : db.getContacts()) { - if (c.getStatus().equals(ADDING)) { - for (AddContactHook hook : addHooks) - hook.addingContact(c); - db.setContactStatus(c.getId(), ACTIVE); - eventBus.broadcast(new ContactAddedEvent(c.getId())); - } else if (c.getStatus().equals(REMOVING)) { - for (RemoveContactHook hook : removeHooks) - hook.removingContact(c); - db.removeContact(c.getId()); - eventBus.broadcast(new ContactRemovedEvent(c.getId())); + List<ContactId> added = new ArrayList<ContactId>(); + List<ContactId> removed = new ArrayList<ContactId>(); + Transaction txn = db.startTransaction(); + try { + for (Contact c : db.getContacts(txn)) { + if (c.getStatus().equals(ADDING)) { + for (AddContactHook hook : addHooks) + hook.addingContact(txn, c); + db.setContactStatus(txn, c.getId(), ACTIVE); + added.add(c.getId()); + } else if (c.getStatus().equals(REMOVING)) { + for (RemoveContactHook hook : removeHooks) + hook.removingContact(txn, c); + db.removeContact(txn, c.getId()); + removed.add(c.getId()); + } } + txn.setComplete(); + } finally { + db.endTransaction(txn); } + for (ContactId c : added) + eventBus.broadcast(new ContactAddedEvent(c)); + for (ContactId c : removed) + eventBus.broadcast(new ContactRemovedEvent(c)); return true; } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -90,24 +103,46 @@ class ContactManagerImpl implements ContactManager, Service, @Override public ContactId addContact(Author remote, AuthorId local) throws DbException { - ContactId c = db.addContact(remote, local); - Contact contact = db.getContact(c); - for (AddContactHook hook : addHooks) hook.addingContact(contact); - db.setContactStatus(c, ACTIVE); + ContactId c; + Transaction txn = db.startTransaction(); + try { + c = db.addContact(txn, remote, local); + Contact contact = db.getContact(txn, c); + for (AddContactHook hook : addHooks) + hook.addingContact(txn, contact); + db.setContactStatus(txn, c, ACTIVE); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } eventBus.broadcast(new ContactAddedEvent(c)); return c; } @Override public Contact getContact(ContactId c) throws DbException { - Contact contact = db.getContact(c); + Contact contact; + Transaction txn = db.startTransaction(); + try { + contact = db.getContact(txn, c); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } if (contact.getStatus().equals(ACTIVE)) return contact; throw new NoSuchContactException(); } @Override public Collection<Contact> getContacts() throws DbException { - Collection<Contact> contacts = db.getContacts(); + Collection<Contact> contacts; + Transaction txn = db.startTransaction(); + try { + contacts = db.getContacts(txn); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } // Filter out any contacts that are being added or removed List<Contact> active = new ArrayList<Contact>(contacts.size()); for (Contact c : contacts) @@ -117,21 +152,30 @@ class ContactManagerImpl implements ContactManager, Service, @Override public void removeContact(ContactId c) throws DbException { - Contact contact = db.getContact(c); - db.setContactStatus(c, REMOVING); - for (RemoveContactHook hook : removeHooks) - hook.removingContact(contact); - db.removeContact(c); + Transaction txn = db.startTransaction(); + try { + removeContact(txn, c); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } eventBus.broadcast(new ContactRemovedEvent(c)); } + private void removeContact(Transaction txn, ContactId c) + throws DbException { + Contact contact = db.getContact(txn, c); + db.setContactStatus(txn, c, REMOVING); + for (RemoveContactHook hook : removeHooks) + hook.removingContact(txn, contact); + db.removeContact(txn, c); + } + @Override - public void removingIdentity(LocalAuthor a) { + public void removingIdentity(Transaction txn, LocalAuthor a) + throws DbException { // Remove any contacts of the local pseudonym that's being removed - try { - for (ContactId c : db.getContacts(a.getId())) removeContact(c); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } + for (ContactId c : db.getContacts(txn, a.getId())) + removeContact(txn, c); } } diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 9e46dabb99a9cdcf59f8c76b95c6a8aed9695523..8d4c71166571957f33fb02f291dedf4ca47318f1 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -66,6 +66,8 @@ import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; import static org.briarproject.api.sync.ValidationManager.Validity.VALID; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; +// TODO: Callers should broadcast events after committing transactions + /** * An implementation of DatabaseComponent using reentrant read-write locks. * Depending on the JVM's lock implementation, this implementation may allow @@ -123,76 +125,55 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { return new Transaction(db.startTransaction()); } - public void abortTransaction(Transaction txn) { - db.abortTransaction(txnClass.cast(txn.unbox())); + public void endTransaction(Transaction transaction) throws DbException { + T txn = txnClass.cast(transaction.unbox()); + if (transaction.isComplete()) db.commitTransaction(txn); + else db.abortTransaction(txn); } - public void commitTransaction(Transaction txn) throws DbException { - db.commitTransaction(txnClass.cast(txn.unbox())); + private T unbox(Transaction transaction) { + if (transaction.isComplete()) throw new IllegalStateException(); + return txnClass.cast(transaction.unbox()); } - public ContactId addContact(Author remote, AuthorId local) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsLocalAuthor(txn, local)) - throw new NoSuchLocalAuthorException(); - if (db.containsContact(txn, remote.getId(), local)) - throw new ContactExistsException(); - ContactId c = db.addContact(txn, remote, local); - db.commitTransaction(txn); - return c; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public ContactId addContact(Transaction transaction, Author remote, + AuthorId local) throws DbException { + T txn = unbox(transaction); + if (!db.containsLocalAuthor(txn, local)) + throw new NoSuchLocalAuthorException(); + if (db.containsContact(txn, remote.getId(), local)) + throw new ContactExistsException(); + return db.addContact(txn, remote, local); } - public void addGroup(Group g) throws DbException { + public void addGroup(Transaction transaction, Group g) throws DbException { boolean added = false; - T txn = db.startTransaction(); - try { - if (!db.containsGroup(txn, g.getId())) { - db.addGroup(txn, g); - added = true; - } - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; + T txn = unbox(transaction); + if (!db.containsGroup(txn, g.getId())) { + db.addGroup(txn, g); + added = true; } if (added) eventBus.broadcast(new GroupAddedEvent(g)); } - public void addLocalAuthor(LocalAuthor a) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsLocalAuthor(txn, a.getId())) - db.addLocalAuthor(txn, a); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void addLocalAuthor(Transaction transaction, LocalAuthor a) + throws DbException { + T txn = unbox(transaction); + if (!db.containsLocalAuthor(txn, a.getId())) + db.addLocalAuthor(txn, a); } - public void addLocalMessage(Message m, ClientId c, Metadata meta, - boolean shared) throws DbException { + public void addLocalMessage(Transaction transaction, Message m, ClientId c, + Metadata meta, boolean shared) throws DbException { boolean added = false; - T txn = db.startTransaction(); - try { - if (!db.containsGroup(txn, m.getGroupId())) - throw new NoSuchGroupException(); - if (!db.containsMessage(txn, m.getId())) { - addMessage(txn, m, VALID, shared, null); - added = true; - } - db.mergeMessageMetadata(txn, m.getId(), meta); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + if (!db.containsGroup(txn, m.getGroupId())) + throw new NoSuchGroupException(); + if (!db.containsMessage(txn, m.getId())) { + addMessage(txn, m, VALID, shared, null); + added = true; + } + db.mergeMessageMetadata(txn, m.getId(), meta); if (added) { eventBus.broadcast(new MessageAddedEvent(m, null)); eventBus.broadcast(new MessageValidatedEvent(m, c, true, true)); @@ -223,538 +204,327 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { } } - public void addTransport(TransportId t, int maxLatency) throws DbException { + public void addTransport(Transaction transaction, TransportId t, + int maxLatency) throws DbException { boolean added = false; - T txn = db.startTransaction(); - try { - if (!db.containsTransport(txn, t)) { - db.addTransport(txn, t, maxLatency); - added = true; - } - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; + T txn = unbox(transaction); + if (!db.containsTransport(txn, t)) { + db.addTransport(txn, t, maxLatency); + added = true; } if (added) eventBus.broadcast(new TransportAddedEvent(t, maxLatency)); } - public void addTransportKeys(ContactId c, TransportKeys k) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsTransport(txn, k.getTransportId())) - throw new NoSuchTransportException(); - db.addTransportKeys(txn, c, k); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void addTransportKeys(Transaction transaction, ContactId c, + TransportKeys k) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsTransport(txn, k.getTransportId())) + throw new NoSuchTransportException(); + db.addTransportKeys(txn, c, k); } - public void deleteMessage(MessageId m) throws DbException { - lock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if (!db.containsMessage(txn, m)) - throw new NoSuchMessageException(); - db.deleteMessage(txn, m); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - lock.writeLock().unlock(); - } + public void deleteMessage(Transaction transaction, MessageId m) + throws DbException { + T txn = unbox(transaction); + if (!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + db.deleteMessage(txn, m); } - public void deleteMessageMetadata(MessageId m) throws DbException { - lock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if (!db.containsMessage(txn, m)) - throw new NoSuchMessageException(); - db.deleteMessageMetadata(txn, m); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - lock.writeLock().unlock(); - } + public void deleteMessageMetadata(Transaction transaction, MessageId m) + throws DbException { + T txn = unbox(transaction); + if (!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + db.deleteMessageMetadata(txn, m); } - public Ack generateAck(ContactId c, int maxMessages) throws DbException { + public Ack generateAck(Transaction transaction, ContactId c, + int maxMessages) throws DbException { Collection<MessageId> ids; - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getMessagesToAck(txn, c, maxMessages); - if (!ids.isEmpty()) db.lowerAckFlag(txn, c, ids); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getMessagesToAck(txn, c, maxMessages); + if (!ids.isEmpty()) db.lowerAckFlag(txn, c, ids); if (ids.isEmpty()) return null; return new Ack(ids); } - public Collection<byte[]> generateBatch(ContactId c, int maxLength, - int maxLatency) throws DbException { + public Collection<byte[]> generateBatch(Transaction transaction, + ContactId c, int maxLength, int maxLatency) throws DbException { Collection<MessageId> ids; - List<byte[]> messages = new ArrayList<byte[]>(); - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getMessagesToSend(txn, c, maxLength); - for (MessageId m : ids) { - messages.add(db.getRawMessage(txn, m)); - db.updateExpiryTime(txn, c, m, maxLatency); - } - if (!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + List<byte[]> messages; + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getMessagesToSend(txn, c, maxLength); + messages = new ArrayList<byte[]>(ids.size()); + for (MessageId m : ids) { + messages.add(db.getRawMessage(txn, m)); + db.updateExpiryTime(txn, c, m, maxLatency); + } + if (!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids); if (messages.isEmpty()) return null; if (!ids.isEmpty()) eventBus.broadcast(new MessagesSentEvent(c, ids)); return Collections.unmodifiableList(messages); } - public Offer generateOffer(ContactId c, int maxMessages, int maxLatency) - throws DbException { + public Offer generateOffer(Transaction transaction, ContactId c, + int maxMessages, int maxLatency) throws DbException { Collection<MessageId> ids; - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getMessagesToOffer(txn, c, maxMessages); - for (MessageId m : ids) db.updateExpiryTime(txn, c, m, maxLatency); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getMessagesToOffer(txn, c, maxMessages); + for (MessageId m : ids) db.updateExpiryTime(txn, c, m, maxLatency); if (ids.isEmpty()) return null; return new Offer(ids); } - public Request generateRequest(ContactId c, int maxMessages) - throws DbException { + public Request generateRequest(Transaction transaction, ContactId c, + int maxMessages) throws DbException { Collection<MessageId> ids; - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getMessagesToRequest(txn, c, maxMessages); - if (!ids.isEmpty()) db.removeOfferedMessages(txn, c, ids); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getMessagesToRequest(txn, c, maxMessages); + if (!ids.isEmpty()) db.removeOfferedMessages(txn, c, ids); if (ids.isEmpty()) return null; return new Request(ids); } - public Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength, - int maxLatency) throws DbException { + public Collection<byte[]> generateRequestedBatch(Transaction transaction, + ContactId c, int maxLength, int maxLatency) throws DbException { Collection<MessageId> ids; - List<byte[]> messages = new ArrayList<byte[]>(); - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getRequestedMessagesToSend(txn, c, maxLength); - for (MessageId m : ids) { - messages.add(db.getRawMessage(txn, m)); - db.updateExpiryTime(txn, c, m, maxLatency); - } - if (!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + List<byte[]> messages; + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getRequestedMessagesToSend(txn, c, maxLength); + messages = new ArrayList<byte[]>(ids.size()); + for (MessageId m : ids) { + messages.add(db.getRawMessage(txn, m)); + db.updateExpiryTime(txn, c, m, maxLatency); + } + if (!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids); if (messages.isEmpty()) return null; if (!ids.isEmpty()) eventBus.broadcast(new MessagesSentEvent(c, ids)); return Collections.unmodifiableList(messages); } - public Contact getContact(ContactId c) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - Contact contact = db.getContact(txn, c); - db.commitTransaction(txn); - return contact; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Contact getContact(Transaction transaction, ContactId c) + throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + return db.getContact(txn, c); } - public Collection<Contact> getContacts() throws DbException { - T txn = db.startTransaction(); - try { - Collection<Contact> contacts = db.getContacts(txn); - db.commitTransaction(txn); - return contacts; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Collection<Contact> getContacts(Transaction transaction) + throws DbException { + T txn = unbox(transaction); + return db.getContacts(txn); } - public Collection<ContactId> getContacts(AuthorId a) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsLocalAuthor(txn, a)) - throw new NoSuchLocalAuthorException(); - Collection<ContactId> contacts = db.getContacts(txn, a); - db.commitTransaction(txn); - return contacts; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Collection<ContactId> getContacts(Transaction transaction, + AuthorId a) throws DbException { + T txn = unbox(transaction); + if (!db.containsLocalAuthor(txn, a)) + throw new NoSuchLocalAuthorException(); + return db.getContacts(txn, a); } - public DeviceId getDeviceId() throws DbException { - T txn = db.startTransaction(); - try { - DeviceId id = db.getDeviceId(txn); - db.commitTransaction(txn); - return id; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public DeviceId getDeviceId(Transaction transaction) throws DbException { + T txn = unbox(transaction); + return db.getDeviceId(txn); } - public Group getGroup(GroupId g) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - Group group = db.getGroup(txn, g); - db.commitTransaction(txn); - return group; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } - } - - public Metadata getGroupMetadata(GroupId g) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - Metadata metadata = db.getGroupMetadata(txn, g); - db.commitTransaction(txn); - return metadata; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Group getGroup(Transaction transaction, GroupId g) + throws DbException { + T txn = unbox(transaction); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + return db.getGroup(txn, g); } - public Collection<Group> getGroups(ClientId c) throws DbException { - T txn = db.startTransaction(); - try { - Collection<Group> groups = db.getGroups(txn, c); - db.commitTransaction(txn); - return groups; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Metadata getGroupMetadata(Transaction transaction, GroupId g) + throws DbException { + T txn = unbox(transaction); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + return db.getGroupMetadata(txn, g); } - public LocalAuthor getLocalAuthor(AuthorId a) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsLocalAuthor(txn, a)) - throw new NoSuchLocalAuthorException(); - LocalAuthor localAuthor = db.getLocalAuthor(txn, a); - db.commitTransaction(txn); - return localAuthor; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Collection<Group> getGroups(Transaction transaction, ClientId c) + throws DbException { + T txn = unbox(transaction); + return db.getGroups(txn, c); } - public Collection<LocalAuthor> getLocalAuthors() throws DbException { - T txn = db.startTransaction(); - try { - Collection<LocalAuthor> authors = db.getLocalAuthors(txn); - db.commitTransaction(txn); - return authors; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public LocalAuthor getLocalAuthor(Transaction transaction, AuthorId a) + throws DbException { + T txn = unbox(transaction); + if (!db.containsLocalAuthor(txn, a)) + throw new NoSuchLocalAuthorException(); + return db.getLocalAuthor(txn, a); } - public Collection<MessageId> getMessagesToValidate(ClientId c) + public Collection<LocalAuthor> getLocalAuthors(Transaction transaction) throws DbException { - T txn = db.startTransaction(); - try { - Collection<MessageId> ids = db.getMessagesToValidate(txn, c); - db.commitTransaction(txn); - return ids; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + return db.getLocalAuthors(txn); } - public byte[] getRawMessage(MessageId m) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsMessage(txn, m)) - throw new NoSuchMessageException(); - byte[] raw = db.getRawMessage(txn, m); - db.commitTransaction(txn); - return raw; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Collection<MessageId> getMessagesToValidate(Transaction transaction, + ClientId c) throws DbException { + T txn = unbox(transaction); + return db.getMessagesToValidate(txn, c); } - public Map<MessageId, Metadata> getMessageMetadata(GroupId g) + public byte[] getRawMessage(Transaction transaction, MessageId m) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g); - db.commitTransaction(txn); - return metadata; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + if (!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + return db.getRawMessage(txn, m); } - public Metadata getMessageMetadata(MessageId m) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsMessage(txn, m)) - throw new NoSuchMessageException(); - Metadata metadata = db.getMessageMetadata(txn, m); - db.commitTransaction(txn); - return metadata; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Map<MessageId, Metadata> getMessageMetadata(Transaction transaction, + GroupId g) throws DbException { + T txn = unbox(transaction); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + return db.getMessageMetadata(txn, g); } - public Collection<MessageStatus> getMessageStatus(ContactId c, GroupId g) + public Metadata getMessageMetadata(Transaction transaction, MessageId m) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - Collection<MessageStatus> statuses = db.getMessageStatus(txn, c, g); - db.commitTransaction(txn); - return statuses; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + if (!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + return db.getMessageMetadata(txn, m); } - public MessageStatus getMessageStatus(ContactId c, MessageId m) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsMessage(txn, m)) - throw new NoSuchMessageException(); - MessageStatus status = db.getMessageStatus(txn, c, m); - db.commitTransaction(txn); - return status; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Collection<MessageStatus> getMessageStatus(Transaction transaction, + ContactId c, GroupId g) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + return db.getMessageStatus(txn, c, g); } - public Settings getSettings(String namespace) throws DbException { - T txn = db.startTransaction(); - try { - Settings s = db.getSettings(txn, namespace); - db.commitTransaction(txn); - return s; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public MessageStatus getMessageStatus(Transaction transaction, ContactId c, + MessageId m) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + return db.getMessageStatus(txn, c, m); } - public Map<ContactId, TransportKeys> getTransportKeys(TransportId t) + public Settings getSettings(Transaction transaction, String namespace) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - Map<ContactId, TransportKeys> keys = db.getTransportKeys(txn, t); - db.commitTransaction(txn); - return keys; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + return db.getSettings(txn, namespace); } - public Map<TransportId, Integer> getTransportLatencies() - throws DbException { - T txn = db.startTransaction(); - try { - Map<TransportId, Integer> latencies = db.getTransportLatencies(txn); - db.commitTransaction(txn); - return latencies; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Map<ContactId, TransportKeys> getTransportKeys( + Transaction transaction, TransportId t) throws DbException { + T txn = unbox(transaction); + if (!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + return db.getTransportKeys(txn, t); } - public void incrementStreamCounter(ContactId c, TransportId t, - long rotationPeriod) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.incrementStreamCounter(txn, c, t, rotationPeriod); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public Map<TransportId, Integer> getTransportLatencies( + Transaction transaction) throws DbException { + T txn = unbox(transaction); + return db.getTransportLatencies(txn); } - public boolean isVisibleToContact(ContactId c, GroupId g) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - boolean visible = db.containsVisibleGroup(txn, c, g); - db.commitTransaction(txn); - return visible; - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void incrementStreamCounter(Transaction transaction, ContactId c, + TransportId t, long rotationPeriod) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.incrementStreamCounter(txn, c, t, rotationPeriod); } - public void mergeGroupMetadata(GroupId g, Metadata meta) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - db.mergeGroupMetadata(txn, g, meta); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public boolean isVisibleToContact(Transaction transaction, ContactId c, + GroupId g) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + return db.containsVisibleGroup(txn, c, g); } - public void mergeMessageMetadata(MessageId m, Metadata meta) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsMessage(txn, m)) - throw new NoSuchMessageException(); - db.mergeMessageMetadata(txn, m, meta); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void mergeGroupMetadata(Transaction transaction, GroupId g, + Metadata meta) throws DbException { + T txn = unbox(transaction); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + db.mergeGroupMetadata(txn, g, meta); + } + + public void mergeMessageMetadata(Transaction transaction, MessageId m, + Metadata meta) throws DbException { + T txn = unbox(transaction); + if (!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + db.mergeMessageMetadata(txn, m, meta); } - public void mergeSettings(Settings s, String namespace) throws DbException { + public void mergeSettings(Transaction transaction, Settings s, + String namespace) throws DbException { boolean changed = false; - T txn = db.startTransaction(); - try { - Settings old = db.getSettings(txn, namespace); - Settings merged = new Settings(); - merged.putAll(old); - merged.putAll(s); - if (!merged.equals(old)) { - db.mergeSettings(txn, s, namespace); - changed = true; - } - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; + T txn = unbox(transaction); + Settings old = db.getSettings(txn, namespace); + Settings merged = new Settings(); + merged.putAll(old); + merged.putAll(s); + if (!merged.equals(old)) { + db.mergeSettings(txn, s, namespace); + changed = true; } if (changed) eventBus.broadcast(new SettingsUpdatedEvent(namespace)); } - public void receiveAck(ContactId c, Ack a) throws DbException { + public void receiveAck(Transaction transaction, ContactId c, Ack a) + throws DbException { Collection<MessageId> acked = new ArrayList<MessageId>(); - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - for (MessageId m : a.getMessageIds()) { - if (db.containsVisibleMessage(txn, c, m)) { - db.raiseSeenFlag(txn, c, m); - acked.add(m); - } + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + for (MessageId m : a.getMessageIds()) { + if (db.containsVisibleMessage(txn, c, m)) { + db.raiseSeenFlag(txn, c, m); + acked.add(m); } - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; } eventBus.broadcast(new MessagesAckedEvent(c, acked)); } - public void receiveMessage(ContactId c, Message m) throws DbException { + public void receiveMessage(Transaction transaction, ContactId c, Message m) + throws DbException { boolean duplicate, visible; - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - duplicate = db.containsMessage(txn, m.getId()); - visible = db.containsVisibleGroup(txn, c, m.getGroupId()); - if (visible) { - if (!duplicate) addMessage(txn, m, UNKNOWN, false, c); - db.raiseAckFlag(txn, c, m.getId()); - } - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + duplicate = db.containsMessage(txn, m.getId()); + visible = db.containsVisibleGroup(txn, c, m.getGroupId()); + if (visible) { + if (!duplicate) addMessage(txn, m, UNKNOWN, false, c); + db.raiseAckFlag(txn, c, m.getId()); } if (visible) { if (!duplicate) eventBus.broadcast(new MessageAddedEvent(m, c)); @@ -762,228 +532,157 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { } } - public void receiveOffer(ContactId c, Offer o) throws DbException { + public void receiveOffer(Transaction transaction, ContactId c, Offer o) + throws DbException { boolean ack = false, request = false; - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - int count = db.countOfferedMessages(txn, c); - for (MessageId m : o.getMessageIds()) { - if (db.containsVisibleMessage(txn, c, m)) { - db.raiseSeenFlag(txn, c, m); - db.raiseAckFlag(txn, c, m); - ack = true; - } else if (count < MAX_OFFERED_MESSAGES) { - db.addOfferedMessage(txn, c, m); - request = true; - count++; - } + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + int count = db.countOfferedMessages(txn, c); + for (MessageId m : o.getMessageIds()) { + if (db.containsVisibleMessage(txn, c, m)) { + db.raiseSeenFlag(txn, c, m); + db.raiseAckFlag(txn, c, m); + ack = true; + } else if (count < MAX_OFFERED_MESSAGES) { + db.addOfferedMessage(txn, c, m); + request = true; + count++; } - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; } if (ack) eventBus.broadcast(new MessageToAckEvent(c)); if (request) eventBus.broadcast(new MessageToRequestEvent(c)); } - public void receiveRequest(ContactId c, Request r) throws DbException { + public void receiveRequest(Transaction transaction, ContactId c, Request r) + throws DbException { boolean requested = false; - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - for (MessageId m : r.getMessageIds()) { - if (db.containsVisibleMessage(txn, c, m)) { - db.raiseRequestedFlag(txn, c, m); - db.resetExpiryTime(txn, c, m); - requested = true; - } + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + for (MessageId m : r.getMessageIds()) { + if (db.containsVisibleMessage(txn, c, m)) { + db.raiseRequestedFlag(txn, c, m); + db.resetExpiryTime(txn, c, m); + requested = true; } - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; } if (requested) eventBus.broadcast(new MessageRequestedEvent(c)); } - public void removeContact(ContactId c) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.removeContact(txn, c); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void removeContact(Transaction transaction, ContactId c) + throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.removeContact(txn, c); } - public void removeGroup(Group g) throws DbException { + public void removeGroup(Transaction transaction, Group g) + throws DbException { Collection<ContactId> affected; - T txn = db.startTransaction(); - try { - GroupId id = g.getId(); - if (!db.containsGroup(txn, id)) - throw new NoSuchGroupException(); - affected = db.getVisibility(txn, id); - db.removeGroup(txn, id); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + GroupId id = g.getId(); + if (!db.containsGroup(txn, id)) + throw new NoSuchGroupException(); + affected = db.getVisibility(txn, id); + db.removeGroup(txn, id); eventBus.broadcast(new GroupRemovedEvent(g)); eventBus.broadcast(new GroupVisibilityUpdatedEvent(affected)); } - public void removeLocalAuthor(AuthorId a) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsLocalAuthor(txn, a)) - throw new NoSuchLocalAuthorException(); - db.removeLocalAuthor(txn, a); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void removeLocalAuthor(Transaction transaction, AuthorId a) + throws DbException { + T txn = unbox(transaction); + if (!db.containsLocalAuthor(txn, a)) + throw new NoSuchLocalAuthorException(); + db.removeLocalAuthor(txn, a); } - public void removeTransport(TransportId t) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.removeTransport(txn, t); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void removeTransport(Transaction transaction, TransportId t) + throws DbException { + T txn = unbox(transaction); + if (!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.removeTransport(txn, t); eventBus.broadcast(new TransportRemovedEvent(t)); } - public void setContactStatus(ContactId c, StorageStatus s) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.setContactStatus(txn, c, s); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void setContactStatus(Transaction transaction, ContactId c, + StorageStatus s) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setContactStatus(txn, c, s); } - public void setLocalAuthorStatus(AuthorId a, StorageStatus s) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsLocalAuthor(txn, a)) - throw new NoSuchLocalAuthorException(); - db.setLocalAuthorStatus(txn, a, s); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void setLocalAuthorStatus(Transaction transaction, AuthorId a, + StorageStatus s) throws DbException { + T txn = unbox(transaction); + if (!db.containsLocalAuthor(txn, a)) + throw new NoSuchLocalAuthorException(); + db.setLocalAuthorStatus(txn, a, s); } - public void setMessageShared(Message m, boolean shared) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsMessage(txn, m.getId())) - throw new NoSuchMessageException(); - db.setMessageShared(txn, m.getId(), shared); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void setMessageShared(Transaction transaction, Message m, + boolean shared) throws DbException { + T txn = unbox(transaction); + if (!db.containsMessage(txn, m.getId())) + throw new NoSuchMessageException(); + db.setMessageShared(txn, m.getId(), shared); if (shared) eventBus.broadcast(new MessageSharedEvent(m)); } - public void setMessageValid(Message m, ClientId c, boolean valid) - throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsMessage(txn, m.getId())) - throw new NoSuchMessageException(); - db.setMessageValid(txn, m.getId(), valid); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void setMessageValid(Transaction transaction, Message m, ClientId c, + boolean valid) throws DbException { + T txn = unbox(transaction); + if (!db.containsMessage(txn, m.getId())) + throw new NoSuchMessageException(); + db.setMessageValid(txn, m.getId(), valid); eventBus.broadcast(new MessageValidatedEvent(m, c, false, valid)); } - public void setReorderingWindow(ContactId c, TransportId t, - long rotationPeriod, long base, byte[] bitmap) throws DbException { - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.setReorderingWindow(txn, c, t, rotationPeriod, base, bitmap); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + public void setReorderingWindow(Transaction transaction, ContactId c, + TransportId t, long rotationPeriod, long base, byte[] bitmap) + throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.setReorderingWindow(txn, c, t, rotationPeriod, base, bitmap); } - public void setVisibleToContact(ContactId c, GroupId g, boolean visible) - throws DbException { + public void setVisibleToContact(Transaction transaction, ContactId c, + GroupId g, boolean visible) throws DbException { boolean wasVisible; - T txn = db.startTransaction(); - try { - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - wasVisible = db.containsVisibleGroup(txn, c, g); - if (visible && !wasVisible) db.addVisibility(txn, c, g); - else if (!visible && wasVisible) db.removeVisibility(txn, c, g); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; - } + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + wasVisible = db.containsVisibleGroup(txn, c, g); + if (visible && !wasVisible) db.addVisibility(txn, c, g); + else if (!visible && wasVisible) db.removeVisibility(txn, c, g); if (visible != wasVisible) { eventBus.broadcast(new GroupVisibilityUpdatedEvent( Collections.singletonList(c))); } } - public void updateTransportKeys(Map<ContactId, TransportKeys> keys) - throws DbException { - T txn = db.startTransaction(); - try { - Map<ContactId, TransportKeys> filtered = - new HashMap<ContactId, TransportKeys>(); - for (Entry<ContactId, TransportKeys> e : keys.entrySet()) { - ContactId c = e.getKey(); - TransportKeys k = e.getValue(); - if (db.containsContact(txn, c) - && db.containsTransport(txn, k.getTransportId())) { - filtered.put(c, k); - } + public void updateTransportKeys(Transaction transaction, + Map<ContactId, TransportKeys> keys) throws DbException { + Map<ContactId, TransportKeys> filtered = + new HashMap<ContactId, TransportKeys>(); + T txn = unbox(transaction); + for (Entry<ContactId, TransportKeys> e : keys.entrySet()) { + ContactId c = e.getKey(); + TransportKeys k = e.getValue(); + if (db.containsContact(txn, c) + && db.containsTransport(txn, k.getTransportId())) { + filtered.put(c, k); } - db.updateTransportKeys(txn, filtered); - db.commitTransaction(txn); - } catch (DbException e) { - db.abortTransaction(txn); - throw e; } + db.updateTransportKeys(txn, filtered); } } diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java index 6820afb47e92cac1958dfbf41dc00bae31e18459..9da89c67191477761cae2178c48ed4afe4fb719c 100644 --- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java @@ -4,7 +4,6 @@ import com.google.inject.Inject; import org.briarproject.api.FormatException; import org.briarproject.api.contact.Contact; -import org.briarproject.api.contact.ContactManager; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfReader; import org.briarproject.api.data.BdfReaderFactory; @@ -13,13 +12,13 @@ import org.briarproject.api.data.MetadataParser; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.Transaction; import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumPost; import org.briarproject.api.forum.ForumPostHeader; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.AuthorId; -import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; @@ -37,7 +36,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Logger; import static java.util.logging.Level.WARNING; @@ -59,22 +57,14 @@ class ForumManagerImpl implements ForumManager { Logger.getLogger(ForumManagerImpl.class.getName()); private final DatabaseComponent db; - private final ContactManager contactManager; - private final IdentityManager identityManager; private final BdfReaderFactory bdfReaderFactory; private final MetadataEncoder metadataEncoder; private final MetadataParser metadataParser; - /** Ensures isolation between database reads and writes. */ - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - @Inject - ForumManagerImpl(DatabaseComponent db, ContactManager contactManager, - IdentityManager identityManager, BdfReaderFactory bdfReaderFactory, + ForumManagerImpl(DatabaseComponent db, BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder, MetadataParser metadataParser) { this.db = db; - this.contactManager = contactManager; - this.identityManager = identityManager; this.bdfReaderFactory = bdfReaderFactory; this.metadataEncoder = metadataEncoder; this.metadataParser = metadataParser; @@ -87,7 +77,6 @@ class ForumManagerImpl implements ForumManager { @Override public void addLocalPost(ForumPost p) throws DbException { - lock.writeLock().lock(); try { BdfDictionary d = new BdfDictionary(); d.put("timestamp", p.getMessage().getTimestamp()); @@ -105,45 +94,65 @@ class ForumManagerImpl implements ForumManager { d.put("local", true); d.put("read", true); Metadata meta = metadataEncoder.encode(d); - db.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true); + Transaction txn = db.startTransaction(); + try { + db.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta, true); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (FormatException e) { throw new RuntimeException(e); - } finally { - lock.writeLock().unlock(); } } @Override public Forum getForum(GroupId g) throws DbException { - lock.readLock().lock(); try { - return parseForum(db.getGroup(g)); + Group group; + Transaction txn = db.startTransaction(); + try { + group = db.getGroup(txn, g); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + return parseForum(group); } catch (FormatException e) { throw new DbException(e); - } finally { - lock.readLock().unlock(); } } @Override public Collection<Forum> getForums() throws DbException { - lock.readLock().lock(); try { + Collection<Group> groups; + Transaction txn = db.startTransaction(); + try { + groups = db.getGroups(txn, CLIENT_ID); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } List<Forum> forums = new ArrayList<Forum>(); - for (Group g : db.getGroups(CLIENT_ID)) forums.add(parseForum(g)); + for (Group g : groups) forums.add(parseForum(g)); return Collections.unmodifiableList(forums); } catch (FormatException e) { throw new DbException(e); - } finally { - lock.readLock().unlock(); } } @Override public byte[] getPostBody(MessageId m) throws DbException { - lock.readLock().lock(); try { - byte[] raw = db.getRawMessage(m); + byte[] raw; + Transaction txn = db.startTransaction(); + try { + raw = db.getRawMessage(txn, m); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } ByteArrayInputStream in = new ByteArrayInputStream(raw, MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); BdfReader r = bdfReaderFactory.createReader(in); @@ -164,74 +173,76 @@ class ForumManagerImpl implements ForumManager { } catch (IOException e) { // Shouldn't happen with ByteArrayInputStream throw new RuntimeException(e); - } finally { - lock.readLock().unlock(); } } @Override public Collection<ForumPostHeader> getPostHeaders(GroupId g) throws DbException { - lock.readLock().lock(); + Set<AuthorId> localAuthorIds = new HashSet<AuthorId>(); + Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>(); + Map<MessageId, Metadata> metadata; + Transaction txn = db.startTransaction(); try { // Load the IDs of the user's identities - Set<AuthorId> localAuthorIds = new HashSet<AuthorId>(); - for (LocalAuthor a : identityManager.getLocalAuthors()) + for (LocalAuthor a : db.getLocalAuthors(txn)) localAuthorIds.add(a.getId()); // Load the IDs of contacts' identities - Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>(); - for (Contact c : contactManager.getContacts()) + for (Contact c : db.getContacts(txn)) contactAuthorIds.add(c.getAuthor().getId()); - // Load and parse the metadata - Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); - Collection<ForumPostHeader> headers = - new ArrayList<ForumPostHeader>(); - for (Entry<MessageId, Metadata> e : metadata.entrySet()) { - MessageId messageId = e.getKey(); - Metadata meta = e.getValue(); - try { - BdfDictionary d = metadataParser.parse(meta); - long timestamp = d.getInteger("timestamp"); - Author author = null; - Author.Status authorStatus = ANONYMOUS; - BdfDictionary d1 = d.getDictionary("author", null); - if (d1 != null) { - AuthorId authorId = new AuthorId(d1.getRaw("id")); - String name = d1.getString("name"); - byte[] publicKey = d1.getRaw("publicKey"); - author = new Author(authorId, name, publicKey); - if (localAuthorIds.contains(authorId)) - authorStatus = VERIFIED; - else if (contactAuthorIds.contains(authorId)) - authorStatus = VERIFIED; - else authorStatus = UNKNOWN; - } - String contentType = d.getString("contentType"); - boolean read = d.getBoolean("read"); - headers.add(new ForumPostHeader(messageId, timestamp, - author, authorStatus, contentType, read)); - } catch (FormatException ex) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, ex.toString(), ex); + // Load the metadata + metadata = db.getMessageMetadata(txn, g); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + // Parse the metadata + Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>(); + for (Entry<MessageId, Metadata> e : metadata.entrySet()) { + try { + BdfDictionary d = metadataParser.parse(e.getValue()); + long timestamp = d.getInteger("timestamp"); + Author author = null; + Author.Status authorStatus = ANONYMOUS; + BdfDictionary d1 = d.getDictionary("author", null); + if (d1 != null) { + AuthorId authorId = new AuthorId(d1.getRaw("id")); + String name = d1.getString("name"); + byte[] publicKey = d1.getRaw("publicKey"); + author = new Author(authorId, name, publicKey); + if (localAuthorIds.contains(authorId)) + authorStatus = VERIFIED; + else if (contactAuthorIds.contains(authorId)) + authorStatus = VERIFIED; + else authorStatus = UNKNOWN; } + String contentType = d.getString("contentType"); + boolean read = d.getBoolean("read"); + headers.add(new ForumPostHeader(e.getKey(), timestamp, author, + authorStatus, contentType, read)); + } catch (FormatException ex) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, ex.toString(), ex); } - return headers; - } finally { - lock.readLock().unlock(); } + return headers; } @Override public void setReadFlag(MessageId m, boolean read) throws DbException { - lock.writeLock().lock(); try { BdfDictionary d = new BdfDictionary(); d.put("read", read); - db.mergeMessageMetadata(m, metadataEncoder.encode(d)); + Metadata meta = metadataEncoder.encode(d); + Transaction txn = db.startTransaction(); + try { + db.mergeMessageMetadata(txn, m, meta); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (FormatException e) { throw new RuntimeException(e); - } finally { - lock.writeLock().unlock(); } } diff --git a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java index 72efd00ac056eed7fbb1a24fae6816aa962adadd..6f8554ce8febc2c884215346d4119ac54592e4ee 100644 --- a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java @@ -5,7 +5,6 @@ import com.google.inject.Inject; import org.briarproject.api.FormatException; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; -import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.data.BdfDictionary; @@ -18,6 +17,7 @@ import org.briarproject.api.data.MetadataParser; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.Transaction; import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumSharingManager; @@ -45,10 +45,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Logger; -import static java.util.logging.Level.WARNING; import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; @@ -62,11 +60,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0]; - private static final Logger LOG = - Logger.getLogger(ForumSharingManagerImpl.class.getName()); - private final DatabaseComponent db; - private final ContactManager contactManager; private final ForumManager forumManager; private final GroupFactory groupFactory; private final PrivateGroupFactory privateGroupFactory; @@ -79,18 +73,14 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, private final Clock clock; private final Group localGroup; - /** Ensures isolation between database reads and writes. */ - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - @Inject ForumSharingManagerImpl(DatabaseComponent db, - ContactManager contactManager, ForumManager forumManager, - GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory, + ForumManager forumManager, GroupFactory groupFactory, + PrivateGroupFactory privateGroupFactory, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder, MetadataParser metadataParser, SecureRandom random, Clock clock) { this.db = db; - this.contactManager = contactManager; this.forumManager = forumManager; this.groupFactory = groupFactory; this.privateGroupFactory = privateGroupFactory; @@ -106,57 +96,39 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, } @Override - public void addingContact(Contact c) { - lock.writeLock().lock(); + public void addingContact(Transaction txn, Contact c) throws DbException { try { // Create a group to share with the contact Group g = getContactGroup(c); // Store the group and share it with the contact - db.addGroup(g); - db.setVisibleToContact(c.getId(), g.getId(), true); + db.addGroup(txn, g); + db.setVisibleToContact(txn, c.getId(), g.getId(), true); // Attach the contact ID to the group BdfDictionary d = new BdfDictionary(); d.put("contactId", c.getId().getInt()); - db.mergeGroupMetadata(g.getId(), metadataEncoder.encode(d)); + db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d)); // Share any forums that are shared with all contacts - List<Forum> shared = getForumsSharedWithAllContacts(); - storeMessage(g.getId(), shared, 0); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + List<Forum> shared = getForumsSharedWithAllContacts(txn); + storeMessage(txn, g.getId(), shared, 0); } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } finally { - lock.writeLock().unlock(); + throw new DbException(e); } } @Override - public void removingContact(Contact c) { - lock.writeLock().lock(); - try { - db.removeGroup(getContactGroup(c)); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } finally { - lock.writeLock().unlock(); - } + public void removingContact(Transaction txn, Contact c) throws DbException { + db.removeGroup(txn, getContactGroup(c)); } @Override - public void validatingMessage(Message m, ClientId c, Metadata meta) { + public void validatingMessage(Transaction txn, Message m, ClientId c, + Metadata meta) throws DbException { if (c.equals(CLIENT_ID)) { - lock.writeLock().lock(); try { - ContactId contactId = getContactId(m.getGroupId()); - setForumVisibility(contactId, getVisibleForums(m)); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); + ContactId contactId = getContactId(txn, m.getGroupId()); + setForumVisibility(txn, contactId, getVisibleForums(txn, m)); } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } finally { - lock.writeLock().unlock(); + throw new DbException(e); } } } @@ -179,149 +151,162 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, @Override public void addForum(Forum f) throws DbException { - lock.writeLock().lock(); + Transaction txn = db.startTransaction(); try { - db.addGroup(f.getGroup()); + db.addGroup(txn, f.getGroup()); + txn.setComplete(); } finally { - lock.writeLock().unlock(); + db.endTransaction(txn); } } @Override public void removeForum(Forum f) throws DbException { - lock.writeLock().lock(); try { - // Update the list of forums shared with each contact - for (Contact c : contactManager.getContacts()) { - Group contactGroup = getContactGroup(c); - removeFromList(contactGroup.getId(), f); + // Update the list shared with each contact + Transaction txn = db.startTransaction(); + try { + for (Contact c : db.getContacts(txn)) + removeFromList(txn, getContactGroup(c).getId(), f); + db.removeGroup(txn, f.getGroup()); + txn.setComplete(); + } finally { + db.endTransaction(txn); } - db.removeGroup(f.getGroup()); } catch (IOException e) { throw new DbException(e); - } finally { - lock.writeLock().unlock(); } } @Override public Collection<Forum> getAvailableForums() throws DbException { - lock.readLock().lock(); try { - // Get any forums we subscribe to - Set<Group> subscribed = new HashSet<Group>(db.getGroups( - forumManager.getClientId())); - // Get all forums shared by contacts Set<Forum> available = new HashSet<Forum>(); - for (Contact c : contactManager.getContacts()) { - Group g = getContactGroup(c); - // Find the latest update version - LatestUpdate latest = findLatest(g.getId(), false); - if (latest != null) { - // Retrieve and parse the latest update - byte[] raw = db.getRawMessage(latest.messageId); - for (Forum f : parseForumList(raw)) { - if (!subscribed.contains(f.getGroup())) - available.add(f); + Transaction txn = db.startTransaction(); + try { + // Get any forums we subscribe to + Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn, + forumManager.getClientId())); + // Get all forums shared by contacts + for (Contact c : db.getContacts(txn)) { + Group g = getContactGroup(c); + // Find the latest update version + LatestUpdate latest = findLatest(txn, g.getId(), false); + if (latest != null) { + // Retrieve and parse the latest update + byte[] raw = db.getRawMessage(txn, latest.messageId); + for (Forum f : parseForumList(raw)) { + if (!subscribed.contains(f.getGroup())) + available.add(f); + } } } + txn.setComplete(); + } finally { + db.endTransaction(txn); } return Collections.unmodifiableSet(available); } catch (IOException e) { throw new DbException(e); - } finally { - lock.readLock().unlock(); } } @Override public Collection<Contact> getSharedBy(GroupId g) throws DbException { - lock.readLock().lock(); try { List<Contact> subscribers = new ArrayList<Contact>(); - for (Contact c : contactManager.getContacts()) { - Group contactGroup = getContactGroup(c); - if (listContains(contactGroup.getId(), g, false)) - subscribers.add(c); + Transaction txn = db.startTransaction(); + try { + for (Contact c : db.getContacts(txn)) { + if (listContains(txn, getContactGroup(c).getId(), g, false)) + subscribers.add(c); + } + txn.setComplete(); + } finally { + db.endTransaction(txn); } return Collections.unmodifiableList(subscribers); } catch (IOException e) { throw new DbException(e); - } finally { - lock.readLock().unlock(); } } @Override public Collection<ContactId> getSharedWith(GroupId g) throws DbException { - lock.readLock().lock(); try { List<ContactId> shared = new ArrayList<ContactId>(); - for (Contact c : contactManager.getContacts()) { - Group contactGroup = getContactGroup(c); - if (listContains(contactGroup.getId(), g, true)) - shared.add(c.getId()); + Transaction txn = db.startTransaction(); + try { + for (Contact c : db.getContacts(txn)) { + if (listContains(txn, getContactGroup(c).getId(), g, true)) + shared.add(c.getId()); + } + txn.setComplete(); + } finally { + db.endTransaction(txn); } return Collections.unmodifiableList(shared); } catch (FormatException e) { throw new DbException(e); - } finally { - lock.readLock().unlock(); } } @Override public void setSharedWith(GroupId g, Collection<ContactId> shared) throws DbException { - lock.writeLock().lock(); try { - // Retrieve the forum - Forum f = parseForum(db.getGroup(g)); - // Remove the forum from the list of forums shared with all contacts - removeFromList(localGroup.getId(), f); - // Update the list of forums shared with each contact - shared = new HashSet<ContactId>(shared); - for (Contact c : contactManager.getContacts()) { - Group contactGroup = getContactGroup(c); - if (shared.contains(c.getId())) { - if (addToList(contactGroup.getId(), f)) { - // If the contact is sharing the forum, make it visible - if (listContains(contactGroup.getId(), g, false)) - db.setVisibleToContact(c.getId(), g, true); + Transaction txn = db.startTransaction(); + try { + // Retrieve the forum + Forum f = parseForum(db.getGroup(txn, g)); + // Remove the forum from the list shared with all contacts + removeFromList(txn, localGroup.getId(), f); + // Update the list shared with each contact + shared = new HashSet<ContactId>(shared); + for (Contact c : db.getContacts(txn)) { + Group cg = getContactGroup(c); + if (shared.contains(c.getId())) { + if (addToList(txn, cg.getId(), f)) { + if (listContains(txn, cg.getId(), g, false)) + db.setVisibleToContact(txn, c.getId(), g, true); + } + } else { + removeFromList(txn, cg.getId(), f); + db.setVisibleToContact(txn, c.getId(), g, false); } - } else { - removeFromList(contactGroup.getId(), f); - db.setVisibleToContact(c.getId(), g, false); } + txn.setComplete(); + } finally { + db.endTransaction(txn); } } catch (FormatException e) { throw new DbException(e); - } finally { - lock.writeLock().unlock(); } } @Override public void setSharedWithAll(GroupId g) throws DbException { - lock.writeLock().lock(); try { - // Retrieve the forum - Forum f = parseForum(db.getGroup(g)); - // Add the forum to the list of forums shared with all contacts - addToList(localGroup.getId(), f); - // Add the forum to the list of forums shared with each contact - for (Contact c : contactManager.getContacts()) { - Group contactGroup = getContactGroup(c); - if (addToList(contactGroup.getId(), f)) { - // If the contact is sharing the forum, make it visible - if (listContains(contactGroup.getId(), g, false)) - db.setVisibleToContact(getContactId(g), g, true); + Transaction txn = db.startTransaction(); + try { + // Retrieve the forum + Forum f = parseForum(db.getGroup(txn, g)); + // Add the forum to the list shared with all contacts + addToList(txn, localGroup.getId(), f); + // Add the forum to the list shared with each contact + for (Contact c : db.getContacts(txn)) { + Group cg = getContactGroup(c); + if (addToList(txn, cg.getId(), f)) { + if (listContains(txn, cg.getId(), g, false)) + db.setVisibleToContact(txn, c.getId(), g, true); + } } + txn.setComplete(); + } finally { + db.endTransaction(txn); } } catch (FormatException e) { throw new DbException(e); - } finally { - lock.writeLock().unlock(); } } @@ -329,23 +314,21 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); } - // Locking: lock.writeLock - private List<Forum> getForumsSharedWithAllContacts() throws DbException, - FormatException { + private List<Forum> getForumsSharedWithAllContacts(Transaction txn) + throws DbException, FormatException { // Ensure the local group exists - db.addGroup(localGroup); + db.addGroup(txn, localGroup); // Find the latest update in the local group - LatestUpdate latest = findLatest(localGroup.getId(), true); + LatestUpdate latest = findLatest(txn, localGroup.getId(), true); if (latest == null) return Collections.emptyList(); // Retrieve and parse the latest update - return parseForumList(db.getRawMessage(latest.messageId)); + return parseForumList(db.getRawMessage(txn, latest.messageId)); } - // Locking: lock.readLock - private LatestUpdate findLatest(GroupId g, boolean local) + private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local) throws DbException, FormatException { LatestUpdate latest = null; - Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); + Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g); for (Entry<MessageId, Metadata> e : metadata.entrySet()) { BdfDictionary d = metadataParser.parse(e.getValue()); if (d.getBoolean("local") != local) continue; @@ -384,16 +367,20 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, } } - // Locking: lock.writeLock - private void storeMessage(GroupId g, List<Forum> forums, long version) - throws DbException, FormatException { - byte[] body = encodeForumList(forums, version); - long now = clock.currentTimeMillis(); - Message m = messageFactory.createMessage(g, now, body); - BdfDictionary d = new BdfDictionary(); - d.put("version", version); - d.put("local", true); - db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d), true); + private void storeMessage(Transaction txn, GroupId g, List<Forum> forums, + long version) throws DbException { + try { + byte[] body = encodeForumList(forums, version); + long now = clock.currentTimeMillis(); + Message m = messageFactory.createMessage(g, now, body); + BdfDictionary d = new BdfDictionary(); + d.put("version", version); + d.put("local", true); + db.addLocalMessage(txn, m, CLIENT_ID, metadataEncoder.encode(d), + true); + } catch (FormatException e) { + throw new RuntimeException(e); + } } private byte[] encodeForumList(List<Forum> forums, long version) { @@ -418,23 +405,21 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, return out.toByteArray(); } - // Locking: lock.readLock - private ContactId getContactId(GroupId contactGroupId) throws DbException, - FormatException { - Metadata meta = db.getGroupMetadata(contactGroupId); + private ContactId getContactId(Transaction txn, GroupId contactGroupId) + throws DbException, FormatException { + Metadata meta = db.getGroupMetadata(txn, contactGroupId); BdfDictionary d = metadataParser.parse(meta); return new ContactId(d.getInteger("contactId").intValue()); } - // Locking: lock.readLock - private Set<GroupId> getVisibleForums(Message remoteUpdate) - throws DbException, FormatException { + private Set<GroupId> getVisibleForums(Transaction txn, + Message remoteUpdate) throws DbException, FormatException { // Get the latest local update - LatestUpdate local = findLatest(remoteUpdate.getGroupId(), true); + LatestUpdate local = findLatest(txn, remoteUpdate.getGroupId(), true); // If there's no local update, no forums are visible if (local == null) return Collections.emptySet(); // Intersect the sets of shared forums - byte[] localRaw = db.getRawMessage(local.messageId); + byte[] localRaw = db.getRawMessage(txn, local.messageId); Set<Forum> shared = new HashSet<Forum>(parseForumList(localRaw)); shared.retainAll(parseForumList(remoteUpdate.getRaw())); // Forums in the intersection should be visible @@ -443,16 +428,15 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, return visible; } - // Locking: lock.writeLock - private void setForumVisibility(ContactId c, Set<GroupId> visible) - throws DbException { - for (Group g : db.getGroups(forumManager.getClientId())) { - boolean isVisible = db.isVisibleToContact(c, g.getId()); + private void setForumVisibility(Transaction txn, ContactId c, + Set<GroupId> visible) throws DbException { + for (Group g : db.getGroups(txn, forumManager.getClientId())) { + boolean isVisible = db.isVisibleToContact(txn, c, g.getId()); boolean shouldBeVisible = visible.contains(g.getId()); if (isVisible && !shouldBeVisible) - db.setVisibleToContact(c, g.getId(), false); + db.setVisibleToContact(txn, c, g.getId(), false); else if (!isVisible && shouldBeVisible) - db.setVisibleToContact(c, g.getId(), true); + db.setVisibleToContact(txn, c, g.getId(), true); } } @@ -491,38 +475,38 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook, } } - // Locking: lock.readLock - private boolean listContains(GroupId g, GroupId forum, boolean local) - throws DbException, FormatException { - LatestUpdate latest = findLatest(g, local); + private boolean listContains(Transaction txn, GroupId g, GroupId forum, + boolean local) throws DbException, FormatException { + LatestUpdate latest = findLatest(txn, g, local); if (latest == null) return false; - List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); + byte[] raw = db.getRawMessage(txn, latest.messageId); + List<Forum> list = parseForumList(raw); for (Forum f : list) if (f.getId().equals(forum)) return true; return false; } - // Locking: lock.writeLock - private boolean addToList(GroupId g, Forum f) throws DbException, - FormatException { - LatestUpdate latest = findLatest(g, true); + private boolean addToList(Transaction txn, GroupId g, Forum f) + throws DbException, FormatException { + LatestUpdate latest = findLatest(txn, g, true); if (latest == null) { - storeMessage(g, Collections.singletonList(f), 0); + storeMessage(txn, g, Collections.singletonList(f), 0); return true; } - List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); + byte[] raw = db.getRawMessage(txn, latest.messageId); + List<Forum> list = parseForumList(raw); if (list.contains(f)) return false; list.add(f); - storeMessage(g, list, latest.version + 1); + storeMessage(txn, g, list, latest.version + 1); return true; } - // Locking: lock.writeLock - private void removeFromList(GroupId g, Forum f) throws DbException, - FormatException { - LatestUpdate latest = findLatest(g, true); + private void removeFromList(Transaction txn, GroupId g, Forum f) + throws DbException, FormatException { + LatestUpdate latest = findLatest(txn, g, true); if (latest == null) return; - List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); - if (list.remove(f)) storeMessage(g, list, latest.version + 1); + byte[] raw = db.getRawMessage(txn, latest.messageId); + List<Forum> list = parseForumList(raw); + if (list.remove(f)) storeMessage(txn, g, list, latest.version + 1); } private static class LatestUpdate { diff --git a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java index 71b34eac925e31a79d413f9f228b51a43dd7e97a..475ead3e4a1321e7906a115b38a09c68f24e3319 100644 --- a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java +++ b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java @@ -5,6 +5,7 @@ import com.google.inject.Inject; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchLocalAuthorException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.EventBus; import org.briarproject.api.event.LocalAuthorAddedEvent; import org.briarproject.api.event.LocalAuthorRemovedEvent; @@ -47,19 +48,31 @@ class IdentityManagerImpl implements IdentityManager, Service { public boolean start() { // Finish adding/removing any partly added/removed pseudonyms try { - for (LocalAuthor a : db.getLocalAuthors()) { - if (a.getStatus().equals(ADDING)) { - for (AddIdentityHook hook : addHooks) - hook.addingIdentity(a); - db.setLocalAuthorStatus(a.getId(), ACTIVE); - eventBus.broadcast(new LocalAuthorAddedEvent(a.getId())); - } else if (a.getStatus().equals(REMOVING)) { - for (RemoveIdentityHook hook : removeHooks) - hook.removingIdentity(a); - db.removeLocalAuthor(a.getId()); - eventBus.broadcast(new LocalAuthorRemovedEvent(a.getId())); + List<AuthorId> added = new ArrayList<AuthorId>(); + List<AuthorId> removed = new ArrayList<AuthorId>(); + Transaction txn = db.startTransaction(); + try { + for (LocalAuthor a : db.getLocalAuthors(txn)) { + if (a.getStatus().equals(ADDING)) { + for (AddIdentityHook hook : addHooks) + hook.addingIdentity(txn, a); + db.setLocalAuthorStatus(txn, a.getId(), ACTIVE); + added.add(a.getId()); + } else if (a.getStatus().equals(REMOVING)) { + for (RemoveIdentityHook hook : removeHooks) + hook.removingIdentity(txn, a); + db.removeLocalAuthor(txn, a.getId()); + removed.add(a.getId()); + } } + txn.setComplete(); + } finally { + db.endTransaction(txn); } + for (AuthorId a : added) + eventBus.broadcast(new LocalAuthorAddedEvent(a)); + for (AuthorId a : removed) + eventBus.broadcast(new LocalAuthorRemovedEvent(a)); return true; } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -84,22 +97,43 @@ class IdentityManagerImpl implements IdentityManager, Service { @Override public void addLocalAuthor(LocalAuthor localAuthor) throws DbException { - db.addLocalAuthor(localAuthor); - for (AddIdentityHook hook : addHooks) hook.addingIdentity(localAuthor); - db.setLocalAuthorStatus(localAuthor.getId(), ACTIVE); + Transaction txn = db.startTransaction(); + try { + db.addLocalAuthor(txn, localAuthor); + for (AddIdentityHook hook : addHooks) + hook.addingIdentity(txn, localAuthor); + db.setLocalAuthorStatus(txn, localAuthor.getId(), ACTIVE); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } eventBus.broadcast(new LocalAuthorAddedEvent(localAuthor.getId())); } @Override public LocalAuthor getLocalAuthor(AuthorId a) throws DbException { - LocalAuthor author = db.getLocalAuthor(a); + LocalAuthor author; + Transaction txn = db.startTransaction(); + try { + author = db.getLocalAuthor(txn, a); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } if (author.getStatus().equals(ACTIVE)) return author; throw new NoSuchLocalAuthorException(); } @Override public Collection<LocalAuthor> getLocalAuthors() throws DbException { - Collection<LocalAuthor> authors = db.getLocalAuthors(); + Collection<LocalAuthor> authors; + Transaction txn = db.startTransaction(); + try { + authors = db.getLocalAuthors(txn); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } // Filter out any pseudonyms that are being added or removed List<LocalAuthor> active = new ArrayList<LocalAuthor>(authors.size()); for (LocalAuthor a : authors) @@ -109,11 +143,17 @@ class IdentityManagerImpl implements IdentityManager, Service { @Override public void removeLocalAuthor(AuthorId a) throws DbException { - LocalAuthor localAuthor = db.getLocalAuthor(a); - db.setLocalAuthorStatus(a, REMOVING); - for (RemoveIdentityHook hook : removeHooks) - hook.removingIdentity(localAuthor); - db.removeLocalAuthor(a); + Transaction txn = db.startTransaction(); + try { + LocalAuthor localAuthor = db.getLocalAuthor(txn, a); + db.setLocalAuthorStatus(txn, a, REMOVING); + for (RemoveIdentityHook hook : removeHooks) + hook.removingIdentity(txn, localAuthor); + db.removeLocalAuthor(txn, a); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } eventBus.broadcast(new LocalAuthorRemovedEvent(a)); } } diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java index 5185cac6e8ae01e2250fd777257e04363e87a5f5..0441d27ce29533486735a94fab4b89b3e85da12f 100644 --- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java +++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java @@ -5,7 +5,6 @@ import com.google.inject.Inject; import org.briarproject.api.FormatException; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; -import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.data.BdfDictionary; @@ -16,7 +15,7 @@ import org.briarproject.api.data.MetadataParser; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; -import org.briarproject.api.db.NoSuchContactException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.messaging.MessagingManager; import org.briarproject.api.messaging.PrivateMessage; import org.briarproject.api.messaging.PrivateMessageHeader; @@ -50,19 +49,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook, Logger.getLogger(MessagingManagerImpl.class.getName()); private final DatabaseComponent db; - private final ContactManager contactManager; private final PrivateGroupFactory privateGroupFactory; private final BdfReaderFactory bdfReaderFactory; private final MetadataEncoder metadataEncoder; private final MetadataParser metadataParser; @Inject - MessagingManagerImpl(DatabaseComponent db, ContactManager contactManager, + MessagingManagerImpl(DatabaseComponent db, PrivateGroupFactory privateGroupFactory, BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder, MetadataParser metadataParser) { this.db = db; - this.contactManager = contactManager; this.privateGroupFactory = privateGroupFactory; this.bdfReaderFactory = bdfReaderFactory; this.metadataEncoder = metadataEncoder; @@ -70,19 +67,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook, } @Override - public void addingContact(Contact c) { + public void addingContact(Transaction txn, Contact c) throws DbException { try { // Create a group to share with the contact Group g = getContactGroup(c); // Store the group and share it with the contact - db.addGroup(g); - db.setVisibleToContact(c.getId(), g.getId(), true); + db.addGroup(txn, g); + db.setVisibleToContact(txn, c.getId(), g.getId(), true); // Attach the contact ID to the group BdfDictionary d = new BdfDictionary(); d.put("contactId", c.getId().getInt()); - db.mergeGroupMetadata(g.getId(), metadataEncoder.encode(d)); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d)); } catch (FormatException e) { throw new RuntimeException(e); } @@ -93,12 +88,8 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook, } @Override - public void removingContact(Contact c) { - try { - db.removeGroup(getContactGroup(c)); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } + public void removingContact(Transaction txn, Contact c) throws DbException { + db.removeGroup(txn, getContactGroup(c)); } @Override @@ -108,15 +99,22 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook, @Override public void addLocalMessage(PrivateMessage m) throws DbException { - BdfDictionary d = new BdfDictionary(); - d.put("timestamp", m.getMessage().getTimestamp()); - if (m.getParent() != null) d.put("parent", m.getParent().getBytes()); - d.put("contentType", m.getContentType()); - d.put("local", true); - d.put("read", true); try { + BdfDictionary d = new BdfDictionary(); + d.put("timestamp", m.getMessage().getTimestamp()); + if (m.getParent() != null) + d.put("parent", m.getParent().getBytes()); + d.put("contentType", m.getContentType()); + d.put("local", true); + d.put("read", true); Metadata meta = metadataEncoder.encode(d); - db.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true); + Transaction txn = db.startTransaction(); + try { + db.addLocalMessage(txn, m.getMessage(), CLIENT_ID, meta, true); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (FormatException e) { throw new RuntimeException(e); } @@ -125,26 +123,48 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook, @Override public ContactId getContactId(GroupId g) throws DbException { try { - BdfDictionary d = metadataParser.parse(db.getGroupMetadata(g)); - long id = d.getInteger("contactId"); - return new ContactId((int) id); + Metadata meta; + Transaction txn = db.startTransaction(); + try { + meta = db.getGroupMetadata(txn, g); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + BdfDictionary d = metadataParser.parse(meta); + return new ContactId(d.getInteger("contactId").intValue()); } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - throw new NoSuchContactException(); + throw new DbException(e); } } @Override public GroupId getConversationId(ContactId c) throws DbException { - return getContactGroup(contactManager.getContact(c)).getId(); + Contact contact; + Transaction txn = db.startTransaction(); + try { + contact = db.getContact(txn, c); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + return getContactGroup(contact).getId(); } @Override public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c) throws DbException { - GroupId groupId = getConversationId(c); - Map<MessageId, Metadata> metadata = db.getMessageMetadata(groupId); - Collection<MessageStatus> statuses = db.getMessageStatus(c, groupId); + GroupId g = getConversationId(c); + Map<MessageId, Metadata> metadata; + Collection<MessageStatus> statuses; + Transaction txn = db.startTransaction(); + try { + metadata = db.getMessageMetadata(txn, g); + statuses = db.getMessageStatus(txn, c, g); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } Collection<PrivateMessageHeader> headers = new ArrayList<PrivateMessageHeader>(); for (MessageStatus s : statuses) { @@ -168,7 +188,14 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook, @Override public byte[] getMessageBody(MessageId m) throws DbException { - byte[] raw = db.getRawMessage(m); + byte[] raw; + Transaction txn = db.startTransaction(); + try { + raw = db.getRawMessage(txn, m); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } ByteArrayInputStream in = new ByteArrayInputStream(raw, MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); BdfReader r = bdfReaderFactory.createReader(in); @@ -191,10 +218,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook, @Override public void setReadFlag(MessageId m, boolean read) throws DbException { - BdfDictionary d = new BdfDictionary(); - d.put("read", read); try { - db.mergeMessageMetadata(m, metadataEncoder.encode(d)); + BdfDictionary d = new BdfDictionary(); + d.put("read", read); + Metadata meta = metadataEncoder.encode(d); + Transaction txn = db.startTransaction(); + try { + db.mergeMessageMetadata(txn, m, meta); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (FormatException e) { throw new RuntimeException(e); } diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java index 66987c4fde649b5e11671d0727fb001ac1589619..bd110d0cf652715543b0b4670adfa7e7fc65f1ba 100644 --- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java +++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java @@ -4,6 +4,7 @@ import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.EventBus; import org.briarproject.api.event.TransportDisabledEvent; import org.briarproject.api.event.TransportEnabledEvent; @@ -27,6 +28,7 @@ import org.briarproject.api.plugins.simplex.SimplexPluginFactory; import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.settings.Settings; +import org.briarproject.api.settings.SettingsManager; import org.briarproject.api.system.Clock; import org.briarproject.api.ui.UiCallback; @@ -60,6 +62,7 @@ class PluginManagerImpl implements PluginManager, Service { private final DatabaseComponent db; private final Poller poller; private final ConnectionManager connectionManager; + private final SettingsManager settingsManager; private final TransportPropertyManager transportPropertyManager; private final UiCallback uiCallback; private final Map<TransportId, Plugin> plugins; @@ -72,6 +75,7 @@ class PluginManagerImpl implements PluginManager, Service { DuplexPluginConfig duplexPluginConfig, Clock clock, DatabaseComponent db, Poller poller, ConnectionManager connectionManager, + SettingsManager settingsManager, TransportPropertyManager transportPropertyManager, UiCallback uiCallback) { this.ioExecutor = ioExecutor; @@ -82,6 +86,7 @@ class PluginManagerImpl implements PluginManager, Service { this.db = db; this.poller = poller; this.connectionManager = connectionManager; + this.settingsManager = settingsManager; this.transportPropertyManager = transportPropertyManager; this.uiCallback = uiCallback; plugins = new ConcurrentHashMap<TransportId, Plugin>(); @@ -181,7 +186,13 @@ class PluginManagerImpl implements PluginManager, Service { } try { long start = clock.currentTimeMillis(); - db.addTransport(id, plugin.getMaxLatency()); + Transaction txn = db.startTransaction(); + try { + db.addTransport(txn, id, plugin.getMaxLatency()); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } long duration = clock.currentTimeMillis() - start; if (LOG.isLoggable(INFO)) LOG.info("Adding transport took " + duration + " ms"); @@ -244,7 +255,13 @@ class PluginManagerImpl implements PluginManager, Service { } try { long start = clock.currentTimeMillis(); - db.addTransport(id, plugin.getMaxLatency()); + Transaction txn = db.startTransaction(); + try { + db.addTransport(txn, id, plugin.getMaxLatency()); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } long duration = clock.currentTimeMillis() - start; if (LOG.isLoggable(INFO)) LOG.info("Adding transport took " + duration + " ms"); @@ -319,7 +336,7 @@ class PluginManagerImpl implements PluginManager, Service { public Settings getSettings() { try { - return db.getSettings(id.getString()); + return settingsManager.getSettings(id.getString()); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); return new Settings(); @@ -348,7 +365,7 @@ class PluginManagerImpl implements PluginManager, Service { public void mergeSettings(Settings s) { try { - db.mergeSettings(s, id.getString()); + settingsManager.mergeSettings(s, id.getString()); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java index a7babd5cdad32f7439e69f081d183e6b388dcf13..1032717f56425f05c1e38fc6e4dab714cbef8299 100644 --- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java +++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java @@ -7,7 +7,6 @@ import org.briarproject.api.FormatException; import org.briarproject.api.TransportId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; -import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.data.BdfDictionary; @@ -21,6 +20,7 @@ import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; import org.briarproject.api.db.NoSuchGroupException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.sync.ClientId; @@ -41,10 +41,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.logging.Logger; -import static java.util.logging.Level.WARNING; import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; @@ -57,11 +54,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0]; - private static final Logger LOG = - Logger.getLogger(TransportPropertyManagerImpl.class.getName()); - private final DatabaseComponent db; - private final ContactManager contactManager; private final PrivateGroupFactory privateGroupFactory; private final MessageFactory messageFactory; private final BdfReaderFactory bdfReaderFactory; @@ -71,18 +64,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, private final Clock clock; private final Group localGroup; - /** Ensures isolation between database reads and writes. */ - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - @Inject TransportPropertyManagerImpl(DatabaseComponent db, - ContactManager contactManager, GroupFactory groupFactory, - PrivateGroupFactory privateGroupFactory, + GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder, MetadataParser metadataParser, Clock clock) { this.db = db; - this.contactManager = contactManager; this.privateGroupFactory = privateGroupFactory; this.messageFactory = messageFactory; this.bdfReaderFactory = bdfReaderFactory; @@ -95,165 +83,171 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, } @Override - public void addingContact(Contact c) { - lock.writeLock().lock(); - try { - // Create a group to share with the contact - Group g = getContactGroup(c); - // Store the group and share it with the contact - db.addGroup(g); - db.setVisibleToContact(c.getId(), g.getId(), true); - // Copy the latest local properties into the group - DeviceId dev = db.getDeviceId(); - Map<TransportId, TransportProperties> local = getLocalProperties(); - for (Entry<TransportId, TransportProperties> e : local.entrySet()) { - storeMessage(g.getId(), dev, e.getKey(), e.getValue(), 1, true, - true); - } - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } finally { - lock.writeLock().unlock(); + public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + // Store the group and share it with the contact + db.addGroup(txn, g); + db.setVisibleToContact(txn, c.getId(), g.getId(), true); + // Copy the latest local properties into the group + DeviceId dev = db.getDeviceId(txn); + Map<TransportId, TransportProperties> local = getLocalProperties(); + for (Entry<TransportId, TransportProperties> e : local.entrySet()) { + storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 1, + true, true); } } @Override - public void removingContact(Contact c) { - lock.writeLock().lock(); - try { - db.removeGroup(getContactGroup(c)); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } finally { - lock.writeLock().unlock(); - } + public void removingContact(Transaction txn, Contact c) throws DbException { + db.removeGroup(txn, getContactGroup(c)); } @Override public void addRemoteProperties(ContactId c, DeviceId dev, Map<TransportId, TransportProperties> props) throws DbException { - lock.writeLock().lock(); + Transaction txn = db.startTransaction(); try { - Group g = getContactGroup(contactManager.getContact(c)); + Group g = getContactGroup(db.getContact(txn, c)); for (Entry<TransportId, TransportProperties> e : props.entrySet()) { - storeMessage(g.getId(), dev, e.getKey(), e.getValue(), 0, false, - false); + storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 0, + false, false); } - } catch (FormatException e) { - throw new DbException(e); + txn.setComplete(); } finally { - lock.writeLock().unlock(); + db.endTransaction(txn); } } @Override public Map<TransportId, TransportProperties> getLocalProperties() throws DbException { - lock.readLock().lock(); try { - // Find the latest local update for each transport - Map<TransportId, LatestUpdate> latest = - findLatest(localGroup.getId(), true); - // Retrieve and parse the latest local properties Map<TransportId, TransportProperties> local = new HashMap<TransportId, TransportProperties>(); - for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) { - byte[] raw = db.getRawMessage(e.getValue().messageId); - local.put(e.getKey(), parseProperties(raw)); + Transaction txn = db.startTransaction(); + try { + // Find the latest local update for each transport + Map<TransportId, LatestUpdate> latest = findLatest(txn, + localGroup.getId(), true); + // Retrieve and parse the latest local properties + for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) { + byte[] raw = db.getRawMessage(txn, e.getValue().messageId); + local.put(e.getKey(), parseProperties(raw)); + } + txn.setComplete(); + } finally { + db.endTransaction(txn); } return Collections.unmodifiableMap(local); } catch (NoSuchGroupException e) { // Local group doesn't exist - there are no local properties return Collections.emptyMap(); - } catch (IOException e) { + } catch (FormatException e) { throw new DbException(e); - } finally { - lock.readLock().unlock(); } } @Override public TransportProperties getLocalProperties(TransportId t) throws DbException { - lock.readLock().lock(); try { - // Find the latest local update - LatestUpdate latest = findLatest(localGroup.getId(), t, true); - if (latest == null) return null; - // Retrieve and parse the latest local properties - return parseProperties(db.getRawMessage(latest.messageId)); + TransportProperties p = null; + Transaction txn = db.startTransaction(); + try { + // Find the latest local update + LatestUpdate latest = findLatest(txn, localGroup.getId(), t, + true); + if (latest != null) { + // Retrieve and parse the latest local properties + byte[] raw = db.getRawMessage(txn, latest.messageId); + p = parseProperties(raw); + } + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + return p; } catch (NoSuchGroupException e) { // Local group doesn't exist - there are no local properties return null; - } catch (IOException e) { + } catch (FormatException e) { throw new DbException(e); - } finally { - lock.readLock().unlock(); } } @Override public Map<ContactId, TransportProperties> getRemoteProperties( TransportId t) throws DbException { - lock.readLock().lock(); try { Map<ContactId, TransportProperties> remote = new HashMap<ContactId, TransportProperties>(); - for (Contact c : contactManager.getContacts()) { - Group g = getContactGroup(c); - // Find the latest remote update - LatestUpdate latest = findLatest(g.getId(), t, false); - if (latest != null) { - // Retrieve and parse the latest remote properties - byte[] raw = db.getRawMessage(latest.messageId); - remote.put(c.getId(), parseProperties(raw)); + Transaction txn = db.startTransaction(); + try { + for (Contact c : db.getContacts(txn)) { + Group g = getContactGroup(c); + // Find the latest remote update + LatestUpdate latest = findLatest(txn, g.getId(), t, false); + if (latest != null) { + // Retrieve and parse the latest remote properties + byte[] raw = db.getRawMessage(txn, latest.messageId); + remote.put(c.getId(), parseProperties(raw)); + } } + txn.setComplete(); + } finally { + db.endTransaction(txn); } return Collections.unmodifiableMap(remote); - } catch (IOException e) { + } catch (FormatException e) { throw new DbException(e); - } finally { - lock.readLock().unlock(); } } @Override public void mergeLocalProperties(TransportId t, TransportProperties p) throws DbException { - lock.writeLock().lock(); try { - // Create the local group if necessary - db.addGroup(localGroup); - // Merge the new properties with any existing properties - TransportProperties merged; - LatestUpdate latest = findLatest(localGroup.getId(), t, true); - if (latest == null) { - merged = p; - } else { - byte[] raw = db.getRawMessage(latest.messageId); - TransportProperties old = parseProperties(raw); - merged = new TransportProperties(old); - merged.putAll(p); - if (merged.equals(old)) return; // Unchanged - } - // Store the merged properties in the local group - DeviceId dev = db.getDeviceId(); - long version = latest == null ? 1 : latest.version + 1; - storeMessage(localGroup.getId(), dev, t, merged, version, true, - false); - // Store the merged properties in each contact's group - for (Contact c : contactManager.getContacts()) { - Group g = getContactGroup(c); - latest = findLatest(g.getId(), t, true); - version = latest == null ? 1 : latest.version + 1; - storeMessage(g.getId(), dev, t, merged, version, true, true); + Transaction txn = db.startTransaction(); + try { + // Create the local group if necessary + db.addGroup(txn, localGroup); + // Merge the new properties with any existing properties + TransportProperties merged; + boolean changed; + LatestUpdate latest = findLatest(txn, localGroup.getId(), t, + true); + if (latest == null) { + merged = p; + changed = true; + } else { + byte[] raw = db.getRawMessage(txn, latest.messageId); + TransportProperties old = parseProperties(raw); + merged = new TransportProperties(old); + merged.putAll(p); + changed = !merged.equals(old); + } + if (changed) { + // Store the merged properties in the local group + DeviceId dev = db.getDeviceId(txn); + long version = latest == null ? 1 : latest.version + 1; + storeMessage(txn, localGroup.getId(), dev, t, merged, + version, true, false); + // Store the merged properties in each contact's group + for (Contact c : db.getContacts(txn)) { + Group g = getContactGroup(c); + latest = findLatest(txn, g.getId(), t, true); + version = latest == null ? 1 : latest.version + 1; + storeMessage(txn, g.getId(), dev, t, merged, version, + true, true); + } + } + txn.setComplete(); + } finally { + db.endTransaction(txn); } - } catch (IOException e) { + } catch (FormatException e) { throw new DbException(e); - } finally { - lock.writeLock().unlock(); } } @@ -261,18 +255,22 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); } - // Locking: lock.writeLock - private void storeMessage(GroupId g, DeviceId dev, TransportId t, - TransportProperties p, long version, boolean local, boolean shared) - throws DbException, FormatException { - byte[] body = encodeProperties(dev, t, p, version); - long now = clock.currentTimeMillis(); - Message m = messageFactory.createMessage(g, now, body); - BdfDictionary d = new BdfDictionary(); - d.put("transportId", t.getString()); - d.put("version", version); - d.put("local", local); - db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d), shared); + private void storeMessage(Transaction txn, GroupId g, DeviceId dev, + TransportId t, TransportProperties p, long version, boolean local, + boolean shared) throws DbException { + try { + byte[] body = encodeProperties(dev, t, p, version); + long now = clock.currentTimeMillis(); + Message m = messageFactory.createMessage(g, now, body); + BdfDictionary d = new BdfDictionary(); + d.put("transportId", t.getString()); + d.put("version", version); + d.put("local", local); + Metadata meta = metadataEncoder.encode(d); + db.addLocalMessage(txn, m, CLIENT_ID, meta, shared); + } catch (FormatException e) { + throw new RuntimeException(e); + } } private byte[] encodeProperties(DeviceId dev, TransportId t, @@ -293,12 +291,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, return out.toByteArray(); } - // Locking: lock.readLock - private Map<TransportId, LatestUpdate> findLatest(GroupId g, boolean local) - throws DbException, FormatException { + private Map<TransportId, LatestUpdate> findLatest(Transaction txn, + GroupId g, boolean local) throws DbException, FormatException { Map<TransportId, LatestUpdate> latestUpdates = new HashMap<TransportId, LatestUpdate>(); - Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); + Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g); for (Entry<MessageId, Metadata> e : metadata.entrySet()) { BdfDictionary d = metadataParser.parse(e.getValue()); if (d.getBoolean("local") == local) { @@ -312,11 +309,10 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, return latestUpdates; } - // Locking: lock.readLock - private LatestUpdate findLatest(GroupId g, TransportId t, boolean local) - throws DbException, FormatException { + private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t, + boolean local) throws DbException, FormatException { LatestUpdate latest = null; - Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); + Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g); for (Entry<MessageId, Metadata> e : metadata.entrySet()) { BdfDictionary d = metadataParser.parse(e.getValue()); if (d.getString("transportId").equals(t.getString()) @@ -330,25 +326,32 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, } private TransportProperties parseProperties(byte[] raw) - throws IOException { + throws FormatException { TransportProperties p = new TransportProperties(); ByteArrayInputStream in = new ByteArrayInputStream(raw, MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); BdfReader r = bdfReaderFactory.createReader(in); - r.readListStart(); - r.skipRaw(); // Device ID - r.skipString(); // Transport ID - r.skipInteger(); // Version - r.readDictionaryStart(); - while (!r.hasDictionaryEnd()) { - String key = r.readString(MAX_PROPERTY_LENGTH); - String value = r.readString(MAX_PROPERTY_LENGTH); - p.put(key, value); + try { + r.readListStart(); + r.skipRaw(); // Device ID + r.skipString(); // Transport ID + r.skipInteger(); // Version + r.readDictionaryStart(); + while (!r.hasDictionaryEnd()) { + String key = r.readString(MAX_PROPERTY_LENGTH); + String value = r.readString(MAX_PROPERTY_LENGTH); + p.put(key, value); + } + r.readDictionaryEnd(); + r.readListEnd(); + if (!r.eof()) throw new FormatException(); + return p; + } catch (FormatException e) { + throw e; + } catch (IOException e) { + // Shouldn't happen with ByteArrayInputStream + throw new RuntimeException(e); } - r.readDictionaryEnd(); - r.readListEnd(); - if (!r.eof()) throw new FormatException(); - return p; } private static class LatestUpdate { diff --git a/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java b/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java index ee7a3ed681ae900589170f53b03176932482e431..639d822bbc0c57c0eed488a6f509e9fa493f58af 100644 --- a/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java +++ b/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java @@ -4,6 +4,7 @@ import com.google.inject.Inject; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.settings.Settings; import org.briarproject.api.settings.SettingsManager; @@ -18,11 +19,24 @@ class SettingsManagerImpl implements SettingsManager { @Override public Settings getSettings(String namespace) throws DbException { - return db.getSettings(namespace); + Transaction txn = db.startTransaction(); + try { + Settings s = db.getSettings(txn, namespace); + txn.setComplete(); + return s; + } finally { + db.endTransaction(txn); + } } @Override public void mergeSettings(Settings s, String namespace) throws DbException { - db.mergeSettings(s, namespace); + Transaction txn = db.startTransaction(); + try { + db.mergeSettings(txn, s, namespace); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } } diff --git a/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java b/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java index d0af71201dcc29592b158a590f55028b7d391c7a..8976eb7d16be1f951098999468f57a810740341c 100644 --- a/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java +++ b/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java @@ -4,6 +4,7 @@ import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; @@ -50,8 +51,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener { private static final ThrowingRunnable<IOException> CLOSE = new ThrowingRunnable<IOException>() { - public void run() {} - }; + public void run() {} + }; private final DatabaseComponent db; private final Executor dbExecutor; @@ -178,7 +179,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener { public void run() { if (interrupted) return; try { - Ack a = db.generateAck(contactId, MAX_MESSAGE_IDS); + Ack a; + Transaction txn = db.startTransaction(); + try { + a = db.generateAck(txn, contactId, MAX_MESSAGE_IDS); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } if (LOG.isLoggable(INFO)) LOG.info("Generated ack: " + (a != null)); if (a != null) writerTasks.add(new WriteAck(a)); @@ -212,8 +220,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener { public void run() { if (interrupted) return; try { - Collection<byte[]> b = db.generateRequestedBatch(contactId, - MAX_PACKET_PAYLOAD_LENGTH, maxLatency); + Collection<byte[]> b; + Transaction txn = db.startTransaction(); + try { + b = db.generateRequestedBatch(txn, contactId, + MAX_PACKET_PAYLOAD_LENGTH, maxLatency); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } if (LOG.isLoggable(INFO)) LOG.info("Generated batch: " + (b != null)); if (b != null) writerTasks.add(new WriteBatch(b)); @@ -247,8 +262,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener { public void run() { if (interrupted) return; try { - Offer o = db.generateOffer(contactId, MAX_MESSAGE_IDS, - maxLatency); + Offer o; + Transaction txn = db.startTransaction(); + try { + o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS, + maxLatency); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } if (LOG.isLoggable(INFO)) LOG.info("Generated offer: " + (o != null)); if (o != null) writerTasks.add(new WriteOffer(o)); @@ -282,7 +304,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener { public void run() { if (interrupted) return; try { - Request r = db.generateRequest(contactId, MAX_MESSAGE_IDS); + Request r; + Transaction txn = db.startTransaction(); + try { + r = db.generateRequest(txn, contactId, MAX_MESSAGE_IDS); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } if (LOG.isLoggable(INFO)) LOG.info("Generated request: " + (r != null)); if (r != null) writerTasks.add(new WriteRequest(r)); diff --git a/briar-core/src/org/briarproject/sync/IncomingSession.java b/briar-core/src/org/briarproject/sync/IncomingSession.java index 6fbfdd2426ce1938f665f83d7dad59124b0dd22d..5e5f4c6df220bcfb23ef55b6417d07eb8f79ed65 100644 --- a/briar-core/src/org/briarproject/sync/IncomingSession.java +++ b/briar-core/src/org/briarproject/sync/IncomingSession.java @@ -5,6 +5,7 @@ import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; @@ -24,7 +25,9 @@ import java.util.logging.Logger; import static java.util.logging.Level.WARNING; -/** An incoming {@link org.briarproject.api.sync.SyncSession SyncSession}. */ +/** + * An incoming {@link org.briarproject.api.sync.SyncSession SyncSession}. + */ class IncomingSession implements SyncSession, EventListener { private static final Logger LOG = @@ -103,7 +106,13 @@ class IncomingSession implements SyncSession, EventListener { public void run() { try { - db.receiveAck(contactId, ack); + Transaction txn = db.startTransaction(); + try { + db.receiveAck(txn, contactId, ack); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); interrupt(); @@ -121,7 +130,13 @@ class IncomingSession implements SyncSession, EventListener { public void run() { try { - db.receiveMessage(contactId, message); + Transaction txn = db.startTransaction(); + try { + db.receiveMessage(txn, contactId, message); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); interrupt(); @@ -139,7 +154,13 @@ class IncomingSession implements SyncSession, EventListener { public void run() { try { - db.receiveOffer(contactId, offer); + Transaction txn = db.startTransaction(); + try { + db.receiveOffer(txn, contactId, offer); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); interrupt(); @@ -157,7 +178,13 @@ class IncomingSession implements SyncSession, EventListener { public void run() { try { - db.receiveRequest(contactId, request); + Transaction txn = db.startTransaction(); + try { + db.receiveRequest(txn, contactId, request); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); interrupt(); diff --git a/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java b/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java index 620e23a4b76b64f527fe6fadd7f4220be64d7557..31ca6268485c4f18b2de9f1c469e1a579287120d 100644 --- a/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java +++ b/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java @@ -4,6 +4,7 @@ import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; @@ -40,8 +41,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener { private static final ThrowingRunnable<IOException> CLOSE = new ThrowingRunnable<IOException>() { - public void run() {} - }; + public void run() {} + }; private final DatabaseComponent db; private final Executor dbExecutor; @@ -119,7 +120,14 @@ class SimplexOutgoingSession implements SyncSession, EventListener { public void run() { if (interrupted) return; try { - Ack a = db.generateAck(contactId, MAX_MESSAGE_IDS); + Ack a; + Transaction txn = db.startTransaction(); + try { + a = db.generateAck(txn, contactId, MAX_MESSAGE_IDS); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } if (LOG.isLoggable(INFO)) LOG.info("Generated ack: " + (a != null)); if (a == null) decrementOutstandingQueries(); @@ -154,8 +162,15 @@ class SimplexOutgoingSession implements SyncSession, EventListener { public void run() { if (interrupted) return; try { - Collection<byte[]> b = db.generateBatch(contactId, - MAX_PACKET_PAYLOAD_LENGTH, maxLatency); + Collection<byte[]> b; + Transaction txn = db.startTransaction(); + try { + b = db.generateBatch(txn, contactId, + MAX_PACKET_PAYLOAD_LENGTH, maxLatency); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } if (LOG.isLoggable(INFO)) LOG.info("Generated batch: " + (b != null)); if (b == null) decrementOutstandingQueries(); diff --git a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java index 5fca1458452be5ed09fc776e6de3d3f3b1c1f773..e075f341f58a4633d5f45223a57af96c659a4573 100644 --- a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java +++ b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java @@ -9,7 +9,7 @@ import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; import org.briarproject.api.db.NoSuchGroupException; -import org.briarproject.api.db.NoSuchMessageException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.MessageAddedEvent; @@ -82,14 +82,17 @@ class ValidationManagerImpl implements ValidationManager, Service, public void run() { try { // TODO: Don't do all of this in a single DB task - for (MessageId id : db.getMessagesToValidate(c)) { - try { - Message m = parseMessage(id, db.getRawMessage(id)); - Group g = db.getGroup(m.getGroupId()); + Transaction txn = db.startTransaction(); + try { + for (MessageId id : db.getMessagesToValidate(txn, c)) { + byte[] raw = db.getRawMessage(txn, id); + Message m = parseMessage(id, raw); + Group g = db.getGroup(txn, m.getGroupId()); validateMessage(m, g); - } catch (NoSuchMessageException e) { - LOG.info("Message removed before validation"); } + txn.setComplete(); + } finally { + db.endTransaction(txn); } } catch (DbException e) { if (LOG.isLoggable(WARNING)) @@ -127,17 +130,21 @@ class ValidationManagerImpl implements ValidationManager, Service, dbExecutor.execute(new Runnable() { public void run() { try { - if (meta == null) { - db.setMessageValid(m, c, false); - } else { - for (ValidationHook hook : hooks) - hook.validatingMessage(m, c, meta); - db.mergeMessageMetadata(m.getId(), meta); - db.setMessageValid(m, c, true); - db.setMessageShared(m, true); + Transaction txn = db.startTransaction(); + try { + if (meta == null) { + db.setMessageValid(txn, m, c, false); + } else { + for (ValidationHook hook : hooks) + hook.validatingMessage(txn, m, c, meta); + db.mergeMessageMetadata(txn, m.getId(), meta); + db.setMessageValid(txn, m, c, true); + db.setMessageShared(txn, m, true); + } + txn.setComplete(); + } finally { + db.endTransaction(txn); } - } catch (NoSuchMessageException e) { - LOG.info("Message removed during validation"); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -159,7 +166,13 @@ class ValidationManagerImpl implements ValidationManager, Service, dbExecutor.execute(new Runnable() { public void run() { try { - validateMessage(m, db.getGroup(m.getGroupId())); + Transaction txn = db.startTransaction(); + try { + validateMessage(m, db.getGroup(txn, m.getGroupId())); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (NoSuchGroupException e) { LOG.info("Group removed before validation"); } catch (DbException e) { diff --git a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java index b1fa018fc63caae5a1bee6bcf0a2a04fb154c947..7e31a877c02f00f1e1f819d1c20dd485b7943ed7 100644 --- a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java +++ b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java @@ -7,6 +7,7 @@ import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventListener; @@ -55,7 +56,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener { @Override public boolean start() { try { - Map<TransportId, Integer> latencies = db.getTransportLatencies(); + Map<TransportId, Integer> latencies; + Transaction txn = db.startTransaction(); + try { + latencies = db.getTransportLatencies(txn); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } for (Entry<TransportId, Integer> e : latencies.entrySet()) addTransport(e.getKey(), e.getValue()); } catch (DbException e) { diff --git a/briar-core/src/org/briarproject/transport/TransportKeyManager.java b/briar-core/src/org/briarproject/transport/TransportKeyManager.java index 2df96ccc71dc88fb2d19e8d4ea1574d740286588..ce15995ac47fc18d8a16ae8e2c1d6fa2fecc27e7 100644 --- a/briar-core/src/org/briarproject/transport/TransportKeyManager.java +++ b/briar-core/src/org/briarproject/transport/TransportKeyManager.java @@ -7,6 +7,7 @@ import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import org.briarproject.api.system.Clock; import org.briarproject.api.system.Timer; import org.briarproject.api.transport.StreamContext; @@ -66,7 +67,13 @@ class TransportKeyManager extends TimerTask { // Load the transport keys from the DB Map<ContactId, TransportKeys> loaded; try { - loaded = db.getTransportKeys(transportId); + Transaction txn = db.startTransaction(); + try { + loaded = db.getTransportKeys(txn, transportId); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); return; @@ -90,7 +97,13 @@ class TransportKeyManager extends TimerTask { for (Entry<ContactId, TransportKeys> e : current.entrySet()) addKeys(e.getKey(), new MutableTransportKeys(e.getValue())); // Write any rotated keys back to the DB - db.updateTransportKeys(rotated); + Transaction txn = db.startTransaction(); + try { + db.updateTransportKeys(txn, rotated); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } finally { @@ -135,7 +148,13 @@ class TransportKeyManager extends TimerTask { // Initialise mutable state for the contact addKeys(c, new MutableTransportKeys(k)); // Write the keys back to the DB - db.addTransportKeys(c, k); + Transaction txn = db.startTransaction(); + try { + db.addTransportKeys(txn, c, k); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } finally { @@ -171,8 +190,14 @@ class TransportKeyManager extends TimerTask { outKeys.getStreamCounter()); // Increment the stream counter and write it back to the DB outKeys.incrementStreamCounter(); - db.incrementStreamCounter(c, transportId, - outKeys.getRotationPeriod()); + Transaction txn = db.startTransaction(); + try { + db.incrementStreamCounter(txn, c, transportId, + outKeys.getRotationPeriod()); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } return ctx; } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -210,9 +235,15 @@ class TransportKeyManager extends TimerTask { inContexts.remove(new Bytes(removeTag)); } // Write the window back to the DB - db.setReorderingWindow(tagCtx.contactId, transportId, - inKeys.getRotationPeriod(), window.getBase(), - window.getBitmap()); + Transaction txn = db.startTransaction(); + try { + db.setReorderingWindow(txn, tagCtx.contactId, transportId, + inKeys.getRotationPeriod(), window.getBase(), + window.getBitmap()); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } return ctx; } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -249,7 +280,13 @@ class TransportKeyManager extends TimerTask { for (Entry<ContactId, TransportKeys> e : current.entrySet()) addKeys(e.getKey(), new MutableTransportKeys(e.getValue())); // Write any rotated keys back to the DB - db.updateTransportKeys(rotated); + Transaction txn = db.startTransaction(); + try { + db.updateTransportKeys(txn, rotated); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } finally { diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java index 0743df9495bca6e22c29329ac6454fa863ac2dc1..48e6ea037b05ad57cf77e2a91952323e5f4d0385 100644 --- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java +++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java @@ -14,6 +14,7 @@ import org.briarproject.api.db.NoSuchLocalAuthorException; import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.NoSuchTransportException; import org.briarproject.api.db.StorageStatus; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.EventBus; import org.briarproject.api.event.GroupAddedEvent; import org.briarproject.api.event.GroupRemovedEvent; @@ -50,11 +51,13 @@ import org.junit.Test; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Map; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; import static org.briarproject.api.sync.ValidationManager.Validity.VALID; +import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -121,14 +124,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { final ShutdownManager shutdown = context.mock(ShutdownManager.class); final EventBus eventBus = context.mock(EventBus.class); context.checking(new Expectations() {{ - exactly(9).of(database).startTransaction(); - will(returnValue(txn)); - exactly(9).of(database).commitTransaction(txn); // open() oneOf(database).open(); will(returnValue(false)); oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); will(returnValue(shutdownHandle)); + // startTransaction() + oneOf(database).startTransaction(); + will(returnValue(txn)); // addLocalAuthor() oneOf(database).containsLocalAuthor(txn, localAuthorId); will(returnValue(false)); @@ -171,6 +174,8 @@ public class DatabaseComponentImplTest extends BriarTestCase { oneOf(database).containsLocalAuthor(txn, localAuthorId); will(returnValue(true)); oneOf(database).removeLocalAuthor(txn, localAuthorId); + // endTransaction() + oneOf(database).commitTransaction(txn); // close() oneOf(shutdown).removeShutdownHook(shutdownHandle); oneOf(database).close(); @@ -179,15 +184,21 @@ public class DatabaseComponentImplTest extends BriarTestCase { shutdown); assertFalse(db.open()); - db.addLocalAuthor(localAuthor); - assertEquals(contactId, db.addContact(author, localAuthorId)); - assertEquals(Collections.singletonList(contact), db.getContacts()); - db.addGroup(group); // First time - listeners called - db.addGroup(group); // Second time - not called - assertEquals(Collections.singletonList(group), db.getGroups(clientId)); - db.removeGroup(group); - db.removeContact(contactId); - db.removeLocalAuthor(localAuthorId); + Transaction transaction = db.startTransaction(); + db.addLocalAuthor(transaction, localAuthor); + assertEquals(contactId, + db.addContact(transaction, author, localAuthorId)); + assertEquals(Collections.singletonList(contact), + db.getContacts(transaction)); + db.addGroup(transaction, group); // First time - listeners called + db.addGroup(transaction, group); // Second time - not called + assertEquals(Collections.singletonList(group), + db.getGroups(transaction, clientId)); + db.removeGroup(transaction, group); + db.removeContact(transaction, contactId); + db.removeLocalAuthor(transaction, localAuthorId); + transaction.setComplete(); + db.endTransaction(transaction); db.close(); context.assertIsSatisfied(); @@ -211,11 +222,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); + Transaction transaction = db.startTransaction(); try { - db.addLocalMessage(message, clientId, metadata, true); + db.addLocalMessage(transaction, message, clientId, metadata, true); fail(); } catch (NoSuchGroupException expected) { // Expected + } finally { + db.endTransaction(transaction); } context.assertIsSatisfied(); @@ -253,7 +267,13 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.addLocalMessage(message, clientId, metadata, true); + Transaction transaction = db.startTransaction(); + try { + db.addLocalMessage(transaction, message, clientId, metadata, true); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -277,126 +297,178 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); + Transaction transaction = db.startTransaction(); try { - db.addTransportKeys(contactId, createTransportKeys()); + db.addTransportKeys(transaction, contactId, createTransportKeys()); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.generateAck(contactId, 123); + db.generateAck(transaction, contactId, 123); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.generateBatch(contactId, 123, 456); + db.generateBatch(transaction, contactId, 123, 456); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.generateOffer(contactId, 123, 456); + db.generateOffer(transaction, contactId, 123, 456); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.generateRequest(contactId, 123); + db.generateRequest(transaction, contactId, 123); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getContact(contactId); + db.getContact(transaction, contactId); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getMessageStatus(contactId, groupId); + db.getMessageStatus(transaction, contactId, groupId); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getMessageStatus(contactId, messageId); + db.getMessageStatus(transaction, contactId, messageId); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.incrementStreamCounter(contactId, transportId, 0); + db.incrementStreamCounter(transaction, contactId, transportId, 0); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.isVisibleToContact(contactId, groupId); + db.isVisibleToContact(transaction, contactId, groupId); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { Ack a = new Ack(Collections.singletonList(messageId)); - db.receiveAck(contactId, a); + db.receiveAck(transaction, contactId, a); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.receiveMessage(contactId, message); + db.receiveMessage(transaction, contactId, message); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { Offer o = new Offer(Collections.singletonList(messageId)); - db.receiveOffer(contactId, o); + db.receiveOffer(transaction, contactId, o); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { Request r = new Request(Collections.singletonList(messageId)); - db.receiveRequest(contactId, r); + db.receiveRequest(transaction, contactId, r); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.removeContact(contactId); + db.removeContact(transaction, contactId); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.setReorderingWindow(contactId, transportId, 0, 0, new byte[4]); + db.setReorderingWindow(transaction, contactId, transportId, 0, 0, + new byte[REORDERING_WINDOW_SIZE / 8]); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.setVisibleToContact(contactId, groupId, true); + db.setVisibleToContact(transaction, contactId, groupId, true); fail(); } catch (NoSuchContactException expected) { // Expected + } finally { + db.endTransaction(transaction); } context.assertIsSatisfied(); @@ -421,25 +493,34 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); + Transaction transaction = db.startTransaction(); try { - db.addContact(author, localAuthorId); + db.addContact(transaction, author, localAuthorId); fail(); } catch (NoSuchLocalAuthorException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getLocalAuthor(localAuthorId); + db.getLocalAuthor(transaction, localAuthorId); fail(); } catch (NoSuchLocalAuthorException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.removeLocalAuthor(localAuthorId); + db.removeLocalAuthor(transaction, localAuthorId); fail(); } catch (NoSuchLocalAuthorException expected) { // Expected + } finally { + db.endTransaction(transaction); } context.assertIsSatisfied(); @@ -468,53 +549,74 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); + Transaction transaction = db.startTransaction(); try { - db.getGroup(groupId); + db.getGroup(transaction, groupId); fail(); } catch (NoSuchGroupException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getGroupMetadata(groupId); + db.getGroupMetadata(transaction, groupId); fail(); } catch (NoSuchGroupException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getMessageStatus(contactId, groupId); + db.getMessageStatus(transaction, contactId, groupId); fail(); } catch (NoSuchGroupException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.isVisibleToContact(contactId, groupId); + db.isVisibleToContact(transaction, contactId, groupId); fail(); } catch (NoSuchGroupException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.mergeGroupMetadata(groupId, metadata); + db.mergeGroupMetadata(transaction, groupId, metadata); fail(); } catch (NoSuchGroupException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.removeGroup(group); + db.removeGroup(transaction, group); fail(); } catch (NoSuchGroupException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.setVisibleToContact(contactId, groupId, true); + db.setVisibleToContact(transaction, contactId, groupId, true); fail(); } catch (NoSuchGroupException expected) { // Expected + } finally { + db.endTransaction(transaction); } context.assertIsSatisfied(); @@ -542,60 +644,84 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); + Transaction transaction = db.startTransaction(); try { - db.deleteMessage(messageId); + db.deleteMessage(transaction, messageId); fail(); } catch (NoSuchMessageException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.deleteMessageMetadata(messageId); + db.deleteMessageMetadata(transaction, messageId); fail(); } catch (NoSuchMessageException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getRawMessage(messageId); + db.getRawMessage(transaction, messageId); fail(); } catch (NoSuchMessageException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getMessageMetadata(messageId); + db.getMessageMetadata(transaction, messageId); fail(); } catch (NoSuchMessageException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.getMessageStatus(contactId, messageId); + db.getMessageStatus(transaction, contactId, messageId); fail(); } catch (NoSuchMessageException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.mergeMessageMetadata(messageId, metadata); + db.mergeMessageMetadata(transaction, messageId, metadata); fail(); } catch (NoSuchMessageException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.setMessageShared(message, true); + db.setMessageShared(transaction, message, true); fail(); } catch (NoSuchMessageException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.setMessageValid(message, clientId, true); + db.setMessageValid(transaction, message, clientId, true); fail(); } catch (NoSuchMessageException expected) { // Expected + } finally { + db.endTransaction(transaction); } context.assertIsSatisfied(); @@ -610,22 +736,21 @@ public class DatabaseComponentImplTest extends BriarTestCase { final ShutdownManager shutdown = context.mock(ShutdownManager.class); final EventBus eventBus = context.mock(EventBus.class); context.checking(new Expectations() {{ - // addLocalAuthor() + // startTransaction() oneOf(database).startTransaction(); will(returnValue(txn)); + // addLocalAuthor() oneOf(database).containsLocalAuthor(txn, localAuthorId); will(returnValue(false)); oneOf(database).addLocalAuthor(txn, localAuthor); - oneOf(database).commitTransaction(txn); // addContact() - oneOf(database).startTransaction(); - will(returnValue(txn)); oneOf(database).containsLocalAuthor(txn, localAuthorId); will(returnValue(true)); oneOf(database).containsContact(txn, authorId, localAuthorId); will(returnValue(false)); oneOf(database).addContact(txn, author, localAuthorId); will(returnValue(contactId)); + // endTransaction() oneOf(database).commitTransaction(txn); // Check whether the transport is in the DB (which it's not) exactly(4).of(database).startTransaction(); @@ -639,35 +764,55 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.addLocalAuthor(localAuthor); - assertEquals(contactId, db.addContact(author, localAuthorId)); + Transaction transaction = db.startTransaction(); + try { + db.addLocalAuthor(transaction, localAuthor); + assertEquals(contactId, + db.addContact(transaction, author, localAuthorId)); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } + transaction = db.startTransaction(); try { - db.getTransportKeys(transportId); + db.getTransportKeys(transaction, transportId); fail(); } catch (NoSuchTransportException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.incrementStreamCounter(contactId, transportId, 0); + db.incrementStreamCounter(transaction, contactId, transportId, 0); fail(); } catch (NoSuchTransportException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.removeTransport(transportId); + db.removeTransport(transaction, transportId); fail(); } catch (NoSuchTransportException expected) { // Expected + } finally { + db.endTransaction(transaction); } + transaction = db.startTransaction(); try { - db.setReorderingWindow(contactId, transportId, 0, 0, new byte[4]); + db.setReorderingWindow(transaction, contactId, transportId, 0, 0, + new byte[REORDERING_WINDOW_SIZE / 8]); fail(); } catch (NoSuchTransportException expected) { // Expected + } finally { + db.endTransaction(transaction); } context.assertIsSatisfied(); @@ -695,8 +840,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - Ack a = db.generateAck(contactId, 123); - assertEquals(messagesToAck, a.getMessageIds()); + Transaction transaction = db.startTransaction(); + try { + Ack a = db.generateAck(transaction, contactId, 123); + assertEquals(messagesToAck, a.getMessageIds()); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -733,8 +884,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - assertEquals(messages, db.generateBatch(contactId, size * 2, - maxLatency)); + Transaction transaction = db.startTransaction(); + try { + assertEquals(messages, db.generateBatch(transaction, contactId, + size * 2, maxLatency)); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -764,8 +921,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - Offer o = db.generateOffer(contactId, 123, maxLatency); - assertEquals(ids, o.getMessageIds()); + Transaction transaction = db.startTransaction(); + try { + Offer o = db.generateOffer(transaction, contactId, 123, maxLatency); + assertEquals(ids, o.getMessageIds()); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -792,8 +955,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - Request r = db.generateRequest(contactId, 123); - assertEquals(ids, r.getMessageIds()); + Transaction transaction = db.startTransaction(); + try { + Request r = db.generateRequest(transaction, contactId, 123); + assertEquals(ids, r.getMessageIds()); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -831,8 +1000,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - assertEquals(messages, db.generateRequestedBatch(contactId, size * 2, - maxLatency)); + Transaction transaction = db.startTransaction(); + try { + assertEquals(messages, db.generateRequestedBatch(transaction, + contactId, size * 2, maxLatency)); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -858,7 +1033,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.receiveAck(contactId, new Ack(Collections.singletonList(messageId))); + Transaction transaction = db.startTransaction(); + try { + Ack a = new Ack(Collections.singletonList(messageId)); + db.receiveAck(transaction, contactId, a); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -896,7 +1078,13 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.receiveMessage(contactId, message); + Transaction transaction = db.startTransaction(); + try { + db.receiveMessage(transaction, contactId, message); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -926,7 +1114,13 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.receiveMessage(contactId, message); + Transaction transaction = db.startTransaction(); + try { + db.receiveMessage(transaction, contactId, message); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -952,7 +1146,13 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.receiveMessage(contactId, message); + Transaction transaction = db.startTransaction(); + try { + db.receiveMessage(transaction, contactId, message); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -998,9 +1198,16 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - Offer o = new Offer(Arrays.asList(messageId, messageId1, messageId2, - messageId3)); - db.receiveOffer(contactId, o); + Transaction transaction = db.startTransaction(); + try { + Offer o = new Offer(Arrays.asList(messageId, messageId1, + messageId2, messageId3)); + db.receiveOffer(transaction, contactId, o); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } + context.assertIsSatisfied(); } @@ -1026,8 +1233,14 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.receiveRequest(contactId, new Request(Collections.singletonList( - messageId))); + Transaction transaction = db.startTransaction(); + try { + Request r = new Request(Collections.singletonList(messageId)); + db.receiveRequest(transaction, contactId, r); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -1056,7 +1269,13 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.setVisibleToContact(contactId, groupId, true); + Transaction transaction = db.startTransaction(); + try { + db.setVisibleToContact(transaction, contactId, groupId, true); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -1083,45 +1302,56 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.setVisibleToContact(contactId, groupId, true); + Transaction transaction = db.startTransaction(); + try { + db.setVisibleToContact(transaction, contactId, groupId, true); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @Test public void testTransportKeys() throws Exception { - final TransportKeys keys = createTransportKeys(); + final TransportKeys transportKeys = createTransportKeys(); + final Map<ContactId, TransportKeys> keys = Collections.singletonMap( + contactId, transportKeys); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class); final EventBus eventBus = context.mock(EventBus.class); context.checking(new Expectations() {{ - // updateTransportKeys() + // startTransaction() oneOf(database).startTransaction(); will(returnValue(txn)); + // updateTransportKeys() oneOf(database).containsContact(txn, contactId); will(returnValue(true)); oneOf(database).containsTransport(txn, transportId); will(returnValue(true)); - oneOf(database).updateTransportKeys(txn, - Collections.singletonMap(contactId, keys)); - oneOf(database).commitTransaction(txn); + oneOf(database).updateTransportKeys(txn, keys); // getTransportKeys() - oneOf(database).startTransaction(); - will(returnValue(txn)); oneOf(database).containsTransport(txn, transportId); will(returnValue(true)); oneOf(database).getTransportKeys(txn, transportId); - will(returnValue(Collections.singletonMap(contactId, keys))); + will(returnValue(keys)); + // endTransaction() oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.updateTransportKeys(Collections.singletonMap(contactId, keys)); - assertEquals(Collections.singletonMap(contactId, keys), - db.getTransportKeys(transportId)); + Transaction transaction = db.startTransaction(); + try { + db.updateTransportKeys(transaction, keys); + assertEquals(keys, db.getTransportKeys(transaction, transportId)); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } @@ -1162,29 +1392,34 @@ public class DatabaseComponentImplTest extends BriarTestCase { final ShutdownManager shutdown = context.mock(ShutdownManager.class); final EventBus eventBus = context.mock(EventBus.class); context.checking(new Expectations() {{ - // mergeSettings() + // startTransaction() oneOf(database).startTransaction(); will(returnValue(txn)); + // mergeSettings() oneOf(database).getSettings(txn, "namespace"); will(returnValue(before)); oneOf(database).mergeSettings(txn, update, "namespace"); - oneOf(database).commitTransaction(txn); oneOf(eventBus).broadcast(with(any(SettingsUpdatedEvent.class))); // mergeSettings() again - oneOf(database).startTransaction(); - will(returnValue(txn)); oneOf(database).getSettings(txn, "namespace"); will(returnValue(merged)); + // endTransaction() oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - // First merge should broadcast an event - db.mergeSettings(update, "namespace"); - // Second merge should not broadcast an event - db.mergeSettings(update, "namespace"); + Transaction transaction = db.startTransaction(); + try { + // First merge should broadcast an event + db.mergeSettings(transaction, update, "namespace"); + // Second merge should not broadcast an event + db.mergeSettings(transaction, update, "namespace"); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } context.assertIsSatisfied(); } diff --git a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java index 23fbddea8dc420ac9afa29677a0454b6c8ec8edf..25cb91d0352c0f7e3d372d47861436696f7f4626 100644 --- a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java +++ b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java @@ -3,6 +3,7 @@ package org.briarproject.plugins; import org.briarproject.BriarTestCase; import org.briarproject.api.TransportId; import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.EventBus; import org.briarproject.api.plugins.ConnectionManager; import org.briarproject.api.plugins.duplex.DuplexPlugin; @@ -14,6 +15,7 @@ import org.briarproject.api.plugins.simplex.SimplexPluginCallback; import org.briarproject.api.plugins.simplex.SimplexPluginConfig; import org.briarproject.api.plugins.simplex.SimplexPluginFactory; import org.briarproject.api.properties.TransportPropertyManager; +import org.briarproject.api.settings.SettingsManager; import org.briarproject.api.system.Clock; import org.briarproject.api.ui.UiCallback; import org.briarproject.system.SystemClock; @@ -46,6 +48,8 @@ public class PluginManagerImplTest extends BriarTestCase { final Poller poller = context.mock(Poller.class); final ConnectionManager connectionManager = context.mock(ConnectionManager.class); + final SettingsManager settingsManager = + context.mock(SettingsManager.class); final TransportPropertyManager transportPropertyManager = context.mock(TransportPropertyManager.class); final UiCallback uiCallback = context.mock(UiCallback.class); @@ -55,18 +59,21 @@ public class PluginManagerImplTest extends BriarTestCase { final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class); final TransportId simplexId = new TransportId("simplex"); final int simplexLatency = 12345; + final Transaction simplexTxn = new Transaction(null); final SimplexPluginFactory simplexFailFactory = context.mock(SimplexPluginFactory.class, "simplexFailFactory"); final SimplexPlugin simplexFailPlugin = context.mock(SimplexPlugin.class, "simplexFailPlugin"); final TransportId simplexFailId = new TransportId("simplex1"); final int simplexFailLatency = 23456; + final Transaction simplexFailTxn = new Transaction(null); // Two duplex plugin factories: one creates a plugin, the other fails final DuplexPluginFactory duplexFactory = context.mock(DuplexPluginFactory.class); final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class); final TransportId duplexId = new TransportId("duplex"); final int duplexLatency = 34567; + final Transaction duplexTxn = new Transaction(null); final DuplexPluginFactory duplexFailFactory = context.mock(DuplexPluginFactory.class, "duplexFailFactory"); final TransportId duplexFailId = new TransportId("duplex1"); @@ -82,7 +89,10 @@ public class PluginManagerImplTest extends BriarTestCase { will(returnValue(simplexPlugin)); // Created oneOf(simplexPlugin).getMaxLatency(); will(returnValue(simplexLatency)); - oneOf(db).addTransport(simplexId, simplexLatency); + oneOf(db).startTransaction(); + will(returnValue(simplexTxn)); + oneOf(db).addTransport(simplexTxn, simplexId, simplexLatency); + oneOf(db).endTransaction(simplexTxn); oneOf(simplexPlugin).start(); will(returnValue(true)); // Started oneOf(simplexPlugin).shouldPoll(); @@ -96,7 +106,11 @@ public class PluginManagerImplTest extends BriarTestCase { will(returnValue(simplexFailPlugin)); // Created oneOf(simplexFailPlugin).getMaxLatency(); will(returnValue(simplexFailLatency)); - oneOf(db).addTransport(simplexFailId, simplexFailLatency); + oneOf(db).startTransaction(); + will(returnValue(simplexFailTxn)); + oneOf(db).addTransport(simplexFailTxn, simplexFailId, + simplexFailLatency); + oneOf(db).endTransaction(simplexFailTxn); oneOf(simplexFailPlugin).start(); will(returnValue(false)); // Failed to start // First duplex plugin @@ -109,7 +123,10 @@ public class PluginManagerImplTest extends BriarTestCase { will(returnValue(duplexPlugin)); // Created oneOf(duplexPlugin).getMaxLatency(); will(returnValue(duplexLatency)); - oneOf(db).addTransport(duplexId, duplexLatency); + oneOf(db).startTransaction(); + will(returnValue(duplexTxn)); + oneOf(db).addTransport(duplexTxn, duplexId, duplexLatency); + oneOf(db).endTransaction(duplexTxn); oneOf(duplexPlugin).start(); will(returnValue(true)); // Started oneOf(duplexPlugin).shouldPoll(); @@ -128,7 +145,8 @@ public class PluginManagerImplTest extends BriarTestCase { }}); PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus, simplexPluginConfig, duplexPluginConfig, clock, db, poller, - connectionManager, transportPropertyManager, uiCallback); + connectionManager, settingsManager, transportPropertyManager, + uiCallback); // Two plugins should be started and stopped assertTrue(p.start()); diff --git a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java index a4bd5d20e44c644504e3449fd919e72c54ab8c99..bfb815c2d52c1d0a4042278f1d4f641a84423f80 100644 --- a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java +++ b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java @@ -13,6 +13,7 @@ import org.briarproject.api.contact.ContactManager; import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.StorageStatus; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventListener; @@ -120,7 +121,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { lifecycleManager.startServices(); lifecycleManager.waitForStartup(); // Add a transport - db.addTransport(transportId, MAX_LATENCY); + Transaction txn = db.startTransaction(); + try { + db.addTransport(txn, transportId, MAX_LATENCY); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } // Add an identity for Alice LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp, @@ -185,7 +192,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { lifecycleManager.startServices(); lifecycleManager.waitForStartup(); // Add a transport - db.addTransport(transportId, MAX_LATENCY); + Transaction txn = db.startTransaction(); + try { + db.addTransport(txn, transportId, MAX_LATENCY); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } // Add an identity for Bob LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob", new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp, diff --git a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java index 7b49108ef5df3c7904d6390872d8ad0f8b7b63d8..635b2b0685934562a494303500850b0900ecd98a 100644 --- a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java +++ b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java @@ -5,6 +5,7 @@ import org.briarproject.TestUtils; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.Transaction; import org.briarproject.api.event.EventBus; import org.briarproject.api.sync.Ack; import org.briarproject.api.sync.MessageId; @@ -49,16 +50,24 @@ public class SimplexOutgoingSessionTest extends BriarTestCase { final SimplexOutgoingSession session = new SimplexOutgoingSession(db, dbExecutor, eventBus, contactId, transportId, maxLatency, packetWriter); + final Transaction noAckTxn = new Transaction(null); + final Transaction noMsgTxn = new Transaction(null); context.checking(new Expectations() {{ // Add listener oneOf(eventBus).addListener(session); // No acks to send - oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS); + oneOf(db).startTransaction(); + will(returnValue(noAckTxn)); + oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS); will(returnValue(null)); + oneOf(db).endTransaction(noAckTxn); // No messages to send - oneOf(db).generateBatch(with(contactId), with(any(int.class)), - with(maxLatency)); + oneOf(db).startTransaction(); + will(returnValue(noMsgTxn)); + oneOf(db).generateBatch(with(noMsgTxn), with(contactId), + with(any(int.class)), with(maxLatency)); will(returnValue(null)); + oneOf(db).endTransaction(noMsgTxn); // Flush the output stream oneOf(packetWriter).flush(); // Remove listener @@ -75,25 +84,41 @@ public class SimplexOutgoingSessionTest extends BriarTestCase { final SimplexOutgoingSession session = new SimplexOutgoingSession(db, dbExecutor, eventBus, contactId, transportId, maxLatency, packetWriter); + final Transaction ackTxn = new Transaction(null); + final Transaction noAckTxn = new Transaction(null); + final Transaction msgTxn = new Transaction(null); + final Transaction noMsgTxn = new Transaction(null); context.checking(new Expectations() {{ // Add listener oneOf(eventBus).addListener(session); // One ack to send - oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS); + oneOf(db).startTransaction(); + will(returnValue(ackTxn)); + oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS); will(returnValue(ack)); + oneOf(db).endTransaction(ackTxn); oneOf(packetWriter).writeAck(ack); - // No more acks - oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS); - will(returnValue(null)); // One message to send - oneOf(db).generateBatch(with(contactId), with(any(int.class)), - with(maxLatency)); + oneOf(db).startTransaction(); + will(returnValue(msgTxn)); + oneOf(db).generateBatch(with(msgTxn), with(contactId), + with(any(int.class)), with(maxLatency)); will(returnValue(Arrays.asList(raw))); + oneOf(db).endTransaction(msgTxn); oneOf(packetWriter).writeMessage(raw); + // No more acks + oneOf(db).startTransaction(); + will(returnValue(noAckTxn)); + oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS); + will(returnValue(null)); + oneOf(db).endTransaction(noAckTxn); // No more messages - oneOf(db).generateBatch(with(contactId), with(any(int.class)), - with(maxLatency)); + oneOf(db).startTransaction(); + will(returnValue(noMsgTxn)); + oneOf(db).generateBatch(with(noMsgTxn), with(contactId), + with(any(int.class)), with(maxLatency)); will(returnValue(null)); + oneOf(db).endTransaction(noMsgTxn); // Flush the output stream oneOf(packetWriter).flush(); // Remove listener