mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
Refactor ValidationManager and fix some bugs. #619
This commit is contained in:
@@ -59,12 +59,12 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
|
||||
import static org.briarproject.api.blogs.MessageType.COMMENT;
|
||||
import static org.briarproject.api.blogs.MessageType.POST;
|
||||
import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
|
||||
@@ -298,8 +298,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
|
||||
meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor()));
|
||||
meta.put(KEY_READ, true);
|
||||
clientHelper.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta,
|
||||
true);
|
||||
clientHelper.addLocalMessage(txn, p.getMessage(), meta, true);
|
||||
|
||||
// broadcast event about new post
|
||||
GroupId groupId = p.getMessage().getGroupId();
|
||||
@@ -346,7 +345,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
meta.put(KEY_AUTHOR, authorToBdfDictionary(author));
|
||||
|
||||
// Send comment
|
||||
clientHelper.addLocalMessage(txn, message, CLIENT_ID, meta, true);
|
||||
clientHelper.addLocalMessage(txn, message, meta, true);
|
||||
|
||||
// broadcast event
|
||||
BlogPostHeader h =
|
||||
@@ -429,7 +428,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
meta.put(KEY_TIME_RECEIVED, pOriginalHeader.getTimeReceived());
|
||||
|
||||
// Send wrapped message and store metadata
|
||||
clientHelper.addLocalMessage(txn, wMessage, CLIENT_ID, meta, true);
|
||||
clientHelper.addLocalMessage(txn, wMessage, meta, true);
|
||||
return wMessage.getId();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
|
||||
@@ -114,7 +114,7 @@ class BlogPostValidator extends BdfMessageValidator {
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
|
||||
meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
|
||||
return new BdfMessageContext(meta, null);
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
|
||||
private BdfMessageContext validateComment(Message m, Group g, BdfList body)
|
||||
@@ -197,7 +197,7 @@ class BlogPostValidator extends BdfMessageValidator {
|
||||
meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
|
||||
meta.put(KEY_TIMESTAMP, wTimestamp);
|
||||
meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
|
||||
return new BdfMessageContext(meta, null);
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
|
||||
private BdfMessageContext validateWrappedComment(Message m, Group g,
|
||||
|
||||
@@ -18,7 +18,6 @@ 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.sync.ClientId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageFactory;
|
||||
@@ -62,11 +61,11 @@ class ClientHelperImpl implements ClientHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalMessage(Message m, ClientId c, BdfDictionary metadata,
|
||||
public void addLocalMessage(Message m, BdfDictionary metadata,
|
||||
boolean shared) throws DbException, FormatException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
addLocalMessage(txn, m, c, metadata, shared);
|
||||
addLocalMessage(txn, m, metadata, shared);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -74,10 +73,10 @@ class ClientHelperImpl implements ClientHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalMessage(Transaction txn, Message m, ClientId c,
|
||||
public void addLocalMessage(Transaction txn, Message m,
|
||||
BdfDictionary metadata, boolean shared)
|
||||
throws DbException, FormatException {
|
||||
db.addLocalMessage(txn, m, c, metadataEncoder.encode(metadata), shared);
|
||||
db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,10 +16,10 @@ import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.InvalidMessageException;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageContext;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.api.sync.MessageContext;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -70,7 +70,7 @@ class MessageQueueManagerImpl implements MessageQueueManager {
|
||||
saveQueueState(txn, queue.getId(), queueState);
|
||||
QueueMessage q = queueMessageFactory.createMessage(queue.getId(),
|
||||
timestamp, queuePosition, body);
|
||||
db.addLocalMessage(txn, q, queue.getClientId(), meta, true);
|
||||
db.addLocalMessage(txn, q, meta, true);
|
||||
return q;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageStatus;
|
||||
import org.briarproject.api.sync.ValidationManager.State;
|
||||
import org.briarproject.api.transport.TransportKeys;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@@ -83,10 +84,10 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Adds a dependency between two MessageIds
|
||||
* Adds a dependency between two messages in the given group.
|
||||
*/
|
||||
void addMessageDependency(T txn, MessageId dependentId,
|
||||
MessageId dependencyId) throws DbException;
|
||||
void addMessageDependency(T txn, GroupId g, MessageId dependent,
|
||||
MessageId dependency) throws DbException;
|
||||
|
||||
/**
|
||||
* Records that a message has been offered by the given contact.
|
||||
@@ -281,11 +282,13 @@ interface Database<T> {
|
||||
Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the dependencies of the given message.
|
||||
* This method makes sure that dependencies in different groups
|
||||
* are returned as {@link ValidationManager.State.INVALID}. Note that this
|
||||
* is not set on the dependencies themselves; the returned states should
|
||||
* only be taken in the context of the given message.
|
||||
* Returns the IDs and states of all dependencies of the given message.
|
||||
* Missing dependencies have the state {@link
|
||||
* org.briarproject.api.sync.ValidationManager.State UNKNOWN}.
|
||||
* Dependencies in other groups have the state {@link
|
||||
* org.briarproject.api.sync.ValidationManager.State INVALID}.
|
||||
* Note that these states are not set on the dependencies themselves; the
|
||||
* returned states should only be taken in the context of the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -293,7 +296,9 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all IDs of messages that depend on the given message.
|
||||
* Returns all IDs and states of all dependents of the given message.
|
||||
* Messages in other groups that declare a dependency on the given message
|
||||
* will be returned even though such dependencies are invalid.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -350,6 +355,13 @@ interface Database<T> {
|
||||
*/
|
||||
Metadata getMessageMetadata(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the validation and delivery state of the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
State getMessageState(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the status of all messages in the given group with respect
|
||||
* to the given contact.
|
||||
@@ -377,15 +389,6 @@ interface Database<T> {
|
||||
Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that need to be delivered to the given
|
||||
* client.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToDeliver(T txn, ClientId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of some messages that are eligible to be offered to the
|
||||
* given contact, up to the given number of messages.
|
||||
@@ -437,6 +440,7 @@ interface Database<T> {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@Nullable
|
||||
byte[] getRawMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
@@ -592,7 +596,7 @@ interface Database<T> {
|
||||
* Marks the given contact as active or inactive.
|
||||
*/
|
||||
void setContactActive(T txn, ContactId c, boolean active)
|
||||
throws DbException;
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given message as shared or unshared.
|
||||
@@ -601,10 +605,9 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given message as valid or invalid.
|
||||
* Sets the validation and delivery state of the given message.
|
||||
*/
|
||||
void setMessageState(T txn, MessageId m, State state)
|
||||
throws DbException;
|
||||
void setMessageState(T txn, MessageId m, State state) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the reordering window for the given contact and transport in the
|
||||
|
||||
@@ -28,10 +28,10 @@ import org.briarproject.api.event.LocalAuthorRemovedEvent;
|
||||
import org.briarproject.api.event.MessageAddedEvent;
|
||||
import org.briarproject.api.event.MessageRequestedEvent;
|
||||
import org.briarproject.api.event.MessageSharedEvent;
|
||||
import org.briarproject.api.event.MessageStateChangedEvent;
|
||||
import org.briarproject.api.event.MessageToAckEvent;
|
||||
import org.briarproject.api.event.MessageToRequestEvent;
|
||||
import org.briarproject.api.event.MessagesAckedEvent;
|
||||
import org.briarproject.api.event.MessageStateChangedEvent;
|
||||
import org.briarproject.api.event.MessagesSentEvent;
|
||||
import org.briarproject.api.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.api.identity.Author;
|
||||
@@ -50,6 +50,7 @@ import org.briarproject.api.sync.Offer;
|
||||
import org.briarproject.api.sync.Request;
|
||||
import org.briarproject.api.sync.ValidationManager.State;
|
||||
import org.briarproject.api.transport.TransportKeys;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -188,7 +189,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public void addLocalMessage(Transaction transaction, Message m, ClientId c,
|
||||
public void addLocalMessage(Transaction transaction, Message m,
|
||||
Metadata meta, boolean shared) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
@@ -197,7 +198,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (!db.containsMessage(txn, m.getId())) {
|
||||
addMessage(txn, m, DELIVERED, shared);
|
||||
transaction.attach(new MessageAddedEvent(m, null));
|
||||
transaction.attach(new MessageStateChangedEvent(m, c, true,
|
||||
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
||||
DELIVERED));
|
||||
if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
|
||||
}
|
||||
@@ -271,6 +272,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.deleteMessageMetadata(txn, m);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Ack generateAck(Transaction transaction, ContactId c,
|
||||
int maxMessages) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
@@ -283,6 +285,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return new Ack(ids);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Collection<byte[]> generateBatch(Transaction transaction,
|
||||
ContactId c, int maxLength, int maxLatency) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
@@ -301,6 +304,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return Collections.unmodifiableList(messages);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Offer generateOffer(Transaction transaction, ContactId c,
|
||||
int maxMessages, int maxLatency) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
@@ -313,6 +317,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return new Offer(ids);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Request generateRequest(Transaction transaction, ContactId c,
|
||||
int maxMessages) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
@@ -326,6 +331,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return new Request(ids);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Collection<byte[]> generateRequestedBatch(Transaction transaction,
|
||||
ContactId c, int maxLength, int maxLatency) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
@@ -420,18 +426,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessagesToValidate(txn, c);
|
||||
}
|
||||
|
||||
public Collection<MessageId> getMessagesToDeliver(Transaction transaction,
|
||||
ClientId c) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getMessagesToDeliver(txn, c);
|
||||
}
|
||||
|
||||
public Collection<MessageId> getPendingMessages(Transaction transaction,
|
||||
ClientId c) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getPendingMessages(txn, c);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] getRawMessage(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
@@ -473,6 +474,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessageMetadataForValidator(txn, m);
|
||||
}
|
||||
|
||||
public State getMessageState(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
return db.getMessageState(txn, m);
|
||||
}
|
||||
|
||||
public Collection<MessageStatus> getMessageStatus(Transaction transaction,
|
||||
ContactId c, GroupId g) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
@@ -720,14 +729,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (shared) transaction.attach(new MessageSharedEvent(m));
|
||||
}
|
||||
|
||||
public void setMessageState(Transaction transaction, Message m, ClientId c,
|
||||
public void setMessageState(Transaction transaction, MessageId m,
|
||||
State state) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m.getId()))
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
db.setMessageState(txn, m.getId(), state);
|
||||
transaction.attach(new MessageStateChangedEvent(m, c, false, state));
|
||||
db.setMessageState(txn, m, state);
|
||||
transaction.attach(new MessageStateChangedEvent(m, false, state));
|
||||
}
|
||||
|
||||
public void addMessageDependencies(Transaction transaction,
|
||||
@@ -737,8 +746,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, dependent.getId()))
|
||||
throw new NoSuchMessageException();
|
||||
for (MessageId dependencyId : dependencies) {
|
||||
db.addMessageDependency(txn, dependent.getId(), dependencyId);
|
||||
for (MessageId dependency : dependencies) {
|
||||
db.addMessageDependency(txn, dependent.getGroupId(),
|
||||
dependent.getId(), dependency);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.briarproject.api.transport.IncomingKeys;
|
||||
import org.briarproject.api.transport.OutgoingKeys;
|
||||
import org.briarproject.api.transport.TransportKeys;
|
||||
import org.briarproject.util.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.sql.Connection;
|
||||
@@ -53,7 +54,6 @@ import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.VALID;
|
||||
import static org.briarproject.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.db.DatabaseConstants.DEVICE_ID_KEY;
|
||||
import static org.briarproject.db.DatabaseConstants.DEVICE_SETTINGS_NAMESPACE;
|
||||
@@ -67,8 +67,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
|
||||
*/
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
private static final int SCHEMA_VERSION = 26;
|
||||
private static final int MIN_SCHEMA_VERSION = 26;
|
||||
private static final int SCHEMA_VERSION = 27;
|
||||
private static final int MIN_SCHEMA_VERSION = 27;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -155,8 +155,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
||||
"CREATE TABLE messageDependencies"
|
||||
+ " (messageId HASH NOT NULL,"
|
||||
+ " (groupId HASH NOT NULL,"
|
||||
+ " messageId HASH NOT NULL,"
|
||||
+ " dependencyId HASH NOT NULL," // Not a foreign key
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
@@ -609,16 +613,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public void addMessageDependency(Connection txn, MessageId dependentId,
|
||||
MessageId dependencyId) throws DbException {
|
||||
public void addMessageDependency(Connection txn, GroupId g,
|
||||
MessageId dependent, MessageId dependency) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql =
|
||||
"INSERT INTO messageDependencies (messageId, dependencyId)"
|
||||
+ " VALUES (?, ?)";
|
||||
String sql = "INSERT INTO messageDependencies"
|
||||
+ " (groupId, messageId, dependencyId)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, dependentId.getBytes());
|
||||
ps.setBytes(2, dependencyId.getBytes());
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setBytes(2, dependent.getBytes());
|
||||
ps.setBytes(3, dependency.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -1453,7 +1458,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT d.dependencyId, m.state, m.groupId"
|
||||
String sql = "SELECT d.dependencyId, m.state, d.groupId, m.groupId"
|
||||
+ " FROM messageDependencies AS d"
|
||||
+ " LEFT OUTER JOIN messages AS m"
|
||||
+ " ON d.dependencyId = m.messageId"
|
||||
@@ -1463,13 +1468,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, State> dependencies = new HashMap<MessageId, State>();
|
||||
while (rs.next()) {
|
||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||
MessageId dependency = new MessageId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
if (state != UNKNOWN) {
|
||||
// set dependency invalid if it is in a different group
|
||||
if (!hasGroupId(txn, m, rs.getBytes(3))) state = INVALID;
|
||||
if (rs.wasNull()) {
|
||||
state = UNKNOWN; // Missing dependency
|
||||
} else {
|
||||
GroupId dependentGroupId = new GroupId(rs.getBytes(3));
|
||||
GroupId dependencyGroupId = new GroupId(rs.getBytes(4));
|
||||
if (!dependentGroupId.equals(dependencyGroupId))
|
||||
state = INVALID; // Dependency in another group
|
||||
}
|
||||
dependencies.put(messageId, state);
|
||||
dependencies.put(dependency, state);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1481,29 +1490,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasGroupId(Connection txn, MessageId m, byte[] g)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT NULL FROM messages"
|
||||
+ " WHERE messageId = ? AND groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setBytes(2, g);
|
||||
rs = ps.executeQuery();
|
||||
boolean same = rs.next();
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return same;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<MessageId, State> getMessageDependents(Connection txn,
|
||||
MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
@@ -1511,7 +1497,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
String sql = "SELECT d.messageId, m.state"
|
||||
+ " FROM messageDependencies AS d"
|
||||
+ " LEFT OUTER JOIN messages AS m"
|
||||
+ " JOIN messages AS m"
|
||||
+ " ON d.messageId = m.messageId"
|
||||
+ " WHERE dependencyId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
@@ -1519,9 +1505,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, State> dependents = new HashMap<MessageId, State>();
|
||||
while (rs.next()) {
|
||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||
MessageId dependent = new MessageId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
dependents.put(messageId, state);
|
||||
dependents.put(dependent, state);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1533,6 +1519,28 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
public State getMessageState(Connection txn, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT state FROM messages WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
State state = State.fromValue(rs.getInt(1));
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return state;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<MessageId> getMessagesToAck(Connection txn, ContactId c,
|
||||
int maxMessages) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
@@ -1655,11 +1663,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
return getMessagesInState(txn, c, UNKNOWN);
|
||||
}
|
||||
|
||||
public Collection<MessageId> getMessagesToDeliver(Connection txn,
|
||||
ClientId c) throws DbException {
|
||||
return getMessagesInState(txn, c, VALID);
|
||||
}
|
||||
|
||||
public Collection<MessageId> getPendingMessages(Connection txn,
|
||||
ClientId c) throws DbException {
|
||||
return getMessagesInState(txn, c, PENDING);
|
||||
@@ -1689,6 +1692,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] getRawMessage(Connection txn, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -49,9 +48,6 @@ import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
|
||||
|
||||
class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumManagerImpl.class.getName());
|
||||
|
||||
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
|
||||
"859a7be50dca035b64bd6902fb797097"
|
||||
+ "795af837abbf8c16d750b3c2ccc186ea"));
|
||||
@@ -133,7 +129,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||
}
|
||||
meta.put(KEY_LOCAL, true);
|
||||
meta.put(KEY_READ, true);
|
||||
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
|
||||
clientHelper.addLocalMessage(p.getMessage(), meta, true);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
}
|
||||
// Return the metadata and dependencies
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
Collection<MessageId> dependencies = null;
|
||||
Collection<MessageId> dependencies = Collections.emptyList();
|
||||
meta.put("timestamp", m.getTimestamp());
|
||||
if (parent != null) {
|
||||
meta.put("parent", parent);
|
||||
|
||||
@@ -177,8 +177,7 @@ class IntroduceeManager {
|
||||
d.put(REMOTE_AUTHOR_IS_US, introducesOtherIdentity);
|
||||
|
||||
// save local state to database
|
||||
clientHelper.addLocalMessage(txn, localMsg,
|
||||
IntroductionManagerImpl.CLIENT_ID, d, false);
|
||||
clientHelper.addLocalMessage(txn, localMsg, d, false);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -98,9 +98,7 @@ class IntroducerManager {
|
||||
d.put(AUTHOR_ID_2, c2.getAuthor().getId());
|
||||
|
||||
// save local state to database
|
||||
clientHelper
|
||||
.addLocalMessage(txn, m, IntroductionManagerImpl.CLIENT_ID, d,
|
||||
false);
|
||||
clientHelper.addLocalMessage(txn, m, d, false);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ class MessagingManagerImpl extends BdfIncomingMessageHook
|
||||
meta.put("contentType", m.getContentType());
|
||||
meta.put("local", true);
|
||||
meta.put("read", true);
|
||||
clientHelper.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true);
|
||||
clientHelper.addLocalMessage(m.getMessage(), meta, true);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
meta.put("transportId", t.getString());
|
||||
meta.put("version", version);
|
||||
meta.put("local", local);
|
||||
clientHelper.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
|
||||
clientHelper.addLocalMessage(txn, m, meta, shared);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
|
||||
|
||||
// save local state to database
|
||||
BdfDictionary d = s.toBdfDictionary();
|
||||
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
|
||||
clientHelper.addLocalMessage(txn, m, d, false);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -652,7 +652,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
|
||||
|
||||
// save local state to database
|
||||
BdfDictionary d = s.toBdfDictionary();
|
||||
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
|
||||
clientHelper.addLocalMessage(txn, m, d, false);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.VALID;
|
||||
|
||||
class ValidationManagerImpl implements ValidationManager, Service,
|
||||
EventListener {
|
||||
@@ -71,8 +70,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
public void startService() {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
for (ClientId c : validators.keySet()) {
|
||||
validateOutstandingMessages(c);
|
||||
deliverOutstandingMessages(c);
|
||||
validateOutstandingMessagesAsync(c);
|
||||
deliverOutstandingMessagesAsync(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,168 +90,156 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
hooks.put(c, hook);
|
||||
}
|
||||
|
||||
private void validateOutstandingMessages(final ClientId c) {
|
||||
private void validateOutstandingMessagesAsync(final ClientId c) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
unvalidated.addAll(db.getMessagesToValidate(txn, c));
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
validateNextMessage(unvalidated);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
validateOutstandingMessages(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void validateNextMessage(final Queue<MessageId> unvalidated) {
|
||||
private void validateOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
unvalidated.addAll(db.getMessagesToValidate(txn, c));
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
validateNextMessageAsync(unvalidated);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNextMessageAsync(final Queue<MessageId> unvalidated) {
|
||||
if (unvalidated.isEmpty()) return;
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Message m = null;
|
||||
Group g = null;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
MessageId id = unvalidated.poll();
|
||||
byte[] raw = db.getRawMessage(txn, id);
|
||||
m = parseMessage(id, raw);
|
||||
g = db.getGroup(txn, m.getGroupId());
|
||||
txn.setComplete();
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Message removed before validation");
|
||||
// Continue to next message
|
||||
} catch (NoSuchGroupException e) {
|
||||
LOG.info("Group removed before validation");
|
||||
// Continue to next message
|
||||
} finally {
|
||||
if (!txn.isComplete()) txn.setComplete();
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
if (m != null && g != null) validateMessage(m, g);
|
||||
validateNextMessage(unvalidated);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
validateNextMessage(unvalidated);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deliverOutstandingMessages(final ClientId c) {
|
||||
private void validateNextMessage(Queue<MessageId> unvalidated) {
|
||||
try {
|
||||
Message m;
|
||||
Group g;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
MessageId id = unvalidated.poll();
|
||||
byte[] raw = db.getRawMessage(txn, id);
|
||||
m = parseMessage(id, raw);
|
||||
g = db.getGroup(txn, m.getGroupId());
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
validateMessageAsync(m, g);
|
||||
validateNextMessageAsync(unvalidated);
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Message removed before validation");
|
||||
validateNextMessageAsync(unvalidated);
|
||||
} catch (NoSuchGroupException e) {
|
||||
LOG.info("Group removed before validation");
|
||||
validateNextMessageAsync(unvalidated);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverOutstandingMessagesAsync(final ClientId c) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Queue<MessageId> validated = new LinkedList<MessageId>();
|
||||
Queue<MessageId> pending = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
validated.addAll(db.getMessagesToDeliver(txn, c));
|
||||
pending.addAll(db.getPendingMessages(txn, c));
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
deliverNextMessage(validated);
|
||||
deliverNextPendingMessage(pending);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
deliverOutstandingMessages(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deliverNextMessage(final Queue<MessageId> validated) {
|
||||
if (validated.isEmpty()) return;
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Message m = null;
|
||||
Group g = null;
|
||||
Metadata meta = null;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
MessageId id = validated.poll();
|
||||
byte[] raw = db.getRawMessage(txn, id);
|
||||
m = parseMessage(id, raw);
|
||||
g = db.getGroup(txn, m.getGroupId());
|
||||
meta = db.getMessageMetadataForValidator(txn, id);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
if (g != null) deliverMessage(m, g.getClientId(), meta);
|
||||
deliverNextMessage(validated);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
private void deliverOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> pending = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
pending.addAll(db.getPendingMessages(txn, c));
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
});
|
||||
deliverNextPendingMessageAsync(pending);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverNextPendingMessage(final Queue<MessageId> pending) {
|
||||
private void deliverNextPendingMessageAsync(
|
||||
final Queue<MessageId> pending) {
|
||||
if (pending.isEmpty()) return;
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Message m = null;
|
||||
ClientId c = null;
|
||||
try {
|
||||
boolean allDelivered = true;
|
||||
Metadata meta = null;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
MessageId id = pending.poll();
|
||||
byte[] raw = db.getRawMessage(txn, id);
|
||||
m = parseMessage(id, raw);
|
||||
Group g = db.getGroup(txn, m.getGroupId());
|
||||
c = g.getClientId();
|
||||
|
||||
// check if a dependency is invalid
|
||||
Map<MessageId, State> states =
|
||||
db.getMessageDependencies(txn, id);
|
||||
for (Entry<MessageId, State> d : states.entrySet()) {
|
||||
if (d.getValue() == INVALID) {
|
||||
throw new InvalidMessageException(
|
||||
"Invalid Dependency");
|
||||
}
|
||||
if (d.getValue() != DELIVERED) allDelivered = false;
|
||||
}
|
||||
if (allDelivered)
|
||||
meta = db.getMessageMetadataForValidator(txn, id);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
if (!txn.isComplete()) txn.setComplete();
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
if (c != null && allDelivered) deliverMessage(m, c, meta);
|
||||
deliverNextPendingMessage(pending);
|
||||
} catch(InvalidMessageException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.log(INFO, e.toString(), e);
|
||||
markMessageInvalid(m, c);
|
||||
deliverNextPendingMessage(pending);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
deliverNextPendingMessage(pending);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deliverNextPendingMessage(Queue<MessageId> pending) {
|
||||
try {
|
||||
boolean anyInvalid = false, allDelivered = true;
|
||||
Queue<MessageId> invalidate = null;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
MessageId id = pending.poll();
|
||||
// Check if message is still pending
|
||||
if (db.getMessageState(txn, id) == PENDING) {
|
||||
// Check if dependencies are valid and delivered
|
||||
Map<MessageId, State> states =
|
||||
db.getMessageDependencies(txn, id);
|
||||
for (Entry<MessageId, State> e : states.entrySet()) {
|
||||
if (e.getValue() == INVALID) anyInvalid = true;
|
||||
if (e.getValue() != DELIVERED) allDelivered = false;
|
||||
}
|
||||
if (anyInvalid) {
|
||||
if (db.getMessageState(txn, id) != INVALID) {
|
||||
invalidateMessage(txn, id);
|
||||
invalidate = getDependentsToInvalidate(txn, id);
|
||||
}
|
||||
} else if (allDelivered) {
|
||||
Message m = parseMessage(id, db.getRawMessage(txn, id));
|
||||
Group g = db.getGroup(txn, m.getGroupId());
|
||||
ClientId c = g.getClientId();
|
||||
Metadata meta = db.getMessageMetadataForValidator(txn,
|
||||
id);
|
||||
if (deliverMessage(txn, m, c, meta)) {
|
||||
pending.addAll(getPendingDependents(txn, id));
|
||||
} else {
|
||||
invalidate = getDependentsToInvalidate(txn, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
if (invalidate != null) invalidateNextMessageAsync(invalidate);
|
||||
deliverNextPendingMessageAsync(pending);
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Message removed before delivery");
|
||||
deliverNextPendingMessageAsync(pending);
|
||||
} catch (NoSuchGroupException e) {
|
||||
LOG.info("Group removed before delivery");
|
||||
deliverNextPendingMessageAsync(pending);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Message parseMessage(MessageId id, byte[] raw) {
|
||||
if (raw.length <= MESSAGE_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
@@ -262,199 +249,168 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
return new Message(id, new GroupId(groupId), timestamp, raw);
|
||||
}
|
||||
|
||||
private void validateMessage(final Message m, final Group g) {
|
||||
private void validateMessageAsync(final Message m, final Group g) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MessageValidator v = validators.get(g.getClientId());
|
||||
if (v == null) {
|
||||
LOG.warning("No validator");
|
||||
} else {
|
||||
try {
|
||||
MessageContext context = v.validateMessage(m, g);
|
||||
storeMessageContext(m, g.getClientId(), context);
|
||||
} catch (InvalidMessageException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.log(INFO, e.toString(), e);
|
||||
markMessageInvalid(m, g.getClientId());
|
||||
}
|
||||
}
|
||||
validateMessage(m, g);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeMessageContext(final Message m, final ClientId c,
|
||||
private void validateMessage(Message m, Group g) {
|
||||
MessageValidator v = validators.get(g.getClientId());
|
||||
if (v == null) {
|
||||
LOG.warning("No validator");
|
||||
} else {
|
||||
try {
|
||||
MessageContext context = v.validateMessage(m, g);
|
||||
storeMessageContextAsync(m, g.getClientId(), context);
|
||||
} catch (InvalidMessageException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
Queue<MessageId> invalidate = new LinkedList<MessageId>();
|
||||
invalidate.add(m.getId());
|
||||
invalidateNextMessageAsync(invalidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void storeMessageContextAsync(final Message m, final ClientId c,
|
||||
final MessageContext result) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
State newState = null;
|
||||
Metadata meta = null;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// store dependencies
|
||||
Collection<MessageId> dependencies =
|
||||
result.getDependencies();
|
||||
if (dependencies != null && dependencies.size() > 0) {
|
||||
db.addMessageDependencies(txn, m, dependencies);
|
||||
}
|
||||
// check if a dependency is invalid
|
||||
// and if all dependencies have been delivered
|
||||
Map<MessageId, State> states =
|
||||
db.getMessageDependencies(txn, m.getId());
|
||||
newState = VALID;
|
||||
for (Entry<MessageId, State> d : states.entrySet()) {
|
||||
if (d.getValue() == INVALID) {
|
||||
throw new InvalidMessageException(
|
||||
"Dependency Invalid");
|
||||
}
|
||||
if (d.getValue() != DELIVERED) {
|
||||
newState = PENDING;
|
||||
LOG.info("depend. undelivered, set to PENDING");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// save metadata and new message state
|
||||
meta = result.getMetadata();
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
db.setMessageState(txn, m, c, newState);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
if (!txn.isComplete()) txn.setComplete();
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
// deliver message if valid
|
||||
if (newState == VALID) {
|
||||
deliverMessage(m, c, meta);
|
||||
}
|
||||
} catch (InvalidMessageException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.log(INFO, e.toString(), e);
|
||||
markMessageInvalid(m, c);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
storeMessageContext(m, c, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deliverMessage(final Message m, final ClientId c,
|
||||
final Metadata meta) {
|
||||
private void storeMessageContext(Message m, ClientId c,
|
||||
MessageContext result) {
|
||||
try {
|
||||
MessageId id = m.getId();
|
||||
boolean anyInvalid = false, allDelivered = true;
|
||||
Queue<MessageId> invalidate = null;
|
||||
Queue<MessageId> pending = null;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// Check if message has any dependencies
|
||||
Collection<MessageId> dependencies = result.getDependencies();
|
||||
if (!dependencies.isEmpty()) {
|
||||
db.addMessageDependencies(txn, m, dependencies);
|
||||
// Check if dependencies are valid and delivered
|
||||
Map<MessageId, State> states =
|
||||
db.getMessageDependencies(txn, id);
|
||||
for (Entry<MessageId, State> e : states.entrySet()) {
|
||||
if (e.getValue() == INVALID) anyInvalid = true;
|
||||
if (e.getValue() != DELIVERED) allDelivered = false;
|
||||
}
|
||||
}
|
||||
if (anyInvalid) {
|
||||
if (db.getMessageState(txn, id) != INVALID) {
|
||||
invalidateMessage(txn, id);
|
||||
invalidate = getDependentsToInvalidate(txn, id);
|
||||
}
|
||||
} else {
|
||||
Metadata meta = result.getMetadata();
|
||||
db.mergeMessageMetadata(txn, id, meta);
|
||||
if (allDelivered) {
|
||||
if (deliverMessage(txn, m, c, meta)) {
|
||||
pending = getPendingDependents(txn, id);
|
||||
} else {
|
||||
invalidate = getDependentsToInvalidate(txn, id);
|
||||
}
|
||||
} else {
|
||||
db.setMessageState(txn, id, PENDING);
|
||||
}
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
if (invalidate != null) invalidateNextMessageAsync(invalidate);
|
||||
if (pending != null) deliverNextPendingMessageAsync(pending);
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Message removed during validation");
|
||||
} catch (NoSuchGroupException e) {
|
||||
LOG.info("Group removed during validation");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deliverMessage(Transaction txn, Message m, ClientId c,
|
||||
Metadata meta) throws DbException {
|
||||
IncomingMessageHook hook = hooks.get(c);
|
||||
if (hook == null) throw new DbException();
|
||||
hook.incomingMessage(txn, m, meta);
|
||||
// TODO: Find a better way for clients to signal validity, #643
|
||||
if (db.getRawMessage(txn, m.getId()) == null) {
|
||||
db.setMessageState(txn, m.getId(), INVALID);
|
||||
return false;
|
||||
} else {
|
||||
db.setMessageState(txn, m.getId(), DELIVERED);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private Queue<MessageId> getPendingDependents(Transaction txn, MessageId m)
|
||||
throws DbException {
|
||||
Queue<MessageId> pending = new LinkedList<MessageId>();
|
||||
Map<MessageId, State> states = db.getMessageDependents(txn, m);
|
||||
for (Entry<MessageId, State> e : states.entrySet()) {
|
||||
if (e.getValue() == PENDING) pending.add(e.getKey());
|
||||
}
|
||||
return pending;
|
||||
}
|
||||
|
||||
private void invalidateNextMessageAsync(final Queue<MessageId> invalidate) {
|
||||
if (invalidate.isEmpty()) return;
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Queue<MessageId> pending = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
IncomingMessageHook hook = hooks.get(c);
|
||||
if (hook != null)
|
||||
hook.incomingMessage(txn, m, meta);
|
||||
|
||||
// check if message was deleted by client
|
||||
if (db.getRawMessage(txn, m.getId()) == null) {
|
||||
throw new InvalidMessageException(
|
||||
"Deleted by Client");
|
||||
}
|
||||
db.setMessageState(txn, m, c, DELIVERED);
|
||||
|
||||
// deliver pending dependents
|
||||
Map<MessageId, State> dependents =
|
||||
db.getMessageDependents(txn, m.getId());
|
||||
for (Entry<MessageId, State> i : dependents
|
||||
.entrySet()) {
|
||||
if (i.getValue() != PENDING) continue;
|
||||
|
||||
// check that all dependencies are delivered
|
||||
Map<MessageId, State> dependencies =
|
||||
db.getMessageDependencies(txn, i.getKey());
|
||||
for (Entry<MessageId, State> j : dependencies
|
||||
.entrySet()) {
|
||||
if (j.getValue() != DELIVERED) return;
|
||||
}
|
||||
pending.add(i.getKey());
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
if (!txn.isComplete()) txn.setComplete();
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
deliverNextMessage(pending);
|
||||
} catch (InvalidMessageException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.log(INFO, e.toString(), e);
|
||||
markMessageInvalid(m, c);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
invalidateNextMessage(invalidate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void markMessageInvalid(final Message m, final ClientId c) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Queue<MessageId> invalid = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
Map<MessageId, State> dependents =
|
||||
db.getMessageDependents(txn, m.getId());
|
||||
db.setMessageState(txn, m, c, INVALID);
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
|
||||
// recursively invalidate all messages that depend on m
|
||||
// TODO check that cycles are properly taken care of
|
||||
for (Entry<MessageId, State> i : dependents
|
||||
.entrySet()) {
|
||||
if (i.getValue() != INVALID) {
|
||||
invalid.add(i.getKey());
|
||||
}
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
markNextMessageInvalid(invalid);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
private void invalidateNextMessage(Queue<MessageId> invalidate) {
|
||||
try {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
MessageId id = invalidate.poll();
|
||||
if (db.getMessageState(txn, id) != INVALID) {
|
||||
invalidateMessage(txn, id);
|
||||
invalidate.addAll(getDependentsToInvalidate(txn, id));
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
});
|
||||
invalidateNextMessageAsync(invalidate);
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Message removed before invalidation");
|
||||
invalidateNextMessageAsync(invalidate);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void markNextMessageInvalid(final Queue<MessageId> invalid) {
|
||||
if (invalid.isEmpty()) return;
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Message m = null;
|
||||
Group g = null;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
MessageId id = invalid.poll();
|
||||
byte[] raw = db.getRawMessage(txn, id);
|
||||
m = parseMessage(id, raw);
|
||||
g = db.getGroup(txn, m.getGroupId());
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
if (g != null) markMessageInvalid(m, g.getClientId());
|
||||
markNextMessageInvalid(invalid);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
private void invalidateMessage(Transaction txn, MessageId m)
|
||||
throws DbException {
|
||||
db.setMessageState(txn, m, INVALID);
|
||||
db.deleteMessage(txn, m);
|
||||
db.deleteMessageMetadata(txn, m);
|
||||
}
|
||||
|
||||
private Queue<MessageId> getDependentsToInvalidate(Transaction txn,
|
||||
MessageId m) throws DbException {
|
||||
Queue<MessageId> invalidate = new LinkedList<MessageId>();
|
||||
Map<MessageId, State> states = db.getMessageDependents(txn, m);
|
||||
for (Entry<MessageId, State> e : states.entrySet()) {
|
||||
if (e.getValue() != INVALID) invalidate.add(e.getKey());
|
||||
}
|
||||
return invalidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -462,31 +418,35 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
if (e instanceof MessageAddedEvent) {
|
||||
// Validate the message if it wasn't created locally
|
||||
MessageAddedEvent m = (MessageAddedEvent) e;
|
||||
if (m.getContactId() != null) loadGroupAndValidate(m.getMessage());
|
||||
if (m.getContactId() != null)
|
||||
loadGroupAndValidateAsync(m.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGroupAndValidate(final Message m) {
|
||||
private void loadGroupAndValidateAsync(final Message m) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Group g;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
g = db.getGroup(txn, m.getGroupId());
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
validateMessage(m, g);
|
||||
} catch (NoSuchGroupException e) {
|
||||
LOG.info("Group removed before validation");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
loadGroupAndValidate(m);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadGroupAndValidate(final Message m) {
|
||||
try {
|
||||
Group g;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
g = db.getGroup(txn, m.getGroupId());
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
validateMessageAsync(m, g);
|
||||
} catch (NoSuchGroupException e) {
|
||||
LOG.info("Group removed before validation");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user