All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.aroma.data.cassandra.CassandraActivityRepository 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.*;
import java.util.function.Function;
import javax.inject.Inject;

import com.datastax.driver.core.*;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tech.aroma.data.ActivityRepository;
import tech.aroma.data.cassandra.Tables.Activity;
import tech.aroma.thrift.LengthOfTime;
import tech.aroma.thrift.User;
import tech.aroma.thrift.events.Event;
import tech.aroma.thrift.exceptions.*;
import tech.aroma.thrift.functions.TimeFunctions;
import tech.sirwellington.alchemy.thrift.ThriftObjects;

import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.ttl;
import static java.util.stream.Collectors.toList;
import static tech.aroma.data.assertions.RequestAssertions.validUserId;
import static tech.sirwellington.alchemy.arguments.Arguments.*;
import static tech.sirwellington.alchemy.arguments.assertions.Assertions.notNull;
import static tech.sirwellington.alchemy.arguments.assertions.BooleanAssertions.trueStatement;
import static tech.sirwellington.alchemy.arguments.assertions.NumberAssertions.greaterThan;
import static tech.sirwellington.alchemy.arguments.assertions.StringAssertions.*;

/**
 *
 * @author SirWellington
 */
final class CassandraActivityRepository implements ActivityRepository
{
    
    private final static Logger LOG = LoggerFactory.getLogger(CassandraActivityRepository.class);
    
    private final Session session;
    private final Function eventMapper;
    
    @Inject
    CassandraActivityRepository(Session session, Function eventMapper)
    {
        checkThat(session, eventMapper)
            .are(notNull());
        
        this.session = session;
        this.eventMapper = eventMapper;
    }
    
    @Override
    public void saveEvent(Event event, User forUser, LengthOfTime lifetime) throws TException
    {
        checkEvent(event);
        checkUser(forUser);
        checkLifetime(lifetime);
        
        User user = forUser;
        
        Statement insertStatement = createStatementToSaveEventForUser(event, user, lifetime);
        
        tryToExecute(insertStatement, "saveEvent");
    }
    
    @Override
    public boolean containsEvent(String eventId, User user) throws TException
    {
        checkEventId(eventId);
        checkUser(user);
        
        Statement query = createQueryToCheckIfEventExists(eventId, user);
        
        ResultSet results = tryToExecute(query, "containsEvent");
        Row row = results.one();
        checkThat(row)
            .throwing(OperationFailedException.class)
            .usingMessage("Failed to query for event with ID " + eventId)
            .is(notNull());
        
        return row.getLong(0) > 0L;
    }
    
    @Override
    public Event getEvent(String eventId, User user) throws TException
    {
        checkEventId(eventId);
        checkUser(user);
        
        Statement query = createQueryToGetEvent(eventId, user);
        
        ResultSet results = tryToExecute(query, "getEvent");
        
        Row row = results.one();
        checkThat(row)
            .throwing(DoesNotExistException.class)
            .usingMessage("No such event with ID " + eventId + " for user " + user)
            .is(notNull());
        
        return mapRowToEvent(row);
    }
    
    @Override
    public List getAllEventsFor(User user) throws TException
    {
        checkUser(user);
        
        Statement query = createQueryToGetAllEventsForUser(user);
        
        ResultSet results = tryToExecute(query, "getAllEvents");
        
        return results.all().parallelStream()
            .map(eventMapper::apply)
            .filter(Objects::nonNull)
            .collect(toList());
    }
    
    @Override
    public void deleteEvent(String eventId, User user) throws TException
    {
        checkEventId(eventId);
        checkUser(user);
        
        Statement deleteStatement = createStatementToDelete(eventId, user);
        
        tryToExecute(deleteStatement, "deleteEvent");
    }
    
    @Override
    public void deleteAllEventsFor(User user) throws TException
    {
        checkUser(user);
        
        Statement deleteStatement = createStatementToDeleteAllEventsFor(user);
        
        tryToExecute(deleteStatement, "deleteAllEvents");
    }
    
    private void checkUser(User user) throws InvalidArgumentException
    {
        checkThat(user)
            .usingMessage("user cannot be null")
            .throwing(InvalidArgumentException.class)
            .is(notNull());
        
        checkThat(user.userId)
            .throwing(InvalidArgumentException.class)
            .is(validUserId());
    }
    
    private void checkEvent(Event event) throws InvalidArgumentException
    {
        checkThat(event)
            .throwing(InvalidArgumentException.class)
            .usingMessage("Event cannot be null")
            .is(notNull());
        
        checkThat(event.eventId)
            .usingMessage("eventId must be a valid UUID")
            .throwing(InvalidArgumentException.class)
            .is(validUUID());
        
        checkThat(event.eventType.isSet())
            .throwing(InvalidArgumentException.class)
            .usingMessage("EventType must be set")
            .is(trueStatement());
    }
    
    private Statement createStatementToSaveEventForUser(Event event, User user, LengthOfTime lifetime) throws TException
    {
        UUID eventId = UUID.fromString(event.eventId);
        UUID userId = UUID.fromString(user.userId);
        String serializedEvent = ThriftObjects.toJson(event);
        
        
        Insert statement = QueryBuilder
            .insertInto(Activity.TABLE_NAME)
            .value(Activity.USER_ID, userId)
            .value(Activity.EVENT_ID, eventId)
            .value(Activity.SERIALIZED_EVENT, serializedEvent);
        
        UUID appId;
        UUID actorId;
        Date timeOfEvent;
        
        if (event.isSetApplicationId())
        {
            appId = UUID.fromString(event.applicationId);
            statement = statement.value(Activity.APP_ID, appId);
        }
        
        
        if (event.isSetUserIdOfActor())
        {
            actorId = UUID.fromString(event.userIdOfActor);
            statement = statement.value(Activity.ACTOR_ID, actorId);
        }
        
        if (event.isSetTimestamp())
        {
            timeOfEvent = new Date(event.timestamp);
            statement = statement.value(Activity.TIME_OF_EVENT, timeOfEvent);
        }

        int ttl = (int) TimeFunctions.toSeconds(lifetime);

        return statement.using(ttl(ttl));
    }
    
    private ResultSet tryToExecute(Statement statement, String operationName) throws OperationFailedException
    {
        try
        {
            return session.execute(statement);
        }
        catch (Exception ex)
        {
            LOG.error("Failed to execute Cassandra Statement: {}", operationName, ex);
            throw new OperationFailedException("Could not perform operation: " + ex.getMessage());
        }
    }
    
    private void checkEventId(String eventId) throws InvalidArgumentException
    {
        checkThat(eventId)
            .throwing(InvalidArgumentException.class)
            .usingMessage("eventId missing")
            .is(nonEmptyString())
            .usingMessage("eventId must be a valid uuid")
            .is(validUUID());
    }
    
    private Statement createQueryToCheckIfEventExists(String eventId, User user)
    {
        UUID eventUuid = UUID.fromString(eventId);
        UUID userUuid = UUID.fromString(user.userId);
        
        return QueryBuilder
            .select()
            .countAll()
            .from(Activity.TABLE_NAME)
            .where(eq(Activity.USER_ID, userUuid))
            .and(eq(Activity.EVENT_ID, eventUuid));
    }
    
    private Statement createQueryToGetEvent(String eventId, User user)
    {
        UUID eventUuid = UUID.fromString(eventId);
        UUID userUuid = UUID.fromString(user.userId);
        
        return QueryBuilder
            .select()
            .all()
            .from(Activity.TABLE_NAME)
            .where(eq(Activity.USER_ID, userUuid))
            .and(eq(Activity.EVENT_ID, eventUuid));
    }
    
    private Statement createQueryToGetAllEventsForUser(User user)
    {
        UUID userUuid = UUID.fromString(user.userId);
        
        return QueryBuilder
            .select()
            .all()
            .from(Activity.TABLE_NAME)
            .where(eq(Activity.USER_ID, userUuid));
    }
    
    private Statement createStatementToDelete(String eventId, User user)
    {
        UUID eventUuid = UUID.fromString(eventId);
        UUID userUuid = UUID.fromString(user.userId);
        
        return QueryBuilder
            .delete()
            .all()
            .from(Activity.TABLE_NAME)
            .where(eq(Activity.USER_ID, userUuid))
            .and(eq(Activity.EVENT_ID, eventUuid));
    }
    
    private Statement createStatementToDeleteAllEventsFor(User user)
    {
        UUID userUuid = UUID.fromString(user.userId);
        
        return QueryBuilder
            .delete()
            .all()
            .from(Activity.TABLE_NAME)
            .where(eq(Activity.USER_ID, userUuid));
    }

    private Event mapRowToEvent(Row row) throws DoesNotExistException
    {
        if (row == null)
        {
            return new Event();
        }

        Event event = eventMapper.apply(row);
        
        checkThat(event)
            .usingMessage("event does not exist")
            .throwing(DoesNotExistException.class)
            .is(notNull());
        
        return event;
    }

    private void checkLifetime(LengthOfTime lifetime) throws InvalidArgumentException
    {
        checkThat(lifetime)
            .throwing(InvalidArgumentException.class)
            .usingMessage("lifetime missing")
            .is(notNull());
        
        checkThat(lifetime.value)
            .throwing(InvalidArgumentException.class)
            .usingMessage("Lifetime duration must be > 0")
            .is(greaterThan(0L));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy