Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,42 @@

/**
* Embeddable value object for BillingChargeSource.
* <p>
* 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.
* <p>
*
* <p>Information about the source of billing charge.
* Contains a single agencyName field identifying the billing agency.
* Embedded within UsageSummary entity.
*
* <p>Per ESPI 4.0 espi.xsd lines 1628-1643.
*
* <p>Note: JAXB annotations are on BillingChargeSourceDto for XML marshalling.
* This entity class is for JPA persistence only.
*
* @see <a href="http://naesb.org/espi">ESPI Specification</a>
*/
@Embeddable
@Data
@NoArgsConstructor
@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.
*
* <p>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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
*
* <p>Per ESPI 4.0 espi.xsd lines 1337-1357.
*
* @see <a href="http://naesb.org/espi">ESPI Specification</a>
*/
@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.
*
* <p>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.
*
* <p>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;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>Represents Atom link elements used in ESPI feed responses for resource relationships.
* Supports the Atom Syndication Format (RFC 4287) link element attributes.
*
* <p>Note: This is a custom class not directly from ESPI XSD. It supports
* the Atom namespace requirements for ESPI RESTful resource representations.
*
* <p>Common link relationships:
* <ul>
* <li>rel="self" - Link to the resource itself</li>
* <li>rel="up" - Link to parent collection</li>
* <li>rel="related" - Link to related resources</li>
* </ul>
*
* @see <a href="https://tools.ietf.org/html/rfc4287#section-4.2.7">RFC 4287 - Atom Link Element</a>
*/
@Embeddable
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class LinkType {

/**
* The link's IRI (Internationalized Resource Identifier).
*
* <p>URI reference to the linked resource.
* RFC 4287: atom:link element's href attribute.
*/
@Column(name = "href")
private String href;

/**
* The link relationship type.
*
* <p>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.
*
* <p>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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>Embeddable component used for precise fractional values in ESPI entities.
* Both numerator and denominator are optional (nullable) per ESPI 4.0 specification.
*
* <p>Per ESPI 4.0 espi.xsd lines 1406-1418.
*
* <p>Note: JAXB annotations are on RationalNumberDto for XML marshalling.
* This entity class is for JPA persistence only.
*
* @see <a href="http://naesb.org/espi">ESPI Specification</a>
*/
@Embeddable
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class RationalNumber {

@Column(name = "numerator")
/**
* Numerator of the rational number.
*
* <p>Optional field (nullable). Type: xs:integer from XSD.
* XSD: espi.xsd line 1413
*
* <p>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.
*
* <p>Optional field (nullable). Type: assumed xs:integer from context.
* XSD: espi.xsd line 1414
*
* <p>Note: XSD does not explicitly specify type for denominator.
* Implementation assumes xs:integer based on RationalNumber semantics.
*
* <p>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;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
*
* <p>Per ESPI 4.0 espi.xsd lines 1419-1431.
*
* <p>Note: JAXB annotations are on ReadingInterharmonicDto for XML marshalling.
* This entity class is for JPA persistence only.
*
* @see <a href="http://naesb.org/espi">ESPI Specification</a>
*/
@Embeddable
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class ReadingInterharmonic {

@Column(name = "denominator")
private Long denominator;
/**
* Numerator of the interharmonic rational number.
*
* <p>Optional field (nullable). Type: xs:integer from XSD.
* XSD: espi.xsd line 1426
*
* <p>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.
*
* <p>Optional field (nullable). Type: assumed xs:integer from context.
* XSD: espi.xsd line 1427
*
* <p>Note: XSD does not explicitly specify type for denominator (schema bug).
* Implementation assumes xs:integer based on ReadingInterharmonic semantics.
*
* <p>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;

}
Loading
Loading