FrameworkImportService.java

package com.taxonomy.catalog.service.importer;

import com.taxonomy.dsl.export.DslMaterializeService;
import com.taxonomy.dsl.mapper.ModelToAstMapper;
import com.taxonomy.dsl.mapping.ExternalModelMapper;
import com.taxonomy.dsl.mapping.MappingProfile;
import com.taxonomy.dsl.mapping.MappingResult;
import com.taxonomy.dsl.mapping.profiles.ApqcMappingProfile;
import com.taxonomy.dsl.mapping.profiles.C4MappingProfile;
import com.taxonomy.dsl.mapping.profiles.UafMappingProfile;
import com.taxonomy.dsl.serializer.TaxDslSerializer;
import com.taxonomy.dto.FrameworkImportResult;
import com.taxonomy.dto.ProfileInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.*;
import com.taxonomy.catalog.model.TaxonomyRelation;
import com.taxonomy.catalog.service.ArchiMateXmlImporter;
import com.taxonomy.dsl.model.CanonicalArchitectureModel;
import com.taxonomy.relations.model.RelationHypothesis;

/**
 * Generic framework import service that combines a {@link MappingProfile} with
 * an {@link ExternalParser} to import external architecture models.
 *
 * <p>Registered profiles:
 * <ul>
 *   <li>{@code archimate} — ArchiMate 3.x XML (not re-parsed here; uses {@code ArchiMateXmlImporter})</li>
 *   <li>{@code uaf} — UAF/DoDAF XMI XML</li>
 *   <li>{@code apqc} — APQC PCF CSV</li>
 *   <li>{@code apqc-excel} — APQC PCF Excel</li>
 *   <li>{@code c4} — C4/Structurizr DSL</li>
 * </ul>
 *
 * <p>Data flow:
 * <pre>
 * File → ExternalParser → ExternalElements/Relations
 *      → ExternalModelMapper (profile) → CanonicalArchitectureModel
 *      → DslSerializer → DSL text
 *      → DslMaterializeService → TaxonomyRelation / RelationHypothesis (DB)
 * </pre>
 */
@Service
public class FrameworkImportService {

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

    private final DslMaterializeService materializeService;
    private final Map<String, RegisteredProfile> profiles = new LinkedHashMap<>();

    public FrameworkImportService(DslMaterializeService materializeService) {
        this.materializeService = materializeService;

        // Register all supported profiles
        register(new UafMappingProfile(), new UafXmlParser());
        register(new ApqcMappingProfile(), new ApqcCsvParser());
        register(new ApqcMappingProfile(), "apqc-excel", "APQC PCF (Excel)", new ApqcExcelParser());
        register(new C4MappingProfile(), new StructurizrDslParser());
    }

    private void register(MappingProfile profile, ExternalParser parser) {
        register(profile, profile.profileId(), profile.displayName(), parser);
    }

    private void register(MappingProfile profile, String profileId, String displayName,
                           ExternalParser parser) {
        profiles.put(profileId, new RegisteredProfile(profile, displayName, parser));
    }

    /**
     * Returns info about all available import profiles.
     */
    public List<ProfileInfo> getAvailableProfiles() {
        List<ProfileInfo> result = new ArrayList<>();
        for (var entry : profiles.entrySet()) {
            RegisteredProfile rp = entry.getValue();
            result.add(new ProfileInfo(
                    entry.getKey(),
                    rp.displayName,
                    rp.profile.supportedElementTypes(),
                    rp.profile.supportedRelationTypes(),
                    rp.parser.fileFormat()));
        }
        return result;
    }

    /**
     * Preview an import (dry run): parse and map but do not materialize.
     */
    public FrameworkImportResult preview(String profileId, InputStream input) {
        RegisteredProfile rp = profiles.get(profileId);
        if (rp == null) {
            return errorResult(profileId, "Unknown profile: " + profileId);
        }

        try {
            ExternalParser.ParsedExternalModel parsed = rp.parser.parse(input);
            ExternalModelMapper mapper = new ExternalModelMapper(rp.profile);
            MappingResult mappingResult = mapper.map(parsed.elements(), parsed.relations());

            return new FrameworkImportResult(
                    profileId,
                    rp.displayName,
                    true,
                    parsed.elements().size(),
                    (int) mappingResult.mappingStatistics().getOrDefault("mappedElements", 0),
                    parsed.relations().size(),
                    (int) mappingResult.mappingStatistics().getOrDefault("mappedRelations", 0),
                    0, 0, null,
                    mappingResult.warnings(),
                    new ArrayList<>(mappingResult.unmappedTypes()),
                    mappingResult.mappingStatistics());
        } catch (Exception e) {
            log.error("Preview failed for profile {}", profileId, e);
            return errorResult(profileId, "Preview failed: " + e.getMessage());
        }
    }

    /**
     * Full import: parse, map, serialize to DSL, and materialize into the database.
     */
    public FrameworkImportResult importFile(String profileId, InputStream input, String branch) {
        RegisteredProfile rp = profiles.get(profileId);
        if (rp == null) {
            return errorResult(profileId, "Unknown profile: " + profileId);
        }

        try {
            // 1. Parse
            ExternalParser.ParsedExternalModel parsed = rp.parser.parse(input);

            // 2. Map to canonical model
            ExternalModelMapper mapper = new ExternalModelMapper(rp.profile);
            MappingResult mappingResult = mapper.map(parsed.elements(), parsed.relations());

            // 3. Serialize to DSL text
            ModelToAstMapper astMapper = new ModelToAstMapper();
            TaxDslSerializer serializer = new TaxDslSerializer();
            var astDoc = astMapper.toDocument(mappingResult.model(), profileId + "-import");
            String dslText = serializer.serialize(astDoc);

            // 4. Materialize
            String path = "import/" + profileId + "-" + System.currentTimeMillis() + ".taxdsl";
            DslMaterializeService.MaterializeResult matResult =
                    materializeService.materialize(dslText, path, branch, null);

            List<String> allWarnings = new ArrayList<>(mappingResult.warnings());
            allWarnings.addAll(matResult.warnings());
            if (!matResult.valid()) {
                allWarnings.addAll(matResult.errors());
            }

            return new FrameworkImportResult(
                    profileId,
                    rp.displayName,
                    matResult.valid(),
                    parsed.elements().size(),
                    (int) mappingResult.mappingStatistics().getOrDefault("mappedElements", 0),
                    parsed.relations().size(),
                    (int) mappingResult.mappingStatistics().getOrDefault("mappedRelations", 0),
                    matResult.relationsCreated(),
                    matResult.hypothesesCreated(),
                    matResult.documentId(),
                    allWarnings,
                    new ArrayList<>(mappingResult.unmappedTypes()),
                    mappingResult.mappingStatistics());
        } catch (Exception e) {
            log.error("Import failed for profile {}", profileId, e);
            return errorResult(profileId, "Import failed: " + e.getMessage());
        }
    }

    private FrameworkImportResult errorResult(String profileId, String message) {
        String displayName = profiles.containsKey(profileId) ?
                profiles.get(profileId).displayName : profileId;
        return new FrameworkImportResult(
                profileId, displayName, false,
                0, 0, 0, 0, 0, 0, null,
                List.of(message), List.of(), Map.of());
    }

    private record RegisteredProfile(
        MappingProfile profile,
        String displayName,
        ExternalParser parser
    ) {}
}