Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit f099054

Browse files
dfcoffinclaude
andcommitted
Fix MapStruct compilation issues with systematic BaseIdentifiedObjectMapper pattern
- Create BaseIdentifiedObjectMapper with common UUID and DateTime conversion methods - Fix UUID mapping issues: entities compute UUID from hashedId, DTOs expect string UUID - Add DateTime conversion methods: LocalDateTime ↔ OffsetDateTime with UTC timezone handling - Update ElectricPowerQualitySummaryMapper to use base mapper pattern with proper qualifiedByName mappings - Establish systematic pattern for entity-to-DTO and DTO-to-entity conversions - Handle relationship mappings properly: ignore complex entity relationships, use entityToId for simple ID references - Provide foundation for fixing remaining MapStruct compilation errors across all mappers - Ensure ESPI-compliant UUID generation and timezone-aware datetime conversions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 39e225d commit f099054

File tree

10 files changed

+281
-150
lines changed

10 files changed

+281
-150
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
*
3+
* Copyright (c) 2018-2025 Green Button Alliance, Inc.
4+
*
5+
* Portions (c) 2013-2018 EnergyOS.org
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
package org.greenbuttonalliance.espi.common.mapper;
22+
23+
import org.mapstruct.Named;
24+
25+
import java.time.LocalDateTime;
26+
import java.time.OffsetDateTime;
27+
import java.time.ZoneOffset;
28+
29+
/**
30+
* Base mapper interface providing common mapping methods for IdentifiedObject conversions.
31+
*
32+
* This mapper provides standard UUID and DateTime conversion methods that are used
33+
* across all entity-to-DTO mappings in the OpenESPI system. It ensures consistent
34+
* handling of ESPI-compliant UUID generation and timezone-aware datetime conversions.
35+
*/
36+
public interface BaseIdentifiedObjectMapper {
37+
38+
/**
39+
* Converts entity UUID (calculated from hashedId) to DTO UUID string.
40+
* Entities use computed UUIDs based on hashedId for ESPI compliance.
41+
*
42+
* @param entity any IdentifiedObject entity with getUuid() method
43+
* @return UUID string for the DTO, or null if entity is null
44+
*/
45+
@Named("entityUuidToString")
46+
default String entityUuidToString(Object entity) {
47+
if (entity == null) {
48+
return null;
49+
}
50+
51+
try {
52+
// Use reflection to call getUuid() method on any IdentifiedObject entity
53+
var method = entity.getClass().getMethod("getUuid");
54+
Object result = method.invoke(entity);
55+
return result != null ? result.toString() : null;
56+
} catch (Exception e) {
57+
// Fallback to null if reflection fails
58+
return null;
59+
}
60+
}
61+
62+
/**
63+
* Converts LocalDateTime from entity to OffsetDateTime for DTO.
64+
* Assumes UTC timezone for ESPI compliance.
65+
*
66+
* @param localDateTime the entity's LocalDateTime
67+
* @return OffsetDateTime with UTC offset, or null if input is null
68+
*/
69+
@Named("localDateTimeToOffsetDateTime")
70+
default OffsetDateTime localDateTimeToOffsetDateTime(LocalDateTime localDateTime) {
71+
return localDateTime != null ? localDateTime.atOffset(ZoneOffset.UTC) : null;
72+
}
73+
74+
/**
75+
* Converts OffsetDateTime from DTO to LocalDateTime for entity.
76+
* Converts to UTC and strips offset information.
77+
*
78+
* @param offsetDateTime the DTO's OffsetDateTime
79+
* @return LocalDateTime in UTC, or null if input is null
80+
*/
81+
@Named("offsetDateTimeToLocalDateTime")
82+
default LocalDateTime offsetDateTimeToLocalDateTime(OffsetDateTime offsetDateTime) {
83+
return offsetDateTime != null ? offsetDateTime.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime() : null;
84+
}
85+
86+
/**
87+
* Maps Long value to Long value (pass-through for ID mappings).
88+
* Used for explicit mapping of relationship IDs.
89+
*
90+
* @param value the source Long value
91+
* @return the same Long value
92+
*/
93+
@Named("longToLong")
94+
default Long longToLong(Long value) {
95+
return value;
96+
}
97+
98+
/**
99+
* Maps String value to String value (pass-through for string mappings).
100+
* Used for explicit mapping of string properties.
101+
*
102+
* @param value the source String value
103+
* @return the same String value
104+
*/
105+
@Named("stringToString")
106+
default String stringToString(String value) {
107+
return value;
108+
}
109+
110+
/**
111+
* Maps Boolean value to Boolean value (pass-through for boolean mappings).
112+
* Used for explicit mapping of boolean properties.
113+
*
114+
* @param value the source Boolean value
115+
* @return the same Boolean value
116+
*/
117+
@Named("booleanToBoolean")
118+
default Boolean booleanToBoolean(Boolean value) {
119+
return value;
120+
}
121+
122+
/**
123+
* Extracts relationship ID from entity for DTO mapping.
124+
* Used for mapping entity relationships to simple ID references in DTOs.
125+
*
126+
* @param entity any entity object with getId() method
127+
* @return the entity's ID, or null if entity is null
128+
*/
129+
@Named("entityToId")
130+
default Long entityToId(Object entity) {
131+
if (entity == null) {
132+
return null;
133+
}
134+
135+
try {
136+
// Use reflection to call getId() method on any entity
137+
var method = entity.getClass().getMethod("getId");
138+
Object result = method.invoke(entity);
139+
return result instanceof Long ? (Long) result : null;
140+
} catch (Exception e) {
141+
// Fallback to null if reflection fails
142+
return null;
143+
}
144+
}
145+
146+
/**
147+
* Placeholder for ID to entity mapping (requires repository lookup).
148+
* This method returns null and should be overridden by specific mappers
149+
* that have access to repository services for entity lookup.
150+
*
151+
* @param id the entity ID
152+
* @return null (to be overridden by specific mappers)
153+
*/
154+
@Named("idToEntity")
155+
default Object idToEntity(Long id) {
156+
// This is a placeholder - specific mappers should override this
157+
// with repository lookups for their specific entity types
158+
return null;
159+
}
160+
}

src/main/java/org/greenbuttonalliance/espi/common/mapper/customer/CustomerAccountMapper.java

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.greenbuttonalliance.espi.common.domain.customer.entity.CustomerAccountEntity;
2424
import org.greenbuttonalliance.espi.common.dto.customer.CustomerAccountDto;
25+
import org.greenbuttonalliance.espi.common.mapper.BaseIdentifiedObjectMapper;
2526
import org.mapstruct.Mapper;
2627
import org.mapstruct.Mapping;
2728
import org.mapstruct.MappingTarget;
@@ -33,7 +34,7 @@
3334
* used for JAXB XML marshalling in the Green Button API.
3435
*/
3536
@Mapper(componentModel = "spring")
36-
public interface CustomerAccountMapper {
37+
public interface CustomerAccountMapper extends BaseIdentifiedObjectMapper {
3738

3839
/**
3940
* Converts a CustomerAccountEntity to a CustomerAccountDto.
@@ -42,23 +43,21 @@ public interface CustomerAccountMapper {
4243
* @param entity the customer account entity
4344
* @return the customer account DTO
4445
*/
45-
@Mapping(target = "uuid", source = "uuid")
46-
@Mapping(target = "published", source = "published")
47-
@Mapping(target = "updated", source = "updated")
46+
@Mapping(target = "uuid", source = "entity", qualifiedByName = "entityUuidToString")
47+
@Mapping(target = "published", source = "published", qualifiedByName = "localDateTimeToOffsetDateTime")
48+
@Mapping(target = "updated", source = "updated", qualifiedByName = "localDateTimeToOffsetDateTime")
4849
@Mapping(target = "relatedLinks", ignore = true) // Links handled separately
4950
@Mapping(target = "selfLink", ignore = true)
5051
@Mapping(target = "upLink", ignore = true)
5152
@Mapping(target = "description", source = "description")
5253
@Mapping(target = "accountId", source = "accountId")
54+
@Mapping(target = "accountNumber", ignore = true) // Not in entity
55+
@Mapping(target = "budgetBill", source = "budgetBill")
5356
@Mapping(target = "billingCycle", source = "billingCycle")
54-
@Mapping(target = "branchCode", source = "branchCode")
55-
@Mapping(target = "currency", source = "currency")
56-
@Mapping(target = "customerName", source = "customerName")
57-
@Mapping(target = "isPrePay", source = "isPrePay")
58-
@Mapping(target = "isResidential", source = "isResidential")
59-
@Mapping(target = "isTaxExempt", source = "isTaxExempt")
6057
@Mapping(target = "lastBillAmount", source = "lastBillAmount")
61-
@Mapping(target = "outstandingBalance", source = "outstandingBalance")
58+
@Mapping(target = "transactionDate", ignore = true) // Not in entity
59+
@Mapping(target = "customer", ignore = true) // Relationship handled separately
60+
@Mapping(target = "customerAgreements", ignore = true) // Relationship handled separately
6261
CustomerAccountDto toDto(CustomerAccountEntity entity);
6362

6463
/**
@@ -69,21 +68,16 @@ public interface CustomerAccountMapper {
6968
* @return the customer account entity
7069
*/
7170
@Mapping(target = "id", ignore = true)
72-
@Mapping(target = "uuid", source = "uuid")
73-
@Mapping(target = "published", source = "published")
74-
@Mapping(target = "updated", source = "updated")
71+
@Mapping(target = "uuid", ignore = true) // UUID is computed from hashedId
72+
@Mapping(target = "published", source = "published", qualifiedByName = "offsetDateTimeToLocalDateTime")
73+
@Mapping(target = "updated", source = "updated", qualifiedByName = "offsetDateTimeToLocalDateTime")
7574
@Mapping(target = "description", source = "description")
7675
@Mapping(target = "accountId", source = "accountId")
76+
@Mapping(target = "budgetBill", source = "budgetBill")
7777
@Mapping(target = "billingCycle", source = "billingCycle")
78-
@Mapping(target = "branchCode", source = "branchCode")
79-
@Mapping(target = "currency", source = "currency")
80-
@Mapping(target = "customerName", source = "customerName")
81-
@Mapping(target = "isPrePay", source = "isPrePay")
82-
@Mapping(target = "isResidential", source = "isResidential")
83-
@Mapping(target = "isTaxExempt", source = "isTaxExempt")
8478
@Mapping(target = "lastBillAmount", source = "lastBillAmount")
85-
@Mapping(target = "outstandingBalance", source = "outstandingBalance")
86-
@Mapping(target = "customer", ignore = true)
79+
@Mapping(target = "notifications", ignore = true) // Relationship handled separately
80+
@Mapping(target = "contactInfo", ignore = true) // Relationship handled separately
8781
@Mapping(target = "relatedLinks", ignore = true)
8882
@Mapping(target = "selfLink", ignore = true)
8983
@Mapping(target = "upLink", ignore = true)
@@ -97,7 +91,11 @@ public interface CustomerAccountMapper {
9791
* @param entity the target entity to update
9892
*/
9993
@Mapping(target = "id", ignore = true)
100-
@Mapping(target = "customer", ignore = true)
94+
@Mapping(target = "uuid", ignore = true) // UUID is computed from hashedId
95+
@Mapping(target = "published", source = "published", qualifiedByName = "offsetDateTimeToLocalDateTime")
96+
@Mapping(target = "updated", source = "updated", qualifiedByName = "offsetDateTimeToLocalDateTime")
97+
@Mapping(target = "notifications", ignore = true) // Relationship handled separately
98+
@Mapping(target = "contactInfo", ignore = true) // Relationship handled separately
10199
@Mapping(target = "relatedLinks", ignore = true)
102100
@Mapping(target = "selfLink", ignore = true)
103101
@Mapping(target = "upLink", ignore = true)

src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/ElectricPowerQualitySummaryMapper.java

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.greenbuttonalliance.espi.common.domain.usage.ElectricPowerQualitySummaryEntity;
2424
import org.greenbuttonalliance.espi.common.dto.usage.ElectricPowerQualitySummaryDto;
25+
import org.greenbuttonalliance.espi.common.mapper.BaseIdentifiedObjectMapper;
2526
import org.mapstruct.Mapper;
2627
import org.mapstruct.Mapping;
2728
import org.mapstruct.MappingTarget;
@@ -35,7 +36,7 @@
3536
@Mapper(componentModel = "spring", uses = {
3637
DateTimeIntervalMapper.class
3738
})
38-
public interface ElectricPowerQualitySummaryMapper {
39+
public interface ElectricPowerQualitySummaryMapper extends BaseIdentifiedObjectMapper {
3940

4041
/**
4142
* Converts an ElectricPowerQualitySummaryEntity to an ElectricPowerQualitySummaryDto.
@@ -44,27 +45,8 @@ public interface ElectricPowerQualitySummaryMapper {
4445
* @param entity the electric power quality summary entity
4546
* @return the electric power quality summary DTO
4647
*/
47-
@Mapping(target = "uuid", source = "uuid")
48-
@Mapping(target = "published", source = "published")
49-
@Mapping(target = "updated", source = "updated")
50-
@Mapping(target = "relatedLinks", ignore = true) // Links handled separately
51-
@Mapping(target = "selfLink", ignore = true)
52-
@Mapping(target = "upLink", ignore = true)
53-
@Mapping(target = "description", source = "description")
54-
@Mapping(target = "flickerPlt", source = "flickerPlt")
55-
@Mapping(target = "flickerPst", source = "flickerPst")
56-
@Mapping(target = "harmonicVoltage", source = "harmonicVoltage")
57-
@Mapping(target = "longInterruptions", source = "longInterruptions")
58-
@Mapping(target = "mainsVoltage", source = "mainsVoltage")
59-
@Mapping(target = "measurementProtocol", source = "measurementProtocol")
60-
@Mapping(target = "powerFrequency", source = "powerFrequency")
61-
@Mapping(target = "rapidVoltageChanges", source = "rapidVoltageChanges")
62-
@Mapping(target = "shortInterruptions", source = "shortInterruptions")
63-
@Mapping(target = "summaryInterval", source = "summaryInterval")
64-
@Mapping(target = "supplyVoltageDips", source = "supplyVoltageDips")
65-
@Mapping(target = "supplyVoltageImbalance", source = "supplyVoltageImbalance")
66-
@Mapping(target = "supplyVoltageVariations", source = "supplyVoltageVariations")
67-
@Mapping(target = "tempOvervoltage", source = "tempOvervoltage")
48+
@Mapping(target = "uuid", source = "entity", qualifiedByName = "entityUuidToString")
49+
@Mapping(target = "usagePointId", source = "usagePoint", qualifiedByName = "entityToId")
6850
ElectricPowerQualitySummaryDto toDto(ElectricPowerQualitySummaryEntity entity);
6951

7052
/**
@@ -75,25 +57,10 @@ public interface ElectricPowerQualitySummaryMapper {
7557
* @return the electric power quality summary entity
7658
*/
7759
@Mapping(target = "id", ignore = true)
78-
@Mapping(target = "uuid", source = "uuid")
79-
@Mapping(target = "published", source = "published")
80-
@Mapping(target = "updated", source = "updated")
81-
@Mapping(target = "description", source = "description")
82-
@Mapping(target = "flickerPlt", source = "flickerPlt")
83-
@Mapping(target = "flickerPst", source = "flickerPst")
84-
@Mapping(target = "harmonicVoltage", source = "harmonicVoltage")
85-
@Mapping(target = "longInterruptions", source = "longInterruptions")
86-
@Mapping(target = "mainsVoltage", source = "mainsVoltage")
87-
@Mapping(target = "measurementProtocol", source = "measurementProtocol")
88-
@Mapping(target = "powerFrequency", source = "powerFrequency")
89-
@Mapping(target = "rapidVoltageChanges", source = "rapidVoltageChanges")
90-
@Mapping(target = "shortInterruptions", source = "shortInterruptions")
91-
@Mapping(target = "summaryInterval", source = "summaryInterval")
92-
@Mapping(target = "supplyVoltageDips", source = "supplyVoltageDips")
93-
@Mapping(target = "supplyVoltageImbalance", source = "supplyVoltageImbalance")
94-
@Mapping(target = "supplyVoltageVariations", source = "supplyVoltageVariations")
95-
@Mapping(target = "tempOvervoltage", source = "tempOvervoltage")
96-
@Mapping(target = "usagePoint", ignore = true)
60+
@Mapping(target = "uuid", ignore = true) // UUID is computed from hashedId
61+
@Mapping(target = "published", ignore = true)
62+
@Mapping(target = "updated", ignore = true)
63+
@Mapping(target = "usagePoint", ignore = true) // Relationships handled separately
9764
@Mapping(target = "relatedLinks", ignore = true)
9865
@Mapping(target = "selfLink", ignore = true)
9966
@Mapping(target = "upLink", ignore = true)
@@ -107,6 +74,9 @@ public interface ElectricPowerQualitySummaryMapper {
10774
* @param entity the target entity to update
10875
*/
10976
@Mapping(target = "id", ignore = true)
77+
@Mapping(target = "uuid", ignore = true) // UUID is computed from hashedId
78+
@Mapping(target = "published", ignore = true)
79+
@Mapping(target = "updated", ignore = true)
11080
@Mapping(target = "usagePoint", ignore = true)
11181
@Mapping(target = "relatedLinks", ignore = true)
11282
@Mapping(target = "selfLink", ignore = true)

src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/IntervalBlockMapper.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.greenbuttonalliance.espi.common.domain.usage.IntervalBlockEntity;
2424
import org.greenbuttonalliance.espi.common.dto.usage.IntervalBlockDto;
25+
import org.greenbuttonalliance.espi.common.mapper.BaseIdentifiedObjectMapper;
2526
import org.mapstruct.Mapper;
2627
import org.mapstruct.Mapping;
2728
import org.mapstruct.MappingTarget;
@@ -36,7 +37,7 @@
3637
IntervalReadingMapper.class,
3738
DateTimeIntervalMapper.class
3839
})
39-
public interface IntervalBlockMapper {
40+
public interface IntervalBlockMapper extends BaseIdentifiedObjectMapper {
4041

4142
/**
4243
* Converts an IntervalBlockEntity to an IntervalBlockDto.
@@ -45,9 +46,9 @@ public interface IntervalBlockMapper {
4546
* @param entity the interval block entity
4647
* @return the interval block DTO
4748
*/
48-
@Mapping(target = "uuid", source = "uuid")
49-
@Mapping(target = "published", source = "published")
50-
@Mapping(target = "updated", source = "updated")
49+
@Mapping(target = "uuid", source = "entity", qualifiedByName = "entityUuidToString")
50+
@Mapping(target = "published", source = "published", qualifiedByName = "localDateTimeToOffsetDateTime")
51+
@Mapping(target = "updated", source = "updated", qualifiedByName = "localDateTimeToOffsetDateTime")
5152
@Mapping(target = "relatedLinks", ignore = true) // Links handled separately
5253
@Mapping(target = "selfLink", ignore = true)
5354
@Mapping(target = "upLink", ignore = true)
@@ -64,9 +65,9 @@ public interface IntervalBlockMapper {
6465
* @return the interval block entity
6566
*/
6667
@Mapping(target = "id", ignore = true)
67-
@Mapping(target = "uuid", source = "uuid")
68-
@Mapping(target = "published", source = "published")
69-
@Mapping(target = "updated", source = "updated")
68+
@Mapping(target = "uuid", ignore = true) // UUID is computed from hashedId
69+
@Mapping(target = "published", source = "published", qualifiedByName = "offsetDateTimeToLocalDateTime")
70+
@Mapping(target = "updated", source = "updated", qualifiedByName = "offsetDateTimeToLocalDateTime")
7071
@Mapping(target = "description", source = "description")
7172
@Mapping(target = "interval", source = "interval")
7273
@Mapping(target = "intervalReadings", source = "intervalReadings")
@@ -84,6 +85,9 @@ public interface IntervalBlockMapper {
8485
* @param entity the target entity to update
8586
*/
8687
@Mapping(target = "id", ignore = true)
88+
@Mapping(target = "uuid", ignore = true) // UUID is computed from hashedId
89+
@Mapping(target = "published", source = "published", qualifiedByName = "offsetDateTimeToLocalDateTime")
90+
@Mapping(target = "updated", source = "updated", qualifiedByName = "offsetDateTimeToLocalDateTime")
8791
@Mapping(target = "meterReading", ignore = true)
8892
@Mapping(target = "relatedLinks", ignore = true)
8993
@Mapping(target = "selfLink", ignore = true)

0 commit comments

Comments
 (0)