@Override
    public synchronized ManagerSpec getManagerSpec()
    {
        Set<Long> rootGroupIds = new HashSet<>();
        Map<Long, ResourceGroupSpec> resourceGroupSpecMap = new HashMap<>();
        Map<Long, ResourceGroupIdTemplate> resourceGroupIdTemplateMap = new HashMap<>();
        Map<Long, ResourceGroupSpecBuilder> recordMap = new HashMap<>();
        Map<Long, Set<Long>> subGroupIdsToBuild = new HashMap<>();
        populateFromDbHelper(recordMap, rootGroupIds, resourceGroupIdTemplateMap, subGroupIdsToBuild);

        // Build up resource group specs from root to leaf
        for (LinkedList<Long> queue = new LinkedList<>(rootGroupIds); !queue.isEmpty(); ) {
            Long id = queue.pollFirst();
            resourceGroupIdTemplateMap.computeIfAbsent(id, k -> {
                ResourceGroupSpecBuilder builder = recordMap.get(k);
                return ResourceGroupIdTemplate.forSubGroupNamed(
                        resourceGroupIdTemplateMap.get(builder.getParentId().get()),
                        builder.getNameTemplate().toString());
            });
            Set<Long> childrenToBuild = subGroupIdsToBuild.getOrDefault(id, ImmutableSet.of());
            // Add to resource group specs if no more child resource groups are left to build
            if (childrenToBuild.isEmpty()) {
                ResourceGroupSpecBuilder builder = recordMap.get(id);
                ResourceGroupSpec resourceGroupSpec = builder.build();
                resourceGroupSpecMap.put(id, resourceGroupSpec);

                // Add this resource group spec to parent subgroups and remove id from subgroup ids to build
                builder.getParentId().ifPresent(parentId -> {
                    recordMap.get(parentId).addSubGroup(resourceGroupSpec);
                    subGroupIdsToBuild.get(parentId).remove(id);
                });
            }
            else {
                // Add this group back to queue since it still has subgroups to build
                queue.addFirst(id);
                // Add this group's subgroups to the queue so that when this id is dequeued again childrenToBuild will be empty
                queue.addAll(0, childrenToBuild);
            }
        }

        // Specs are built from db records, validate and return manager spec
        List<ResourceGroupSpec> rootGroups = rootGroupIds.stream().map(resourceGroupSpecMap::get).collect(toList());

        List<SelectorSpec> selectors = resourceGroupsDao.getSelectors(environment)
                .stream()
                .map(selectorRecord ->
                        new SelectorSpec(
                                selectorRecord.getUserRegex(),
                                selectorRecord.getSourceRegex(),
                                selectorRecord.getQueryType(),
                                selectorRecord.getClientTags(),
                                selectorRecord.getSelectorResourceEstimate(),
                                selectorRecord.getClientInfoRegex(),
                                selectorRecord.getSchema(),
                                selectorRecord.getPrincipalRegex(),
                                resourceGroupIdTemplateMap.get(selectorRecord.getResourceGroupId()))
                ).collect(toList());
        ManagerSpec managerSpec = new ManagerSpec(rootGroups, selectors, getCpuQuotaPeriodFromDb());
        return managerSpec;
    }
@Test
    public void testEnvironments()
    {
        H2DaoProvider daoProvider = setup("test_configuration");
        H2ResourceGroupsDao dao = daoProvider.get();
        dao.createResourceGroupsGlobalPropertiesTable();
        dao.createResourceGroupsTable();
        dao.createSelectorsTable();
        String prodEnvironment = "prod";
        String devEnvironment = "dev";
        dao.insertResourceGroupsGlobalProperties("cpu_quota_period", "1h");
        // two resource groups are the same except the group for the prod environment has a larger softMemoryLimit
        dao.insertResourceGroup(1, "prod_global", "10MB", 1000, 100, 100, "weighted", null, true, "1h", "1d", "1h", "1MB", "1h", 0, null, prodEnvironment);
        dao.insertResourceGroup(2, "dev_global", "1MB", 1000, 100, 100, "weighted", null, true, "1h", "1d", "1h", "1MB", "1h", 0, null, devEnvironment);
        dao.insertSelector(1, 1, ".*prod_user.*", null, null, null, null, null);
        dao.insertSelector(2, 2, ".*dev_user.*", null, null, null, null, null);

        // check the prod configuration
        DbManagerSpecProvider dbManagerSpecProvider = new DbManagerSpecProvider(daoProvider.get(), prodEnvironment, new ReloadingResourceGroupConfig());
        ManagerSpec managerSpec = dbManagerSpecProvider.getManagerSpec();
        assertEquals(managerSpec.getRootGroups().size(), 1);
        assertEquals(managerSpec.getSelectors().size(), 1);
        SelectorSpec prodSelector = managerSpec.getSelectors().get(0);
        assertEquals(prodSelector.getGroup().toString(), "prod_global");

        // check the dev configuration
        dbManagerSpecProvider = new DbManagerSpecProvider(daoProvider.get(), devEnvironment, new ReloadingResourceGroupConfig());
        managerSpec = dbManagerSpecProvider.getManagerSpec();
        assertEquals(managerSpec.getRootGroups().size(), 1);
        assertEquals(managerSpec.getSelectors().size(), 1);
        prodSelector = managerSpec.getSelectors().get(0);
        assertEquals(prodSelector.getGroup().toString(), "dev_global");
    }