Migrate `MessagesDynamoDbRule` to `MessagesDynamoDbExtension`
This commit is contained in:
parent
6a5d475198
commit
83e0a19561
|
@ -25,6 +25,7 @@ import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex;
|
import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
|
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.KeyType;
|
import software.amazon.awssdk.services.dynamodb.model.KeyType;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.LocalSecondaryIndex;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
|
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
|
||||||
|
|
||||||
public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback {
|
public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback {
|
||||||
|
@ -45,6 +46,7 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
|
||||||
|
|
||||||
private final List<AttributeDefinition> attributeDefinitions;
|
private final List<AttributeDefinition> attributeDefinitions;
|
||||||
private final List<GlobalSecondaryIndex> globalSecondaryIndexes;
|
private final List<GlobalSecondaryIndex> globalSecondaryIndexes;
|
||||||
|
private final List<LocalSecondaryIndex> localSecondaryIndexes;
|
||||||
|
|
||||||
private final long readCapacityUnits;
|
private final long readCapacityUnits;
|
||||||
private final long writeCapacityUnits;
|
private final long writeCapacityUnits;
|
||||||
|
@ -53,12 +55,16 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
|
||||||
private DynamoDbAsyncClient dynamoAsyncDB2;
|
private DynamoDbAsyncClient dynamoAsyncDB2;
|
||||||
private AmazonDynamoDB legacyDynamoClient;
|
private AmazonDynamoDB legacyDynamoClient;
|
||||||
|
|
||||||
private DynamoDbExtension(String tableName, String hashKey, String rangeKey, List<AttributeDefinition> attributeDefinitions, List<GlobalSecondaryIndex> globalSecondaryIndexes, long readCapacityUnits,
|
private DynamoDbExtension(String tableName, String hashKey, String rangeKey,
|
||||||
|
List<AttributeDefinition> attributeDefinitions, List<GlobalSecondaryIndex> globalSecondaryIndexes,
|
||||||
|
final List<LocalSecondaryIndex> localSecondaryIndexes,
|
||||||
|
long readCapacityUnits,
|
||||||
long writeCapacityUnits) {
|
long writeCapacityUnits) {
|
||||||
|
|
||||||
this.tableName = tableName;
|
this.tableName = tableName;
|
||||||
this.hashKeyName = hashKey;
|
this.hashKeyName = hashKey;
|
||||||
this.rangeKeyName = rangeKey;
|
this.rangeKeyName = rangeKey;
|
||||||
|
this.localSecondaryIndexes = localSecondaryIndexes;
|
||||||
|
|
||||||
this.readCapacityUnits = readCapacityUnits;
|
this.readCapacityUnits = readCapacityUnits;
|
||||||
this.writeCapacityUnits = writeCapacityUnits;
|
this.writeCapacityUnits = writeCapacityUnits;
|
||||||
|
@ -108,6 +114,7 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
|
||||||
.keySchema(keySchemaElements)
|
.keySchema(keySchemaElements)
|
||||||
.attributeDefinitions(attributeDefinitions.isEmpty() ? null : attributeDefinitions)
|
.attributeDefinitions(attributeDefinitions.isEmpty() ? null : attributeDefinitions)
|
||||||
.globalSecondaryIndexes(globalSecondaryIndexes.isEmpty() ? null : globalSecondaryIndexes)
|
.globalSecondaryIndexes(globalSecondaryIndexes.isEmpty() ? null : globalSecondaryIndexes)
|
||||||
|
.localSecondaryIndexes(localSecondaryIndexes.isEmpty() ? null : localSecondaryIndexes)
|
||||||
.provisionedThroughput(ProvisionedThroughput.builder()
|
.provisionedThroughput(ProvisionedThroughput.builder()
|
||||||
.readCapacityUnits(readCapacityUnits)
|
.readCapacityUnits(readCapacityUnits)
|
||||||
.writeCapacityUnits(writeCapacityUnits)
|
.writeCapacityUnits(writeCapacityUnits)
|
||||||
|
@ -150,7 +157,8 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class DynamoDbExtensionBuilder {
|
public static class DynamoDbExtensionBuilder {
|
||||||
|
|
||||||
private String tableName = DEFAULT_TABLE_NAME;
|
private String tableName = DEFAULT_TABLE_NAME;
|
||||||
|
|
||||||
private String hashKey;
|
private String hashKey;
|
||||||
|
@ -158,6 +166,7 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
|
||||||
|
|
||||||
private List<AttributeDefinition> attributeDefinitions = new ArrayList<>();
|
private List<AttributeDefinition> attributeDefinitions = new ArrayList<>();
|
||||||
private List<GlobalSecondaryIndex> globalSecondaryIndexes = new ArrayList<>();
|
private List<GlobalSecondaryIndex> globalSecondaryIndexes = new ArrayList<>();
|
||||||
|
private List<LocalSecondaryIndex> localSecondaryIndexes = new ArrayList<>();
|
||||||
|
|
||||||
private long readCapacityUnits = DEFAULT_PROVISIONED_THROUGHPUT.readCapacityUnits();
|
private long readCapacityUnits = DEFAULT_PROVISIONED_THROUGHPUT.readCapacityUnits();
|
||||||
private long writeCapacityUnits = DEFAULT_PROVISIONED_THROUGHPUT.writeCapacityUnits();
|
private long writeCapacityUnits = DEFAULT_PROVISIONED_THROUGHPUT.writeCapacityUnits();
|
||||||
|
@ -166,22 +175,22 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamoDbExtensionBuilder tableName(String databaseName) {
|
public DynamoDbExtensionBuilder tableName(String databaseName) {
|
||||||
this.tableName = databaseName;
|
this.tableName = databaseName;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamoDbExtensionBuilder hashKey(String hashKey) {
|
public DynamoDbExtensionBuilder hashKey(String hashKey) {
|
||||||
this.hashKey = hashKey;
|
this.hashKey = hashKey;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamoDbExtensionBuilder rangeKey(String rangeKey) {
|
public DynamoDbExtensionBuilder rangeKey(String rangeKey) {
|
||||||
this.rangeKey = rangeKey;
|
this.rangeKey = rangeKey;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamoDbExtensionBuilder attributeDefinition(AttributeDefinition attributeDefinition) {
|
public DynamoDbExtensionBuilder attributeDefinition(AttributeDefinition attributeDefinition) {
|
||||||
attributeDefinitions.add(attributeDefinition);
|
attributeDefinitions.add(attributeDefinition);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -191,9 +200,14 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamoDbExtension build() {
|
public DynamoDbExtensionBuilder localSecondaryIndex(LocalSecondaryIndex index) {
|
||||||
|
localSecondaryIndexes.add(index);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamoDbExtension build() {
|
||||||
return new DynamoDbExtension(tableName, hashKey, rangeKey,
|
return new DynamoDbExtension(tableName, hashKey, rangeKey,
|
||||||
attributeDefinitions, globalSecondaryIndexes, readCapacityUnits, writeCapacityUnits);
|
attributeDefinitions, globalSecondaryIndexes, localSecondaryIndexes, readCapacityUnits, writeCapacityUnits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTimeout;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -24,158 +25,159 @@ import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.junit.After;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.Before;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.Rule;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope.Type;
|
||||||
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
|
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
|
||||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
|
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||||
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbRule;
|
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension;
|
||||||
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||||
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
|
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
|
||||||
|
|
||||||
public class MessagePersisterIntegrationTest extends AbstractRedisClusterTest {
|
class MessagePersisterIntegrationTest {
|
||||||
|
|
||||||
@Rule
|
@RegisterExtension
|
||||||
public MessagesDynamoDbRule messagesDynamoDbRule = new MessagesDynamoDbRule();
|
static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build();
|
||||||
|
|
||||||
private ExecutorService notificationExecutorService;
|
@RegisterExtension
|
||||||
private MessagesCache messagesCache;
|
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
|
||||||
private MessagesManager messagesManager;
|
|
||||||
private MessagePersister messagePersister;
|
|
||||||
private Account account;
|
|
||||||
|
|
||||||
private static final Duration PERSIST_DELAY = Duration.ofMinutes(10);
|
private ExecutorService notificationExecutorService;
|
||||||
|
private MessagesCache messagesCache;
|
||||||
|
private MessagesManager messagesManager;
|
||||||
|
private MessagePersister messagePersister;
|
||||||
|
private Account account;
|
||||||
|
|
||||||
@Before
|
private static final Duration PERSIST_DELAY = Duration.ofMinutes(10);
|
||||||
@Override
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
getRedisCluster().useCluster(connection -> {
|
@BeforeEach
|
||||||
connection.sync().flushall();
|
void setUp() throws Exception {
|
||||||
connection.sync().upstream().commands().configSet("notify-keyspace-events", "K$glz");
|
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> {
|
||||||
});
|
connection.sync().flushall();
|
||||||
|
connection.sync().upstream().commands().configSet("notify-keyspace-events", "K$glz");
|
||||||
|
});
|
||||||
|
|
||||||
final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(messagesDynamoDbRule.getDynamoDbClient(), MessagesDynamoDbRule.TABLE_NAME, Duration.ofDays(7));
|
final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(),
|
||||||
final AccountsManager accountsManager = mock(AccountsManager.class);
|
MessagesDynamoDbExtension.TABLE_NAME, Duration.ofDays(14));
|
||||||
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||||
|
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||||
|
|
||||||
notificationExecutorService = Executors.newSingleThreadExecutor();
|
notificationExecutorService = Executors.newSingleThreadExecutor();
|
||||||
messagesCache = new MessagesCache(getRedisCluster(), getRedisCluster(), notificationExecutorService);
|
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||||
messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, mock(PushLatencyManager.class), mock(ReportMessageManager.class));
|
REDIS_CLUSTER_EXTENSION.getRedisCluster(), notificationExecutorService);
|
||||||
messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, dynamicConfigurationManager, PERSIST_DELAY);
|
messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, mock(PushLatencyManager.class),
|
||||||
|
mock(ReportMessageManager.class));
|
||||||
|
messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager,
|
||||||
|
dynamicConfigurationManager, PERSIST_DELAY);
|
||||||
|
|
||||||
account = mock(Account.class);
|
account = mock(Account.class);
|
||||||
|
|
||||||
final UUID accountUuid = UUID.randomUUID();
|
final UUID accountUuid = UUID.randomUUID();
|
||||||
|
|
||||||
when(account.getNumber()).thenReturn("+18005551234");
|
when(account.getNumber()).thenReturn("+18005551234");
|
||||||
when(account.getUuid()).thenReturn(accountUuid);
|
when(account.getUuid()).thenReturn(accountUuid);
|
||||||
when(accountsManager.get(accountUuid)).thenReturn(Optional.of(account));
|
when(accountsManager.get(accountUuid)).thenReturn(Optional.of(account));
|
||||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration());
|
when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration());
|
||||||
|
|
||||||
messagesCache.start();
|
messagesCache.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@AfterEach
|
||||||
@Override
|
void tearDown() throws Exception {
|
||||||
public void tearDown() throws Exception {
|
notificationExecutorService.shutdown();
|
||||||
super.tearDown();
|
notificationExecutorService.awaitTermination(15, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
notificationExecutorService.shutdown();
|
@Test
|
||||||
notificationExecutorService.awaitTermination(15, TimeUnit.SECONDS);
|
void testScheduledPersistMessages() {
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 15_000)
|
final int messageCount = 377;
|
||||||
public void testScheduledPersistMessages() throws Exception {
|
final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(messageCount);
|
||||||
final int messageCount = 377;
|
final Instant now = Instant.now();
|
||||||
final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(messageCount);
|
|
||||||
final Instant now = Instant.now();
|
|
||||||
|
|
||||||
for (int i = 0; i < messageCount; i++) {
|
assertTimeout(Duration.ofSeconds(15), () -> {
|
||||||
final UUID messageGuid = UUID.randomUUID();
|
|
||||||
final long timestamp = now.minus(PERSIST_DELAY.multipliedBy(2)).toEpochMilli() + i;
|
|
||||||
|
|
||||||
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, timestamp);
|
for (int i = 0; i < messageCount; i++) {
|
||||||
|
final UUID messageGuid = UUID.randomUUID();
|
||||||
|
final long timestamp = now.minus(PERSIST_DELAY.multipliedBy(2)).toEpochMilli() + i;
|
||||||
|
|
||||||
messagesCache.insert(messageGuid, account.getUuid(), 1, message);
|
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, timestamp);
|
||||||
expectedMessages.add(message);
|
|
||||||
|
messagesCache.insert(messageGuid, account.getUuid(), 1, message);
|
||||||
|
expectedMessages.add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
REDIS_CLUSTER_EXTENSION.getRedisCluster()
|
||||||
|
.useCluster(connection -> connection.sync().set(MessagesCache.NEXT_SLOT_TO_PERSIST_KEY,
|
||||||
|
String.valueOf(SlotHash.getSlot(MessagesCache.getMessageQueueKey(account.getUuid(), 1)) - 1)));
|
||||||
|
|
||||||
|
final AtomicBoolean messagesPersisted = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
messagesManager.addMessageAvailabilityListener(account.getUuid(), 1, new MessageAvailabilityListener() {
|
||||||
|
@Override
|
||||||
|
public void handleNewMessagesAvailable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
getRedisCluster().useCluster(connection -> connection.sync().set(MessagesCache.NEXT_SLOT_TO_PERSIST_KEY, String.valueOf(SlotHash.getSlot(MessagesCache.getMessageQueueKey(account.getUuid(), 1)) - 1)));
|
@Override
|
||||||
|
public void handleNewEphemeralMessageAvailable() {
|
||||||
final AtomicBoolean messagesPersisted = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
messagesManager.addMessageAvailabilityListener(account.getUuid(), 1, new MessageAvailabilityListener() {
|
|
||||||
@Override
|
|
||||||
public void handleNewMessagesAvailable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNewEphemeralMessageAvailable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleMessagesPersisted() {
|
|
||||||
synchronized (messagesPersisted) {
|
|
||||||
messagesPersisted.set(true);
|
|
||||||
messagesPersisted.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
messagePersister.start();
|
|
||||||
|
|
||||||
synchronized (messagesPersisted) {
|
|
||||||
while (!messagesPersisted.get()) {
|
|
||||||
messagesPersisted.wait();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
messagePersister.stop();
|
@Override
|
||||||
|
public void handleMessagesPersisted() {
|
||||||
|
synchronized (messagesPersisted) {
|
||||||
|
messagesPersisted.set(true);
|
||||||
|
messagesPersisted.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
final List<MessageProtos.Envelope> persistedMessages = new ArrayList<>(messageCount);
|
messagePersister.start();
|
||||||
|
|
||||||
DynamoDbClient dynamoDB = messagesDynamoDbRule.getDynamoDbClient();
|
synchronized (messagesPersisted) {
|
||||||
|
while (!messagesPersisted.get()) {
|
||||||
|
messagesPersisted.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messagePersister.stop();
|
||||||
|
|
||||||
|
final List<MessageProtos.Envelope> persistedMessages = new ArrayList<>(messageCount);
|
||||||
|
|
||||||
|
DynamoDbClient dynamoDB = dynamoDbExtension.getDynamoDbClient();
|
||||||
for (Map<String, AttributeValue> item : dynamoDB
|
for (Map<String, AttributeValue> item : dynamoDB
|
||||||
.scan(ScanRequest.builder().tableName(MessagesDynamoDbRule.TABLE_NAME).build()).items()) {
|
.scan(ScanRequest.builder().tableName(MessagesDynamoDbExtension.TABLE_NAME).build()).items()) {
|
||||||
persistedMessages.add(MessageProtos.Envelope.newBuilder()
|
persistedMessages.add(MessageProtos.Envelope.newBuilder()
|
||||||
.setServerGuid(AttributeValues.getUUID(item, "U", null).toString())
|
.setServerGuid(AttributeValues.getUUID(item, "U", null).toString())
|
||||||
.setType(MessageProtos.Envelope.Type.valueOf(AttributeValues.getInt(item, "T", -1)))
|
.setType(Type.forNumber(AttributeValues.getInt(item, "T", -1)))
|
||||||
.setTimestamp(AttributeValues.getLong(item, "TS", -1))
|
.setTimestamp(AttributeValues.getLong(item, "TS", -1))
|
||||||
.setServerTimestamp(extractServerTimestamp(AttributeValues.getByteArray(item, "S", null)))
|
.setServerTimestamp(extractServerTimestamp(AttributeValues.getByteArray(item, "S", null)))
|
||||||
.setContent(ByteString.copyFrom(AttributeValues.getByteArray(item, "C", null)))
|
.setContent(ByteString.copyFrom(AttributeValues.getByteArray(item, "C", null)))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(expectedMessages, persistedMessages);
|
assertEquals(expectedMessages, persistedMessages);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static UUID convertBinaryToUuid(byte[] bytes) {
|
private static long extractServerTimestamp(byte[] bytes) {
|
||||||
ByteBuffer bb = ByteBuffer.wrap(bytes);
|
ByteBuffer bb = ByteBuffer.wrap(bytes);
|
||||||
long msb = bb.getLong();
|
bb.getLong();
|
||||||
long lsb = bb.getLong();
|
return bb.getLong();
|
||||||
return new UUID(msb, lsb);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static long extractServerTimestamp(byte[] bytes) {
|
private MessageProtos.Envelope generateRandomMessage(final UUID messageGuid, final long timestamp) {
|
||||||
ByteBuffer bb = ByteBuffer.wrap(bytes);
|
return MessageProtos.Envelope.newBuilder()
|
||||||
bb.getLong();
|
.setTimestamp(timestamp)
|
||||||
return bb.getLong();
|
.setServerTimestamp(timestamp)
|
||||||
}
|
.setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
|
||||||
|
.setType(MessageProtos.Envelope.Type.CIPHERTEXT)
|
||||||
private MessageProtos.Envelope generateRandomMessage(final UUID messageGuid, final long timestamp) {
|
.setServerGuid(messageGuid.toString())
|
||||||
return MessageProtos.Envelope.newBuilder()
|
.build();
|
||||||
.setTimestamp(timestamp)
|
}
|
||||||
.setServerTimestamp(timestamp)
|
|
||||||
.setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
|
|
||||||
.setType(MessageProtos.Envelope.Type.CIPHERTEXT)
|
|
||||||
.setServerGuid(messageGuid.toString())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,18 @@ import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.junit.Before;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.ClassRule;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
|
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
|
||||||
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
|
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
|
||||||
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbRule;
|
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension;
|
||||||
|
|
||||||
|
class MessagesDynamoDbTest {
|
||||||
|
|
||||||
|
|
||||||
public class MessagesDynamoDbTest {
|
|
||||||
private static final Random random = new Random();
|
private static final Random random = new Random();
|
||||||
private static final MessageProtos.Envelope MESSAGE1;
|
private static final MessageProtos.Envelope MESSAGE1;
|
||||||
private static final MessageProtos.Envelope MESSAGE2;
|
private static final MessageProtos.Envelope MESSAGE2;
|
||||||
|
@ -61,27 +64,31 @@ public class MessagesDynamoDbTest {
|
||||||
|
|
||||||
private MessagesDynamoDb messagesDynamoDb;
|
private MessagesDynamoDb messagesDynamoDb;
|
||||||
|
|
||||||
@ClassRule
|
|
||||||
public static MessagesDynamoDbRule dynamoDbRule = new MessagesDynamoDbRule();
|
|
||||||
|
|
||||||
@Before
|
@RegisterExtension
|
||||||
public void setup() {
|
static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build();
|
||||||
messagesDynamoDb = new MessagesDynamoDb(dynamoDbRule.getDynamoDbClient(), MessagesDynamoDbRule.TABLE_NAME, Duration.ofDays(7));
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(), MessagesDynamoDbExtension.TABLE_NAME,
|
||||||
|
Duration.ofDays(14));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testServerStart() {
|
void testServerStart() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSimpleFetchAfterInsert() {
|
void testSimpleFetchAfterInsert() {
|
||||||
final UUID destinationUuid = UUID.randomUUID();
|
final UUID destinationUuid = UUID.randomUUID();
|
||||||
final int destinationDeviceId = random.nextInt(255) + 1;
|
final int destinationDeviceId = random.nextInt(255) + 1;
|
||||||
messagesDynamoDb.store(List.of(MESSAGE1, MESSAGE2, MESSAGE3), destinationUuid, destinationDeviceId);
|
messagesDynamoDb.store(List.of(MESSAGE1, MESSAGE2, MESSAGE3), destinationUuid, destinationDeviceId);
|
||||||
|
|
||||||
final List<OutgoingMessageEntity> messagesStored = messagesDynamoDb.load(destinationUuid, destinationDeviceId, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE);
|
final List<OutgoingMessageEntity> messagesStored = messagesDynamoDb.load(destinationUuid, destinationDeviceId,
|
||||||
|
MessagesDynamoDb.RESULT_SET_CHUNK_SIZE);
|
||||||
assertThat(messagesStored).isNotNull().hasSize(3);
|
assertThat(messagesStored).isNotNull().hasSize(3);
|
||||||
final MessageProtos.Envelope firstMessage = MESSAGE1.getServerGuid().compareTo(MESSAGE3.getServerGuid()) < 0 ? MESSAGE1 : MESSAGE3;
|
final MessageProtos.Envelope firstMessage =
|
||||||
|
MESSAGE1.getServerGuid().compareTo(MESSAGE3.getServerGuid()) < 0 ? MESSAGE1 : MESSAGE3;
|
||||||
final MessageProtos.Envelope secondMessage = firstMessage == MESSAGE1 ? MESSAGE3 : MESSAGE1;
|
final MessageProtos.Envelope secondMessage = firstMessage == MESSAGE1 ? MESSAGE3 : MESSAGE1;
|
||||||
assertThat(messagesStored).element(0).satisfies(verify(firstMessage));
|
assertThat(messagesStored).element(0).satisfies(verify(firstMessage));
|
||||||
assertThat(messagesStored).element(1).satisfies(verify(secondMessage));
|
assertThat(messagesStored).element(1).satisfies(verify(secondMessage));
|
||||||
|
@ -89,61 +96,76 @@ public class MessagesDynamoDbTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteForDestination() {
|
void testDeleteForDestination() {
|
||||||
final UUID destinationUuid = UUID.randomUUID();
|
final UUID destinationUuid = UUID.randomUUID();
|
||||||
final UUID secondDestinationUuid = UUID.randomUUID();
|
final UUID secondDestinationUuid = UUID.randomUUID();
|
||||||
messagesDynamoDb.store(List.of(MESSAGE1), destinationUuid, 1);
|
messagesDynamoDb.store(List.of(MESSAGE1), destinationUuid, 1);
|
||||||
messagesDynamoDb.store(List.of(MESSAGE2), secondDestinationUuid, 1);
|
messagesDynamoDb.store(List.of(MESSAGE2), secondDestinationUuid, 1);
|
||||||
messagesDynamoDb.store(List.of(MESSAGE3), destinationUuid, 2);
|
messagesDynamoDb.store(List.of(MESSAGE3), destinationUuid, 2);
|
||||||
|
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE1));
|
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE3));
|
.element(0).satisfies(verify(MESSAGE1));
|
||||||
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
|
.element(0).satisfies(verify(MESSAGE3));
|
||||||
|
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull()
|
||||||
|
.hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
||||||
|
|
||||||
messagesDynamoDb.deleteAllMessagesForAccount(destinationUuid);
|
messagesDynamoDb.deleteAllMessagesForAccount(destinationUuid);
|
||||||
|
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().isEmpty();
|
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().isEmpty();
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().isEmpty();
|
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().isEmpty();
|
||||||
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull()
|
||||||
|
.hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteForDestinationDevice() {
|
void testDeleteForDestinationDevice() {
|
||||||
final UUID destinationUuid = UUID.randomUUID();
|
final UUID destinationUuid = UUID.randomUUID();
|
||||||
final UUID secondDestinationUuid = UUID.randomUUID();
|
final UUID secondDestinationUuid = UUID.randomUUID();
|
||||||
messagesDynamoDb.store(List.of(MESSAGE1), destinationUuid, 1);
|
messagesDynamoDb.store(List.of(MESSAGE1), destinationUuid, 1);
|
||||||
messagesDynamoDb.store(List.of(MESSAGE2), secondDestinationUuid, 1);
|
messagesDynamoDb.store(List.of(MESSAGE2), secondDestinationUuid, 1);
|
||||||
messagesDynamoDb.store(List.of(MESSAGE3), destinationUuid, 2);
|
messagesDynamoDb.store(List.of(MESSAGE3), destinationUuid, 2);
|
||||||
|
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE1));
|
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE3));
|
.element(0).satisfies(verify(MESSAGE1));
|
||||||
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
|
.element(0).satisfies(verify(MESSAGE3));
|
||||||
|
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull()
|
||||||
|
.hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
||||||
|
|
||||||
messagesDynamoDb.deleteAllMessagesForDevice(destinationUuid, 2);
|
messagesDynamoDb.deleteAllMessagesForDevice(destinationUuid, 2);
|
||||||
|
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE1));
|
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
|
.element(0).satisfies(verify(MESSAGE1));
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().isEmpty();
|
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().isEmpty();
|
||||||
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull()
|
||||||
|
.hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteMessageByDestinationAndGuid() {
|
void testDeleteMessageByDestinationAndGuid() {
|
||||||
final UUID destinationUuid = UUID.randomUUID();
|
final UUID destinationUuid = UUID.randomUUID();
|
||||||
final UUID secondDestinationUuid = UUID.randomUUID();
|
final UUID secondDestinationUuid = UUID.randomUUID();
|
||||||
messagesDynamoDb.store(List.of(MESSAGE1), destinationUuid, 1);
|
messagesDynamoDb.store(List.of(MESSAGE1), destinationUuid, 1);
|
||||||
messagesDynamoDb.store(List.of(MESSAGE2), secondDestinationUuid, 1);
|
messagesDynamoDb.store(List.of(MESSAGE2), secondDestinationUuid, 1);
|
||||||
messagesDynamoDb.store(List.of(MESSAGE3), destinationUuid, 2);
|
messagesDynamoDb.store(List.of(MESSAGE3), destinationUuid, 2);
|
||||||
|
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE1));
|
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE3));
|
.element(0).satisfies(verify(MESSAGE1));
|
||||||
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
|
.element(0).satisfies(verify(MESSAGE3));
|
||||||
|
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull()
|
||||||
|
.hasSize(1).element(0).satisfies(verify(MESSAGE2));
|
||||||
|
|
||||||
messagesDynamoDb.deleteMessageByDestinationAndGuid(secondDestinationUuid,
|
messagesDynamoDb.deleteMessageByDestinationAndGuid(secondDestinationUuid,
|
||||||
UUID.fromString(MESSAGE2.getServerGuid()));
|
UUID.fromString(MESSAGE2.getServerGuid()));
|
||||||
|
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE1));
|
assertThat(messagesDynamoDb.load(destinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1).element(0).satisfies(verify(MESSAGE3));
|
.element(0).satisfies(verify(MESSAGE1));
|
||||||
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().isEmpty();
|
assertThat(messagesDynamoDb.load(destinationUuid, 2, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull().hasSize(1)
|
||||||
|
.element(0).satisfies(verify(MESSAGE3));
|
||||||
|
assertThat(messagesDynamoDb.load(secondDestinationUuid, 1, MessagesDynamoDb.RESULT_SET_CHUNK_SIZE)).isNotNull()
|
||||||
|
.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void verify(OutgoingMessageEntity retrieved, MessageProtos.Envelope inserted) {
|
private static void verify(OutgoingMessageEntity retrieved, MessageProtos.Envelope inserted) {
|
||||||
|
@ -164,6 +186,7 @@ public class MessagesDynamoDbTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class VerifyMessage implements Consumer<OutgoingMessageEntity> {
|
private static final class VerifyMessage implements Consumer<OutgoingMessageEntity> {
|
||||||
|
|
||||||
private final MessageProtos.Envelope expected;
|
private final MessageProtos.Envelope expected;
|
||||||
|
|
||||||
public VerifyMessage(MessageProtos.Envelope expected) {
|
public VerifyMessage(MessageProtos.Envelope expected) {
|
||||||
|
|
|
@ -5,42 +5,36 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.tests.util;
|
package org.whispersystems.textsecuregcm.tests.util;
|
||||||
|
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
|
|
||||||
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
|
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.KeyType;
|
import software.amazon.awssdk.services.dynamodb.model.KeyType;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.LocalSecondaryIndex;
|
import software.amazon.awssdk.services.dynamodb.model.LocalSecondaryIndex;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.Projection;
|
import software.amazon.awssdk.services.dynamodb.model.Projection;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.ProjectionType;
|
import software.amazon.awssdk.services.dynamodb.model.ProjectionType;
|
||||||
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
|
|
||||||
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||||
|
|
||||||
public class MessagesDynamoDbRule extends LocalDynamoDbRule {
|
public class MessagesDynamoDbExtension {
|
||||||
|
|
||||||
public static final String TABLE_NAME = "Signal_Messages_UnitTest";
|
public static final String TABLE_NAME = "Signal_Messages_UnitTest";
|
||||||
|
|
||||||
@Override
|
public static DynamoDbExtension build() {
|
||||||
protected void before() throws Throwable {
|
return DynamoDbExtension.builder()
|
||||||
super.before();
|
|
||||||
getDynamoDbClient().createTable(CreateTableRequest.builder()
|
|
||||||
.tableName(TABLE_NAME)
|
.tableName(TABLE_NAME)
|
||||||
.keySchema(KeySchemaElement.builder().attributeName("H").keyType(KeyType.HASH).build(),
|
.hashKey("H")
|
||||||
KeySchemaElement.builder().attributeName("S").keyType(KeyType.RANGE).build())
|
.rangeKey("S")
|
||||||
.attributeDefinitions(
|
.attributeDefinition(
|
||||||
AttributeDefinition.builder().attributeName("H").attributeType(ScalarAttributeType.B).build(),
|
AttributeDefinition.builder().attributeName("H").attributeType(ScalarAttributeType.B).build())
|
||||||
AttributeDefinition.builder().attributeName("S").attributeType(ScalarAttributeType.B).build(),
|
.attributeDefinition(
|
||||||
|
AttributeDefinition.builder().attributeName("S").attributeType(ScalarAttributeType.B).build())
|
||||||
|
.attributeDefinition(
|
||||||
AttributeDefinition.builder().attributeName("U").attributeType(ScalarAttributeType.B).build())
|
AttributeDefinition.builder().attributeName("U").attributeType(ScalarAttributeType.B).build())
|
||||||
.provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(20L).writeCapacityUnits(20L).build())
|
.localSecondaryIndex(LocalSecondaryIndex.builder().indexName("Message_UUID_Index")
|
||||||
.localSecondaryIndexes(LocalSecondaryIndex.builder().indexName("Message_UUID_Index")
|
|
||||||
.keySchema(KeySchemaElement.builder().attributeName("H").keyType(KeyType.HASH).build(),
|
.keySchema(KeySchemaElement.builder().attributeName("H").keyType(KeyType.HASH).build(),
|
||||||
KeySchemaElement.builder().attributeName("U").keyType(KeyType.RANGE).build())
|
KeySchemaElement.builder().attributeName("U").keyType(KeyType.RANGE).build())
|
||||||
.projection(Projection.builder().projectionType(ProjectionType.KEYS_ONLY).build())
|
.projection(Projection.builder().projectionType(ProjectionType.KEYS_ONLY).build())
|
||||||
.build())
|
.build())
|
||||||
.build());
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void after() {
|
|
||||||
super.after();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -5,9 +5,10 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.websocket;
|
package org.whispersystems.textsecuregcm.websocket;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTimeout;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyList;
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
@ -34,10 +35,10 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.junit.After;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.Before;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.Rule;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||||
|
@ -45,195 +46,208 @@ import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
|
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
|
||||||
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
|
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
|
||||||
import org.whispersystems.textsecuregcm.push.ReceiptSender;
|
import org.whispersystems.textsecuregcm.push.ReceiptSender;
|
||||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
|
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
|
||||||
import org.whispersystems.textsecuregcm.storage.MessagesCache;
|
import org.whispersystems.textsecuregcm.storage.MessagesCache;
|
||||||
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
|
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
|
||||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
|
import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
|
||||||
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbRule;
|
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension;
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
import org.whispersystems.textsecuregcm.util.Pair;
|
||||||
import org.whispersystems.websocket.WebSocketClient;
|
import org.whispersystems.websocket.WebSocketClient;
|
||||||
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
|
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
|
||||||
|
|
||||||
public class WebSocketConnectionIntegrationTest extends AbstractRedisClusterTest {
|
class WebSocketConnectionIntegrationTest {
|
||||||
|
|
||||||
@Rule
|
@RegisterExtension
|
||||||
public MessagesDynamoDbRule messagesDynamoDbRule = new MessagesDynamoDbRule();
|
static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build();
|
||||||
|
|
||||||
private ExecutorService executorService;
|
@RegisterExtension
|
||||||
private MessagesDynamoDb messagesDynamoDb;
|
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
|
||||||
private MessagesCache messagesCache;
|
|
||||||
private ReportMessageManager reportMessageManager;
|
|
||||||
private Account account;
|
|
||||||
private Device device;
|
|
||||||
private WebSocketClient webSocketClient;
|
|
||||||
private WebSocketConnection webSocketConnection;
|
|
||||||
private ScheduledExecutorService retrySchedulingExecutor;
|
|
||||||
|
|
||||||
private long serialTimestamp = System.currentTimeMillis();
|
private ExecutorService executorService;
|
||||||
|
private MessagesDynamoDb messagesDynamoDb;
|
||||||
|
private MessagesCache messagesCache;
|
||||||
|
private ReportMessageManager reportMessageManager;
|
||||||
|
private Account account;
|
||||||
|
private Device device;
|
||||||
|
private WebSocketClient webSocketClient;
|
||||||
|
private WebSocketConnection webSocketConnection;
|
||||||
|
private ScheduledExecutorService retrySchedulingExecutor;
|
||||||
|
|
||||||
@Before
|
private long serialTimestamp = System.currentTimeMillis();
|
||||||
@Override
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
executorService = Executors.newSingleThreadExecutor();
|
@BeforeEach
|
||||||
messagesCache = new MessagesCache(getRedisCluster(), getRedisCluster(), executorService);
|
void setUp() throws Exception {
|
||||||
messagesDynamoDb = new MessagesDynamoDb(messagesDynamoDbRule.getDynamoDbClient(), MessagesDynamoDbRule.TABLE_NAME, Duration.ofDays(7));
|
|
||||||
reportMessageManager = mock(ReportMessageManager.class);
|
|
||||||
account = mock(Account.class);
|
|
||||||
device = mock(Device.class);
|
|
||||||
webSocketClient = mock(WebSocketClient.class);
|
|
||||||
retrySchedulingExecutor = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
|
|
||||||
when(account.getNumber()).thenReturn("+18005551234");
|
executorService = Executors.newSingleThreadExecutor();
|
||||||
when(account.getUuid()).thenReturn(UUID.randomUUID());
|
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||||
when(device.getId()).thenReturn(1L);
|
REDIS_CLUSTER_EXTENSION.getRedisCluster(), executorService);
|
||||||
|
messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(), MessagesDynamoDbExtension.TABLE_NAME,
|
||||||
|
Duration.ofDays(7));
|
||||||
|
reportMessageManager = mock(ReportMessageManager.class);
|
||||||
|
account = mock(Account.class);
|
||||||
|
device = mock(Device.class);
|
||||||
|
webSocketClient = mock(WebSocketClient.class);
|
||||||
|
retrySchedulingExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
webSocketConnection = new WebSocketConnection(
|
when(account.getNumber()).thenReturn("+18005551234");
|
||||||
mock(ReceiptSender.class),
|
when(account.getUuid()).thenReturn(UUID.randomUUID());
|
||||||
new MessagesManager(messagesDynamoDb, messagesCache, mock(PushLatencyManager.class), reportMessageManager),
|
when(device.getId()).thenReturn(1L);
|
||||||
new AuthenticatedAccount(() -> new Pair<>(account, device)),
|
|
||||||
device,
|
|
||||||
webSocketClient,
|
|
||||||
retrySchedulingExecutor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
webSocketConnection = new WebSocketConnection(
|
||||||
@Override
|
mock(ReceiptSender.class),
|
||||||
public void tearDown() throws Exception {
|
new MessagesManager(messagesDynamoDb, messagesCache, mock(PushLatencyManager.class), reportMessageManager),
|
||||||
executorService.shutdown();
|
new AuthenticatedAccount(() -> new Pair<>(account, device)),
|
||||||
executorService.awaitTermination(2, TimeUnit.SECONDS);
|
device,
|
||||||
|
webSocketClient,
|
||||||
|
retrySchedulingExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
retrySchedulingExecutor.shutdown();
|
@AfterEach
|
||||||
retrySchedulingExecutor.awaitTermination(2, TimeUnit.SECONDS);
|
void tearDown() throws Exception {
|
||||||
|
executorService.shutdown();
|
||||||
|
executorService.awaitTermination(2, TimeUnit.SECONDS);
|
||||||
|
|
||||||
super.tearDown();
|
retrySchedulingExecutor.shutdown();
|
||||||
}
|
retrySchedulingExecutor.awaitTermination(2, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
@Test(timeout = 15_000)
|
@Test
|
||||||
public void testProcessStoredMessages() throws InterruptedException {
|
void testProcessStoredMessages() {
|
||||||
final int persistedMessageCount = 207;
|
final int persistedMessageCount = 207;
|
||||||
final int cachedMessageCount = 173;
|
final int cachedMessageCount = 173;
|
||||||
|
|
||||||
final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(persistedMessageCount + cachedMessageCount);
|
final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(persistedMessageCount + cachedMessageCount);
|
||||||
|
|
||||||
{
|
assertTimeout(Duration.ofSeconds(15), () -> {
|
||||||
final List<MessageProtos.Envelope> persistedMessages = new ArrayList<>(persistedMessageCount);
|
|
||||||
|
|
||||||
for (int i = 0; i < persistedMessageCount; i++) {
|
{
|
||||||
final MessageProtos.Envelope envelope = generateRandomMessage(UUID.randomUUID());
|
final List<MessageProtos.Envelope> persistedMessages = new ArrayList<>(persistedMessageCount);
|
||||||
|
|
||||||
persistedMessages.add(envelope);
|
for (int i = 0; i < persistedMessageCount; i++) {
|
||||||
expectedMessages.add(envelope);
|
final MessageProtos.Envelope envelope = generateRandomMessage(UUID.randomUUID());
|
||||||
}
|
|
||||||
|
|
||||||
messagesDynamoDb.store(persistedMessages, account.getUuid(), device.getId());
|
persistedMessages.add(envelope);
|
||||||
|
expectedMessages.add(envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < cachedMessageCount; i++) {
|
messagesDynamoDb.store(persistedMessages, account.getUuid(), device.getId());
|
||||||
final UUID messageGuid = UUID.randomUUID();
|
}
|
||||||
final MessageProtos.Envelope envelope = generateRandomMessage(messageGuid);
|
|
||||||
|
|
||||||
messagesCache.insert(messageGuid, account.getUuid(), device.getId(), envelope);
|
for (int i = 0; i < cachedMessageCount; i++) {
|
||||||
expectedMessages.add(envelope);
|
final UUID messageGuid = UUID.randomUUID();
|
||||||
}
|
final MessageProtos.Envelope envelope = generateRandomMessage(messageGuid);
|
||||||
|
|
||||||
final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class);
|
messagesCache.insert(messageGuid, account.getUuid(), device.getId(), envelope);
|
||||||
final AtomicBoolean queueCleared = new AtomicBoolean(false);
|
expectedMessages.add(envelope);
|
||||||
|
}
|
||||||
|
|
||||||
when(successResponse.getStatus()).thenReturn(200);
|
final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class);
|
||||||
when(webSocketClient.sendRequest(eq("PUT"), eq("/api/v1/message"), anyList(), any())).thenReturn(CompletableFuture.completedFuture(successResponse));
|
final AtomicBoolean queueCleared = new AtomicBoolean(false);
|
||||||
|
|
||||||
when(webSocketClient.sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), anyList(), any())).thenAnswer((Answer<CompletableFuture<WebSocketResponseMessage>>)invocation -> {
|
when(successResponse.getStatus()).thenReturn(200);
|
||||||
|
when(webSocketClient.sendRequest(eq("PUT"), eq("/api/v1/message"), anyList(), any())).thenReturn(
|
||||||
|
CompletableFuture.completedFuture(successResponse));
|
||||||
|
|
||||||
|
when(webSocketClient.sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), anyList(), any())).thenAnswer(
|
||||||
|
(Answer<CompletableFuture<WebSocketResponseMessage>>) invocation -> {
|
||||||
synchronized (queueCleared) {
|
synchronized (queueCleared) {
|
||||||
queueCleared.set(true);
|
queueCleared.set(true);
|
||||||
queueCleared.notifyAll();
|
queueCleared.notifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletableFuture.completedFuture(successResponse);
|
return CompletableFuture.completedFuture(successResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
webSocketConnection.processStoredMessages();
|
||||||
|
|
||||||
|
synchronized (queueCleared) {
|
||||||
|
while (!queueCleared.get()) {
|
||||||
|
queueCleared.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") final ArgumentCaptor<Optional<byte[]>> messageBodyCaptor = ArgumentCaptor.forClass(
|
||||||
|
Optional.class);
|
||||||
|
|
||||||
|
verify(webSocketClient, times(persistedMessageCount + cachedMessageCount)).sendRequest(eq("PUT"),
|
||||||
|
eq("/api/v1/message"), anyList(), messageBodyCaptor.capture());
|
||||||
|
verify(webSocketClient).sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), anyList(), eq(Optional.empty()));
|
||||||
|
|
||||||
|
final List<MessageProtos.Envelope> sentMessages = new ArrayList<>();
|
||||||
|
|
||||||
|
for (final Optional<byte[]> maybeMessageBody : messageBodyCaptor.getAllValues()) {
|
||||||
|
maybeMessageBody.ifPresent(messageBytes -> {
|
||||||
|
try {
|
||||||
|
sentMessages.add(MessageProtos.Envelope.parseFrom(messageBytes));
|
||||||
|
} catch (final InvalidProtocolBufferException e) {
|
||||||
|
fail("Could not parse sent message");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
webSocketConnection.processStoredMessages();
|
assertEquals(expectedMessages, sentMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (queueCleared) {
|
@Test
|
||||||
while (!queueCleared.get()) {
|
void testProcessStoredMessagesClientClosed() {
|
||||||
queueCleared.wait();
|
final int persistedMessageCount = 207;
|
||||||
}
|
final int cachedMessageCount = 173;
|
||||||
|
|
||||||
|
final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(persistedMessageCount + cachedMessageCount);
|
||||||
|
|
||||||
|
assertTimeout(Duration.ofSeconds(15), () -> {
|
||||||
|
|
||||||
|
{
|
||||||
|
final List<MessageProtos.Envelope> persistedMessages = new ArrayList<>(persistedMessageCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < persistedMessageCount; i++) {
|
||||||
|
final MessageProtos.Envelope envelope = generateRandomMessage(UUID.randomUUID());
|
||||||
|
persistedMessages.add(envelope);
|
||||||
|
expectedMessages.add(envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
messagesDynamoDb.store(persistedMessages, account.getUuid(), device.getId());
|
||||||
final ArgumentCaptor<Optional<byte[]>> messageBodyCaptor = ArgumentCaptor.forClass(Optional.class);
|
}
|
||||||
|
|
||||||
verify(webSocketClient, times(persistedMessageCount + cachedMessageCount)).sendRequest(eq("PUT"), eq("/api/v1/message"), anyList(), messageBodyCaptor.capture());
|
for (int i = 0; i < cachedMessageCount; i++) {
|
||||||
verify(webSocketClient).sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), anyList(), eq(Optional.empty()));
|
final UUID messageGuid = UUID.randomUUID();
|
||||||
|
final MessageProtos.Envelope envelope = generateRandomMessage(messageGuid);
|
||||||
|
messagesCache.insert(messageGuid, account.getUuid(), device.getId(), envelope);
|
||||||
|
|
||||||
final List<MessageProtos.Envelope> sentMessages = new ArrayList<>();
|
expectedMessages.add(envelope);
|
||||||
|
}
|
||||||
|
|
||||||
for (final Optional<byte[]> maybeMessageBody : messageBodyCaptor.getAllValues()) {
|
when(webSocketClient.sendRequest(eq("PUT"), eq("/api/v1/message"), anyList(), any())).thenReturn(
|
||||||
maybeMessageBody.ifPresent(messageBytes -> {
|
CompletableFuture.failedFuture(new IOException("Connection closed")));
|
||||||
try {
|
|
||||||
sentMessages.add(MessageProtos.Envelope.parseFrom(messageBytes));
|
|
||||||
} catch (final InvalidProtocolBufferException e) {
|
|
||||||
fail("Could not parse sent message");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(expectedMessages, sentMessages);
|
webSocketConnection.processStoredMessages();
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 15_000)
|
//noinspection unchecked
|
||||||
public void testProcessStoredMessagesClientClosed() {
|
ArgumentCaptor<Optional<byte[]>> messageBodyCaptor = ArgumentCaptor.forClass(Optional.class);
|
||||||
final int persistedMessageCount = 207;
|
|
||||||
final int cachedMessageCount = 173;
|
|
||||||
|
|
||||||
final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(persistedMessageCount + cachedMessageCount);
|
verify(webSocketClient, atMost(persistedMessageCount + cachedMessageCount)).sendRequest(eq("PUT"),
|
||||||
|
eq("/api/v1/message"), anyList(), messageBodyCaptor.capture());
|
||||||
{
|
verify(webSocketClient, never()).sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), anyList(),
|
||||||
final List<MessageProtos.Envelope> persistedMessages = new ArrayList<>(persistedMessageCount);
|
eq(Optional.empty()));
|
||||||
|
|
||||||
for (int i = 0; i < persistedMessageCount; i++) {
|
|
||||||
final MessageProtos.Envelope envelope = generateRandomMessage(UUID.randomUUID());
|
|
||||||
persistedMessages.add(envelope);
|
|
||||||
expectedMessages.add(envelope);
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesDynamoDb.store(persistedMessages, account.getUuid(), device.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < cachedMessageCount; i++) {
|
|
||||||
final UUID messageGuid = UUID.randomUUID();
|
|
||||||
final MessageProtos.Envelope envelope = generateRandomMessage(messageGuid);
|
|
||||||
messagesCache.insert(messageGuid, account.getUuid(), device.getId(), envelope);
|
|
||||||
|
|
||||||
expectedMessages.add(envelope);
|
|
||||||
}
|
|
||||||
|
|
||||||
when(webSocketClient.sendRequest(eq("PUT"), eq("/api/v1/message"), anyList(), any())).thenReturn(CompletableFuture.failedFuture(new IOException("Connection closed")));
|
|
||||||
|
|
||||||
webSocketConnection.processStoredMessages();
|
|
||||||
|
|
||||||
//noinspection unchecked
|
|
||||||
ArgumentCaptor<Optional<byte[]>> messageBodyCaptor = ArgumentCaptor.forClass(Optional.class);
|
|
||||||
|
|
||||||
verify(webSocketClient, atMost(persistedMessageCount + cachedMessageCount)).sendRequest(eq("PUT"), eq("/api/v1/message"), anyList(), messageBodyCaptor.capture());
|
|
||||||
verify(webSocketClient, never()).sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), anyList(), eq(Optional.empty()));
|
|
||||||
|
|
||||||
final List<MessageProtos.Envelope> sentMessages = messageBodyCaptor.getAllValues().stream()
|
final List<MessageProtos.Envelope> sentMessages = messageBodyCaptor.getAllValues().stream()
|
||||||
.map(Optional::get)
|
.map(Optional::get)
|
||||||
.map(messageBytes -> {
|
.map(messageBytes -> {
|
||||||
try {
|
try {
|
||||||
return Envelope.parseFrom(messageBytes);
|
return Envelope.parseFrom(messageBytes);
|
||||||
} catch (InvalidProtocolBufferException e) {
|
} catch (InvalidProtocolBufferException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
assertTrue(expectedMessages.containsAll(sentMessages));
|
assertTrue(expectedMessages.containsAll(sentMessages));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageProtos.Envelope generateRandomMessage(final UUID messageGuid) {
|
private MessageProtos.Envelope generateRandomMessage(final UUID messageGuid) {
|
||||||
|
|
|
@ -65,18 +65,19 @@ public class WebSocketResourceProviderFactory<T extends Principal> extends WebSo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WebSocketResourceProvider<T>(getRemoteAddress(request),
|
return new WebSocketResourceProvider<>(getRemoteAddress(request),
|
||||||
this.jerseyApplicationHandler,
|
this.jerseyApplicationHandler,
|
||||||
this.environment.getRequestLog(),
|
this.environment.getRequestLog(),
|
||||||
authenticated,
|
authenticated,
|
||||||
this.environment.getMessageFactory(),
|
this.environment.getMessageFactory(),
|
||||||
ofNullable(this.environment.getConnectListener()),
|
ofNullable(this.environment.getConnectListener()),
|
||||||
this.environment.getIdleTimeoutMillis());
|
this.environment.getIdleTimeoutMillis());
|
||||||
} catch (AuthenticationException | IOException e) {
|
} catch (AuthenticationException | IOException e) {
|
||||||
logger.warn("Authentication failure", e);
|
logger.warn("Authentication failure", e);
|
||||||
try {
|
try {
|
||||||
response.sendError(500, "Failure");
|
response.sendError(500, "Failure");
|
||||||
} catch (IOException ex) {}
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue