diff --git a/CLAUDE.md b/CLAUDE.md index 642ca0ff..048c11c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -131,6 +131,27 @@ The project uses MapStruct for entity-to-DTO mappings: DTOs mirror ESPI XML schema structure and are used exclusively for XML representations. JSON is only used by openespi-authserver for OAuth2 operations. +#### JAXB Annotation Guidelines +**CRITICAL: JAXB annotations MUST only be applied to DTO classes, NEVER to entity or embeddable classes.** + +- **Entity/Embeddable Classes** (`domain/` packages): + - Use JPA annotations only: `@Entity`, `@Table`, `@Column`, `@Embeddable`, etc. + - **ABSOLUTELY NO JAXB annotations**: Do not use `@XmlType`, `@XmlAccessorType`, `@XmlElement`, `@XmlRootElement` + - Purpose: JPA persistence layer only + +- **DTO Classes** (`dto/` packages): + - Use JAXB annotations: `@XmlType`, `@XmlAccessorType`, `@XmlElement`, `@XmlRootElement` + - NO JPA annotations + - Purpose: XML marshalling/unmarshalling only + +**If an embeddable class needs XML serialization but has no DTO:** Create a corresponding DTO class rather than adding JAXB annotations to the embeddable. This rule has NO exceptions. + +**Rationale:** When both entity and DTO classes have JAXB annotations with the same XML type name and namespace, JAXB throws `IllegalAnnotationsException` due to duplicate type definitions when both are loaded in the same context. This strict separation ensures: +1. Clean architecture (persistence vs. presentation layers) +2. No JAXB namespace conflicts +3. Entities can be refactored without affecting XML schema +4. DTOs can be optimized for XML without affecting database schema + ## Database Management ### Supported Databases diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/BillingChargeSource.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/BillingChargeSource.java index 2c48c1dc..f9dc44d9 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/BillingChargeSource.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/BillingChargeSource.java @@ -30,12 +30,17 @@ /** * Embeddable value object for BillingChargeSource. - *
- * Information about the source of billing charge. - * Per ESPI 4.0 XSD (espi.xsd:1628-1643), BillingChargeSource extends Object - * and contains a single agencyName field. - *
+ * + *
Information about the source of billing charge. + * Contains a single agencyName field identifying the billing agency. * Embedded within UsageSummary entity. + * + *
Per ESPI 4.0 espi.xsd lines 1628-1643. + * + *
Note: JAXB annotations are on BillingChargeSourceDto for XML marshalling. + * This entity class is for JPA persistence only. + * + * @see ESPI Specification */ @Embeddable @Data @@ -43,22 +48,24 @@ @AllArgsConstructor public class BillingChargeSource implements Serializable { - @Serial - private static final long serialVersionUID = 1L; + @Serial + private static final long serialVersionUID = 1L; - /** - * Name of the billing source agency. - * Maximum length 256 characters per String256 type. - */ - @Column(name = "billing_charge_source_agency_name", length = 256) - private String agencyName; + /** + * Name of the billing source agency. + * + *
Optional field (nullable). Maximum length 256 characters per String256 type. + * XSD: espi.xsd line 1635 + */ + @Column(name = "billing_charge_source_agency_name", length = 256) + private String agencyName; - /** - * Checks if this billing charge source has a value. - * - * @return true if agency name is present - */ - public boolean hasValue() { - return agencyName != null && !agencyName.trim().isEmpty(); - } + /** + * Checks if this billing charge source has a value. + * + * @return true if agency name is present + */ + public boolean hasValue() { + return agencyName != null && !agencyName.trim().isEmpty(); + } } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/DateTimeInterval.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/DateTimeInterval.java index 9501d8f3..c68e65ff 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/DateTimeInterval.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/DateTimeInterval.java @@ -19,24 +19,61 @@ package org.greenbuttonalliance.espi.common.domain.common; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.Setter; import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; +import lombok.Setter; + import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlType; +/** + * Date and time interval with duration. + * + *
Embeddable component used for time period specifications in ESPI entities. + * Represents a time interval with start timestamp and duration in seconds. + * Both fields are required per ESPI 4.0 specification. + * + *
Per ESPI 4.0 espi.xsd lines 1337-1357. + * + * @see ESPI Specification + */ @Embeddable +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "DateTimeInterval", namespace = "http://naesb.org/espi", propOrder = { + "duration", + "start" +}) @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@EqualsAndHashCode public class DateTimeInterval { - @Column(name = "start") - private Long start; - - @Column(name = "duration") + /** + * Duration of the interval, in seconds. + * + *
Required field. Type: UInt32 (unsigned 32-bit integer, max 4,294,967,295). + * XSD: espi.xsd line 1344 + */ + @XmlElement(name = "duration", namespace = "http://naesb.org/espi", required = true) + @Column(name = "duration", nullable = false) private Long duration; + /** + * Date and time that this interval started. + * + *
Required field. Type: TimeType (seconds since Unix epoch, Jan 1, 1970 UTC). + * XSD: espi.xsd line 1349 + */ + @XmlElement(name = "start", namespace = "http://naesb.org/espi", required = true) + @Column(name = "start", nullable = false) + private Long start; + } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/LinkType.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/LinkType.java index c86921c5..7252067e 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/LinkType.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/LinkType.java @@ -19,29 +19,75 @@ package org.greenbuttonalliance.espi.common.domain.common; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.Setter; import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; +import lombok.Setter; + import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +/** + * Embeddable value object for Atom feed links. + * + *
Represents Atom link elements used in ESPI feed responses for resource relationships. + * Supports the Atom Syndication Format (RFC 4287) link element attributes. + * + *
Note: This is a custom class not directly from ESPI XSD. It supports + * the Atom namespace requirements for ESPI RESTful resource representations. + * + *
Common link relationships: + *
URI reference to the linked resource. + * RFC 4287: atom:link element's href attribute. + */ @Column(name = "href") private String href; + /** + * The link relationship type. + * + *
Describes the relationship between the current resource and the linked resource. + * Common values: "self", "up", "related", "alternate". + * RFC 4287: atom:link element's rel attribute. + */ @Column(name = "rel") private String rel; + /** + * Advisory media type hint. + * + *
MIME type of the linked resource (e.g., "application/xml", "text/html"). + * RFC 4287: atom:link element's type attribute. + */ @Column(name = "type") private String type; + /** + * Convenience constructor for links without media type. + * + * @param href the link URI + * @param rel the relationship type + */ public LinkType(String href, String rel) { this.href = href; this.rel = rel; diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/RationalNumber.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/RationalNumber.java index 858e6a64..51413625 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/RationalNumber.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/RationalNumber.java @@ -19,25 +19,62 @@ package org.greenbuttonalliance.espi.common.domain.common; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.Setter; import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; -import jakarta.persistence.Column; +import lombok.Setter; + import jakarta.persistence.Embeddable; + import java.math.BigInteger; +/** + * Rational number represented as numerator / denominator. + * + *
Embeddable component used for precise fractional values in ESPI entities. + * Both numerator and denominator are optional (nullable) per ESPI 4.0 specification. + * + *
Per ESPI 4.0 espi.xsd lines 1406-1418. + * + *
Note: JAXB annotations are on RationalNumberDto for XML marshalling. + * This entity class is for JPA persistence only. + * + * @see ESPI Specification + */ @Embeddable @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@EqualsAndHashCode public class RationalNumber { - @Column(name = "numerator") + /** + * Numerator of the rational number. + * + *
Optional field (nullable). Type: xs:integer from XSD. + * XSD: espi.xsd line 1413 + * + *
Note: Uses DECIMAL(38,0) column type for database compatibility while maintaining + * BigInteger type in Java for XSD compliance. Column type is specified in entity + * @AttributeOverride annotations. + */ private BigInteger numerator; - @Column(name = "denominator") + /** + * Denominator of the rational number. + * + *
Optional field (nullable). Type: assumed xs:integer from context. + * XSD: espi.xsd line 1414 + * + *
Note: XSD does not explicitly specify type for denominator. + * Implementation assumes xs:integer based on RationalNumber semantics. + * + *
Uses DECIMAL(38,0) column type for database compatibility while maintaining + * BigInteger type in Java for XSD compliance. Column type is specified in entity + * @AttributeOverride annotations. + */ private BigInteger denominator; } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/ReadingInterharmonic.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/ReadingInterharmonic.java index 9baef8b4..619af7c5 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/ReadingInterharmonic.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/ReadingInterharmonic.java @@ -19,24 +19,64 @@ package org.greenbuttonalliance.espi.common.domain.common; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.Setter; import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; -import jakarta.persistence.Column; +import lombok.Setter; + import jakarta.persistence.Embeddable; +import java.math.BigInteger; + +/** + * Interharmonic reading represented as numerator / denominator. + * + *
Embeddable component used for harmonic and interharmonic measurements in reading types. + * Harmonics are identified by denominator = 1. Both numerator and denominator are + * optional (nullable) per ESPI 4.0 specification. + * + *
Per ESPI 4.0 espi.xsd lines 1419-1431. + * + *
Note: JAXB annotations are on ReadingInterharmonicDto for XML marshalling. + * This entity class is for JPA persistence only. + * + * @see ESPI Specification + */ @Embeddable @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@EqualsAndHashCode public class ReadingInterharmonic { - @Column(name = "denominator") - private Long denominator; + /** + * Numerator of the interharmonic rational number. + * + *
Optional field (nullable). Type: xs:integer from XSD. + * XSD: espi.xsd line 1426 + * + *
Note: Uses DECIMAL(38,0) column type for database compatibility while maintaining + * BigInteger type in Java for XSD compliance. Column type is specified in entity + * @AttributeOverride annotations. + */ + private BigInteger numerator; - @Column(name = "numerator") - private Long numerator; + /** + * Denominator of the interharmonic rational number. + * Harmonics are identified by denominator = 1. + * + *
Optional field (nullable). Type: assumed xs:integer from context. + * XSD: espi.xsd line 1427 + * + *
Note: XSD does not explicitly specify type for denominator (schema bug). + * Implementation assumes xs:integer based on ReadingInterharmonic semantics. + * + *
Uses DECIMAL(38,0) column type for database compatibility while maintaining + * BigInteger type in Java for XSD compliance. Column type is specified in entity + * @AttributeOverride annotations. + */ + private BigInteger denominator; } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/SummaryMeasurement.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/SummaryMeasurement.java index c174b4dc..ac014b05 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/SummaryMeasurement.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/SummaryMeasurement.java @@ -19,26 +19,84 @@ package org.greenbuttonalliance.espi.common.domain.common; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.Setter; import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; +import lombok.Setter; +import org.greenbuttonalliance.espi.common.domain.usage.enums.UnitMultiplierKind; +import org.greenbuttonalliance.espi.common.domain.usage.enums.UnitSymbolKind; + +import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +/** + * Summary measurement data embedded in usage summary entities. + * + *
Embeddable component used for aggregate measurement values with units and timestamps. + * All fields are optional (nullable) per ESPI 4.0 specification. + * + *
Per ESPI 4.0 espi.xsd lines 1094-1129. + * + *
Note: JAXB annotations are on SummaryMeasurementDto for XML marshalling. + * This entity class is for JPA persistence only. + * + * @see ESPI Specification + */ @Embeddable @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@EqualsAndHashCode public class SummaryMeasurement { - private String powerOfTenMultiplier; + /** + * The multiplier part of the unit of measure, e.g. "kilo" (k). + * + *
Optional field (nullable). Type: UnitMultiplierKind enum. + * XSD: espi.xsd line 1101 + */ + @Column(name = "power_of_ten_multiplier") + @Enumerated(EnumType.STRING) + private UnitMultiplierKind powerOfTenMultiplier; + /** + * The date and time (if needed) of the summary measurement. + * + *
Optional field (nullable). Type: TimeType (seconds since Unix epoch). + * XSD: espi.xsd line 1106 + */ + @Column(name = "time_stamp") private Long timeStamp; - private String uom; + /** + * The units of the reading, e.g. "Wh". + * + *
Optional field (nullable). Type: UnitSymbolKind enum. + * XSD: espi.xsd line 1111 + */ + @Column(name = "uom") + @Enumerated(EnumType.STRING) + private UnitSymbolKind uom; + /** + * The value of the summary measurement. + * + *
Optional field (nullable). Type: Int48 (48-bit signed integer). + * XSD: espi.xsd line 1116 + */ + @Column(name = "value") private Long value; + /** + * Reference URI to a full ReadingType resource. + * + *
Optional field (nullable). Type: xs:anyURI.
+ * XSD: espi.xsd line 1121
+ */
+ @Column(name = "reading_type_ref")
private String readingTypeRef;
}
\ No newline at end of file
diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsageSummaryEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsageSummaryEntity.java
index a4a82558..fe2473fb 100644
--- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsageSummaryEntity.java
+++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsageSummaryEntity.java
@@ -625,12 +625,12 @@ public Double getCostAdditionalLastPeriodInDollars() {
/**
* Gets the commodity type being measured based on UOM.
- *
+ *
* @return commodity type description
*/
public String getCommodityType() {
if (overallConsumptionLastPeriod != null && overallConsumptionLastPeriod.getUom() != null) {
- String uom = overallConsumptionLastPeriod.getUom();
+ String uom = overallConsumptionLastPeriod.getUom().name();
if (uom.contains("WH") || uom.contains("W")) return "Electricity";
if (uom.contains("BTU") || uom.contains("THERM")) return "Gas";
if (uom.contains("GAL") || uom.contains("L")) return "Water";
diff --git a/openespi-common/src/main/resources/db/migration/V1__Create_Base_Tables.sql b/openespi-common/src/main/resources/db/migration/V1__Create_Base_Tables.sql
index a7679257..ceb03a38 100644
--- a/openespi-common/src/main/resources/db/migration/V1__Create_Base_Tables.sql
+++ b/openespi-common/src/main/resources/db/migration/V1__Create_Base_Tables.sql
@@ -246,8 +246,8 @@ CREATE TABLE reading_types
tou VARCHAR(50),
uom VARCHAR(50),
cpp VARCHAR(50),
- interharmonic_numerator BIGINT,
- interharmonic_denominator BIGINT,
+ interharmonic_numerator DECIMAL(38,0),
+ interharmonic_denominator DECIMAL(38,0),
measuring_period VARCHAR(50),
argument_numerator DECIMAL(38,0),
argument_denominator DECIMAL(38,0)
diff --git a/openespi-common/src/main/resources/schema/ESPI_4.1/customer_4.1.xsd b/openespi-common/src/main/resources/schema/ESPI_4.1/customer_4.1.xsd
index 186517e0..d8ddd5d9 100755
--- a/openespi-common/src/main/resources/schema/ESPI_4.1/customer_4.1.xsd
+++ b/openespi-common/src/main/resources/schema/ESPI_4.1/customer_4.1.xsd
@@ -633,8 +633,13 @@ Additional Complex Types