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

tech.aroma.service.operations.DeleteMessageOperation Maven / Gradle / Ivy

/*
 * Copyright 2016 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.service.operations;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.inject.Inject;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sir.wellington.alchemy.collections.lists.Lists;
import sir.wellington.alchemy.collections.sets.Sets;
import tech.aroma.data.ActivityRepository;
import tech.aroma.data.ApplicationRepository;
import tech.aroma.data.FollowerRepository;
import tech.aroma.data.MessageRepository;
import tech.aroma.data.UserRepository;
import tech.aroma.thrift.Application;
import tech.aroma.thrift.User;
import tech.aroma.thrift.events.ApplicationMessagesDeleted;
import tech.aroma.thrift.events.Event;
import tech.aroma.thrift.events.EventType;
import tech.aroma.thrift.exceptions.InvalidArgumentException;
import tech.aroma.thrift.exceptions.UnauthorizedException;
import tech.aroma.thrift.service.DeleteMessageRequest;
import tech.aroma.thrift.service.DeleteMessageResponse;
import tech.sirwellington.alchemy.arguments.AlchemyAssertion;
import tech.sirwellington.alchemy.thrift.operations.ThriftOperation;

import static java.lang.String.format;
import static java.time.Instant.now;
import static java.util.stream.Collectors.toList;
import static tech.aroma.data.assertions.RequestAssertions.validApplicationId;
import static tech.aroma.data.assertions.RequestAssertions.validMessageId;
import static tech.sirwellington.alchemy.arguments.Arguments.checkThat;
import static tech.sirwellington.alchemy.arguments.assertions.Assertions.notNull;
import static tech.sirwellington.alchemy.arguments.assertions.CollectionAssertions.elementInCollection;
import static tech.sirwellington.alchemy.arguments.assertions.StringAssertions.nonEmptyString;
import static tech.sirwellington.alchemy.generator.AlchemyGenerator.one;
import static tech.sirwellington.alchemy.generator.StringGenerators.uuids;

/**
 *
 * @author SirWellington
 */
final class DeleteMessageOperation implements ThriftOperation
{

    private final static Logger LOG = LoggerFactory.getLogger(DeleteMessageOperation.class);
    
    private final ActivityRepository activityRepo;
    private final ApplicationRepository appRepo;
    private final FollowerRepository followerRepo;
    private final MessageRepository messageRepo;
    private final UserRepository userRepo;

    @Inject
    DeleteMessageOperation(ActivityRepository activityRepo,
                           ApplicationRepository appRepo,
                           FollowerRepository followerRepo,
                           MessageRepository messageRepo,
                           UserRepository userRepo)
    {
        checkThat(activityRepo, appRepo, followerRepo, messageRepo, userRepo)
            .are(notNull());
        
        this.activityRepo = activityRepo;
        this.appRepo = appRepo;
        this.followerRepo = followerRepo;
        this.messageRepo = messageRepo;
        this.userRepo = userRepo;
    }
    
    @Override
    public DeleteMessageResponse process(DeleteMessageRequest request) throws TException
    {
        checkThat(request)
            .throwing(ex -> new InvalidArgumentException(ex.getMessage()))
            .is(good());

        String appId = request.applicationId;
        String userId = request.token.userId;
        Application app = appRepo.getById(appId);

        checkThat(userId)
            .usingMessage("Not Authorized to delete messages for App")
            .throwing(UnauthorizedException.class)
            .is(elementInCollection(app.owners));

        int count;
        if (request.deleteAll)
        {
            count = deleteAllMessages(appId);
            saveActivityThatAppMessagesDeletedBy(userId, app, count);
        }
        else
        {
            count = deleteWithOptions(request);
        }

        return new DeleteMessageResponse().setMessagesDeleted(count);

    }

