ApqcHierarchyService.java

package com.taxonomy.architecture.service;

import com.taxonomy.dto.ApqcHierarchyNode;
import com.taxonomy.dsl.mapper.AstToModelMapper;
import com.taxonomy.dsl.model.ArchitectureElement;
import com.taxonomy.dsl.model.CanonicalArchitectureModel;
import com.taxonomy.dsl.model.TaxonomyRootTypes;
import com.taxonomy.dsl.parser.TaxDslParser;
import com.taxonomy.architecture.model.ArchitectureDslDocument;
import com.taxonomy.architecture.repository.ArchitectureDslDocumentRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * Service that extracts the APQC process hierarchy from imported DSL documents.
 *
 * <p>Scans all stored DSL documents for elements with the
 * {@code x-source-framework: "apqc"} extension and reconstructs the
 * parent–child hierarchy using {@code x-apqc-parent} chains.
 */
@Service
public class ApqcHierarchyService {

    private static final Logger log = LoggerFactory.getLogger(ApqcHierarchyService.class);

    private final ArchitectureDslDocumentRepository documentRepository;

    public ApqcHierarchyService(ArchitectureDslDocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }

    /**
     * Build the APQC process hierarchy from all stored DSL documents.
     *
     * @return list of root APQC hierarchy nodes with children linked
     */
    public List<ApqcHierarchyNode> buildHierarchy() {
        TaxDslParser parser = new TaxDslParser();
        AstToModelMapper mapper = new AstToModelMapper();
        List<ArchitectureElement> apqcElements = new ArrayList<>();

        for (ArchitectureDslDocument doc : documentRepository.findAll()) {
            if (doc.getRawContent() == null || doc.getRawContent().isBlank()) continue;
            try {
                var ast = parser.parse(doc.getRawContent(), doc.getPath());
                CanonicalArchitectureModel model = mapper.map(ast);
                for (ArchitectureElement el : model.getElements()) {
                    if ("apqc".equals(el.getExtensions().get("x-source-framework"))) {
                        apqcElements.add(el);
                    }
                }
            } catch (Exception e) {
                log.debug("Skipping unparseable DSL document '{}': {}", doc.getPath(), e.getMessage());
            }
        }

        // Build lookup map and tree
        Map<String, ApqcHierarchyNode> nodeMap = new LinkedHashMap<>();
        for (ArchitectureElement el : apqcElements) {
            Map<String, String> ext = el.getExtensions();
            String level = ext.getOrDefault("x-apqc-level", "Unknown");
            String pcfId = ext.getOrDefault("x-apqc-pcf-id", "");
            String parentId = ext.get("x-apqc-parent");
            String taxonomyRoot = TaxonomyRootTypes.rootFor(el.getType());
            nodeMap.put(el.getId(), new ApqcHierarchyNode(
                    el.getId(), el.getTitle(), level, pcfId, parentId,
                    taxonomyRoot != null ? taxonomyRoot : "",
                    new ArrayList<>()));
        }

        // Link children to parents
        List<ApqcHierarchyNode> roots = new ArrayList<>();
        for (ApqcHierarchyNode node : nodeMap.values()) {
            if (node.parentId() != null && nodeMap.containsKey(node.parentId())) {
                nodeMap.get(node.parentId()).children().add(node);
            } else {
                roots.add(node);
            }
        }

        return roots;
    }
}