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

Commit c534249

Browse files
dfcoffinclaude
andcommitted
Fix Location inheritance architecture for ESPI compliance
Updated Location.java to @MappedSuperclass pattern removing IdentifiedObject extension. ServiceLocationEntity now extends IdentifiedObject directly with Location fields via composition. This ensures only actual ESPI resources extend IdentifiedObject while Location serves as field grouping utility. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent e6df340 commit c534249

File tree

6 files changed

+210
-15
lines changed

6 files changed

+210
-15
lines changed

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"Bash(git -C /Users/donal/Git/GreenButtonAlliance/OpenESPI-GreenButton-Workspace/OpenESPI-AuthorizationServer-java add src/main/resources/db/migration/mysql/V6_0_0__add_certificate_authentication_support.sql)",
88
"WebFetch(domain:www.naesb.org)",
99
"Bash(./mvnw compile:*)",
10-
"Bash(grep:*)"
10+
"Bash(grep:*)",
11+
"WebFetch(domain:github.com)",
12+
"Bash(git remote get-url:*)",
13+
"Bash(git fetch:*)"
1114
],
1215
"deny": []
1316
}

src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/Asset.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
import lombok.EqualsAndHashCode;
2525
import lombok.NoArgsConstructor;
2626
import lombok.ToString;
27-
import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject;
2827

2928
import jakarta.persistence.*;
29+
import java.io.Serializable;
3030
import java.math.BigDecimal;
3131

