Java tutorial
/******************************************************************************* * Copyright (c) 2015, 2018 Stichting Yona Foundation This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. *******************************************************************************/ package nu.yona.server.messaging.service; import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import nu.yona.server.analysis.entities.IntervalActivity; import nu.yona.server.exceptions.InvalidMessageActionException; import nu.yona.server.messaging.entities.Message; import nu.yona.server.messaging.entities.MessageDestination; import nu.yona.server.messaging.entities.MessageRepository; import nu.yona.server.messaging.entities.MessageSource; import nu.yona.server.messaging.entities.MessageSourceRepository; import nu.yona.server.subscriptions.entities.User; import nu.yona.server.subscriptions.service.BuddyService; import nu.yona.server.subscriptions.service.UserAnonymizedDto; import nu.yona.server.subscriptions.service.UserDto; import nu.yona.server.subscriptions.service.UserService; import nu.yona.server.util.Require; @Service public class MessageService { @Autowired private UserService userService; @Autowired private BuddyService buddyService; @Autowired private TheDtoManager dtoManager; @Autowired private MessageSourceRepository messageSourceRepository; @Autowired(required = false) private MessageRepository messageRepository; @Transactional public Page<MessageDto> getReceivedMessages(UserDto user, boolean onlyUnreadMessages, Pageable pageable) { return wrapMessagesAsDtos(user, getReceivedMessageEntities(user, onlyUnreadMessages, pageable), pageable); } @Transactional public Page<Message> getReceivedMessageEntities(UUID userId, Pageable pageable) { UserDto user = userService.getPrivateValidatedUser(userId); return getReceivedMessageEntities(user, false, pageable); } private Page<Message> getReceivedMessageEntities(UserDto user, boolean onlyUnreadMessages, Pageable pageable) { MessageSource messageSource = getAnonymousMessageSource(user); return messageSource.getReceivedMessages(pageable, onlyUnreadMessages); } public Page<Message> getReceivedMessageEntitiesSinceDate(UUID userId, LocalDateTime earliestDateTime, Pageable pageable) { UserDto user = userService.getPrivateValidatedUser(userId); MessageSource messageSource = getAnonymousMessageSource(user); return messageSource.getReceivedMessages(pageable, earliestDateTime); } @Transactional public boolean prepareMessageCollection(User user) { boolean updated = false; if (mustTransferDirectMessagesToAnonymousDestination(user)) { transferDirectMessagesToAnonymousDestination(user); updated = true; } if (mustProcessUnprocessedMessages(user)) { processUnprocessedMessages(user); updated = true; } return updated; } private boolean mustTransferDirectMessagesToAnonymousDestination(User user) { return getNamedMessageSource(user).getMessages(null).hasContent(); } @Transactional private void transferDirectMessagesToAnonymousDestination(User user) { MessageSource directMessageSource = getNamedMessageSource(user); Page<Message> directMessages = directMessageSource.getMessages(null); MessageSource anonymousMessageSource = getAnonymousMessageSource(user); MessageDestination anonymousMessageDestination = anonymousMessageSource.getDestination(); MessageDestination directMessageDestination = directMessageSource.getDestination(); for (Message directMessage : directMessages) { directMessageDestination.remove(directMessage); anonymousMessageDestination.send(directMessage); } MessageDestination.getRepository().save(directMessageDestination); MessageDestination.getRepository().save(anonymousMessageDestination); } private boolean mustProcessUnprocessedMessages(User user) { return !getUnprocessedMessages(user).isEmpty(); } private List<Long> getUnprocessedMessages(User user) { MessageDestination anonymousMessageDestination = getAnonymousMessageSource(user).getDestination(); return Message.getRepository().findUnprocessedMessagesFromDestination(anonymousMessageDestination.getId()); } @Transactional private void processUnprocessedMessages(User user) { List<Long> idsOfUnprocessedMessages = getUnprocessedMessages(user); MessageActionDto emptyPayload = new MessageActionDto(Collections.emptyMap()); for (long id : idsOfUnprocessedMessages) { UserDto userDto = userService.getPrivateUser(user.getId()); // Inside loop, as message processing might change it handleMessageAction(userDto, id, "process", emptyPayload); } } @Transactional public MessageDto getMessage(UserDto user, long messageId) { MessageSource messageSource = getAnonymousMessageSource(user); return dtoManager.createInstance(user, messageSource.getMessage(messageId)); } @Transactional public MessageActionDto handleMessageAction(UserDto user, long id, String action, MessageActionDto requestPayload) { MessageSource messageSource = getAnonymousMessageSource(user); return dtoManager.handleAction(user, messageSource.getMessage(id), action, requestPayload); } @Transactional public MessageActionDto deleteMessage(UserDto user, long id) { MessageSource messageSource = getAnonymousMessageSource(user); Message message = messageSource.getMessage(id); deleteMessage(user, message); return MessageActionDto.createInstanceActionDone(); } private void deleteMessage(UserDto user, Message message) { MessageDto messageDto = dtoManager.createInstance(user, message); Require.that(messageDto.canBeDeleted(), InvalidMessageActionException::unprocessedMessageCannotBeDeleted); deleteMessages(Collections.singleton(message)); } private void deleteMessages(Collection<Message> messages) { Set<Message> messagesToBeDeleted = messages.stream() .flatMap(m -> m.getMessagesToBeCascadinglyDeleted().stream()).collect(Collectors.toSet()); messagesToBeDeleted.addAll(messages); Set<MessageDestination> involvedMessageDestinations = messagesToBeDeleted.stream() .map(Message::getMessageDestination).collect(Collectors.toSet()); messagesToBeDeleted.forEach(Message::prepareForDelete); involvedMessageDestinations.forEach(d -> MessageDestination.getRepository().saveAndFlush(d)); messagesToBeDeleted.forEach(m -> m.getMessageDestination().remove(m)); involvedMessageDestinations.forEach(d -> MessageDestination.getRepository().save(d)); } private MessageSource getNamedMessageSource(User user) { return getMessageSource(user.getNamedMessageSourceId()); } private MessageSource getAnonymousMessageSource(UserDto user) { return getMessageSource(user.getOwnPrivateData().getAnonymousMessageSourceId()); } private MessageSource getAnonymousMessageSource(User user) { return getMessageSource(user.getAnonymousMessageSourceId()); } private MessageSource getMessageSource(UUID id) { return messageSourceRepository.findOne(id); } private Page<MessageDto> wrapMessagesAsDtos(UserDto user, Page<? extends Message> messageEntities, Pageable pageable) { List<MessageDto> allMessagePayloads = wrapMessagesAsDtos(user, messageEntities.getContent()); return new PageImpl<>(allMessagePayloads, pageable, messageEntities.getTotalElements()); } private List<MessageDto> wrapMessagesAsDtos(UserDto user, List<? extends Message> messageEntities) { return messageEntities.stream().map(m -> messageToDto(user, m)).collect(Collectors.toList()); } public MessageDto messageToDto(UserDto user, Message message) { return dtoManager.createInstance(user, message); } public static interface DtoManager { MessageDto createInstance(UserDto actingUser, Message messageEntity); MessageActionDto handleAction(UserDto actingUser, Message messageEntity, String action, MessageActionDto requestPayload); } @Component public static class TheDtoManager implements DtoManager { private final Map<Class<? extends Message>, DtoManager> managers = new HashMap<>(); @Override public MessageDto createInstance(UserDto user, Message messageEntity) { return getManager(messageEntity).createInstance(user, messageEntity); } @Override public MessageActionDto handleAction(UserDto user, Message messageEntity, String action, MessageActionDto requestPayload) { return getManager(messageEntity).handleAction(user, messageEntity, action, requestPayload); } public void addManager(Class<? extends Message> messageEntityClass, DtoManager manager) { DtoManager previousManager = managers.put(messageEntityClass, manager); assert previousManager == null; } private DtoManager getManager(Message messageEntity) { assert messageEntity != null; return managers.entrySet().stream().filter(e -> e.getKey().isInstance(messageEntity)).findAny() .map(Map.Entry::getValue) .orElseThrow(() -> MessageServiceException.noDtoManagerRegistered(messageEntity.getClass())); } } @Transactional public void sendMessageAndFlushToDatabase(Message message, MessageDestinationDto destination) { MessageDestination destinationEntity = MessageDestination.getRepository().findOne(destination.getId()); destinationEntity.send(message); MessageDestination.getRepository().saveAndFlush(destinationEntity); } @Transactional public void sendMessage(Message message, MessageDestination destinationEntity) { destinationEntity.send(message); } @Transactional public void removeMessagesFromUser(MessageDestinationDto destination, UUID sentByUserAnonymizedId) { if (sentByUserAnonymizedId == null) { throw new IllegalArgumentException("sentByUserAnonymizedId cannot be null"); } MessageDestination destinationEntity = MessageDestination.getRepository().findOne(destination.getId()); deleteMessages(destinationEntity.getMessagesFromUser(sentByUserAnonymizedId)); } @Transactional public void broadcastMessageToBuddies(UserAnonymizedDto userAnonymized, Supplier<Message> messageSupplier) { buddyService.getBuddyDestinations(userAnonymized) .forEach(destination -> sendMessageAndFlushToDatabase(messageSupplier.get(), destination)); } @Transactional public void sendMessageToUserAnonymized(UserAnonymizedDto userAnonymized, Message message) { sendMessageAndFlushToDatabase(message, userAnonymized.getAnonymousDestination()); } @Transactional public Page<MessageDto> getActivityRelatedMessages(UUID userId, IntervalActivity intervalActivityEntity, Pageable pageable) { UserDto user = userService.getPrivateValidatedUser(userId); MessageSource messageSource = getAnonymousMessageSource(user); return wrapMessagesAsDtos(user, messageSource.getActivityRelatedMessages(intervalActivityEntity, pageable), pageable); } public void deleteMessagesForIntervalActivities(Collection<IntervalActivity> intervalActivities) { if (intervalActivities.isEmpty()) { return; } deleteMessages(messageRepository.findByIntervalActivity(intervalActivities)); } }