    private AlchemyAssertion good()
    {
        return request ->
        {
            checkThat(request)
                .usingMessage("request is null")
                .is(notNull());
            
            checkThat(request.token)
                .usingMessage("request missing token")
                .is(notNull());
            
            checkThat(request.token.userId)
                .usingMessage("request missing userId in Token")
                .is(nonEmptyString());
            
            checkThat(request.applicationId)
                .is(validApplicationId());
            
            if (request.isSetMessageId())
            {
                checkThat(request.messageId)
                    .is(validMessageId());
            }
            
            if (request.isSetMessageIds())
            {
                for (String messageId : request.messageIds)
                {
                    checkThat(messageId)
                        .is(validMessageId());
                }
            }
        };
    }

    private int deleteWithOptions(DeleteMessageRequest request)
    {
        String appId = request.applicationId;

        Set messagesToDelete = Sets.create();

        if (request.isSetMessageId())
        {
            messagesToDelete.add(request.messageId);
        }

        if (request.isSetMessageIds())
        {
            messagesToDelete.addAll(request.messageIds);
        }

        messagesToDelete.parallelStream()
            .forEach(msg -> this.deleteMessage(appId, msg));
        LOG.debug("Deleted {} messages for App [{]]", messagesToDelete.size(), appId);

        return messagesToDelete.size();
    }

    private int deleteAllMessages(String appId) throws TException
    {
        Long count = messageRepo.getCountByApplication(appId);
        messageRepo.deleteAllMessages(appId);

        LOG.debug("Deleted all {} messages for App {}", count, appId);
        return count.intValue();
    }

    private void deleteMessage(String appId, String messageId)
    {
        try
        {
            messageRepo.deleteMessage(appId, messageId);
        }
        catch (TException ex)
        {
            //Ignoring this is not good long-term behavior
            LOG.error("Could not delete message with ID [{}] for App [{}]", messageId, appId, ex);
        }
    }

    private void saveActivityThatAppMessagesDeletedBy(String userId, Application app, int count) throws TException
    {
        User userDeleting = userRepo.getUser(userId);
        
        Event event = createEventRememberingAppMessagesDeleted(userDeleting, app, count);
        
        getUsersToNotifyFor(app)
            .parallelStream()
            .forEach(user -> this.tryToSaveEvent(event, user));
    }
    
    private Event createEventRememberingAppMessagesDeleted(User actor, Application app, int totalMessagesDeleted)
    {
        ApplicationMessagesDeleted messagesDeleted = new ApplicationMessagesDeleted()
            .setMessage(format("%d messages deleted for App %s", totalMessagesDeleted, app.name))
            .setTotalMessagesDeleted(totalMessagesDeleted);
            
        EventType eventType = createEventTypeFor(messagesDeleted);
        
        Event event = new Event()
            .setActor(actor)
            .setUserIdOfActor(actor.userId)
            .setApplication(app)
            .setApplicationId(app.applicationId)
            .setTimestamp(now().toEpochMilli())
            .setEventId(one(uuids))
        .setEventType(eventType);
        
        return event;
    }
    
 

    private EventType createEventTypeFor(ApplicationMessagesDeleted messagesDeleted)
    {
        EventType eventType = new EventType();
        eventType.setApplicationMessageDeleted(messagesDeleted);
        return eventType;
    }
    
    private List getUsersToNotifyFor(Application app) throws TException
    {
        String appId = app.applicationId;

        List owners = app.owners.stream()
            .map(this::toUser)
            .filter(Objects::nonNull)
            .collect(toList());

        List followers = followerRepo.getApplicationFollowers(appId);

        return Lists.combine(owners, followers);
    }

    private User toUser(String userId)
    {
        try
        {
            return userRepo.getUser(userId);
        }
        catch (Exception ex)
        {
            LOG.warn("Failed to load User with ID [{}]", userId, ex);
            return null;
        }
    }

    private void tryToSaveEvent(Event event, User forUser)
    {
        try
        {
            activityRepo.saveEvent(event, forUser);
        }
        catch (Exception ex)
        {
            LOG.error("Failed to save Event {} for User {}", event, forUser, ex);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy