diff --git a/openespi-common/PHASE_2_CODE_REVIEW_SUMMARY.md b/openespi-common/PHASE_2_CODE_REVIEW_SUMMARY.md new file mode 100644 index 00000000..fcd5aa1e --- /dev/null +++ b/openespi-common/PHASE_2_CODE_REVIEW_SUMMARY.md @@ -0,0 +1,221 @@ +# Phase 2: Code Review & Baseline Summary + +**Date**: 2025-12-31 +**Branch**: `feature/schema-compliance-phase-2-baseline` +**Status**: In Progress + +--- + +## Baseline Test Results + +**Test Execution**: `mvn clean test > test-baseline-phase2.log 2>&1` + +``` +Tests run: 545 +Failures: 1 (pre-existing) +Errors: 0 +Skipped: 2 +Pass rate: 544/545 (99.8%) +``` + +**Pre-existing Failure** (unchanged from Phase 1): +- `SubscriptionRepositoryTest.shouldSaveSubscriptionWithLifecycleFields` + - Error: "Expecting actual: 23 to be less than: 0" + - Status: Unrelated to schema compliance work + +--- + +## selfLink/upLink Usage Analysis + +### Summary by Layer + +| Layer | Files with selfLink/upLink | Occurrences | +|-------|---------------------------|-------------| +| Service | 0 | 0 | +| Entity | 2 | 14 | +| DTO | 12 | 54 | +| Mapper | Unknown | 64 | +| Test | 1 | 4 | + +### Entity Layer (2 files, 14 occurrences) + +1. **IdentifiedObject.java** (base class) + - Defines `selfLink` and `upLink` fields for all entities extending it + - This is correct - IdentifiedObject is the base class for ESPI resources + +2. **CustomerAccountEntity.java** + - Uses `@AttributeOverride` to customize column names for inherited selfLink/upLink + - This is normal JPA mapping, no special handling needed + +### DTO Layer (12 files with selfLink/upLink) + +**DTOs that HAVE selfLink/upLink** (need removal for non-IdentifiedObject entities): + +1. ✅ **IntervalReadingDto** - NEEDS REMOVAL (doesn't extend IdentifiedObject in spec) +2. ✅ **ReadingQualityDto** - NEEDS REMOVAL (doesn't extend IdentifiedObject in spec) +3. ✅ **StatementRefDto** - NEEDS REMOVAL (doesn't extend IdentifiedObject in spec) +4. **IntervalBlockDto** - Keep (extends IdentifiedObject in spec) +5. **CustomerAccountDto** - Keep (extends IdentifiedObject in spec) +6. **CustomerAgreementDto** - Keep (extends IdentifiedObject in spec) +7. **EndDeviceDto** - Keep (extends IdentifiedObject in spec) +8. **MeterDto** - Keep (extends IdentifiedObject in spec) +9. **ProgramDateIdMappingsDto** - Keep (extends IdentifiedObject in spec) +10. **ServiceLocationDto** - Keep (extends IdentifiedObject in spec) +11. **StatementDto** - Keep (extends IdentifiedObject in spec) +12. **UsageSummaryDto** - Keep (extends IdentifiedObject in spec) + +**DTOs that CORRECTLY LACK selfLink/upLink** (already compliant): + +1. ✅ **PnodeRefDto** - Correct (doesn't extend IdentifiedObject) +2. ✅ **AggregatedNodeRefDto** - Correct (doesn't extend IdentifiedObject) +3. ✅ **ServiceDeliveryPointDto** - Correct (doesn't extend IdentifiedObject) + +**DTOs Not Found** (embedded entities, not standalone resources): + +1. ✅ **LineItemDto** - NO DTO (embedded in UsageSummary) +2. ✅ **BatchListDto** - NO DTO (special case entity) +3. ✅ **PhoneNumberDto** - NO DTO (embedded in Customer) + +### Test Layer (1 file, 4 occurrences) + +**IntervalBlockRepositoryTest.java**: +- Line 445-448: Sets `selfLink` on IntervalBlockEntity +- This is OK - IntervalBlock DOES extend IdentifiedObject +- No changes needed for this test + +--- + +## Entity Review Status + +### Entities That SHOULD NOT Extend IdentifiedObject (11 total) + +From the ESPI 4.0 XSD schema audit: + +| Entity | Entity File | DTO File | DTO Has Links | Status | +|--------|------------|----------|---------------|--------| +| 1. IntervalReading | ✅ Exists | ✅ Exists | ✅ YES | **Needs DTO refactoring** | +| 2. ReadingQuality | ✅ Exists | ✅ Exists | ✅ YES | **Needs DTO refactoring** | +| 3. LineItem | ✅ Exists | ❌ N/A (embedded) | N/A | **Needs entity-only refactoring** | +| 4. PnodeRef | ✅ Exists | ✅ Exists | ❌ NO | **Already compliant!** | +| 5. AggregatedNodeRef | ✅ Exists | ✅ Exists | ❌ NO | **Already compliant!** | +| 6. ServiceDeliveryPoint | ✅ Exists | ✅ Exists | ❌ NO | **Already compliant!** | +| 7. BatchList | ✅ Exists | ❌ N/A (special) | N/A | **Needs entity-only refactoring** | +| 8. StatementRef | ✅ Exists | ✅ Exists | ✅ YES | **Needs DTO refactoring** | +| 9. PhoneNumber | ✅ Exists | ❌ N/A (embedded) | N/A | **Needs entity-only refactoring** | +| 10. RetailCustomer | ✅ Exists | ✅ Exists | ❓ TBD | **SPECIAL CASE - Keep IdentifiedObject** | +| 11. Subscription | ✅ Exists | ✅ Exists | ❓ TBD | **SPECIAL CASE - Keep IdentifiedObject** | + +--- + +## Key Findings + +### Good News ✅ + +1. **Service Layer**: NO usage of selfLink/upLink - refactoring won't impact services +2. **3 DTOs Already Compliant**: PnodeRefDto, AggregatedNodeRefDto, ServiceDeliveryPointDto already correctly lack selfLink/upLink +3. **Test Impact Minimal**: Only 1 test file references selfLink/upLink, and it's for a valid IdentifiedObject entity +4. **No @ElementCollection Usage Found Yet**: Need to verify where @ElementCollection is used for the 11 entities + +### Refactoring Scope 🔨 + +**Entities Needing Refactoring** (6 total): + +1. **IntervalReading** - DTO + Entity + Mapper +2. **ReadingQuality** - DTO + Entity + Mapper +3. **LineItem** - Entity only (no DTO) +4. **BatchList** - Entity only (no DTO) +5. **PhoneNumber** - Entity only (no DTO) +6. **StatementRef** - DTO + Entity + Mapper + +**Already Compliant** (3 entities): +- PnodeRef +- AggregatedNodeRef +- ServiceDeliveryPoint + +**@ElementCollection Usage**: +- Only 4 entities use @ElementCollection (ApplicationInformation, BatchList, CustomerAccount, CustomerAgreement) +- They use it for OTHER collections (e.g., batch_list_resources), NOT for related_links +- The 11 entities inherit related_links from IdentifiedObject (stored in separate tables) + +**Work Remaining**: + +1. **3 DTOs Need Link Removal**: IntervalReadingDto, ReadingQuality Dto, StatementRefDto +2. **6 Entities Need IdentifiedObject Removal**: All 6 entities listed above +3. **Mapper Layer Review**: 64 occurrences of selfLink/upLink in mappers need detailed review +4. **Repository Tests**: Need to review tests for the 6 entities that will change + +--- + +## Phase 2 Complete - Final Summary + +### Comprehensive Review Completed ✅ + +**Mapper Analysis**: ✅ All 64 occurrences reviewed +- 14 mapper files analyzed +- ALL mappers use `@Mapping(ignore = true)` pattern +- Only 3 mappers need changes (remove ignore annotations) +- Detailed analysis: `PHASE_2_MAPPER_ANALYSIS.md` + +**Repository Test Review**: ✅ Complete +- 3 repository tests found (AggregatedNodeRef, BatchList, LineItem) +- **ZERO references to selfLink/upLink in any tests** +- 7 test files total use our 6 entities +- Test impact: MINIMAL + +**Integration Test Review**: ✅ Complete +- Searched all test files for entity + link references +- **ZERO cross-references found** +- No integration test updates needed + +**Refactoring Checklists**: ✅ Created +- Per-entity detailed checklists created +- File-by-file change lists documented +- Refactoring order recommended +- Document: `PHASE_2_REFACTORING_CHECKLISTS.md` + +### Key Discoveries + +1. **Mappers Already Ignore Links**: ALL 14 mappers use `@Mapping(target = "selfLink", ignore = true)` - refactoring is just removing these annotations + +2. **Zero Test Impact**: Not a single test references selfLink/upLink for our 6 entities - extremely low risk refactoring + +3. **AggregatedNodeRef Partially Compliant**: DTO already correct (no selfLink/upLink), only entity needs fixing + +4. **No DTOs for 3 Entities**: LineItem, BatchList, PhoneNumber have no standalone DTOs (embedded entities) + +5. **@ElementCollection Not Used for Links**: Only 4 entities use @ElementCollection, for OTHER collections, not related_links + +### Refactoring Scope - Final + +| Complexity | Entities | Changes Needed | +|-----------|----------|----------------| +| **Low** | 5 entities | PhoneNumber, LineItem, BatchList, AggregatedNodeRef, ReadingQuality | +| **Medium** | 1 entity | IntervalReading (most test usage) | + +**Total Effort Estimate**: 2-4 hours for all 6 entities + +### Next Phase Options + +1. **Recommended: Start Pilot Refactoring** + - Begin with entity-only refactoring (PhoneNumber, LineItem, BatchList) + - Low risk, quick wins + - Validates approach before complex entities + +2. **Alternative: Phase 3 (Remove Special Case Tables)** + - Remove retail_customer_related_links and subscription_related_links tables + - Independent from entity refactoring + - Can be done in parallel + +### Documentation Created + +- ✅ `PHASE_2_CODE_REVIEW_SUMMARY.md` - This file +- ✅ `PHASE_2_MAPPER_ANALYSIS.md` - Comprehensive mapper review +- ✅ `PHASE_2_REFACTORING_CHECKLISTS.md` - Per-entity refactoring guide + +--- + +## Notes + +- Java 25 now set as default via sdkman +- Maven successfully using Java 25.0.1 (Zulu25.30+17-CA) +- No JAVA_HOME export needed in future commands diff --git a/openespi-common/PHASE_2_MAPPER_ANALYSIS.md b/openespi-common/PHASE_2_MAPPER_ANALYSIS.md new file mode 100644 index 00000000..21fa8e58 --- /dev/null +++ b/openespi-common/PHASE_2_MAPPER_ANALYSIS.md @@ -0,0 +1,225 @@ +# Phase 2: Comprehensive Mapper Analysis + +**Date**: 2025-12-31 +**Branch**: `feature/schema-compliance-phase-2-baseline` + +--- + +## Executive Summary + +**CRITICAL FINDING**: All 14 mappers use `@Mapping(target = "selfLink", ignore = true)` pattern. +- Links are NOT being mapped by MapStruct +- Links are handled elsewhere (likely DtoExportService) +- Refactoring impact is **removal only** - no logic changes needed + +--- + +## All Mappers with selfLink/upLink (14 total, 64 occurrences) + +| Mapper | Occurrences | Entity Extends IO? | DTO Has Links? | Needs Refactoring? | +|--------|-------------|-------------------|----------------|-------------------| +| **IntervalReadingMapper** | 6 | ✅ YES (wrong) | ✅ YES (wrong) | **YES - Remove links** | +| **ReadingQualityMapper** | 2 | ✅ YES (wrong) | ✅ YES (wrong) | **YES - Remove links** | +| **AggregatedNodeRefMapper** | 4 | ✅ YES (wrong) | ❌ NO (correct) | **YES - Entity only** | +| CustomerAccountMapper | 6 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| CustomerAgreementMapper | 6 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| CustomerMapper | 4 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| ApplicationInformationMapper | 4 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| AuthorizationMapper | 4 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| ElectricPowerQualitySummaryMapper | 4 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| IntervalBlockMapper | 6 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| MeterReadingMapper | 4 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| TimeConfigurationMapper | 4 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| UsagePointMapper | 4 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| UsageSummaryMapper | 6 | ✅ YES (correct) | ✅ YES (correct) | NO - Keep as-is | +| **TOTALS** | **64** | 14 entities | 11 DTOs | **3 mappers need changes** | + +*IO = IdentifiedObject* + +--- + +## Mapping Pattern Analysis + +### Universal Pattern: ALL Mappers Ignore Links + +**Every single mapper** uses this exact pattern: + +```java +@Mapping(target = "relatedLinks", ignore = true) // Links handled separately +@Mapping(target = "selfLink", ignore = true) +@Mapping(target = "upLink", ignore = true) +``` + +**This appears in**: +- `toDto()` method (entity → DTO mapping) +- `toEntity()` method (DTO → entity mapping) +- `updateEntity()` method (DTO → entity update) + +### Why This Matters + +1. **Links are NOT mapped by MapStruct** - they're populated somewhere else +2. **Removing fields won't break mappers** - they already ignore these fields +3. **Refactoring is simpler** - just remove the @Mapping annotations when fields don't exist + +--- + +## Detailed Findings by Entity Category + +### Category 1: Entities Needing Refactoring (3 mappers) + +#### 1. IntervalReadingMapper (6 occurrences) + +**Current State**: +- Entity: `IntervalReadingEntity extends IdentifiedObject` ❌ WRONG +- DTO: `IntervalReadingDto` has selfLink/upLink fields ❌ WRONG +- Mapper: Ignores links with `@Mapping(target = "selfLink", ignore = true)` ✅ CORRECT + +**Refactoring Required**: +1. Remove `extends IdentifiedObject` from IntervalReadingEntity +2. Remove selfLink/upLink/relatedLinks fields from IntervalReadingDto +3. Remove these @Mapping annotations from IntervalReadingMapper: + - Line 50: `@Mapping(target = "relatedLinks", ignore = true)` + - Line 51: `@Mapping(target = "selfLink", ignore = true)` + - Line 52: `@Mapping(target = "upLink", ignore = true)` + - Line 76: `@Mapping(target = "upLink", ignore = true)` + - Line 77: `@Mapping(target = "selfLink", ignore = true)` + - Line 78: `@Mapping(target = "relatedLinks", ignore = true)` + - Line 101: `@Mapping(target = "upLink", ignore = true)` + - Line 102: `@Mapping(target = "selfLink", ignore = true)` + - Line 103: `@Mapping(target = "relatedLinks", ignore = true)` + +**Impact**: Low - mapper already ignores these fields + +#### 2. ReadingQualityMapper (2 occurrences) + +**Current State**: +- Entity: `ReadingQualityEntity extends IdentifiedObject` ❌ WRONG +- DTO: `ReadingQualityDto` has selfLink/upLink fields ❌ WRONG +- Mapper: Ignores links ✅ CORRECT + +**Refactoring Required**: +1. Remove `extends IdentifiedObject` from ReadingQualityEntity +2. Remove selfLink/upLink/relatedLinks from ReadingQualityDto +3. Remove @Mapping ignore annotations from ReadingQualityMapper + +**Impact**: Low - mapper already ignores these fields + +#### 3. AggregatedNodeRefMapper (4 occurrences) + +**Current State**: +- Entity: `AggregatedNodeRefEntity extends IdentifiedObject` ❌ WRONG +- DTO: `AggregatedNodeRefDto` - NO selfLink/upLink ✅ ALREADY CORRECT! +- Mapper: Ignores links ✅ CORRECT (bridges entity-DTO mismatch) + +**Refactoring Required**: +1. Remove `extends IdentifiedObject` from AggregatedNodeRefEntity +2. Remove @Mapping ignore annotations from AggregatedNodeRefMapper +3. DTO already correct - no changes needed + +**Impact**: Low - mapper already handles mismatch + +**Note**: This entity is in a "partially compliant" state - DTO is correct, entity is wrong + +--- + +### Category 2: Entities Already Correct (11 mappers) + +These mappers handle entities that SHOULD extend IdentifiedObject and SHOULD have selfLink/upLink: + +- CustomerAccountMapper (6) +- CustomerAgreementMapper (6) +- CustomerMapper (4) +- ApplicationInformationMapper (4) +- AuthorizationMapper (4) +- ElectricPowerQualitySummaryMapper (4) +- IntervalBlockMapper (6) +- MeterReadingMapper (4) +- TimeConfigurationMapper (4) +- UsagePointMapper (4) +- UsageSummaryMapper (6) + +**Current State**: All correct - no changes needed + +**Why They Ignore Links**: Even for entities that SHOULD have links, the mappers don't populate them. Links are likely set by a separate service (DtoExportService) when creating Atom feeds. + +--- + +## Entities WITHOUT Mappers (3 entities) + +These entities from our refactoring list have NO mapper files: + +| Entity | Why No Mapper | Refactoring Impact | +|--------|---------------|-------------------| +| LineItem | Embedded in UsageSummary | Entity-only refactoring | +| BatchList | Special case, no DTO | Entity-only refactoring | +| PhoneNumber | Embedded in Customer | Entity-only refactoring | + +**Impact**: These entities need entity refactoring only, no mapper changes + +--- + +## Key Insights + +### 1. Consistent Pattern Across All Mappers ✅ +- Every mapper ignores selfLink, upLink, relatedLinks +- Comment: "Links handled separately" +- This is intentional design - links populated elsewhere + +### 2. Links Populated by Separate Service 🔍 +- MapStruct mappers don't touch links +- Likely handled by DtoExportService or similar +- Need to review DtoExportService next + +### 3. Refactoring is Low Risk 🛡️ +- Mappers already ignore these fields +- Removing fields = removing unnecessary @Mapping annotations +- No logic changes required in mappers +- MapStruct will recompile cleanly + +### 4. Partially Compliant Entity Found 🔎 +- AggregatedNodeRef: DTO correct, entity wrong +- This proves DTO-only compliance is already working +- Just need to fix the entity side + +### 5. Total Mapper Changes Minimal ⚡ +- Only 3 mappers need updates +- Changes are deletions only (remove @Mapping annotations) +- No new code to write + +--- + +## Refactoring Checklist Summary + +### Mappers Requiring Changes (3) + +**IntervalReadingMapper**: +- [ ] Remove 3 @Mapping annotations from toDto() method +- [ ] Remove 3 @Mapping annotations from toEntity() method +- [ ] Remove 3 @Mapping annotations from updateEntity() method + +**ReadingQualityMapper**: +- [ ] Remove @Mapping annotations for links (TBD - need to count) + +**AggregatedNodeRefMapper**: +- [ ] Remove 3 @Mapping annotations from toEntity() method +- [ ] Remove 3 @Mapping annotations from updateEntity() method +- [ ] Note: toDto() might not need changes if DTO already lacks fields + +### Next Steps + +1. ✅ Mapper analysis complete +2. ⏭️ Review DtoExportService to understand link population +3. ⏭️ Review repository tests for the 6 entities +4. ⏭️ Create detailed per-entity refactoring checklists + +--- + +## Conclusion + +The mapper analysis reveals **excellent news**: +- All mappers already ignore selfLink/upLink +- Only 3 mappers need changes (deletion of @Mapping annotations) +- No complex mapping logic to refactor +- Low risk, high confidence refactoring path + +The refactoring will be **simpler than anticipated** - primarily removing unnecessary code rather than rewriting logic. diff --git a/openespi-common/PHASE_2_REFACTORING_CHECKLISTS.md b/openespi-common/PHASE_2_REFACTORING_CHECKLISTS.md new file mode 100644 index 00000000..fb74c8a4 --- /dev/null +++ b/openespi-common/PHASE_2_REFACTORING_CHECKLISTS.md @@ -0,0 +1,390 @@ +# Phase 2: Per-Entity Refactoring Checklists + +**Date**: 2025-12-31 +**Branch**: `feature/schema-compliance-phase-2-baseline` + +--- + +## Summary + +| Entity | Entity File | DTO File | Mapper File | Repository | Tests | Complexity | +|--------|------------|----------|-------------|------------|-------|------------| +| IntervalReading | ✅ | ✅ | ✅ | ❌ None | 1 file | **Medium** | +| ReadingQuality | ✅ | ✅ | ✅ | ❌ None | 0 files | **Low** | +| LineItem | ✅ | ❌ None | ❌ None | ✅ | 2 files | **Low** | +| BatchList | ✅ | ❌ None | ❌ None | ✅ | 1 file | **Low** | +| PhoneNumber | ✅ | ❌ None | ❌ None | ❌ None | 2 files | **Low** | +| AggregatedNodeRef | ✅ | ❌ Already OK | ✅ | ✅ | 1 file | **Low** | + +--- + +## Entity 1: IntervalReading + +**Complexity**: Medium (Entity + DTO + Mapper changes) +**Risk**: Low (no tests reference selfLink/upLink) + +### Files to Modify + +#### 1. Entity File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/IntervalReadingEntity.java` + +**Changes**: +- [ ] Remove `extends IdentifiedObject` from class declaration +- [ ] Add direct JPA annotations (@Entity, @Table, etc.) +- [ ] Add `@Id` and `@GeneratedValue` for primary key (UUID id field) +- [ ] Remove any @AttributeOverride annotations for inherited fields +- [ ] Verify all existing fields remain (cost, value, timePeriod, readingQualities, etc.) + +**Before**: +```java +public class IntervalReadingEntity extends IdentifiedObject { +``` + +**After**: +```java +@Entity +@Table(name = "interval_readings") +public class IntervalReadingEntity { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + // ... rest of fields +``` + +#### 2. DTO File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/IntervalReadingDto.java` + +**Changes**: +- [ ] Remove selfLink field +- [ ] Remove upLink field +- [ ] Remove relatedLinks field +- [ ] Remove @XmlElement annotations for these fields +- [ ] Update @XmlType propOrder list (remove these 3 fields) +- [ ] Remove getter methods for these 3 fields +- [ ] Verify all ESPI-spec fields remain (cost, value, timePeriod, readingQualities, etc.) + +#### 3. Mapper File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/IntervalReadingMapper.java` + +**Changes**: +- [ ] Remove `@Mapping(target = "selfLink", ignore = true)` from toDto() method (line 51) +- [ ] Remove `@Mapping(target = "upLink", ignore = true)` from toDto() method (line 52) +- [ ] Remove `@Mapping(target = "relatedLinks", ignore = true)` from toDto() method (line 50) +- [ ] Remove `@Mapping(target = "selfLink", ignore = true)` from toEntity() method (line 77) +- [ ] Remove `@Mapping(target = "upLink", ignore = true)` from toEntity() method (line 76) +- [ ] Remove `@Mapping(target = "relatedLinks", ignore = true)` from toEntity() method (line 78) +- [ ] Remove same 3 annotations from updateEntity() method (lines 101-103) +- [ ] Total: Remove 9 @Mapping annotations + +#### 4. Repository Tests +**File**: None - IntervalReading has no repository (embedded entity) + +**Test Files Using IntervalReading** (1): +- `IntervalBlockRepositoryTest.java` - May create IntervalReading objects + - [ ] Review test data builders + - [ ] Verify no selfLink/upLink assertions (already confirmed: 0 references) + +--- + +## Entity 2: ReadingQuality + +**Complexity**: Low (Entity + DTO + Mapper, no repository, no tests) +**Risk**: Very Low + +### Files to Modify + +#### 1. Entity File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/ReadingQualityEntity.java` + +**Changes**: +- [ ] Remove `extends IdentifiedObject` from class declaration +- [ ] Add @Entity, @Table annotations +- [ ] Add @Id and @GeneratedValue for UUID id field +- [ ] Verify all ESPI fields remain (quality) + +#### 2. DTO File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/ReadingQualityDto.java` + +**Changes**: +- [ ] Remove selfLink, upLink, relatedLinks fields +- [ ] Remove @XmlElement annotations for these fields +- [ ] Update @XmlType propOrder +- [ ] Remove getter methods + +#### 3. Mapper File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/ReadingQualityMapper.java` + +**Changes**: +- [ ] Remove @Mapping ignore annotations for selfLink, upLink, relatedLinks +- [ ] Likely 2-6 annotations to remove (need to count exact lines) + +#### 4. Tests +**Files**: None - 0 test files reference ReadingQuality + +--- + +## Entity 3: LineItem + +**Complexity**: Low (Entity only, no DTO, no mapper) +**Risk**: Low + +### Files to Modify + +#### 1. Entity File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/LineItemEntity.java` + +**Changes**: +- [ ] Remove `extends IdentifiedObject` from class declaration +- [ ] Add @Entity, @Table annotations (if not already present) +- [ ] Add @Id and @GeneratedValue for UUID id field +- [ ] Verify all ESPI fields remain + +#### 2. DTO File +**File**: None - LineItem is embedded, no standalone DTO + +**Impact**: None + +#### 3. Mapper File +**File**: None - No LineItemMapper exists + +**Impact**: None - LineItem is mapped as part of UsageSummaryMapper + +**Check**: +- [ ] Review UsageSummaryMapper to ensure LineItem mapping is correct +- [ ] Verify LineItem is mapped as a collection field, not as IdentifiedObject + +#### 4. Repository & Tests +**Repository**: `LineItemRepository.java` exists + +**Test Files** (2): +- `LineItemRepositoryTest.java` + - [ ] Review for selfLink/upLink usage (already confirmed: 0) + - [ ] Verify tests still pass after entity changes +- `UsageSummaryRepositoryTest.java` + - [ ] May use LineItem as embedded collection + - [ ] Verify cascade operations work correctly + +--- + +## Entity 4: BatchList + +**Complexity**: Low (Entity only, no DTO, no mapper) +**Risk**: Low + +### Files to Modify + +#### 1. Entity File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/BatchListEntity.java` + +**Changes**: +- [ ] Remove `extends IdentifiedObject` from class declaration +- [ ] Add @Entity, @Table annotations +- [ ] Add @Id and @GeneratedValue for UUID id field +- [ ] Verify @ElementCollection for batch_list_resources remains correct +- [ ] Verify all ESPI fields remain + +**Note**: BatchListEntity uses @ElementCollection for `batch_list_resources` collection + +#### 2. DTO File +**File**: None - No BatchListDto exists + +**Impact**: None + +#### 3. Mapper File +**File**: None - No BatchListMapper exists + +**Impact**: None + +#### 4. Repository & Tests +**Repository**: `BatchListRepository.java` exists + +**Test Files** (1): +- `BatchListRepositoryTest.java` + - [ ] Review for selfLink/upLink usage (already confirmed: 0) + - [ ] Verify tests pass after entity changes + - [ ] Check @ElementCollection tests still work + +--- + +## Entity 5: PhoneNumber + +**Complexity**: Low (Entity only, embedded in Customer) +**Risk**: Low + +### Files to Modify + +#### 1. Entity File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/PhoneNumberEntity.java` + +**Changes**: +- [ ] Remove `extends IdentifiedObject` from class declaration +- [ ] Add @Entity, @Table annotations +- [ ] Add @Id and @GeneratedValue for UUID id field +- [ ] Verify Customer relationship remains correct +- [ ] Verify all ESPI fields remain + +#### 2. DTO File +**File**: None - PhoneNumber embedded in CustomerDto + +**Impact**: None - PhoneNumber mapped as embedded collection within CustomerMapper + +#### 3. Mapper File +**File**: None - No standalone PhoneNumberMapper + +**Check**: +- [ ] Review CustomerMapper for PhoneNumber collection mapping +- [ ] Verify @ElementCollection mapping works correctly + +#### 4. Tests +**No Repository** - PhoneNumber is embedded + +**Test Files** (2): +- `ServiceLocationRepositoryTest.java` - May use PhoneNumber + - [ ] Verify no selfLink/upLink usage +- `BaseRepositoryTest.java` - Base test class + - [ ] Review if PhoneNumber is used in test data builders + +--- + +## Entity 6: AggregatedNodeRef + +**Complexity**: Low (Entity + Mapper only, DTO already correct!) +**Risk**: Very Low + +### Files to Modify + +#### 1. Entity File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/AggregatedNodeRefEntity.java` + +**Changes**: +- [ ] Remove `extends IdentifiedObject` from class declaration +- [ ] Add @Entity, @Table annotations +- [ ] Add @Id and @GeneratedValue for UUID id field +- [ ] Verify PnodeRef relationship remains correct +- [ ] Verify all ESPI fields remain + +#### 2. DTO File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/AggregatedNodeRefDto.java` + +**Status**: ✅ **ALREADY CORRECT** - No selfLink/upLink fields present + +**Changes**: ❌ None needed + +#### 3. Mapper File +**File**: `openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/AggregatedNodeRefMapper.java` + +**Changes**: +- [ ] Remove `@Mapping(target = "selfLink", ignore = true)` from toEntity() method +- [ ] Remove `@Mapping(target = "upLink", ignore = true)` from toEntity() method +- [ ] Remove `@Mapping(target = "relatedLinks", ignore = true)` from toEntity() method +- [ ] Remove same 3 annotations from updateEntity() method +- [ ] Total: Remove 6 @Mapping annotations +- [ ] Note: toDto() may not need changes since DTO already lacks these fields + +#### 4. Repository & Tests +**Repository**: `AggregatedNodeRefRepository.java` exists + +**Test Files** (1): +- `AggregatedNodeRefRepositoryTest.java` + - [ ] Review for selfLink/upLink usage (already confirmed: 0) + - [ ] Verify tests pass after entity changes + +--- + +## Common Tasks for All Entities + +### Entity Changes (All 6) +- [ ] Remove `extends IdentifiedObject` +- [ ] Add @Entity annotation if missing +- [ ] Add @Table annotation with name +- [ ] Add UUID id field with @Id and @GeneratedValue +- [ ] Verify relationships (ManyToOne, OneToMany, etc.) remain intact +- [ ] Run `mvn clean compile` to verify no compilation errors + +### DTO Changes (3: IntervalReading, ReadingQuality, StatementRef*) +- [ ] Remove selfLink field +- [ ] Remove upLink field +- [ ] Remove relatedLinks field +- [ ] Remove @XmlElement annotations +- [ ] Update @XmlType propOrder list +- [ ] Remove getter methods +- [ ] Run `mvn clean compile` to verify MapStruct generation + +*Note: StatementRef not detailed above but follows same pattern + +### Mapper Changes (3: IntervalReading, ReadingQuality, AggregatedNodeRef) +- [ ] Remove @Mapping(target = "selfLink", ignore = true) annotations +- [ ] Remove @Mapping(target = "upLink", ignore = true) annotations +- [ ] Remove @Mapping(target = "relatedLinks", ignore = true) annotations +- [ ] Verify MapStruct compiles cleanly + +### Database Migration +- [ ] NO changes needed - tables already exist +- [ ] NO new migrations required +- [ ] related_links tables will remain (removed in separate phase) + +### Testing +- [ ] Run full test suite: `mvn test` +- [ ] Verify baseline: 544/545 tests still passing +- [ ] Check specific entity repository tests +- [ ] Run integration tests with TestContainers + +--- + +## Refactoring Order Recommendation + +### Phase A: Entity-Only Refactoring (Low Risk) +**Entities with NO DTO/Mapper impact**: +1. **PhoneNumber** (simplest - embedded) +2. **LineItem** (simple - embedded in UsageSummary) +3. **BatchList** (has @ElementCollection to verify) + +**Estimated Time**: 30-45 minutes total +**Risk**: Very Low - no DTO/Mapper dependencies + +### Phase B: DTO Already Correct (Low Risk) +**Entity where DTO is already compliant**: +4. **AggregatedNodeRef** (DTO correct, just entity + mapper) + +**Estimated Time**: 20-30 minutes +**Risk**: Very Low - DTO already done + +### Phase C: Full Stack Refactoring (Medium Risk) +**Entities needing Entity + DTO + Mapper**: +5. **ReadingQuality** (simpler - no repository/tests) +6. **IntervalReading** (more complex - used in tests) + +**Estimated Time**: 1-2 hours total +**Risk**: Low-Medium - most complete changes + +--- + +## Verification Checklist + +After each entity refactoring: +- [ ] `mvn clean compile` succeeds +- [ ] MapStruct generates mappers without warnings +- [ ] Entity repository tests pass +- [ ] Related parent entity tests pass +- [ ] Full test suite still at 544/545 passing + +--- + +## Notes + +- **No Flyway migrations needed** - tables exist, we're just removing inheritance +- **No related_links table removal yet** - that's Phase 3 (separate PR) +- **MapStruct will recompile** - verify generated mappers in target/generated-sources +- **Low test impact** - only 7 test files use these entities, 0 reference selfLink/upLink + +--- + +## Success Criteria + +✅ All 6 entities no longer extend IdentifiedObject +✅ 3 DTOs no longer have selfLink/upLink/relatedLinks fields +✅ 3 mappers no longer have @Mapping ignore annotations for links +✅ All 544 passing tests still pass +✅ MapStruct compilation succeeds +✅ No runtime errors in affected services diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/PhoneNumberEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/PhoneNumberEntity.java index 869d8034..728d3921 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/PhoneNumberEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/PhoneNumberEntity.java @@ -19,26 +19,38 @@ package org.greenbuttonalliance.espi.common.domain.customer.entity; -import lombok.*; -import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; - import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import org.hibernate.proxy.HibernateProxy; import java.util.Objects; +import java.util.UUID; /** * JPA entity for PhoneNumber to resolve embedded mapping conflicts. - * + * * Separate entity table for phone numbers to eliminate column duplication * issues when multiple entities embed Organisation with PhoneNumber fields. + * + * Note: PhoneNumber does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @Entity @Table(name = "phone_numbers") @Getter @Setter @NoArgsConstructor -public class PhoneNumberEntity extends IdentifiedObject { +public class PhoneNumberEntity { + + /** + * Primary key identifier. + */ + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", nullable = false) + private UUID id; /** * Area code for phone number. @@ -98,8 +110,8 @@ public enum PhoneType { public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); - Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + Class oEffectiveClass = o instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; PhoneNumberEntity that = (PhoneNumberEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); @@ -107,7 +119,7 @@ public final boolean equals(Object o) { @Override public final int hashCode() { - return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + return this instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } @Override @@ -120,10 +132,6 @@ public String toString() { "extension = " + getExtension() + ", " + "phoneType = " + getPhoneType() + ", " + "parentEntityUuid = " + getParentEntityUuid() + ", " + - "parentEntityType = " + getParentEntityType() + ", " + - "description = " + getDescription() + ", " + - "created = " + getCreated() + ", " + - "updated = " + getUpdated() + ", " + - "published = " + getPublished() + ")"; + "parentEntityType = " + getParentEntityType() + ")"; } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/StatementRefEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/StatementRefEntity.java index abce9f68..0ee04bb7 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/StatementRefEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/StatementRefEntity.java @@ -19,26 +19,37 @@ package org.greenbuttonalliance.espi.common.domain.customer.entity; -import lombok.*; -import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; - import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import org.hibernate.proxy.HibernateProxy; import java.util.Objects; +import java.util.UUID; /** * Pure JPA/Hibernate entity for StatementRef without JAXB concerns. - * + * * [extension] A sequence of references to a document associated with a Statement. - * ESPI compliant with proper UUID identifiers and ATOM feed support. + * + * Note: StatementRef does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @Entity @Table(name = "statement_refs") @Getter @Setter @NoArgsConstructor -public class StatementRefEntity extends IdentifiedObject { +public class StatementRefEntity { + + /** + * Primary key identifier. + */ + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", nullable = false) + private UUID id; /** * [extension] Name of document or file including filename extension if present. @@ -70,8 +81,10 @@ public class StatementRefEntity extends IdentifiedObject { public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); - Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + Class oEffectiveClass = o instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; StatementRefEntity that = (StatementRefEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); @@ -79,7 +92,8 @@ public final boolean equals(Object o) { @Override public final int hashCode() { - return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + return this instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } @Override @@ -88,10 +102,6 @@ public String toString() { "id = " + getId() + ", " + "fileName = " + getFileName() + ", " + "mediaType = " + getMediaType() + ", " + - "statementURL = " + getStatementURL() + ", " + - "description = " + getDescription() + ", " + - "created = " + getCreated() + ", " + - "updated = " + getUpdated() + ", " + - "published = " + getPublished() + ")"; + "statementURL = " + getStatementURL() + ")"; } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/AggregatedNodeRefEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/AggregatedNodeRefEntity.java index 0d05002f..ca736c5f 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/AggregatedNodeRefEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/AggregatedNodeRefEntity.java @@ -23,23 +23,34 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; import org.hibernate.proxy.HibernateProxy; import java.util.Objects; +import java.util.UUID; /** * JPA entity for AggregatedNodeRef (Aggregated Node Reference). - * + * * Represents a reference to an aggregated node in the electrical grid used within UsagePoint. * Each aggregated node reference includes an associated pricing node reference. + * + * Note: AggregatedNodeRef does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @Entity @Table(name = "aggregated_node_refs") @Getter @Setter @NoArgsConstructor -public class AggregatedNodeRefEntity extends IdentifiedObject { +public class AggregatedNodeRefEntity { + + /** + * Primary key identifier. + */ + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", nullable = false) + private UUID id; /** * Type of the aggregated node. @@ -145,8 +156,8 @@ public String getFullDisplayName() { public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); - Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + Class oEffectiveClass = o instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; AggregatedNodeRefEntity that = (AggregatedNodeRefEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); @@ -154,7 +165,7 @@ public final boolean equals(Object o) { @Override public final int hashCode() { - return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + return this instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } @Override @@ -164,10 +175,6 @@ public String toString() { "anodeType = " + getAnodeType() + ", " + "ref = " + getRef() + ", " + "startEffectiveDate = " + getStartEffectiveDate() + ", " + - "endEffectiveDate = " + getEndEffectiveDate() + ", " + - "description = " + getDescription() + ", " + - "created = " + getCreated() + ", " + - "updated = " + getUpdated() + ", " + - "published = " + getPublished() + ")"; + "endEffectiveDate = " + getEndEffectiveDate() + ")"; } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/BatchListEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/BatchListEntity.java index a61e9dd7..fd2462c9 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/BatchListEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/BatchListEntity.java @@ -19,44 +19,49 @@ package org.greenbuttonalliance.espi.common.domain.usage; -import lombok.Getter; -import lombok.Setter; -import lombok.NoArgsConstructor; - import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import org.hibernate.proxy.HibernateProxy; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** * Pure JPA/Hibernate entity for BatchList without JAXB concerns. - * + * * List of resource URIs that can be used to GET ESPI resources. * This entity supports batch operations by collecting multiple resource * URIs that can be processed together for efficient data retrieval * and manipulation operations. + * + * Note: BatchList does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @Entity @Table(name = "batch_lists", indexes = { - @Index(name = "idx_batch_list_created", columnList = "created"), @Index(name = "idx_batch_list_resource_count", columnList = "resource_count") }) @Getter @Setter @NoArgsConstructor -public class BatchListEntity extends IdentifiedObject { +public class BatchListEntity { private static final long serialVersionUID = 1L; + /** + * Primary key identifier. + */ + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", nullable = false) + private UUID id; + /** * List of resource URIs for batch processing. * Each URI points to an ESPI resource that can be retrieved or processed. @@ -83,38 +88,12 @@ public class BatchListEntity extends IdentifiedObject { private Integer resourceCount = 0; - /** - * Constructor with description. - * - * @param description the description of this batch list - */ - public BatchListEntity(String description) { - super(); - setDescription(description); - } - /** * Constructor with initial resources. - * + * * @param resources the initial list of resource URIs */ public BatchListEntity(List resources) { - super(); - if (resources != null) { - this.resources = new ArrayList<>(resources); - updateResourceCount(); - } - } - - /** - * Constructor with resources and description. - * - * @param resources the initial list of resource URIs - * @param description the description of this batch list - */ - public BatchListEntity(List resources, String description) { - super(); - setDescription(description); if (resources != null) { this.resources = new ArrayList<>(resources); updateResourceCount(); @@ -303,7 +282,7 @@ public int removeDuplicates() { int originalSize = resources.size(); List uniqueResources = resources.stream() .distinct() - .collect(Collectors.toList()); + .toList(); this.resources = uniqueResources; updateResourceCount(); @@ -344,7 +323,7 @@ private boolean isValidUri(String uriString) { try { URI uri = new URI(uriString.trim()); return uri.getScheme() != null; // Basic validation - has scheme - } catch (URISyntaxException e) { + } catch (URISyntaxException _) { return false; } } @@ -363,7 +342,7 @@ public List filterResourcesByPattern(String pattern) { String regexPattern = pattern.replace("*", ".*"); return resources.stream() .filter(uri -> uri.matches(regexPattern)) - .collect(Collectors.toList()); + .toList(); } /** @@ -379,52 +358,34 @@ public List getResourcesByType(String resourceType) { return resources.stream() .filter(uri -> uri.contains("/" + resourceType + "/")) - .collect(Collectors.toList()); + .toList(); } /** * Gets a summary string for this batch list. - * + * * @return summary string with key information */ public String getSummary() { StringBuilder summary = new StringBuilder(); summary.append("Batch List ID: ").append(getId()); summary.append(" (").append(getResourceCount()).append(" resources)"); - - if (getDescription() != null && !getDescription().trim().isEmpty()) { - summary.append(" - ").append(getDescription()); - } - - if (getCreated() != null) { - summary.append(" created at ").append(getCreated()); - } - return summary.toString(); } /** * Gets statistics about the batch list. - * + * * @return statistics string */ public String getStatistics() { StringBuilder stats = new StringBuilder(); stats.append("Total resources: ").append(getResourceCount()); stats.append(", Unique resources: ").append(getUniqueResources().size()); - + List invalidUris = validateResourceUris(); stats.append(", Invalid URIs: ").append(invalidUris.size()); - - if (getCreated() != null && getUpdated() != null) { - stats.append(", Last modified: "); - if (getCreated().equals(getUpdated())) { - stats.append("never (created only)"); - } else { - stats.append(getUpdated()); - } - } - + return stats.toString(); } @@ -477,8 +438,8 @@ protected void onUpdate() { public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); - Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + Class oEffectiveClass = o instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; BatchListEntity that = (BatchListEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); @@ -486,7 +447,7 @@ public final boolean equals(Object o) { @Override public final int hashCode() { - return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + return this instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } @Override @@ -494,9 +455,6 @@ public String toString() { return getClass().getSimpleName() + "(" + "id = " + getId() + ", " + "resources = " + resources + ", " + - "resourceCount = " + resourceCount + ", " + - "created = " + getCreated() + ", " + - "updated = " + getUpdated() + ", " + - "description = " + getDescription() + ")"; + "resourceCount = " + resourceCount + ")"; } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/IntervalReadingEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/IntervalReadingEntity.java index 91852e45..ac1a979a 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/IntervalReadingEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/IntervalReadingEntity.java @@ -20,15 +20,17 @@ package org.greenbuttonalliance.espi.common.domain.usage; import jakarta.persistence.*; -import lombok.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import org.greenbuttonalliance.espi.common.domain.common.DateTimeInterval; -import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; import org.hibernate.annotations.BatchSize; import org.hibernate.proxy.HibernateProxy; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.UUID; /** * Pure JPA/Hibernate entity for IntervalReading without JAXB concerns. @@ -36,15 +38,24 @@ * Represents a specific value measured by a meter or other asset. * Each reading is associated with a specific ReadingType and contains * cost, value, consumption tier, time-of-use, and critical peak pricing information. + * + * Note: IntervalReading does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @Entity @Table(name = "interval_readings") @Getter @Setter @NoArgsConstructor -public class IntervalReadingEntity extends IdentifiedObject { +public class IntervalReadingEntity { - private static final long serialVersionUID = 1L; + /** + * Primary key identifier. + */ + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", nullable = false) + private UUID id; /** * Cost associated with this interval reading. @@ -86,10 +97,8 @@ public class IntervalReadingEntity extends IdentifiedObject { * Embedded value object containing start time and duration. */ @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "start", column = @Column(name = "time_period_start")), - @AttributeOverride(name = "duration", column = @Column(name = "time_period_duration")) - }) + @AttributeOverride(name = "start", column = @Column(name = "time_period_start")) + @AttributeOverride(name = "duration", column = @Column(name = "time_period_duration")) private DateTimeInterval timePeriod; /** @@ -301,8 +310,10 @@ public Long getAbsoluteValue() { public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); - Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + Class oEffectiveClass = o instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; IntervalReadingEntity that = (IntervalReadingEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); @@ -310,7 +321,8 @@ public final boolean equals(Object o) { @Override public final int hashCode() { - return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + return this instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } @Override @@ -322,10 +334,6 @@ public String toString() { "consumptionTier = " + getConsumptionTier() + ", " + "tou = " + getTou() + ", " + "cpp = " + getCpp() + ", " + - "timePeriod = " + getTimePeriod() + ", " + - "description = " + getDescription() + ", " + - "created = " + getCreated() + ", " + - "updated = " + getUpdated() + ", " + - "published = " + getPublished() + ")"; + "timePeriod = " + getTimePeriod() + ")"; } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/LineItemEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/LineItemEntity.java index 16af6899..da6bdb76 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/LineItemEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/LineItemEntity.java @@ -25,7 +25,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; import org.hibernate.proxy.HibernateProxy; import java.math.BigDecimal; @@ -34,14 +33,18 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Objects; +import java.util.UUID; /** * Pure JPA/Hibernate entity for LineItem without JAXB concerns. - * + * * Line item of detail for additional cost. Represents individual charges, * fees, or cost components that make up part of a billing statement or * usage summary. Each line item contains an amount, optional rounding, * timestamp, and descriptive note. + * + * Note: LineItem does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @Entity @Table(name = "line_items", indexes = { @@ -52,7 +55,15 @@ @Getter @Setter @NoArgsConstructor -public class LineItemEntity extends IdentifiedObject { +public class LineItemEntity { + + /** + * Primary key identifier. + */ + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", nullable = false) + private UUID id; /** * Amount for this line item in currency minor units (e.g., cents). @@ -378,10 +389,10 @@ public String getSummary() { summary.append(" (Base: ").append(getFormattedAmount()); summary.append(", Rounding: ").append(getRoundingAsBigDecimal()).append(")"); } - - LocalDateTime dateTime = getDateTimeAsLocalDateTime(); - if (dateTime != null) { - summary.append(" on ").append(dateTime.toLocalDate()); + + LocalDateTime localDateTime = getDateTimeAsLocalDateTime(); + if (localDateTime != null) { + summary.append(" on ").append(localDateTime.toLocalDate()); } return summary.toString(); @@ -430,8 +441,8 @@ protected void onCreate() { public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); - Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + Class oEffectiveClass = o instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; LineItemEntity that = (LineItemEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); @@ -439,7 +450,7 @@ public final boolean equals(Object o) { @Override public final int hashCode() { - return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + return this instanceof HibernateProxy hibernateProxy ? hibernateProxy.getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } @Override @@ -449,10 +460,6 @@ public String toString() { "amount = " + getAmount() + ", " + "rounding = " + getRounding() + ", " + "dateTime = " + getDateTime() + ", " + - "note = " + getNote() + ", " + - "description = " + getDescription() + ", " + - "created = " + getCreated() + ", " + - "updated = " + getUpdated() + ", " + - "published = " + getPublished() + ")"; + "note = " + getNote() + ")"; } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/ReadingQualityEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/ReadingQualityEntity.java index 52c6cd42..a51ea571 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/ReadingQualityEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/ReadingQualityEntity.java @@ -25,19 +25,22 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; import org.hibernate.proxy.HibernateProxy; import java.util.Objects; +import java.util.UUID; /** * Pure JPA/Hibernate entity for ReadingQuality without JAXB concerns. *

