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

yakworks.rally.orgs.OrgDimensionService.groovy Maven / Gradle / Ivy

There is a newer version: 7.3.76
Show newest version
/*
* Copyright 2021 Yak.Works - Licensed under the Apache License, Version 2.0 (the "License")
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
package yakworks.rally.orgs

import java.util.concurrent.ConcurrentHashMap

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cache.CacheManager
import org.springframework.cache.annotation.Cacheable
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service

import grails.gorm.transactions.Transactional
import yakworks.commons.lang.EnumUtils
import yakworks.rally.orgs.model.OrgType

/**
 * See https://github.com/9ci/9ci/blob/master/documentation/SpecsAndDesigns/orgType-levels-hierarchy-members.md
 * Organization types are arranged in hierarchie, and configured in AppSetupConfig.
 *
 * OrgDimensionService provides methods to parse the orgdimension level hierarchie and get children, parents and
 * immediate parent orgtypes for any orgtype.
 */
@Service @Lazy
@Slf4j
@CompileStatic
class OrgDimensionService {

    @Autowired(required = false) //required = false so unit tests work
    CacheManager cacheManager

    //this is what should be inject at startup
    Map dimensionsConfig

    @CompileStatic
    static enum DimLevel {
        PARENTS, CHILDREN
    }

    //DimensionLevel instances created by parsing the paths specified in config are cached here.
    //DimensionLevel contains just immediate parents and children not all children and parents recursive.
    private final Map dimensionsCache = new ConcurrentHashMap()

    //all levels configured in dimension paths
    private final List allLevels = []

    /**
     * Parse dimension levels from appsetup config and cache on server startup,
     * but it can be called manually afterwards (eg from tests), and it will reset the cache and repopulate it based
     * on the current appsetup config.
     */
    void init() {
        if(dimensionsConfig){
            parsePathsAndInitCache(dimensionsConfig*.value)
        }
    }

    // used for tests to set both the appConfig.orgDimensions and then init
    void testInit(String path) {
        clearCache()
        dimensionsConfig = path ? [primary: path] : null
        init()
    }

    boolean isOrgMemberEnabled(){
        dimensionsConfig
    }

    //Parse given paths and populate dimensionsCache with DimensionLevel instances
    @Transactional(readOnly = true)
    void parsePathsAndInitCache(List paths) {
        //first clear caches
        clearCache()

        for (String path : paths) {
            String[] arr = path.split("\\.").reverse()
            OrgType previousType
            for (String orgTypeName : arr) {
                OrgType typeEnum = EnumUtils.getEnum(OrgType, orgTypeName)
                DimensionLevel dlevel = getOrCreateCachedDimensionLevel(typeEnum)
                if (previousType) {
                    DimensionLevel parent = getOrCreateCachedDimensionLevel(previousType)
                    parent.addChild(dlevel)
                }
                previousType = typeEnum
            }
        }

        createClientCompanyDimLevel(OrgType.Client, allLevels)
        createClientCompanyDimLevel(OrgType.Company, allLevels)

    }

    void clearCache(){
        dimensionsCache.clear()
        allLevels.clear()
        cacheManager?.getCache("OrgDimension.parentLevels")?.clear()
        cacheManager?.getCache("OrgDimension.childLevels")?.clear()
    }

    List getLevels(DimLevel dimLevel, OrgType orgType) {
        DimensionLevel dimensionLevel = dimensionsCache[orgType]
        if (!dimensionLevel) return []

        def levels = [] as List

        if (dimLevel == DimLevel.CHILDREN) {
            dimensionLevel.children.each {
                levels.add(it.orgType)
                levels.addAll(getLevels(DimLevel.CHILDREN, it.orgType))
            }
        } else if (dimLevel == DimLevel.PARENTS) {
            dimensionLevel.parents.each {
                levels.add(it.orgType)
                levels.addAll(getLevels(DimLevel.PARENTS, it.orgType))
            }
        }
        return levels.unique().collect()
    }

    /**
     * Get all child levels for given orgtype
     */
    @Cacheable('OrgDimension.childLevels')
    List getChildLevels(OrgType typeEnum) {
        return getLevels(DimLevel.CHILDREN, typeEnum)
    }

    /**
     * Get all parent levels for given orgtype
     */
    @Cacheable('OrgDimension.parentLevels')
    List getParentLevels(OrgType type) {
        return getLevels(DimLevel.PARENTS, type)
    }

    /**
     * Get immediate parents for given orgtype
     * For example, given org dimension
     *   - CustAccount.Branch.Division
     *   - CustAccount.Customer.Division
     * then getImmediateParents(CustAccount) will return [Branch,Customer]
     * and getImmediateParents(Customer) and getImmediateParents(Branch) will both return [Division]
     * and getImmediateParents(Division) would return and empty list meaning its top level
     */
    List getImmediateParents(OrgType type) {
        DimensionLevel level = dimensionsCache[type]
        if (!level) return []
        Set parents = level.parents
        List parentList = parents*.orgType
        return parentList ?: [] as List
    }

    /**
     * All levels configured in dimension paths
     * @return List < OrgType >
     */
    List getAllLevels() {
        return allLevels
    }

    /**
     * Find if a DimensionLevel is already created for given name and cached in dimensionsCache, or create a new and
     * cache it.
     */
    protected DimensionLevel getOrCreateCachedDimensionLevel(OrgType orgType) {
        DimensionLevel dlevel = dimensionsCache.get(orgType)
        if (!dlevel) {
            dlevel = new DimensionLevel(orgType)
            dimensionsCache[orgType] = dlevel
            allLevels << orgType
        }
        return dlevel
    }

    /**
     * Find if a DimensionLevel is already created for given name and cached in dimensionsCache, or create a new and
     * cache it.
     */
    protected DimensionLevel createClientCompanyDimLevel(OrgType orgType, List allOrgTypes) {
        DimensionLevel dlevel = dimensionsCache.get(orgType)
        if (!dlevel) {
            dlevel = new DimensionLevel(orgType)
            allOrgTypes.each {
                def childDim = getOrCreateCachedDimensionLevel(it)
                dlevel.children.add(childDim)
            }
            dimensionsCache[orgType] = dlevel
        }
        return dlevel
    }

    /**
     * Data structure to hold a dimension level and its immediate parents and children.
     */
    @CompileStatic
    protected class DimensionLevel {
        OrgType orgType
        Set parents = [] as Set
        Set children = [] as Set

        DimensionLevel(OrgType orgType){
            this.orgType = orgType
        }
        void addChild(DimensionLevel child) {
            if (!children.contains(child)) {
                children.add(child)
                if (!child.parents.contains(this)) {
                    child.parents.add(this)
                }
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy