001package gudusoft.gsqlparser.slpc.adapter;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.dlineage.dataflow.model.xml.column;
005import gudusoft.gsqlparser.dlineage.dataflow.model.xml.table;
006import gudusoft.gsqlparser.slpc.model.SlpcObject;
007import gudusoft.gsqlparser.sqlenv.ApproximateIdentifierKey;
008import gudusoft.gsqlparser.sqlenv.CollatorProvider;
009import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
010import gudusoft.gsqlparser.sqlenv.IdentifierInputForm;
011import gudusoft.gsqlparser.sqlenv.IdentifierProfile;
012import gudusoft.gsqlparser.sqlenv.IdentifierService;
013import gudusoft.gsqlparser.sqlenv.PersistentIdentifierKey;
014
015import java.util.ArrayList;
016import java.util.Collections;
017import java.util.List;
018
019/**
020 * Opt-in identity bridge using the repository's persistent identifier API.
021 * The caller must provide every segment and its provenance; this class never
022 * splits or normalizes legacy display names on its own.
023 */
024public final class GspPersistentIdentityResolver implements LegacyAdapterContext.IdentityResolver {
025
026    public interface BindingProvider {
027        Binding bind(table relation, column field);
028    }
029
030    public static final class Binding {
031        private final String qualificationPolicyId;
032        private final List<Segment> segments;
033        public Binding(String qualificationPolicyId, List<Segment> segments) {
034            if (qualificationPolicyId == null || qualificationPolicyId.isEmpty()) throw new IllegalArgumentException("qualificationPolicyId must not be empty");
035            if (qualificationPolicyId.startsWith("x-")) throw new IllegalArgumentException("GSP persistent identity requires a registered SLPC qualification policy");
036            if (segments == null || segments.isEmpty()) throw new IllegalArgumentException("identity segments must not be empty");
037            this.qualificationPolicyId = qualificationPolicyId;
038            this.segments = Collections.unmodifiableList(new ArrayList<Segment>(segments));
039        }
040    }
041
042    public static final class Segment {
043        private final String segmentKind, text;
044        private final ESQLDataObjectType objectType;
045        private final IdentifierInputForm inputForm;
046        public Segment(String segmentKind, ESQLDataObjectType objectType, String text, IdentifierInputForm inputForm) {
047            if (segmentKind == null || objectType == null || text == null || inputForm == null) throw new IllegalArgumentException("identity segment fields must not be null");
048            this.segmentKind = segmentKind; this.objectType = objectType; this.text = text; this.inputForm = inputForm;
049        }
050    }
051
052    private final IdentifierService identifierService;
053    private final BindingProvider bindingProvider;
054    private final boolean approximateFallback;
055
056    public GspPersistentIdentityResolver(EDbVendor vendor, IdentifierProfile.VendorFlags flags,
057            BindingProvider bindingProvider, boolean approximateFallback) {
058        if (vendor == null || flags == null || bindingProvider == null) throw new IllegalArgumentException("vendor, flags and bindingProvider are required");
059        CollatorProvider collator = vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql ? new CollatorProvider() : null;
060        this.identifierService = new IdentifierService(IdentifierProfile.forVendor(vendor, flags), collator);
061        this.bindingProvider = bindingProvider;
062        this.approximateFallback = approximateFallback;
063    }
064
065    @Override
066    public LegacyAdapterContext.ResolvedIdentity resolve(table relation, column field) {
067        Binding binding = bindingProvider.bind(relation, field);
068        if (binding == null) return null;
069        try {
070            List<Object> segments = new ArrayList<Object>();
071            for (Segment segment : binding.segments) {
072                PersistentIdentifierKey key = identifierService.persistentKeyV1(segment.objectType, segment.text, segment.inputForm);
073                segments.add(exactSegment(segment.segmentKind, key));
074            }
075            SlpcObject identity = SlpcObject.builder().put("kind", "COMPOSITE_EXACT").put("strength", "EXACT")
076                    .put("qualificationPolicyId", binding.qualificationPolicyId).put("segments", segments).build();
077            return new LegacyAdapterContext.ResolvedIdentity(identity, "RESOLVED", "ELIGIBLE");
078        } catch (UnsupportedOperationException unsupported) {
079            if (!approximateFallback) return null;
080            List<Object> segments = new ArrayList<Object>();
081            for (Segment segment : binding.segments) {
082                ApproximateIdentifierKey key = identifierService.approximateKeyV1(segment.objectType, segment.text, segment.inputForm);
083                segments.add(approximateSegment(segment.segmentKind, key));
084            }
085            SlpcObject identity = SlpcObject.builder().put("kind", "COMPOSITE_APPROXIMATE").put("strength", "APPROXIMATE")
086                    .put("qualificationPolicyId", binding.qualificationPolicyId).put("segments", segments).build();
087            return new LegacyAdapterContext.ResolvedIdentity(identity, "GUESSED", "INELIGIBLE");
088        }
089    }
090
091    private static SlpcObject exactSegment(String kind, PersistentIdentifierKey key) {
092        return SlpcObject.builder().put("segmentKind", kind).put("identityKind", "GSP_PERSISTENT_V1")
093                .put("policyId", key.getPolicyId()).put("objectGroup", key.getGroup().name()).put("payload", key.getPayload()).build();
094    }
095
096    private static SlpcObject approximateSegment(String kind, ApproximateIdentifierKey key) {
097        return SlpcObject.builder().put("segmentKind", kind).put("identityKind", "GSP_APPROXIMATE_V1")
098                .put("policyId", key.getPolicyId()).put("objectGroup", key.getGroup().name()).put("payload", key.getPayload()).build();
099    }
100}