nu.yona.server.goals.service.ActivityCategoryService.java Source code

Java tutorial

Introduction

Here is the source code for nu.yona.server.goals.service.ActivityCategoryService.java

Source

/*******************************************************************************
 * 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.goals.service;

import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.transaction.Transactional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import nu.yona.server.Translator;
import nu.yona.server.goals.entities.ActivityCategory;
import nu.yona.server.goals.entities.ActivityCategoryRepository;

@CacheConfig(cacheNames = "activityCategories")
@Service
public class ActivityCategoryService {
    /**
     * The FilterService is created as a small nested class, to ensure ActivityCategoryService.getAllActivityCategories is called
     * from outside, through the Spring proxy. Calling it from inside means the call does not go through the Spring proxy and thus
     * Spring cannot intercept the call to fetch the data from the cache.
     */
    @Service
    public static class FilterService {
        @Autowired
        private ActivityCategoryService activityCategoryService;

        public Set<ActivityCategoryDto> getMatchingCategoriesForSmoothwallCategories(
                Set<String> smoothwallCategories) {
            return activityCategoryService.getAllActivityCategories().stream().filter(ac -> {
                Set<String> acSmoothwallCategories = new HashSet<>(ac.getSmoothwallCategories());
                acSmoothwallCategories.retainAll(smoothwallCategories);
                return !acSmoothwallCategories.isEmpty();
            }).collect(Collectors.toSet());
        }

        public Set<ActivityCategoryDto> getMatchingCategoriesForApp(String application) {
            return activityCategoryService.getAllActivityCategories().stream()
                    .filter(ac -> ac.getApplications().contains(application)).collect(Collectors.toSet());
        }
    }

    @Autowired
    private ActivityCategoryRepository repository;

    private static final Logger logger = LoggerFactory.getLogger(ActivityCategoryService.class);

    @Transactional
    public ActivityCategoryDto getActivityCategory(UUID id) {
        ActivityCategory activityCategoryEntity = getEntityById(id);
        return ActivityCategoryDto.createInstance(activityCategoryEntity);
    }

    @Cacheable(value = "activityCategorySet", key = "'instance'")
    @Transactional
    public Set<ActivityCategoryDto> getAllActivityCategories() {
        // Sort the collection to make it simpler to compare the activity categories
        return StreamSupport.stream(repository.findAll().spliterator(), false)
                .map(ActivityCategoryDto::createInstance)
                .collect(Collectors
                        .toCollection(() -> new TreeSet<>((Comparator<ActivityCategoryDto> & Serializable) (l,
                                r) -> l.getName().compareTo(r.getName()))));
    }

    @CacheEvict(value = "activityCategorySet", key = "'instance'")
    @Transactional
    public ActivityCategoryDto addActivityCategory(ActivityCategoryDto activityCategoryDto) {
        logger.info("Adding activity category '{}' with ID '{}'",
                activityCategoryDto.getName(Translator.EN_US_LOCALE), activityCategoryDto.getId());
        assertNoDuplicateNames(Collections.emptySet(), activityCategoryDto.getLocalizableNameByLocale());
        return ActivityCategoryDto
                .createInstance(repository.save(activityCategoryDto.createActivityCategoryEntity()));
    }

    @CacheEvict(value = "activityCategorySet", key = "'instance'")
    @Transactional
    public ActivityCategoryDto updateActivityCategory(UUID id, ActivityCategoryDto activityCategoryDto) {
        ActivityCategory originalEntity = getEntityById(id);
        logger.info("Updating activity category '{}' with ID '{}'", getName(originalEntity), id);
        assertNoDuplicateNames(Collections.singleton(id), activityCategoryDto.getLocalizableNameByLocale());
        return ActivityCategoryDto.createInstance(updateActivityCategory(originalEntity, activityCategoryDto));
    }

    @CacheEvict(value = "activityCategorySet", key = "'instance'")
    @Transactional
    public void updateActivityCategorySet(Set<ActivityCategoryDto> activityCategoryDtos) {
        logger.info("Updating entire activity category set");
        Set<ActivityCategory> activityCategoriesInRepository = getAllActivityCategoryEntities();
        deleteRemovedActivityCategories(activityCategoriesInRepository, activityCategoryDtos);
        explicitlyFlushUpdatesToDatabase();
        addOrUpdateNewActivityCategories(activityCategoryDtos, activityCategoriesInRepository);
        logger.info("Activity category set update completed");
    }

    @CacheEvict(value = "activityCategorySet", key = "'instance'")
    @Transactional
    public void deleteActivityCategory(UUID id) {
        deleteActivityCategory(getEntityById(id));
    }

    private void deleteActivityCategory(ActivityCategory entity) {
        logger.info("Deleting activity category '{}' with ID '{}'", getName(entity), entity.getId());
        repository.delete(entity);
    }

    private void assertNoDuplicateNames(Set<UUID> idsToSkip, Map<Locale, String> localizableName) {
        Iterable<ActivityCategory> allCategories = repository.findAll();
        List<ActivityCategory> categoriesToConsider = StreamSupport.stream(allCategories.spliterator(), false)
                .filter(c -> !idsToSkip.contains(c.getId())).collect(Collectors.toList());
        for (Entry<Locale, String> localeAndName : localizableName.entrySet()) {
            assertNoDuplicateNames(categoriesToConsider, localeAndName);
        }
    }

    private void assertNoDuplicateNames(List<ActivityCategory> categoriesToConsider,
            Entry<Locale, String> localeAndName) {
        if (categoriesToConsider.stream().anyMatch(
                c -> localeAndName.getValue().equals(c.getLocalizableName().get(localeAndName.getKey())))) {
            throw ActivityCategoryException.duplicateName(localeAndName.getKey(), localeAndName.getValue());
        }
    }

    private ActivityCategory updateActivityCategory(ActivityCategory activityCategoryTargetEntity,
            ActivityCategoryDto activityCategorySourceDto) {
        return repository.save(activityCategorySourceDto.updateActivityCategory(activityCategoryTargetEntity));
    }

    private ActivityCategory getEntityById(UUID id) {
        ActivityCategory entity = repository.findOne(id);
        if (entity == null) {
            throw ActivityCategoryException.notFound(id);
        }
        return entity;
    }

    private Set<ActivityCategory> getAllActivityCategoryEntities() {
        Set<ActivityCategory> activityCategories = new HashSet<>();
        for (ActivityCategory activityCategory : repository.findAll()) {
            activityCategories.add(activityCategory);
        }
        return activityCategories;
    }

    private void addOrUpdateNewActivityCategories(Set<ActivityCategoryDto> activityCategoryDtos,
            Set<ActivityCategory> activityCategoriesInRepository) {
        Map<UUID, ActivityCategory> activityCategoriesInRepositoryMap = activityCategoriesInRepository.stream()
                .collect(Collectors.toMap(ActivityCategory::getId, ac -> ac));

        activityCategoryDtos.stream()
                .forEach(a -> addOrUpdateNewActivityCategory(a, activityCategoriesInRepositoryMap));
    }

    private void addOrUpdateNewActivityCategory(ActivityCategoryDto activityCategoryDto,
            Map<UUID, ActivityCategory> activityCategoriesInRepositoryMap) {
        ActivityCategory activityCategoryEntity = activityCategoriesInRepositoryMap
                .get(activityCategoryDto.getId());
        if (activityCategoryEntity == null) {
            addActivityCategory(activityCategoryDto);
            explicitlyFlushUpdatesToDatabase();
        } else {
            if (!isUpToDate(activityCategoryEntity, activityCategoryDto)) {
                updateActivityCategory(activityCategoryDto.getId(), activityCategoryDto);
                explicitlyFlushUpdatesToDatabase();
            }
        }
    }

    private boolean isUpToDate(ActivityCategory entity, ActivityCategoryDto dto) {
        return entity.getLocalizableName().equals(dto.getLocalizableNameByLocale())
                && entity.isMandatoryNoGo() == dto.isMandatoryNoGo()
                && entity.getSmoothwallCategories().equals(dto.getSmoothwallCategories())
                && entity.getApplications().equals(dto.getApplications())
                && entity.getLocalizableDescription().equals(dto.getLocalizableDescriptionByLocale());
    }

    private void deleteRemovedActivityCategories(Set<ActivityCategory> activityCategoriesInRepository,
            Set<ActivityCategoryDto> activityCategoryDtos) {
        Map<UUID, ActivityCategoryDto> activityCategoryDtosMap = activityCategoryDtos.stream()
                .collect(Collectors.toMap(ActivityCategoryDto::getId, ac -> ac));

        activityCategoriesInRepository.stream().filter(ac -> !activityCategoryDtosMap.containsKey(ac.getId()))
                .forEach(this::deleteActivityCategory);
    }

    private String getName(ActivityCategory entity) {
        return entity.getLocalizableName().get(Translator.EN_US_LOCALE);
    }

    /**
     * This method calls flush on the activity category repository. It shouldn't be necessary to do this, but the implicit flush
     * (done just before JPA sends a query to the database) causes a "java.sql.SQLException: invalid transaction state: read-only
     * SQL-transaction". Explicit flushes prevent that issue. This requires the repository to be a JpaRepository rather than a
     * CrudRepository.
     */
    private void explicitlyFlushUpdatesToDatabase() {
        repository.flush();
    }
}