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}