fi.vm.sade.osoitepalvelu.kooste.service.organisaatio.DefaultOrganisaatioService.java Source code

Java tutorial

Introduction

Here is the source code for fi.vm.sade.osoitepalvelu.kooste.service.organisaatio.DefaultOrganisaatioService.java

Source

/*
 * Copyright (c) 2013 The Finnish National Board of Education - Opetushallitus
 *
 * This program is free software: Licensed under the EUPL, Version 1.1 or - as
 * soon as they will be approved by the European Commission - subsequent versions
 * of the EUPL (the "Licence");
 *
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at: http://www.osor.eu/eupl/
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * European Union Public Licence for more details.
 */

package fi.vm.sade.osoitepalvelu.kooste.service.organisaatio;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.googlecode.ehcache.annotations.Cacheable;
import com.googlecode.ehcache.annotations.PartialCacheKey;
import com.googlecode.ehcache.annotations.TriggersRemove;
import fi.vm.sade.osoitepalvelu.kooste.common.route.CamelRequestContext;
import fi.vm.sade.osoitepalvelu.kooste.common.util.AndPredicateAdapter;
import fi.vm.sade.osoitepalvelu.kooste.common.util.KoodiHelper;
import fi.vm.sade.osoitepalvelu.kooste.dao.organisaatio.OrganisaatioRepository;
import fi.vm.sade.osoitepalvelu.kooste.domain.OrganisaatioDetails;
import fi.vm.sade.osoitepalvelu.kooste.service.AbstractService;
import fi.vm.sade.osoitepalvelu.kooste.service.organisaatio.dto.converter.OrganisaatioDtoConverter;
import fi.vm.sade.osoitepalvelu.kooste.route.OrganisaatioServiceRoute;
import fi.vm.sade.osoitepalvelu.kooste.route.dto.*;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class DefaultOrganisaatioService extends AbstractService implements OrganisaatioService {
    private static final long serialVersionUID = 6255113288596549870L;

    @Autowired(required = false)
    private OrganisaatioServiceRoute organisaatioServiceRoute;

    @Autowired(required = false)
    private OrganisaatioRepository organisaatioRepository;

    @Autowired
    private OrganisaatioDtoConverter dtoConverter;

    @Value("${organisaatio.cache.livetime.seconds}")
    private long cacheTimeoutSeconds;

    @Override
    @Cacheable(cacheName = "organisaatioHakuResultsCache")
    public List<OrganisaatioYhteystietoHakuResultDto> findOrganisaatioYhteystietos(
            @PartialCacheKey OrganisaatioYhteystietoCriteriaDto criteria, @PartialCacheKey Locale locale,
            CamelRequestContext requestContext) {
        List<OrganisaatioDetails> results;
        // Disabling the conditions that should not effect searches by parent/children relation
        // These are only applied in the end results:
        criteria.setUseKieli(false);
        criteria.setUseKunta(false);
        criteria.setUseOrganisaatioTyyppi(false);

        if (criteria.getNumberOfUsedConditions() < 1) {
            // We can use these now because no other conditions are used (that would result in search by parent/children)
            // and can thus only search once:
            criteria.useAll();
            results = organisaatioRepository.findOrganisaatios(criteria, locale);
        } else if (criteria.isOidUsed()) {
            results = organisaatioRepository.findOrganisaatios(criteria, locale);
            results = mergeParents(results, locale);
        } else {
            // Search is done so that only one condiction is on at the same time.
            boolean yTunnusUsed = criteria.isYtunnusUsed();
            boolean oppilaitostyyppiUsed = criteria.isOppilaitostyyppiUsed();

            criteria.setUseYtunnus(false);
            criteria.setUseOppilaitotyyppi(false);

            // Order of search:
            // 1. YTunnus / koulutuksen jrjestj
            // 2. Oppilaitostyyppi
            // 3. Last round --> parents on

            // First round
            // One of these on: ytunnus, oppilaitostyyppi
            if (yTunnusUsed) {
                // Two rounds possible (ytunnus, oppilaitostyyppi)
                criteria.setUseYtunnus(true);
            } else if (oppilaitostyyppiUsed) {
                // One round possible (oppilaitostyyppi)
                criteria.setUseOppilaitotyyppi(true);
            }
            results = organisaatioRepository.findOrganisaatios(criteria, locale);

            criteria.setUseYtunnus(false);
            criteria.setUseOppilaitotyyppi(false);

            // Second round
            // One of these on: oppilaitostyyppi
            if (yTunnusUsed && oppilaitostyyppiUsed) {
                criteria.setUseOppilaitotyyppi(true);
                results = mergeWithChildren(results, criteria, locale, false);
            }

            criteria.setUseYtunnus(false);
            criteria.setUseOppilaitotyyppi(false);

            // Last round round --> include parents
            results = mergeWithChildren(results, criteria, locale, true);

            // we need to merge parents to the search results as well:
            results = mergeParents(results, locale);
        }

        // restore to defaults:
        criteria.useAll();

        results = new ArrayList<OrganisaatioDetails>(
                Collections2.filter(results, createResultAfterFilter(criteria)));

        return dtoConverter.convert(results, new ArrayList<OrganisaatioYhteystietoHakuResultDto>(),
                OrganisaatioYhteystietoHakuResultDto.class);
    }

    private Predicate<FilterableOrganisaatio> createResultAfterFilter(OrganisaatioYhteystietoCriteriaDto criteria) {
        AndPredicateAdapter<FilterableOrganisaatio> predicate = new AndPredicateAdapter<FilterableOrganisaatio>();

        final List<String> organisaatioTyyppis = criteria.getOrganisaatioTyyppis();
        if (!organisaatioTyyppis.isEmpty()) {
            predicate = predicate.and(new Predicate<FilterableOrganisaatio>() {
                public boolean apply(FilterableOrganisaatio result) {
                    for (String tyyppi : result.getTyypit()) {
                        if (organisaatioTyyppis.contains(tyyppi)) {
                            return true;
                        }
                    }
                    return false;
                }
            });
        }

        final List<String> kuntas = criteria.getKuntaList();
        if (!kuntas.isEmpty()) {
            predicate = predicate.and(new Predicate<FilterableOrganisaatio>() {
                public boolean apply(FilterableOrganisaatio organisaatio) {
                    return kuntas.contains(organisaatio.getKotipaikka());
                }
            });
        }

        final List<String> kielis = criteria.getKieliList();
        if (!kielis.isEmpty()) {
            predicate = predicate.and(new Predicate<FilterableOrganisaatio>() {
                public boolean apply(FilterableOrganisaatio organisaatio) {
                    for (String kieli : KoodiHelper.removeVersion(organisaatio.getKielet())) {
                        if (kielis.contains(kieli)) {
                            return true;
                        }
                    }
                    return false;
                }
            });
        }

        return predicate;
    }

    private List<String> extractParentOids(List<OrganisaatioDetails> organisaatios) {
        List<String> allParents = new ArrayList<String>();
        for (OrganisaatioDetails organisaatio : organisaatios) {
            allParents.addAll(parseParentOidPath(organisaatio));
        }
        return allParents;
    }

    /**
     * Searches for children for which any of their parents is included in the given results and the given criteria
     * applies.
     *
     * @param results to search children for
     * @param criteria to search children with
     * @param locale to order the results by (localization locale for the name)
     * @param includeParents if true, results should include the given results (parents for the searched children)
     * @return the results (given includeParents is true) and their children so that children are inserted after
     * their parents.
     */
    private List<OrganisaatioDetails> mergeWithChildren(List<OrganisaatioDetails> results,
            OrganisaatioYhteystietoCriteriaDto criteria, Locale locale, boolean includeParents) {
        List<OrganisaatioDetails> allChildren = organisaatioRepository
                .findChildren(Collections2.transform(results, FilterableOrganisaatio.GET_OID), criteria, locale);

        Set<String> added = new TreeSet<String>();
        Map<String, List<OrganisaatioDetails>> childrenByAllParents = new TreeMap<String, List<OrganisaatioDetails>>();
        for (OrganisaatioDetails child : allChildren) {
            List<String> parentOids = parseParentOidPath(child);
            for (String parentOid : parentOids) {
                List<OrganisaatioDetails> parentsChildren = childrenByAllParents.get(parentOid);
                if (parentsChildren == null) {
                    parentsChildren = new ArrayList<OrganisaatioDetails>();
                    childrenByAllParents.put(parentOid, parentsChildren);
                }
                parentsChildren.add(child);
            }
        }

        List<OrganisaatioDetails> mergedResults = new ArrayList<OrganisaatioDetails>();
        for (OrganisaatioDetails result : results) {
            if (includeParents && !added.contains(result.getOid())) {
                mergedResults.add(result);
                added.add(result.getOid());
            }
            List<OrganisaatioDetails> parentsChildren = childrenByAllParents.get(result.getOid());
            if (parentsChildren != null) {
                for (OrganisaatioDetails child : parentsChildren) {
                    if (!added.contains(child.getOid())) {
                        mergedResults.add(child);
                        added.add(child.getOid());
                    }
                }
            }
        }

        return mergedResults;
    }

    /**
     * @param results to merge parents for
     * @param locale for ordering the results (localization locale for the name)
     * @return results with all their possible parents so that parents are inserted before their children in
     * inheritance order
     */
    private List<OrganisaatioDetails> mergeParents(List<OrganisaatioDetails> results, Locale locale) {
        List<OrganisaatioDetails> allParents = organisaatioRepository
                .findOrganisaatiosByOids(extractParentOids(results), locale);
        Map<String, OrganisaatioDetails> byOids = new TreeMap<String, OrganisaatioDetails>();
        for (OrganisaatioDetails parent : allParents) {
            byOids.put(parent.getOid(), parent);
        }

        Set<String> added = new TreeSet<String>();
        List<OrganisaatioDetails> mergedResults = new ArrayList<OrganisaatioDetails>();
        for (OrganisaatioDetails result : results) {
            List<String> parentOids = parseParentOidPath(result);
            for (String parentOid : parentOids) {
                OrganisaatioDetails parent = byOids.get(parentOid);
                if (parent != null && !added.contains(parent.getOid())) {
                    mergedResults.add(parent);
                    added.add(parent.getOid());
                }
            }
            if (!added.contains(result.getOid())) {
                mergedResults.add(result);
                added.add(result.getOid());
            }
        }

        return mergedResults;
    }

    /**
     * Normalizes the organisaatio's parent OID path. In the details returned by the organisaatio-service the form
     * of parentOidPath is an array with (most likely only) single element where the parent OIDs are concatenated by
     * |-character and the value is also prefixed and postfixed with additional |-characters.
     *
     * @param child
     * @return the parentOidPath of child as an array of single OIDs
     */
    private List<String> parseParentOidPath(OrganisaatioDetails child) {
        List<String> parentOids = new ArrayList<String>();
        for (String pathPart : child.getParentOidPath()) {
            List<String> pathParts = new ArrayList<String>(Arrays.asList(pathPart.split("\\|")));
            if (pathParts.size() > 1 && "".equals(pathParts.get(0))) {
                pathParts.remove(0);
            }
            if (pathParts.size() > 1 && "".equals(pathParts.get(pathParts.size() - 1))) {
                pathParts.remove(pathParts.get(pathParts.size() - 1));
            }
            parentOids.addAll(pathParts);
        }
        return parentOids;
    }

    @Override
    public void updateOrganisaatioYtunnusDetails(CamelRequestContext requestContext) {
        OrganisaatioHierarchyResultsDto results = organisaatioServiceRoute.findOrganisaatioHierachy(requestContext);
        logger.debug("UPDATING organisaatio's ytunnus details.");
        int count = updateYtunnusResults(results.getOrganisaatiot());
        logger.debug("UPDATED " + count + " organisaatio's ytunnus details.");
    }

    @Override
    public List<String> findAllOidsOfCachedOrganisaatios() {
        return organisaatioRepository.findAllOids();
    }

    @Override
    public String findOidByOppilaitoskoodi(String oppilaitosKoodi) {
        return organisaatioRepository.findOidByOppilaitoskoodi(oppilaitosKoodi);
    }

    private int updateYtunnusResults(List<OrganisaatioHierarchyDto> organisaatiot) {
        int updated = 0;
        for (OrganisaatioHierarchyDto organisaatio : organisaatiot) {
            OrganisaatioDetails details = organisaatioRepository.findOne(organisaatio.getOid());
            if (details != null && organisaatio.getYtunnus() != null) {
                details.setYtunnus(organisaatio.getYtunnus());
                organisaatioRepository.save(details);
                updated++;
            }
            updated += updateYtunnusResults(organisaatio.getChildren());
        }
        return updated;
    }

    @Override
    @Cacheable(cacheName = "organisaatioByOidCache")
    public OrganisaatioDetailsDto getdOrganisaatioByOid(@PartialCacheKey String oid,
            CamelRequestContext requestContext) {
        if (isCacheUsed()) {
            OrganisaatioDetails details = organisaatioRepository.findOne(oid);
            if (details != null && isCacheUsable(details.getCachedAt(), requestContext.getCacheCheckMoment())) {
                logger.debug("MongoDB cached organisaatio {}", oid);
                OrganisaatioDetailsDto detailsDto = dtoConverter.convert(details, new OrganisaatioDetailsDto());
                // Voitaisiin tehd nin, jos saataisiin jostain lista vain aktiivista organisaatioista, mutta nyt
                // tytyy pit kaikki muistissa, koska muuten jouduttaisiin aina hakemaan lakkautettujen tiedot:
                //                if (details.isLakkautettu()) {
                //                    organisaatioRepository.delete(details.getOid());
                //                    logger.info("Deleted lakkautettu organisaatio {} from MongoDB.", oid);
                //                }
                return detailsDto;
            }
        }
        OrganisaatioDetailsDto dto = organisaatioServiceRoute.getdOrganisaatioByOid(oid, requestContext);
        if (isCacheUsed()) {
            OrganisaatioDetails details = dtoConverter.convert(dto, new OrganisaatioDetails());
            //            if (!details.isLakkautettu()) {
            organisaatioRepository.save(details);
            logger.debug("Persisted organisaatio {} to MongoDB cache.", oid);
            //            } else {
            //                organisaatioRepository.delete(details.getOid());
            //                logger.info("Ensure deleted lakkautettu organisaatio {} from MongoDB.", oid);
            //            }
        }
        return dto;
    }

    @Override
    @TriggersRemove(cacheName = "organisaatioByOidCache")
    public void purgeOrganisaatioByOidCache(@PartialCacheKey String oid) {
        OrganisaatioDetails details = organisaatioRepository.findOne(oid);
        if (details != null) {
            organisaatioRepository.delete(details);
        }
    }

    private boolean isCacheUsable(DateTime updatedAt, DateTime now) {
        return updatedAt.plus(cacheTimeoutSeconds * MILLIS_IN_SECOND).compareTo(now) > 0
                && updatedAt.isAfter(OrganisaatioDetails.MODEL_CHANGED_AT);
    }

    private boolean isCacheUsed() {
        return organisaatioRepository != null && cacheTimeoutSeconds >= 0;
    }

    public long getCacheTimeoutSeconds() {
        return cacheTimeoutSeconds;
    }

    public void setCacheTimeoutSeconds(long cacheTimeoutSeconds) {
        this.cacheTimeoutSeconds = cacheTimeoutSeconds;
    }
}