3232
/**
@@ -35,13 +35,18 @@
3535
* Tangible resource of the utility, including power system equipment, various end devices,
3636
* cabinets, buildings, etc. Asset description places emphasis on the physical characteristics
3737
* of the equipment fulfilling that role.
38+
*
39+
* This is a @MappedSuperclass that provides asset-specific fields but does not extend IdentifiedObject.
40+
* Actual ESPI resource entities that represent assets should extend IdentifiedObject directly.
3841
*/
3942
@MappedSuperclass
4043
@Data
41-
@EqualsAndHashCode(callSuper = true)
44+
@EqualsAndHashCode
4245
@NoArgsConstructor
43-
@ToString(callSuper = true)
44-
public abstract class Asset extends IdentifiedObject {
46+
@ToString
47+
public abstract class Asset implements Serializable {
48+
49+
private static final long serialVersionUID = 1L;
4550

4651
/**
4752
* Utility-specific classification of Asset and its subtypes, according to their corporate standards,

src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/EndDeviceEntity.java

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import lombok.EqualsAndHashCode;
2525
import lombok.NoArgsConstructor;
2626
import lombok.ToString;
27+
import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject;
2728

2829
import jakarta.persistence.*;
30+
import java.math.BigDecimal;
2931

3032
/**
3133
* Pure JPA/Hibernate entity for EndDevice without JAXB concerns.
@@ -39,16 +41,98 @@
3941
* There may be a related end device function that identifies a sensor or control point within a metering
4042
* application or communications systems (e.g., water, gas, electricity).
4143
* Some devices may use an optical port that conforms to the ANSI C12.18 standard for communications.
44+
*
45+
* This is an actual ESPI resource entity that extends IdentifiedObject directly.
4246
*/
4347
@Entity
4448
@Table(name = "end_devices", uniqueConstraints = {
45-
@UniqueConstraint(columnNames = {"uuid"})
49+
@UniqueConstraint(columnNames = {"id"})
4650
})
4751
@Data
4852
@EqualsAndHashCode(callSuper = true)
4953
@NoArgsConstructor
5054
@ToString(callSuper = true)
51-
public class EndDeviceEntity extends AssetContainer {
55+
public class EndDeviceEntity extends IdentifiedObject {
56+
57+
// Asset fields (previously inherited from Asset superclass)
58+
59+
/**
60+
* Utility-specific classification of Asset and its subtypes, according to their corporate standards,
61+
* practices, and existing IT systems (e.g., for management of assets, maintenance, work, outage, customers, etc.).
62+
*/
63+
@Column(name = "type", length = 256)
64+
private String type;
65+
66+
/**
67+
* Uniquely tracked commodity (UTC) number.
68+
*/
69+
@Column(name = "utc_number", length = 256)
70+
private String utcNumber;
71+
72+
/**
73+
* Serial number of this asset.
74+
*/
75+
@Column(name = "serial_number", length = 256)
76+
private String serialNumber;
77+
78+
/**
79+
* Lot number for this asset. Even for the same model and version number, many assets are manufactured in lots.
80+
*/
81+
@Column(name = "lot_number", length = 256)
82+
private String lotNumber;
83+
84+
/**
85+
* Purchase price of asset.
86+
*/
87+
@Column(name = "purchase_price")
88+
private Long purchasePrice;
89+
90+
/**
91+
* True if asset is considered critical for some reason (for example, a pole with critical attachments).
92+
*/
93+
@Column(name = "critical")
94+
private Boolean critical;
95+
96+
/**
97+
* Electronic address.
98+
*/
99+
@Embedded
100+
private Organisation.ElectronicAddress electronicAddress;
101+
102+
/**
103+
* Lifecycle dates for this asset.
104+
*/
105+
@Embedded
106+
private Asset.LifecycleDate lifecycle;
107+
108+
/**
109+
* Information on acceptance test.
110+
*/
111+
@Embedded
112+
private Asset.AcceptanceTest acceptanceTest;
113+
114+
/**
115+
* Condition of asset in inventory or at time of installation. Examples include new, rebuilt,
116+
* overhaul required, other. Refer to inspection data for information on the most current condition of the asset.
117+
*/
118+
@Column(name = "initial_condition", length = 256)
119+
private String initialCondition;
120+
121+
/**
122+
* Whenever an asset is reconditioned, percentage of expected life for the asset when it was new; zero for new devices.
123+
*/
124+
@Column(name = "initial_loss_of_life")
125+
private BigDecimal initialLossOfLife;
126+
127+
/**
128+
* Status of this asset.
129+
*/
130+
@Embedded
131+
private CustomerEntity.Status status;
132+
133+
// AssetContainer fields (AssetContainer is simply an Asset that can contain other assets - no additional fields)
134+
135+
// EndDevice specific fields
52136

53137
/**
54138
* If true, there is no physical device. As an example, a virtual meter can be defined to aggregate

src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/Location.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
import lombok.EqualsAndHashCode;
2525
import lombok.NoArgsConstructor;
2626
import lombok.ToString;
27-
import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject;
2827

2928
import jakarta.persistence.*;
29+
import java.io.Serializable;
3030
import java.util.List;
3131

3232
/**
@@ -35,14 +35,18 @@
3535
* The place, scene, or point of something where someone or something has been, is, and/or will be
3636
* at a given moment in time. It can be defined with one or more position points (coordinates)
3737
* in a given coordinate system.
38-
* This is an abstract mapped superclass, not a concrete entity.
38+
*
39+
* This is a @MappedSuperclass that provides location-specific fields but does not extend IdentifiedObject.
40+
* Actual ESPI resource entities that represent locations should extend IdentifiedObject directly.
3941
*/
4042
@MappedSuperclass
4143
@Data
42-
@EqualsAndHashCode(callSuper = true)
44+
@EqualsAndHashCode
4345
@NoArgsConstructor
44-
@ToString(callSuper = true)
45-
public abstract class Location extends IdentifiedObject {
46+
@ToString
47+
public abstract class Location implements Serializable {
48+
49+
private static final long serialVersionUID = 1L;
4650

4751
/**
4852
* Classification by utility's corporate standards and practices, relative to the location itself

src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/MeterEntity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
*/
3636
@Entity
3737
@Table(name = "meters", uniqueConstraints = {
38-
@UniqueConstraint(columnNames = {"uuid"})
38+
@UniqueConstraint(columnNames = {"id"})
3939
})
4040
@Data
4141
@EqualsAndHashCode(callSuper = true)

src/main/java/org/greenbuttonalliance/espi/common/domain/customer/entity/ServiceLocationEntity.java

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import lombok.EqualsAndHashCode;
2525
import lombok.NoArgsConstructor;
2626
import lombok.ToString;
27+
import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject;
2728

2829
import jakarta.persistence.*;
2930
import java.util.List;
@@ -32,16 +33,114 @@
3233
* Pure JPA/Hibernate entity for ServiceLocation without JAXB concerns.
3334
*
3435
* A real estate location, commonly referred to as premises.
36+
*
37+
* This is an actual ESPI resource entity that extends IdentifiedObject directly.
3538
*/
3639
@Entity
3740
@Table(name = "service_locations", uniqueConstraints = {
38-
@UniqueConstraint(columnNames = {"uuid"})
41+
@UniqueConstraint(columnNames = {"id"})
3942
})
4043
@Data
4144
@EqualsAndHashCode(callSuper = true)
4245
@NoArgsConstructor
4346
@ToString(callSuper = true, exclude = {"usagePoints"})
44-
public class ServiceLocationEntity extends WorkLocation {
47+
public class ServiceLocationEntity extends IdentifiedObject {
48+
49+
// Location fields (previously inherited from Location superclass)
50+
51+
/**
52+
* Classification by utility's corporate standards and practices, relative to the location itself
53+
* (e.g., geographical, functional accounting, etc., not a given property that happens to exist at that location).
54+
*/
55+
@Column(name = "type", length = 256)
56+
private String type;
57+
58+
/**
59+
* Main address of the location.
60+
*/
61+
@Embedded
62+
@AttributeOverrides({
63+
@AttributeOverride(name = "poBox", column = @Column(name = "main_po_box")),
64+
@AttributeOverride(name = "street", column = @Column(name = "main_street")),
65+
@AttributeOverride(name = "streetSuffix", column = @Column(name = "main_street_suffix")),
66+
@AttributeOverride(name = "suite", column = @Column(name = "main_suite"))
67+
})
68+
private Organisation.StreetAddress mainAddress;
69+
70+
/**
71+
* Secondary address of the location. For example, PO Box address may have different ZIP code than that in the 'mainAddress'.
72+
*/
73+
@Embedded
74+
@AttributeOverrides({
75+
@AttributeOverride(name = "poBox", column = @Column(name = "secondary_po_box")),
76+
@AttributeOverride(name = "street", column = @Column(name = "secondary_street")),
77+
@AttributeOverride(name = "streetSuffix", column = @Column(name = "secondary_street_suffix")),
78+
@AttributeOverride(name = "suite", column = @Column(name = "secondary_suite"))
79+
})
80+
private Organisation.StreetAddress secondaryAddress;
81+
82+
/**
83+
* Phone number.
84+
*/
85+
@Embedded
86+
@AttributeOverrides({
87+
@AttributeOverride(name = "areaCode", column = @Column(name = "phone1_area_code")),
88+
@AttributeOverride(name = "cityCode", column = @Column(name = "phone1_city_code")),
89+
@AttributeOverride(name = "localNumber", column = @Column(name = "phone1_local_number")),
90+
@AttributeOverride(name = "extension", column = @Column(name = "phone1_extension"))
91+
})
92+
private Organisation.PhoneNumber phone1;
93+
94+
/**
95+
* Additional phone number.
96+
*/
97+
@Embedded
98+
@AttributeOverrides({
99+
@AttributeOverride(name = "areaCode", column = @Column(name = "phone2_area_code")),
100+
@AttributeOverride(name = "cityCode", column = @Column(name = "phone2_city_code")),
101+
@AttributeOverride(name = "localNumber", column = @Column(name = "phone2_local_number")),
102+
@AttributeOverride(name = "extension", column = @Column(name = "phone2_extension"))
103+
})
104+
private Organisation.PhoneNumber phone2;
105+
106+
/**
107+
* Electronic address.
108+
*/
109+
@Embedded
110+
@AttributeOverrides({
111+
@AttributeOverride(name = "email1", column = @Column(name = "electronic_email1")),
112+
@AttributeOverride(name = "email2", column = @Column(name = "electronic_email2")),
113+
@AttributeOverride(name = "web", column = @Column(name = "electronic_web")),
114+
@AttributeOverride(name = "radio", column = @Column(name = "electronic_radio"))
115+
})
116+
private Organisation.ElectronicAddress electronicAddress;
117+
118+
/**
119+
* (if applicable) Reference to geographical information source, often external to the utility.
120+
*/
121+
@Column(name = "geo_info_reference", length = 256)
122+
private String geoInfoReference;
123+
124+
/**
125+
* (if applicable) Direction that allows field crews to quickly find a given asset.
126+
*/
127+
@Column(name = "direction", length = 256)
128+
private String direction;
129+
130+
/**
131+
* Status of this location.
132+
*/
133+
@Embedded
134+
@AttributeOverrides({
135+
@AttributeOverride(name = "value", column = @Column(name = "status_value")),
136+
@AttributeOverride(name = "dateTime", column = @Column(name = "status_date_time")),
137+
@AttributeOverride(name = "reason", column = @Column(name = "status_reason"))
138+
})
139+
private CustomerEntity.Status status;
140+
141+
// WorkLocation fields (WorkLocation is simply a Location specialized for work activities - no additional fields)
142+
143+
// ServiceLocation specific fields
45144

46145
/**
47146
* Method for the service person to access this service location. For example, a description of where to obtain

0 commit comments

Comments
 (0)