Skip to content

Standardize relatedLinks Infrastructure Across All Entities #97

@dfcoffin

Description

@dfcoffin

Problem Statement

Entities extending IdentifiedObject have inconsistent implementations for Atom <link rel="related"> elements, causing compilation errors and architectural inconsistency.

Current State:

  • IdentifiedObject defines relatedLinks as List<LinkType> (3-column structure: rel, href, type)
  • 13 entity-specific related_links tables use single-column VARCHAR(1024) structure
  • Attempting to add custom List<String> relatedLinks fields causes compilation errors
  • Entities cannot use bidirectional Atom links as required by NAESB ESPI 4.0

Compilation Errors:

[ERROR] name clash: setRelatedLinks(java.util.List<java.lang.String>) in CustomerAgreementEntity
        and setRelatedLinks(java.util.List<LinkType>) in IdentifiedObject have the same erasure
[ERROR] return type java.util.List<java.lang.String> is not compatible with
        java.util.List<org.greenbuttonalliance.espi.common.domain.common.LinkType>

Root Cause

Database schema design for *_related_links tables is inconsistent with IdentifiedObject:

IdentifiedObject expects (CORRECT):

CREATE TABLE identified_object_related_links (
    identified_object_id CHAR(36) NOT NULL,
    rel VARCHAR(255),              -- Relationship type: "related", "self", "up"
    href VARCHAR(1024),            -- Link URL
    link_type VARCHAR(255)         -- ESPI type: "espi-entry/UsagePoint" or "espi-feed/UsagePoint"
);

Entity-specific tables currently have (WRONG):

CREATE TABLE customer_agreement_related_links (
    customer_agreement_id CHAR(36) NOT NULL,
    related_links VARCHAR(1024)    -- Single column storing only href
);

Affected Entities and Tables

Usage Domain (4 entities - V1 migration)

Entity Table Extends IdentifiedObject Custom relatedLinks Field
ApplicationInformationEntity application_information_related_links ✅ Yes ❌ No
AuthorizationEntity authorization_related_links ✅ Yes ❌ No
ReadingTypeEntity reading_type_related_links ✅ Yes ❌ No
ElectricPowerQualitySummaryEntity electric_power_quality_summary_related_links ✅ Yes ❌ No

Customer Domain (9 entities - V3 migration)

Entity Table Extends IdentifiedObject Custom relatedLinks Field
CustomerEntity customer_related_links ✅ Yes ❌ No
CustomerAgreementEntity customer_agreement_related_links ✅ Yes ⚠️ Added in Phase A0 (breaks build)
CustomerAccountEntity customer_account_related_links ✅ Yes ❌ No
ServiceLocationEntity service_location_related_links ✅ Yes ⚠️ Added in Phase A0 (breaks build)
ServiceSupplierEntity service_supplier_related_links ✅ Yes ⚠️ Added in Phase A0 (breaks build)
EndDeviceEntity end_device_related_links ✅ Yes ⚠️ Added in Phase A0 (breaks build)
MeterEntity meter_related_links ✅ Yes (via EndDevice) ⚠️ Inherited Phase A0 (breaks build)
ProgramDateIdMappingsEntity program_date_id_mapping_related_links ✅ Yes ⚠️ Added in Phase A0 (breaks build)
StatementEntity statement_related_links ✅ Yes ❌ No

Total: 13 tables need standardization

Usage Entities Without relatedLinks Tables

These entities extend IdentifiedObject but have NO related_links tables:

  • IntervalBlockEntity ✅
  • MeterReadingEntity ✅
  • UsagePointEntity ✅
  • UsageSummaryEntity ✅
  • TimeConfigurationEntity ✅

ESPI Link Type Values

CRITICAL: Link type= attribute is NOT a media type. It uses ESPI-specific format.

Usage Domain Resources (usage.xsd)

<!-- href ends with identifier → single entry -->
<link rel="related"
      href="https://api.example.com/resource/IntervalBlock/123"
      type="espi-entry/IntervalBlock"/>

<!-- href ends with ResourceName → collection/feed -->
<link rel="related"
      href="https://api.example.com/resource/UsagePoint/456/MeterReading"
      type="espi-feed/MeterReading"/>

Customer Domain Resources (customer.xsd)

<!-- href ends with identifier → single entry -->
<link rel="related"
      href="https://api.example.com/resource/CustomerAgreement/789"
      type="cust-entry/CustomerAgreement"/>

<!-- href ends with ResourceName → collection/feed -->
<link rel="related"
      href="https://api.example.com/resource/ServiceLocation/abc/EndDevice"
      type="cust-feed/EndDevice"/>

Link Type Determination (Marshalling Concern)

The -entry vs -feed distinction is determined during XML marshalling (Java → XML):

Rule: Examine the href= attribute value:

  • If href ends with ResourceName → type="{prefix}-feed/{ResourceName}"
  • If href ends with identifier → type="{prefix}-entry/{ResourceName}"

Examples:

href="https://api.example.com/resource/UsagePoint/123/MeterReading"
      ↑ ends with "MeterReading" → type="espi-feed/MeterReading"

href="https://api.example.com/resource/MeterReading/456"
      ↑ ends with "456" (identifier) → type="espi-entry/MeterReading"

During unmarshalling (XML → Java):

  • The type value is already provided in the XML
  • Simply store it in LinkType.type field

Proposed Solution

Phase 1: Update Existing Flyway Scripts

IMPORTANT: Since this is a development system not yet deployed, we can update V1 and V3 scripts directly.

Update 13 related_links tables to use 3-column structure (rel, href, link_type) instead of single-column (related_links).

See full implementation details in ISSUE_DRAFT_RELATED_LINKS_STANDARDIZATION.md

Phase 2: Revert Phase A0 Changes

Remove the List<String> relatedLinks fields from 5 entities causing compilation errors.

Phase 3: Add @AssociationOverride Annotations

All 13 entities should override IdentifiedObject's relatedLinks table name via @AssociationOverride.

Phase 4: Service Layer Usage

Services create links with rel and href, marshalling layer determines type based on href pattern.

Phase 5: Marshalling Layer Logic

Add determineEspiLinkType() helper method to DtoExportService.

Implementation Tasks

  • Update V1 Flyway script (4 tables)
  • Update V3 Flyway script (10 tables)
  • Revert Phase A0 changes (5 entities)
  • Add @AssociationOverride annotations (13 entities)
  • Add marshalling logic
  • Update CLAUDE.md documentation
  • Update tests
  • Build and integration tests

Success Criteria

  • All compilation errors resolved
  • All 13 related_links tables use 3-column structure
  • All entities inherit relatedLinks from IdentifiedObject
  • All tests passing (~760+ tests)
  • XML output has correct ESPI type values
  • Phase 17 can proceed without blockers

Dependencies

Blocks: Issue #28 Phase 17: ProgramDateIdMappings implementation

Requires: Phase A0 branch must be reverted

Estimated Effort

Total: ~13-16 hours

Priority

CRITICAL - Blocks Phase 17 implementation and prevents build from succeeding.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions