CatalogFacade.java

package com.taxonomy.catalog.service;

import com.taxonomy.dto.ArchiMateImportResult;
import com.taxonomy.dto.TaxonomyNodeDto;
import com.taxonomy.dto.TaxonomyRelationDto;
import com.taxonomy.catalog.model.TaxonomyNode;
import com.taxonomy.workspace.service.WorkspaceContext;
import com.taxonomy.workspace.service.WorkspaceContextResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.util.List;

/**
 * High-level facade that aggregates the catalog domain services into a
 * single, coarse-grained API for use by controllers and other consumers.
 *
 * <p>Each method orchestrates one or more fine-grained service calls to
 * fulfil a complete use-case, keeping controllers thin and business logic
 * in the service layer.
 */
@Service
public class CatalogFacade {

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

    private final TaxonomyService taxonomyService;
    private final TaxonomyRelationService relationService;
    private final ArchiMateXmlImporter archiMateXmlImporter;
    private final SearchService searchService;
    private final WorkspaceContextResolver contextResolver;

    public CatalogFacade(TaxonomyService taxonomyService,
                         TaxonomyRelationService relationService,
                         ArchiMateXmlImporter archiMateXmlImporter,
                         SearchService searchService,
                         WorkspaceContextResolver contextResolver) {
        this.taxonomyService = taxonomyService;
        this.relationService = relationService;
        this.archiMateXmlImporter = archiMateXmlImporter;
        this.searchService = searchService;
        this.contextResolver = contextResolver;
    }

    /**
     * Returns a taxonomy node identified by {@code rootCode} together with its
     * full relation context (both outgoing and incoming relations).
     *
     * @param rootCode the taxonomy code (e.g. {@code "CP-1023"})
     * @return the node DTO enriched with relations, or {@code null} if the code
     *         does not match any node
     */
    @Transactional(readOnly = true)
    public TaxonomyNodeDto getTreeWithRelations(String rootCode) {
        TaxonomyNode node = taxonomyService.getNodeByCode(rootCode);
        if (node == null) {
            log.debug("No taxonomy node found for code '{}'", rootCode);
            return null;
        }

        TaxonomyNodeDto dto = taxonomyService.toDto(node);

        WorkspaceContext ctx = contextResolver.resolveCurrentContext();
        List<TaxonomyRelationDto> relations = relationService.getRelationsForNode(rootCode, ctx.workspaceId());
        List<TaxonomyRelationDto> outgoing = relations.stream()
                .filter(r -> rootCode.equals(r.getSourceCode()))
                .toList();
        List<TaxonomyRelationDto> incoming = relations.stream()
                .filter(r -> rootCode.equals(r.getTargetCode()))
                .toList();
        dto.setOutgoingRelations(outgoing);
        dto.setIncomingRelations(incoming);

        return dto;
    }

    /**
     * Imports an ArchiMate 3.x XML file, mapping elements and relationships to
     * taxonomy nodes and relations.
     *
     * @param xmlStream the XML input stream
     * @return the import result containing match/create statistics and notes
     */
    public ArchiMateImportResult importAndValidate(InputStream xmlStream) {
        log.info("Starting ArchiMate XML import via facade");
        ArchiMateImportResult result = archiMateXmlImporter.importXml(xmlStream);
        log.info("ArchiMate import complete – {} elements matched, {} relations created",
                result.getElementsMatched(), result.getRelationsImported());
        return result;
    }

    /**
     * Performs a full-text search and enriches each result with its relation
     * context (outgoing and incoming relations).
     *
     * @param query      the search query string
     * @param maxResults the maximum number of results to return
     * @return search results with relation context attached
     */
    @Transactional(readOnly = true)
    public List<TaxonomyNodeDto> searchWithContext(String query, int maxResults) {
        List<TaxonomyNodeDto> results = searchService.search(query, maxResults);

        WorkspaceContext ctx = contextResolver.resolveCurrentContext();
        for (TaxonomyNodeDto dto : results) {
            List<TaxonomyRelationDto> relations = relationService.getRelationsForNode(dto.getCode(), ctx.workspaceId());
            List<TaxonomyRelationDto> outgoing = relations.stream()
                    .filter(r -> dto.getCode().equals(r.getSourceCode()))
                    .toList();
            List<TaxonomyRelationDto> incoming = relations.stream()
                    .filter(r -> dto.getCode().equals(r.getTargetCode()))
                    .toList();
            dto.setOutgoingRelations(outgoing);
            dto.setIncomingRelations(incoming);
        }

        return results;
    }
}