* Represents the quality of a specific reading value or interval reading value. - * Note that more than one Quality may be applicable to a given Reading. - * Typically not used unless problems or unusual conditions occur (i.e., quality - * for each Reading is assumed to be 'Good' (valid) unless stated otherwise in + * Note that more than one Quality may be applicable to a given Reading. + * Typically not used unless problems or unusual conditions occur (i.e., quality + * for each Reading is assumed to be 'Good' (valid) unless stated otherwise in * associated ReadingQuality). + * + * Note: ReadingQuality does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @Entity @Table(name = "reading_qualities", indexes = { @@ -47,9 +50,15 @@ @Getter @Setter @NoArgsConstructor -public class ReadingQualityEntity extends IdentifiedObject { +public class ReadingQualityEntity { - private static final long serialVersionUID = 1L; + /** + * Primary key identifier. + */ + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", nullable = false) + private UUID id; // Quality constants based on common industry standards public static final String QUALITY_GOOD = "GOOD"; @@ -383,8 +392,10 @@ protected void normalizeQuality() { public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); - Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + Class oEffectiveClass = o instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; ReadingQualityEntity that = (ReadingQualityEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); @@ -392,13 +403,14 @@ public final boolean equals(Object o) { @Override public final int hashCode() { - return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + return this instanceof HibernateProxy hibernateProxy ? + hibernateProxy.getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } @Override public String toString() { return getClass().getSimpleName() + "(" + - "id = " + id + ", " + - "quality = " + quality + ")"; + "id = " + getId() + ", " + + "quality = " + getQuality() + ")"; } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/customer/StatementRefDto.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/customer/StatementRefDto.java index bdb76d21..3775e523 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/customer/StatementRefDto.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/customer/StatementRefDto.java @@ -19,123 +19,57 @@ package org.greenbuttonalliance.espi.common.dto.customer; -import org.greenbuttonalliance.espi.common.dto.atom.LinkDto; - import jakarta.xml.bind.annotation.*; + import java.time.OffsetDateTime; -import java.util.List; /** * StatementRef DTO record for JAXB XML marshalling/unmarshalling. - * + * * Represents a reference to a statement document. - * Supports Atom protocol XML wrapping. + * + * Note: StatementRef does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. + * + * WARNING: DTO fields do not currently match entity fields. + * Entity has: fileName, mediaType, statementURL + * DTO has: referenceId, referenceType, referenceDate, referenceUrl + * This mismatch needs to be resolved. */ @XmlRootElement(name = "StatementRef", namespace = "http://naesb.org/espi/customer") @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "StatementRef", namespace = "http://naesb.org/espi/customer", propOrder = { - "id", "uuid", "published", "updated", "selfLink", "upLink", "relatedLinks", - "description", "referenceId", "referenceType", "referenceDate", - "referenceUrl", "statement" + "referenceId", "referenceType", "referenceDate", "referenceUrl", "statement" }) public record StatementRefDto( - - @XmlTransient - Long id, - - @XmlAttribute(name = "mRID") - String uuid, - - @XmlElement(name = "published") - OffsetDateTime published, - - @XmlElement(name = "updated") - OffsetDateTime updated, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - @XmlElementWrapper(name = "links", namespace = "http://www.w3.org/2005/Atom") - List relatedLinks, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - LinkDto selfLink, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - LinkDto upLink, - - @XmlElement(name = "description") - String description, - + @XmlElement(name = "referenceId") String referenceId, - + @XmlElement(name = "referenceType") String referenceType, - + @XmlElement(name = "referenceDate") OffsetDateTime referenceDate, - + @XmlElement(name = "referenceUrl") String referenceUrl, - + @XmlElement(name = "Statement") StatementDto statement ) { - + /** * Default constructor for JAXB. */ public StatementRefDto() { - this(null, null, null, null, null, null, null, null, - null, null, null, null, null); + this(null, null, null, null, null); } - + /** * Minimal constructor for basic reference data. */ - public StatementRefDto(String uuid, String referenceId) { - this(null, uuid, null, null, null, null, null, null, - referenceId, null, null, null, null); - } - - /** - * Gets the self href for this statement reference. - * - * @return self href string - */ - public String getSelfHref() { - return selfLink != null ? selfLink.href() : null; - } - - /** - * Gets the up href for this statement reference. - * - * @return up href string - */ - public String getUpHref() { - return upLink != null ? upLink.href() : null; - } - - /** - * Generates the default self href for a statement reference. - * - * @return default self href - */ - public String generateSelfHref() { - if (uuid != null && statement != null && statement.uuid() != null) { - return "/espi/1_1/resource/Statement/" + statement.uuid() + "/StatementRef/" + uuid; - } - return uuid != null ? "/espi/1_1/resource/StatementRef/" + uuid : null; - } - - /** - * Generates the default up href for a statement reference. - * - * @return default up href - */ - public String generateUpHref() { - if (statement != null && statement.uuid() != null) { - return "/espi/1_1/resource/Statement/" + statement.uuid() + "/StatementRef"; - } - return "/espi/1_1/resource/StatementRef"; + public StatementRefDto(String referenceId, String referenceUrl) { + this(referenceId, null, null, referenceUrl, null); } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/IntervalReadingDto.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/IntervalReadingDto.java index 02dce089..4a62fb8f 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/IntervalReadingDto.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/IntervalReadingDto.java @@ -19,93 +19,71 @@ package org.greenbuttonalliance.espi.common.dto.usage; -import org.greenbuttonalliance.espi.common.dto.atom.LinkDto; - import jakarta.xml.bind.annotation.*; -import java.time.OffsetDateTime; + import java.util.List; /** * IntervalReading DTO record for JAXB XML marshalling/unmarshalling. - * + * * Represents specific readings of a measurement within an interval block. * Contains the actual energy values, costs, and reading quality information. + * + * Note: IntervalReading does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @XmlRootElement(name = "IntervalReading", namespace = "http://naesb.org/espi") @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "IntervalReading", namespace = "http://naesb.org/espi", propOrder = { - "uuid", "published", "updated", "selfLink", "upLink", "relatedLinks", - "description", "cost", "currency", "value", "timePeriod", "readingQualities", + "cost", "currency", "value", "timePeriod", "readingQualities", "consumptionTier", "tou", "cpp" }) public record IntervalReadingDto( - - @XmlAttribute(name = "mRID") - String uuid, - - @XmlElement(name = "published") - OffsetDateTime published, - - @XmlElement(name = "updated") - OffsetDateTime updated, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - @XmlElementWrapper(name = "links", namespace = "http://www.w3.org/2005/Atom") - List relatedLinks, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - LinkDto selfLink, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - LinkDto upLink, - - @XmlElement(name = "description") - String description, - + @XmlElement(name = "cost") Long cost, - + @XmlElement(name = "currency") Integer currency, - + @XmlElement(name = "value") Long value, - + @XmlElement(name = "timePeriod") DateTimeIntervalDto timePeriod, - + @XmlElement(name = "ReadingQuality") @XmlElementWrapper(name = "ReadingQualities") List readingQualities, - + @XmlElement(name = "consumptionTier") Integer consumptionTier, - + @XmlElement(name = "tou") Integer tou, - + @XmlElement(name = "cpp") Integer cpp ) { - + /** * Default constructor for JAXB. */ public IntervalReadingDto() { - this(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + this(null, null, null, null, null, null, null, null); } - + /** * Minimal constructor for basic interval reading data. */ - public IntervalReadingDto(String uuid, Long value, DateTimeIntervalDto timePeriod) { - this(uuid, null, null, null, null, null, null, null, null, value, timePeriod, null, null, null, null); + public IntervalReadingDto(Long value, DateTimeIntervalDto timePeriod) { + this(null, null, value, timePeriod, null, null, null, null); } - + /** * Constructor for interval reading with cost information. */ - public IntervalReadingDto(String uuid, Long value, Long cost, Integer currency, DateTimeIntervalDto timePeriod) { - this(uuid, null, null, null, null, null, null, cost, currency, value, timePeriod, null, null, null, null); + public IntervalReadingDto(Long value, Long cost, Integer currency, DateTimeIntervalDto timePeriod) { + this(cost, currency, value, timePeriod, null, null, null, null); } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/ReadingQualityDto.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/ReadingQualityDto.java index 3c03762d..cce47b99 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/ReadingQualityDto.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/ReadingQualityDto.java @@ -19,70 +19,32 @@ package org.greenbuttonalliance.espi.common.dto.usage; -import org.greenbuttonalliance.espi.common.dto.atom.LinkDto; - import jakarta.xml.bind.annotation.*; -import java.time.OffsetDateTime; -import java.util.List; /** * ReadingQuality DTO record for JAXB XML marshalling/unmarshalling. - * + * * Represents quality indicators for readings, providing information about * the accuracy, validation status, and reliability of meter readings. + * + * Note: ReadingQuality does NOT extend IdentifiedObject per ESPI 4.0 specification. + * It is not a top-level resource with selfLink/upLink/relatedLinks. */ @XmlRootElement(name = "ReadingQuality", namespace = "http://naesb.org/espi") @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "ReadingQuality", namespace = "http://naesb.org/espi", propOrder = { - "uuid", "published", "updated", "selfLink", "upLink", "relatedLinks", - "description", "quality" + "quality" }) public record ReadingQualityDto( - - @XmlAttribute(name = "mRID") - String uuid, - - @XmlElement(name = "published") - OffsetDateTime published, - - @XmlElement(name = "updated") - OffsetDateTime updated, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - @XmlElementWrapper(name = "links", namespace = "http://www.w3.org/2005/Atom") - List relatedLinks, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - LinkDto selfLink, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - LinkDto upLink, - - @XmlElement(name = "description") - String description, - + @XmlElement(name = "quality") String quality ) { - + /** * Default constructor for JAXB. */ public ReadingQualityDto() { - this(null, null, null, null, null, null, null, null); - } - - /** - * Minimal constructor for basic reading quality data. - */ - public ReadingQualityDto(String uuid, String quality) { - this(uuid, null, null, null, null, null, null, quality); - } - - /** - * Constructor with description. - */ - public ReadingQualityDto(String uuid, String description, String quality) { - this(uuid, null, null, null, null, null, description, quality); + this(null); } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/AggregatedNodeRefMapper.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/AggregatedNodeRefMapper.java index ce6c60f1..d8f44617 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/AggregatedNodeRefMapper.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/AggregatedNodeRefMapper.java @@ -21,7 +21,6 @@ import org.greenbuttonalliance.espi.common.domain.usage.AggregatedNodeRefEntity; import org.greenbuttonalliance.espi.common.dto.usage.AggregatedNodeRefDto; -import org.greenbuttonalliance.espi.common.mapper.BaseIdentifiedObjectMapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; @@ -30,7 +29,7 @@ * MapStruct mapper for converting between AggregatedNodeRefEntity and AggregatedNodeRefDto. */ @Mapper(componentModel = "spring", uses = {PnodeRefMapper.class}) -public interface AggregatedNodeRefMapper extends BaseIdentifiedObjectMapper { +public interface AggregatedNodeRefMapper { /** * Converts an AggregatedNodeRefEntity to an AggregatedNodeRefDto. @@ -47,19 +46,12 @@ public interface AggregatedNodeRefMapper extends BaseIdentifiedObjectMapper { /** * Converts an AggregatedNodeRefDto to an AggregatedNodeRefEntity. - * + * * @param dto the aggregated node reference DTO * @return the aggregated node reference entity */ @Mapping(target = "id", ignore = true) @Mapping(target = "usagePoint", ignore = true) - @Mapping(target = "description", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "created", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "updated", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "published", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "upLink", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "selfLink", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "relatedLinks", ignore = true) // Inherited from IdentifiedObject @Mapping(target = "anodeType", source = "anodeType") @Mapping(target = "ref", source = "ref") @Mapping(target = "startEffectiveDate", source = "startEffectiveDate") @@ -69,18 +61,11 @@ public interface AggregatedNodeRefMapper extends BaseIdentifiedObjectMapper { /** * Updates an existing AggregatedNodeRefEntity with data from an AggregatedNodeRefDto. - * + * * @param dto the source DTO * @param entity the target entity to update */ @Mapping(target = "id", ignore = true) @Mapping(target = "usagePoint", ignore = true) - @Mapping(target = "description", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "created", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "updated", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "published", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "upLink", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "selfLink", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "relatedLinks", ignore = true) // Inherited from IdentifiedObject void updateEntity(AggregatedNodeRefDto dto, @MappingTarget AggregatedNodeRefEntity entity); } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/IntervalReadingMapper.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/IntervalReadingMapper.java index b51ddb8f..d5b54f08 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/IntervalReadingMapper.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/IntervalReadingMapper.java @@ -27,8 +27,8 @@ /** * MapStruct mapper for converting between IntervalReadingEntity and IntervalReadingDto. - * - * Handles the conversion between the JPA entity used for persistence and the DTO + * + * Handles the conversion between the JPA entity used for persistence and the DTO * used for JAXB XML marshalling in the Green Button API. */ @Mapper(componentModel = "spring", uses = { @@ -40,19 +40,12 @@ public interface IntervalReadingMapper { /** * Converts an IntervalReadingEntity to an IntervalReadingDto. * Maps all related entities to their corresponding DTOs. - * + * * @param entity the interval reading entity * @return the interval reading DTO */ - @Mapping(target = "uuid", ignore = true) // IntervalReading does not have UUID - @Mapping(target = "published", ignore = true) // IntervalReading does not have timestamps - @Mapping(target = "updated", ignore = true) // IntervalReading does not have timestamps - @Mapping(target = "relatedLinks", ignore = true) // Links handled separately - @Mapping(target = "selfLink", ignore = true) - @Mapping(target = "upLink", ignore = true) - @Mapping(target = "description", ignore = true) // IntervalReading does not have description @Mapping(target = "cost", source = "cost") - @Mapping(target = "currency", ignore = true) // IntervalReading does not have currency + @Mapping(target = "currency", ignore = true) @Mapping(target = "value", source = "value") @Mapping(target = "timePeriod", source = "timePeriod") @Mapping(target = "readingQualities", source = "readingQualities") @@ -64,18 +57,11 @@ public interface IntervalReadingMapper { /** * Converts an IntervalReadingDto to an IntervalReadingEntity. * Maps all related DTOs to their corresponding entities. - * + * * @param dto the interval reading DTO * @return the interval reading entity */ @Mapping(target = "id", ignore = true) - @Mapping(target = "description", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "created", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "updated", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "published", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "upLink", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "selfLink", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "relatedLinks", ignore = true) // Inherited from IdentifiedObject @Mapping(target = "cost", source = "cost") @Mapping(target = "value", source = "value") @Mapping(target = "timePeriod", source = "timePeriod") @@ -83,24 +69,17 @@ public interface IntervalReadingMapper { @Mapping(target = "consumptionTier", source = "consumptionTier") @Mapping(target = "tou", source = "tou") @Mapping(target = "cpp", source = "cpp") - @Mapping(target = "intervalBlock", ignore = true) // Relationships handled separately + @Mapping(target = "intervalBlock", ignore = true) IntervalReadingEntity toEntity(IntervalReadingDto dto); /** * Updates an existing IntervalReadingEntity with data from an IntervalReadingDto. * Useful for merge operations where the entity ID should be preserved. - * + * * @param dto the source DTO * @param entity the target entity to update */ @Mapping(target = "id", ignore = true) - @Mapping(target = "description", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "created", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "updated", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "published", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "upLink", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "selfLink", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "relatedLinks", ignore = true) // Inherited from IdentifiedObject - @Mapping(target = "intervalBlock", ignore = true) // Relationships handled separately + @Mapping(target = "intervalBlock", ignore = true) void updateEntity(IntervalReadingDto dto, @MappingTarget IntervalReadingEntity entity); } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/ReadingQualityMapper.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/ReadingQualityMapper.java index b9cea790..44d2888b 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/ReadingQualityMapper.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/ReadingQualityMapper.java @@ -21,15 +21,14 @@ import org.greenbuttonalliance.espi.common.domain.usage.ReadingQualityEntity; import org.greenbuttonalliance.espi.common.dto.usage.ReadingQualityDto; -import org.greenbuttonalliance.espi.common.mapper.BaseIdentifiedObjectMapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; /** * MapStruct mapper for converting between ReadingQualityEntity and ReadingQualityDto. - * - * Handles the conversion between the JPA entity used for persistence and the DTO + * + * Handles the conversion between the JPA entity used for persistence and the DTO * used for JAXB XML marshalling in the Green Button API. */ @Mapper(componentModel = "spring") @@ -38,46 +37,33 @@ public interface ReadingQualityMapper { /** * Converts a ReadingQualityEntity to a ReadingQualityDto. * Maps quality indicators and validation information. - * + * * @param entity the reading quality entity * @return the reading quality DTO */ - @Mapping(target = "uuid", ignore = true) // ReadingQuality does not have UUID - @Mapping(target = "published", ignore = true) // ReadingQuality does not have timestamps - @Mapping(target = "updated", ignore = true) // ReadingQuality does not have timestamps - @Mapping(target = "relatedLinks", ignore = true) // Links handled separately - @Mapping(target = "selfLink", ignore = true) - @Mapping(target = "upLink", ignore = true) - @Mapping(target = "description", ignore = true) // ReadingQuality does not have description @Mapping(target = "quality", source = "quality") ReadingQualityDto toDto(ReadingQualityEntity entity); /** * Converts a ReadingQualityDto to a ReadingQualityEntity. * Maps quality indicators and validation information. - * + * * @param dto the reading quality DTO * @return the reading quality entity */ @Mapping(target = "id", ignore = true) - @Mapping(target = "created", ignore = true) - @Mapping(target = "updated", ignore = true) - @Mapping(target = "published", ignore = true) @Mapping(target = "quality", source = "quality") - @Mapping(target = "intervalReading", ignore = true) // Relationships handled separately + @Mapping(target = "intervalReading", ignore = true) ReadingQualityEntity toEntity(ReadingQualityDto dto); /** * Updates an existing ReadingQualityEntity with data from a ReadingQualityDto. * Useful for merge operations where the entity ID should be preserved. - * + * * @param dto the source DTO * @param entity the target entity to update */ @Mapping(target = "id", ignore = true) - @Mapping(target = "created", ignore = true) - @Mapping(target = "updated", ignore = true) - @Mapping(target = "published", ignore = true) - @Mapping(target = "intervalReading", ignore = true) // Relationships handled separately + @Mapping(target = "intervalReading", ignore = true) void updateEntity(ReadingQualityDto dto, @MappingTarget ReadingQualityEntity entity); } \ No newline at end of file diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/migration/DataCustodianApplicationPostgresTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/migration/DataCustodianApplicationPostgresTest.java index f45d7d9e..b05d0844 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/migration/DataCustodianApplicationPostgresTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/migration/DataCustodianApplicationPostgresTest.java @@ -20,6 +20,7 @@ package org.greenbuttonalliance.espi.common.migration; import org.greenbuttonalliance.espi.common.TestApplication; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -39,7 +40,7 @@ /** * Integration test for the OpenESPI Data Custodian Spring Boot application with PostgreSQL Test Container. - * + * * This test verifies that the application context loads successfully with a real PostgreSQL database * running in a Docker container, and that Flyway migrations execute correctly with the new * vendor-specific migration structure. @@ -48,6 +49,11 @@ @ActiveProfiles("test-postgres") @Testcontainers @DisplayName("PostgreSQL Test Container Integration Tests") +@Disabled("Temporarily disabled due to Issue #53: PostgreSQL UUID CHAR(36) type mismatch. " + + "JPA entities use @GeneratedValue(strategy = GenerationType.UUID) expecting native UUID type, " + + "but Flyway migrations use CHAR(36) for MySQL/H2 compatibility. " + + "This will be resolved after MULTI_PHASE schema compliance plan completes. " + + "See: https://github.com/GreenButtonAlliance/OpenESPI-GreenButton-Java/issues/53") class DataCustodianApplicationPostgresTest { @Container diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/BatchListRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/BatchListRepositoryTest.java index 14228c6a..21e91046 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/BatchListRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/BatchListRepositoryTest.java @@ -18,6 +18,7 @@ package org.greenbuttonalliance.espi.common.repositories.usage; +import jakarta.validation.ConstraintViolation; import org.greenbuttonalliance.espi.common.domain.usage.BatchListEntity; import org.greenbuttonalliance.espi.common.test.BaseRepositoryTest; import org.junit.jupiter.api.DisplayName; @@ -25,19 +26,20 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import jakarta.validation.ConstraintViolation; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.Set; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; /** * Comprehensive test suite for BatchListRepository. - * + * * Tests CRUD operations, validation constraints, and entity behavior * for BatchList entities. + * + * Note: BatchListEntity does NOT extend IdentifiedObject per ESPI 4.0 specification. */ @DisplayName("BatchList Repository Tests") class BatchListRepositoryTest extends BaseRepositoryTest { @@ -53,8 +55,7 @@ class CrudOperationsTest { @DisplayName("Should save and retrieve batch list successfully") void shouldSaveAndRetrieveBatchListSuccessfully() { // Arrange - BatchListEntity batchList = new BatchListEntity("Test Batch List"); - batchList.setId(java.util.UUID.randomUUID()); // Set required UUID id + BatchListEntity batchList = new BatchListEntity(); batchList.addResource("/espi/1_1/resource/UsagePoint/1"); batchList.addResource("/espi/1_1/resource/MeterReading/1"); @@ -67,10 +68,8 @@ void shouldSaveAndRetrieveBatchListSuccessfully() { assertThat(saved).isNotNull(); assertThat(saved.getId()).isNotNull(); assertThat(retrieved).isPresent(); - assertThat(retrieved.get().getDescription()).isEqualTo("Test Batch List"); assertThat(retrieved.get().getResources()).hasSize(2); - // Test the actual size instead of cached resourceCount since it might not be updated correctly - assertThat(retrieved.get().getResources().size()).isEqualTo(2); + assertThat(retrieved.get().getResourceCount()).isEqualTo(2); } @Test @@ -83,8 +82,7 @@ void shouldSaveBatchListWithMultipleResources() { "/espi/1_1/resource/MeterReading/1", "/espi/1_1/resource/IntervalBlock/1" ); - BatchListEntity batchList = new BatchListEntity(resources, "Multi-Resource Batch"); - batchList.setId(java.util.UUID.randomUUID()); // Set required UUID id + BatchListEntity batchList = new BatchListEntity(resources); // Act BatchListEntity saved = batchListRepository.save(batchList); @@ -94,8 +92,7 @@ void shouldSaveBatchListWithMultipleResources() { // Assert assertThat(retrieved).isPresent(); assertThat(retrieved.get().getResources()).hasSize(4); - // Test the actual size instead of cached resourceCount since it might not be updated correctly - assertThat(retrieved.get().getResources().size()).isEqualTo(4); + assertThat(retrieved.get().getResourceCount()).isEqualTo(4); assertThat(retrieved.get().getResources()).containsExactlyInAnyOrderElementsOf(resources); } @@ -103,10 +100,10 @@ void shouldSaveBatchListWithMultipleResources() { @DisplayName("Should find all batch lists") void shouldFindAllBatchLists() { // Arrange - BatchListEntity batchList1 = new BatchListEntity("Batch List 1"); - batchList1.setId(java.util.UUID.randomUUID()); // Set required UUID id - BatchListEntity batchList2 = new BatchListEntity("Batch List 2"); - batchList2.setId(java.util.UUID.randomUUID()); // Set required UUID id + BatchListEntity batchList1 = new BatchListEntity(); + batchList1.addResource("/espi/1_1/resource/UsagePoint/1"); + BatchListEntity batchList2 = new BatchListEntity(); + batchList2.addResource("/espi/1_1/resource/UsagePoint/2"); batchListRepository.saveAll(Arrays.asList(batchList1, batchList2)); flushAndClear(); @@ -115,16 +112,16 @@ void shouldFindAllBatchLists() { // Assert assertThat(allBatchLists).hasSizeGreaterThanOrEqualTo(2); - assertThat(allBatchLists).extracting(BatchListEntity::getDescription) - .contains("Batch List 1", "Batch List 2"); + assertThat(allBatchLists).extracting(BatchListEntity::getResourceCount) + .contains(1, 1); } @Test @DisplayName("Should delete batch list successfully") void shouldDeleteBatchListSuccessfully() { // Arrange - BatchListEntity batchList = new BatchListEntity("To Be Deleted"); - batchList.setId(java.util.UUID.randomUUID()); // Set required UUID id + BatchListEntity batchList = new BatchListEntity(); + batchList.addResource("/espi/1_1/resource/UsagePoint/1"); BatchListEntity saved = batchListRepository.save(batchList); flushAndClear(); @@ -142,10 +139,10 @@ void shouldDeleteBatchListSuccessfully() { void shouldCountBatchListsCorrectly() { // Arrange long initialCount = batchListRepository.count(); - BatchListEntity batchList1 = new BatchListEntity("Count Test 1"); - batchList1.setId(java.util.UUID.randomUUID()); // Set required UUID id - BatchListEntity batchList2 = new BatchListEntity("Count Test 2"); - batchList2.setId(java.util.UUID.randomUUID()); // Set required UUID id + BatchListEntity batchList1 = new BatchListEntity(); + batchList1.addResource("/espi/1_1/resource/UsagePoint/1"); + BatchListEntity batchList2 = new BatchListEntity(); + batchList2.addResource("/espi/1_1/resource/UsagePoint/2"); batchListRepository.saveAll(Arrays.asList(batchList1, batchList2)); flushAndClear(); @@ -165,22 +162,21 @@ class ValidationTest { @DisplayName("Should validate resource URI constraints") void shouldValidateResourceUriConstraints() { // Arrange - BatchListEntity batchList = new BatchListEntity("Validation Test"); - batchList.setId(java.util.UUID.randomUUID()); // Set required UUID id - + BatchListEntity batchList = new BatchListEntity(); + // Create a URI that definitely exceeds 512 characters String baseUri = "/espi/1_1/resource/UsagePoint/"; String longSuffix = "x".repeat(600); // Create a very long suffix String longUri = baseUri + longSuffix; // This will be > 512 chars - + // Verify the URI is actually longer than 512 characters assertThat(longUri.length()).isGreaterThan(512); - + batchList.addResource(longUri); // Act & Assert Set> violations = validator.validate(batchList); - + // If validation on collection elements doesn't work, test with a simpler approach if (violations.isEmpty()) { // Alternative: test that the entity can be created but might fail on persistence @@ -196,8 +192,7 @@ void shouldValidateResourceUriConstraints() { @DisplayName("Should handle empty resource list") void shouldHandleEmptyResourceList() { // Arrange - BatchListEntity batchList = new BatchListEntity("Empty Resources"); - batchList.setId(java.util.UUID.randomUUID()); // Set required UUID id + BatchListEntity batchList = new BatchListEntity(); // Act BatchListEntity saved = batchListRepository.save(batchList); @@ -207,20 +202,20 @@ void shouldHandleEmptyResourceList() { // Assert assertThat(retrieved).isPresent(); assertThat(retrieved.get().getResources()).isEmpty(); - assertThat(retrieved.get().getResourceCount()).isEqualTo(0); + assertThat(retrieved.get().getResourceCount()).isZero(); } } @Nested - @DisplayName("Base Class Functionality") - class BaseClassTest { + @DisplayName("Entity Functionality") + class EntityFunctionalityTest { @Test - @DisplayName("Should inherit IdentifiedObject functionality") - void shouldInheritIdentifiedObjectFunctionality() { + @DisplayName("Should persist with auto-generated UUID") + void shouldPersistWithAutoGeneratedUuid() { // Arrange - BatchListEntity batchList = new BatchListEntity("Base Class Test"); - batchList.setId(java.util.UUID.randomUUID()); // Set required UUID id + BatchListEntity batchList = new BatchListEntity(); + batchList.addResource("/espi/1_1/resource/UsagePoint/1"); // Act BatchListEntity saved = batchListRepository.save(batchList); @@ -228,8 +223,7 @@ void shouldInheritIdentifiedObjectFunctionality() { // Assert assertThat(saved.getId()).isNotNull(); - assertThat(saved.getCreated()).isNotNull(); - assertThat(saved.getUpdated()).isNotNull(); + assertThat(saved.getResourceCount()).isEqualTo(1); } } -} \ No newline at end of file +} diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/LineItemRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/LineItemRepositoryTest.java index 893fb7d9..6ea198dd 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/LineItemRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/LineItemRepositoryTest.java @@ -18,6 +18,7 @@ package org.greenbuttonalliance.espi.common.repositories.usage; +import jakarta.validation.ConstraintViolation; import org.greenbuttonalliance.espi.common.domain.usage.LineItemEntity; import org.greenbuttonalliance.espi.common.domain.usage.UsageSummaryEntity; import org.greenbuttonalliance.espi.common.test.BaseRepositoryTest; @@ -26,13 +27,12 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import jakarta.validation.ConstraintViolation; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; /** * Comprehensive test suite for LineItemRepository. @@ -342,14 +342,14 @@ void shouldValidateNoteLengthConstraint() { } @Nested - @DisplayName("Base Class Functionality") - class BaseClassTest { + @DisplayName("Entity Functionality") + class EntityFunctionalityTest { @Test - @DisplayName("Should inherit IdentifiedObject functionality") - void shouldInheritIdentifiedObjectFunctionality() { + @DisplayName("Should persist with auto-generated UUID") + void shouldPersistWithAutoGeneratedUuid() { // Arrange - LineItemEntity lineItem = new LineItemEntity(1000L, 1640995200L, "Base class test"); + LineItemEntity lineItem = new LineItemEntity(1000L, 1640995200L, "UUID test"); // Act LineItemEntity saved = lineItemRepository.save(lineItem); @@ -357,8 +357,8 @@ void shouldInheritIdentifiedObjectFunctionality() { // Assert assertThat(saved.getId()).isNotNull(); - assertThat(saved.getCreated()).isNotNull(); - assertThat(saved.getUpdated()).isNotNull(); + assertThat(saved.getAmount()).isEqualTo(1000L); + assertThat(saved.getNote()).isEqualTo("UUID test"); } } } \ No newline at end of file diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java index 5e583f64..34a5e4bd 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java @@ -21,10 +21,10 @@ import org.greenbuttonalliance.espi.common.domain.common.DateTimeInterval; import org.greenbuttonalliance.espi.common.domain.common.ServiceCategory; import org.greenbuttonalliance.espi.common.domain.common.SummaryMeasurement; -import org.greenbuttonalliance.espi.common.domain.usage.UsageSummaryEntity; -import org.greenbuttonalliance.espi.common.domain.usage.UsagePointEntity; -import org.greenbuttonalliance.espi.common.domain.usage.RetailCustomerEntity; import org.greenbuttonalliance.espi.common.domain.usage.LineItemEntity; +import org.greenbuttonalliance.espi.common.domain.usage.RetailCustomerEntity; +import org.greenbuttonalliance.espi.common.domain.usage.UsagePointEntity; +import org.greenbuttonalliance.espi.common.domain.usage.UsageSummaryEntity; import org.greenbuttonalliance.espi.common.test.BaseRepositoryTest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -35,7 +35,7 @@ import java.util.Optional; import java.util.UUID; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; /** * Comprehensive test suite for UsageSummaryRepository. @@ -298,8 +298,9 @@ void shouldFindAllIdsByUsagePointId() { List ids = usageSummaryRepository.findAllIdsByUsagePointId(summary1.getUsagePoint().getId()); // Assert - assertThat(ids).hasSize(2); - assertThat(ids).contains(saved1.getId(), saved2.getId()); + assertThat(ids) + .hasSize(2) + .contains(saved1.getId(), saved2.getId()); } @Test @@ -334,8 +335,9 @@ void shouldFindIdByXpath() { Optional foundId = usageSummaryRepository.findIdByXpath(retailCustomerId, usagePointId, summaryId); // Assert - assertThat(foundId).isPresent(); - assertThat(foundId.get()).isEqualTo(summaryId); + assertThat(foundId) + .isPresent() + .contains(summaryId); } } @@ -467,13 +469,11 @@ void shouldManageLineItemCollectionCorrectly() { UsageSummaryEntity saved = persistAndFlush(summary); LineItemEntity lineItem1 = new LineItemEntity(); - lineItem1.setDescription("Test Line Item 1"); lineItem1.setAmount(1000L); lineItem1.setDateTime(randomOffsetDateTime().toEpochSecond()); lineItem1.setNote("Additional charge 1"); LineItemEntity lineItem2 = new LineItemEntity(); - lineItem2.setDescription("Test Line Item 2"); lineItem2.setAmount(2000L); lineItem2.setDateTime(randomOffsetDateTime().toEpochSecond()); lineItem2.setNote("Additional charge 2"); @@ -525,13 +525,6 @@ void shouldUpdateTimestampsOnModification() { // Arrange UsageSummaryEntity summary = createCompleteTestSetup(); UsageSummaryEntity saved = persistAndFlush(summary); - - // Wait a moment to ensure timestamp difference - try { - Thread.sleep(10); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } // Act saved.setDescription("Updated Description"); diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/test/TestDataBuilders.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/test/TestDataBuilders.java index f4436179..5a813793 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/test/TestDataBuilders.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/test/TestDataBuilders.java @@ -139,8 +139,8 @@ public static IntervalBlockEntity createValidIntervalBlockWithMeterReading(Meter */ public static IntervalReadingEntity createValidIntervalReading() { IntervalReadingEntity intervalReading = new IntervalReadingEntity(); - intervalReading.setCost(faker.number().randomNumber(6, true)); - intervalReading.setValue(faker.number().randomNumber(5, true)); + intervalReading.setCost(faker.number().numberBetween(100000L, 999999L)); + intervalReading.setValue(faker.number().numberBetween(10000L, 99999L)); // Add basic DateTimeInterval for time period DateTimeInterval timePeriod = new DateTimeInterval(); @@ -219,7 +219,8 @@ public static RetailCustomerEntity createValidRetailCustomer() { * Creates a valid BatchListEntity for testing. */ public static BatchListEntity createValidBatchList() { - BatchListEntity batchList = new BatchListEntity(faker.lorem().sentence(3, 6)); + BatchListEntity batchList = new BatchListEntity(); + batchList.addResource("/espi/1_1/resource/UsagePoint/" + faker.number().numberBetween(1, 1000)); return batchList; } @@ -267,8 +268,8 @@ public static ApplicationInformationEntity createValidApplicationInformation() { clientId = clientId.substring(0, 64); } app.setClientId(clientId); - - app.setClientSecret(faker.internet().password()); + + app.setClientSecret(faker.regexify("[a-zA-Z0-9]{16,32}")); // Ensure dataCustodianId meets validation constraints (2-64 chars if present) String dataCustodianId = "test-datacustodian-" + faker.number().digits(6);