
tech.aroma.data.cassandra.CassandraMessageRepository Maven / Gradle / Ivy
/*
* Copyright 2017 RedRoma, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tech.aroma.data.cassandra;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import javax.inject.Inject;
import com.datastax.driver.core.*;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sir.wellington.alchemy.collections.lists.Lists;
import tech.aroma.data.MessageRepository;
import tech.aroma.data.cassandra.Tables.Messages;
import tech.aroma.thrift.LengthOfTime;
import tech.aroma.thrift.Message;
import tech.aroma.thrift.exceptions.*;
import tech.aroma.thrift.functions.TimeFunctions;
import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
import static java.lang.String.format;
import static tech.aroma.data.cassandra.Tables.Messages.*;
import static tech.sirwellington.alchemy.arguments.Arguments.*;
import static tech.sirwellington.alchemy.arguments.assertions.Assertions.notNull;
import static tech.sirwellington.alchemy.arguments.assertions.StringAssertions.*;
/**
* @author SirWellington
*/
final class CassandraMessageRepository implements MessageRepository
{
private final static Logger LOG = LoggerFactory.getLogger(CassandraMessageRepository.class);
private final Session cassandra;
private final Function messageMapper;
@Inject
CassandraMessageRepository(Session cassandra,
Function messageMapper)
{
checkThat(cassandra, messageMapper)
.are(notNull());
this.cassandra = cassandra;
this.messageMapper = messageMapper;
}
@Override
public void saveMessage(Message message, LengthOfTime lifetime) throws TException
{
checkThat(message, lifetime)
.throwing(InvalidArgumentException.class)
.is(notNull());
Statement insertStatement = createInsertForMessage(message, lifetime);
Statement updateTotalMessagesByApp = createUpdateForMessageByApp(message);
Statement updateTotalMessageByTitle = createUpdateForMessageCounterByTitle(message);
try
{
cassandra.execute(insertStatement);
LOG.debug("Successfully saved message in Cassandra with a lifetime of {}: {}", lifetime, message);
cassandra.executeAsync(updateTotalMessageByTitle);
cassandra.executeAsync(updateTotalMessagesByApp);
LOG.debug("Successfully Updated Total Message Counters for App {} and title {}", message.applicationId, message.title);
}
catch (Exception ex)
{
LOG.error("Failed to store message in Cassandra: {}", message, ex);
throw new OperationFailedException("Could save Message");
}
}
@Override
public Message getMessage(String applicationId, String messageId) throws TException
{
checkMessageId(messageId);
checkAppId(applicationId);
Statement query = createQueryForMessageWithId(applicationId, messageId);
LOG.debug("Querying cassandra for message with ID [{}] and App [{}]", messageId, applicationId);
ResultSet results = tryToExecute(query, "Failed to query for message with ID: " + messageId);
Row row = results.one();
checkThat(row)
.throwing(MessageDoesNotExistException.class)
.usingMessage(format("No Message with App ID [%s] and Msg ID [%s]", applicationId, messageId))
.is(notNull());
Message message = createMessageFromRow(row);
return message;
}
@Override
public void deleteMessage(String applicationId, String messageId) throws TException
{
checkAppId(applicationId);
checkMessageId(messageId);
Statement deleteStatement = createDeleteStatementFor(applicationId, messageId);
tryToExecute(deleteStatement, "Failed to delete message with ID: " + messageId);
}
@Override
public boolean containsMessage(String applicationId, String messageId) throws TException
{
checkMessageId(messageId);
checkAppId(applicationId);
Statement query = createQueryToCheckIfMessageExists(applicationId, messageId);
ResultSet results = tryToExecute(query, "Could not query for message: " + messageId);
Row row = results.one();
checkRowIsPresent(row);
long count = row.getLong(0);
return count > 0;
}
@Override
public List getByHostname(String hostname) throws TException
{
checkThat(hostname)
.usingMessage("missing hostname")
.throwing(InvalidArgumentException.class)
.is(nonEmptyString())
.is(stringWithLengthGreaterThanOrEqualTo(1));
Statement query = createQueryToFindMessageByHostname(hostname);
ResultSet results = tryToExecute(query, "Could not query for mesages by hostname: " + hostname);
List messages = Lists.create();
for (Row row : results)
{
Message message = createMessageFromRow(row);
messages.add(message);
}
LOG.debug("Found {} messages by hostname {}", messages.size(), hostname);
return messages;
}
@Override
public List getByApplication(String applicationId) throws TException
{
checkAppId(applicationId);
Statement query = createQueryToFindMessagesByApplication(applicationId);
ResultSet results = tryToExecute(query, "Could not query for messages by App: " + applicationId);
List messages = Lists.create();
for (Row row : results)
{
Message message = createMessageFromRow(row);
messages.add(message);
}
LOG.debug("Found {} messages by app with ID [{}]", messages.size(), applicationId);
return messages;
}
@Override
public List getByTitle(String applicationId, String title) throws TException
{
checkAppId(applicationId);
checkTitle(title);
Statement query = createQueryToFindMessagesByTitle(title);
ResultSet results = tryToExecute(query, "Could not get messages by Title: " + title + ", App: " + applicationId);
List messages = Lists.create();
for (Row row : results)
{
Message message = createMessageFromRow(row);
messages.add(message);
}
LOG.debug("Found {} messages by app with ID [{}]", messages.size(), applicationId);
return messages;
}
@Override
public long getCountByApplication(String applicationId) throws TException
{
checkAppId(applicationId);
Statement query = createQueryToCountMessagesByApplication(applicationId);
ResultSet results = tryToExecute(query, "Failed to count messages for App: " + applicationId);
Row row = results.one();
checkRowIsPresent(row);
return row.getLong(0);
}
@Override
public void deleteAllMessages(String applicationId) throws TException
{
checkAppId(applicationId);
Statement deleteStatement = createStatementToDeleteAllMessagesFor(applicationId);
tryToExecute(deleteStatement, "Failed to delete All Messages for App: " + applicationId);
}
private Statement createInsertForMessage(Message message, LengthOfTime lifetime) throws InvalidArgumentException
{
checkThat(message.messageId, message.applicationId)
.throwing(InvalidArgumentException.class)
.are(nonEmptyString())
.are(validUUID());
//UUIDs
UUID msgId = UUID.fromString(message.messageId);
UUID appId = UUID.fromString(message.applicationId);
//Urgency
String urgency = message.urgency != null ? message.urgency.toString() : null;
Long timeToLive = TimeFunctions.toSeconds(lifetime);
return QueryBuilder
.insertInto(Messages.TABLE_NAME)
.value(MESSAGE_ID, msgId)
.value(APP_ID, appId)
.value(APP_NAME, message.applicationName)
.value(BODY, message.body)
.value(DEVICE_NAME, message.deviceName)
.value(HOSTNAME, message.hostname)
.value(MAC_ADDRESS, message.macAddress)
.value(TITLE, message.title)
.value(URGENCY, urgency)
.value(TIME_CREATED, message.timeOfCreation)
.value(TIME_RECEIVED, message.timeMessageReceived)
.using(ttl(timeToLive.intValue()));
}
private Statement createUpdateForMessageByApp(Message message)
{
UUID appId = UUID.fromString(message.applicationId);
return QueryBuilder
.update(Messages.TABLE_NAME_TOTALS_BY_APP)
.where(eq(APP_ID, appId))
.with(incr(TOTAL_MESSAGES, 1));
}
private Statement createUpdateForMessageCounterByTitle(Message message)
{
UUID appId = UUID.fromString(message.applicationId);
return QueryBuilder
.update(Messages.TABLE_NAME_TOTALS_BY_TITLE)
.where(eq(APP_ID, appId))
.and(eq(TITLE, message.title))
.with(incr(TOTAL_MESSAGES, 1));
}
private void checkMessageId(String messageId) throws InvalidArgumentException
{
checkThat(messageId)
.usingMessage("missing messageId")
.throwing(InvalidArgumentException.class)
.is(nonEmptyString())
.usingMessage("messageId must be a UUID")
.is(validUUID());
}
private Statement createQueryForMessageWithId(String applicationId, String messageId)
{
UUID appId = UUID.fromString(applicationId);
UUID msgId = UUID.fromString(messageId);
return QueryBuilder
.select()
.all()
.from(Messages.TABLE_NAME)
.where(eq(MESSAGE_ID, msgId))
.and(eq(APP_ID, appId))
.limit(2);
}
private void checkRowIsPresent(Row row) throws OperationFailedException
{
checkThat(row)
.usingMessage("query produced no rows")
.throwing(OperationFailedException.class)
.is(notNull());
}
private Message createMessageFromRow(Row row) throws OperationFailedException
{
return messageMapper.apply(row);
}
private Statement createDeleteStatementFor(String applicationId, String messageId)
{
UUID msgId = UUID.fromString(messageId);
UUID appId = UUID.fromString(applicationId);
Statement deleteFromMainTable = QueryBuilder
.delete()
.all()
.from(Messages.TABLE_NAME)
.where(eq(APP_ID, appId))
.and(eq(MESSAGE_ID, msgId));
return deleteFromMainTable;
}
private Statement createQueryToCheckIfMessageExists(String applicationId, String messageId)
{
UUID msgId = UUID.fromString(messageId);
UUID appId = UUID.fromString(applicationId);
return QueryBuilder
.select()
.countAll()
.from(Messages.TABLE_NAME)
.where(eq(APP_ID, appId))
.and(eq(MESSAGE_ID, msgId));
}
private Statement createQueryToFindMessageByHostname(String hostname)
{
return QueryBuilder
.select()
.all()
.from(Messages.TABLE_NAME)
.where(eq(HOSTNAME, hostname));
}
private void checkTitle(String title) throws InvalidArgumentException
{
checkThat(title)
.throwing(InvalidArgumentException.class)
.is(nonEmptyString())
.is(stringWithLengthGreaterThanOrEqualTo(2));
}
private Statement createQueryToFindMessagesByApplication(String applicationId)
{
UUID appId = UUID.fromString(applicationId);
return QueryBuilder
.select()
.all()
.from(Messages.TABLE_NAME)
.where(eq(APP_ID, appId))
.orderBy(desc(MESSAGE_ID))
.limit(3000);
}
private Statement createQueryToCountMessagesByApplication(String applicationId)
{
UUID appId = UUID.fromString(applicationId);
return QueryBuilder
.select()
.countAll()
.from(Messages.TABLE_NAME)
.where(eq(APP_ID, appId));
}
private Statement createQueryToFindMessagesByTitle(String title)
{
return QueryBuilder
.select()
.all()
.from(Messages.TABLE_NAME)
.where(eq(TITLE, title));
}
private void checkAppId(String applicationId) throws InvalidArgumentException
{
checkThat(applicationId)
.throwing(InvalidArgumentException.class)
.usingMessage("missing appId")
.is(nonEmptyString())
.usingMessage("appId must be a UUID Type")
.is(validUUID());
}
private Statement createStatementToDeleteAllMessagesFor(String applicationId)
{
UUID appId = UUID.fromString(applicationId);
Statement deleteFromMainTable = QueryBuilder
.delete()
.all()
.from(Messages.TABLE_NAME)
.where(eq(APP_ID, appId));
return deleteFromMainTable;
}
private ResultSet tryToExecute(Statement statement, String errorMessage) throws OperationFailedException
{
ResultSet results;
try
{
results = cassandra.execute(statement);
}
catch (Exception ex)
{
LOG.error("Failed to execute Cassandra statement: {}", statement, ex);
throw new OperationFailedException(errorMessage + " | " + ex.getMessage());
}
checkThat(results)
.throwing(OperationFailedException.class)
.usingMessage("Cassandra returned null response")
.is(notNull());
return results;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy