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
59 changes: 32 additions & 27 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

OpenESPI-GreenButton-Java is a monorepo implementation of the NAESB Energy Services Provider Interface (ESPI) 4.0 specification for Green Button energy data standards. The project has been migrated to Java 21, Jakarta EE 9+, and Spring Boot 3.5.
OpenESPI-GreenButton-Java is a monorepo implementation of the NAESB Energy Services Provider Interface (ESPI) 4.0 specification for Green Button energy data standards. The project has been migrated to Java 25, Jakarta EE 10+, and Spring Boot 4.0.

## Build and Test Commands

### Build All Modules
```bash
# From repository root
# From repository root - builds all modules (common, datacustodian, thirdparty, authserver)
mvn clean install

# Build only fully-migrated Spring Boot 3.5 modules (excludes thirdparty)
# Build only core modules (common, datacustodian, thirdparty) - omits authserver
mvn clean install -Pspring-boot-only
```

Expand Down Expand Up @@ -43,7 +43,7 @@ cd openespi-datacustodian && mvn spring-boot:run -Dspring-boot.run.profiles=dev-
# Authorization Server
cd openespi-authserver && mvn spring-boot:run

# Third Party (when migration complete)
# Third Party
cd openespi-thirdparty && mvn spring-boot:run
```

Expand Down Expand Up @@ -122,7 +122,7 @@ REST controllers are in `openespi-datacustodian/src/main/java/org/greenbuttonall
- **web/custodian/** - Data custodian-specific endpoints
- **web/customer/** - Retail customer portal endpoints

Note: Many REST controllers have `.disabled` extension during the Spring Boot 3.5 migration. They need to be re-enabled and tested after core functionality is validated.
Note: Many REST controllers have `.disabled` extension during the Spring Boot 4.0 migration. They need to be re-enabled and tested after core functionality is validated.

### DTO and Mapping Layer
The project uses MapStruct for entity-to-DTO mappings:
Expand Down Expand Up @@ -158,15 +158,15 @@ IMPORTANT: When adding/modifying entities, ensure Flyway migration scripts are u

## Key Technologies

### Spring Boot 3.5 Stack
- **Spring Boot**: 3.5.0
- **Spring Security**: 6.x (OAuth2 Resource Server and Client)
- **Spring Data JPA**: 3.x with Hibernate 6.x
### Spring Boot 4.0 Stack
- **Spring Boot**: 4.0.1
- **Spring Security**: 7.x (OAuth2 Resource Server and Client)
- **Spring Data JPA**: 4.x with Hibernate 7.x
- **Spring Authorization Server**: Latest (for openespi-authserver only)

### Persistence
- **JPA/Hibernate**: 6.x with Jakarta Persistence API
- **UUID Primary Keys**: All entities use UUID instead of Long IDs
- **JPA/Hibernate**: 7.x with Jakarta Persistence API 3.2
- **UUID Primary Keys**: All entities use UUID Version 5 instead of Long IDs
- **Flyway**: Database migration management
- **HikariCP**: Connection pooling

Expand All @@ -178,8 +178,8 @@ IMPORTANT: When adding/modifying entities, ensure Flyway migration scripts are u

### Build Tools
- **Maven**: 3.9+
- **MapStruct**: 1.6.0 for DTO mapping
- **Lombok**: 1.18.34 for reducing boilerplate
- **MapStruct**: 1.6.3 for DTO mapping
- **Lombok**: 1.18.42 for reducing boilerplate

## ESPI 4.0 Compliance

Expand Down Expand Up @@ -259,25 +259,30 @@ ESPI uses Atom XML feeds for data exchange. Key patterns:

## Migration Status

The codebase is actively being migrated to Spring Boot 3.5. Key migration achievements:
- Java 21 upgrade complete across all modules
- Jakarta EE 9+ migration complete (javax → jakarta namespace)
- Spring Boot 3.5 migration complete for common, datacustodian, authserver
- UUID primary keys migrated from Long IDs
- OAuth2 modernized with Spring Security 6.x patterns
The codebase has been migrated to Spring Boot 4.0.1 and Java 25. Key migration achievements:
- Java 25 upgrade complete across all modules
- Jakarta EE 10+ migration complete (javax → jakarta namespace)
- Spring Boot 4.0.1 migration complete for common, datacustodian, authserver
- UUID Version 5 primary keys migrated from Long IDs
- OAuth2 modernized with Spring Security 7.x patterns
- RestTemplate replaced with WebClient
- Spring Data JPA 4.x with Hibernate 7.x

### Known Issues
Check migration status documents for current issues:
- `2025-07-15_Claude_Code_Spring_Boot_3.5_Migration_Plan.md` - Overall migration plan
- `openespi-common/SPRING_BOOT_3.5_MIGRATION_STATUS.md` - Common module status
- `2025-07-15_Claude_Code_Spring_Boot_3.5_Migration_Plan.md` - Historical migration plan
- `openespi-common/SPRING_BOOT_3.5_MIGRATION_STATUS.md` - Historical common module status
- `openespi-authserver/MIGRATION_ROADMAP.md` - Auth server status

## Future Updates
## Current Technology Stack

Planned technology upgrades:
- **Java 25**: Upgrade from Java 21 to Java 25 LTS when released
- **Spring Boot 4.0**: Migrate from Spring Boot 3.5 to Spring Boot 4.0
Current versions:
- **Java**: 25
- **Spring Boot**: 4.0.1
- **Spring Security**: 7.x
- **Spring Data JPA**: 4.x
- **Hibernate**: 7.x
- **Jakarta EE**: 10+

## OAuth2 Security

Expand All @@ -297,7 +302,7 @@ The system implements OAuth2 authorization code flow:
## Troubleshooting

### Build Failures
- Ensure Java 21 is installed: `java -version`
- Ensure Java 25 is installed: `java -version`
- Clean build: `mvn clean install`
- Check for profile-specific issues: review active Spring profile

Expand All @@ -322,7 +327,7 @@ The system implements OAuth2 authorization code flow:

- **README.md** - Project overview and quick start
- **BRANCH_STRATEGY.md** - Git workflow and branching strategy
- **2025-07-15_Claude_Code_Spring_Boot_3.5_Migration_Plan.md** - Migration status
- **2025-07-15_Claude_Code_Spring_Boot_3.5_Migration_Plan.md** - Historical migration status (Spring Boot 3.5 to 4.0)
- **openespi-common/CONTRIBUTING.md** - Contribution guidelines
- **openespi-authserver/DEPLOYMENT_GUIDE.md** - Production deployment
- **openespi-authserver/CERTIFICATE_AUTHENTICATION.md** - Client certificate auth
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@

package org.greenbuttonalliance.espi.common.domain.customer.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.greenbuttonalliance.espi.common.domain.customer.enums.NotificationMethodKind;

import jakarta.persistence.*;
import java.io.Serializable;
import java.time.OffsetDateTime;

/**
Expand All @@ -38,7 +41,7 @@
@Data
@NoArgsConstructor
@ToString
public class AccountNotification {
public class AccountNotification implements Serializable {

/**
* Method by which the customer was notified.
Expand All @@ -64,11 +67,4 @@ public class AccountNotification {
*/
@Column(name = "customer_notification_kind", length = 256)
private String customerNotificationKind;

/**
* Customer account this notification belongs to
* Note: This should be handled at the Entity level, not in an Embeddable
*/
// @Embedded - Removed as this creates circular reference issues
// private CustomerAccountEntity customerAccount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,33 @@
*/
@Entity
@Table(name = "customer_accounts")
@AttributeOverrides({
// Resolve any potential column conflicts by ensuring unique column names
@AttributeOverride(name = "upLink.rel", column = @Column(name = "customer_account_up_link_rel")),
@AttributeOverride(name = "upLink.href", column = @Column(name = "customer_account_up_link_href")),
@AttributeOverride(name = "upLink.type", column = @Column(name = "customer_account_up_link_type")),
@AttributeOverride(name = "selfLink.rel", column = @Column(name = "customer_account_self_link_rel")),
@AttributeOverride(name = "selfLink.href", column = @Column(name = "customer_account_self_link_href")),
@AttributeOverride(name = "selfLink.type", column = @Column(name = "customer_account_self_link_type"))
})
// Resolve any potential column conflicts by ensuring unique column names
@AttributeOverride(name = "upLink.rel", column = @Column(name = "customer_account_up_link_rel"))
@AttributeOverride(name = "upLink.href", column = @Column(name = "customer_account_up_link_href"))
@AttributeOverride(name = "upLink.type", column = @Column(name = "customer_account_up_link_type"))
@AttributeOverride(name = "selfLink.rel", column = @Column(name = "customer_account_self_link_rel"))
@AttributeOverride(name = "selfLink.href", column = @Column(name = "customer_account_self_link_href"))
@AttributeOverride(name = "selfLink.type", column = @Column(name = "customer_account_self_link_type"))
@Getter
@Setter
@NoArgsConstructor
public class CustomerAccountEntity extends IdentifiedObject {

// Document fields (previously inherited from Document superclass)

// Field order matches customer.xsd Document type definition (lines 819-872)

/**
* Type of this document.
*/
@Column(name = "document_type", length = 256)
private String type;

/**
* Name of the author of this document.
*/
@Column(name = "author_name", length = 256)
private String authorName;

/**
* Date and time that this document was created.
*/
Expand All @@ -76,6 +87,12 @@ public class CustomerAccountEntity extends IdentifiedObject {
@Column(name = "revision_number", length = 256)
private String revisionNumber;

/**
* Electronic address for the document.
*/
@Embedded
private Organisation.ElectronicAddress electronicAddress;

/**
* Subject of this document, intended for this document to be found by a search engine.
*/
Expand All @@ -89,10 +106,10 @@ public class CustomerAccountEntity extends IdentifiedObject {
private String title;

/**
* Type of this document.
* Status of this document.
*/
@Column(name = "document_type", length = 256)
private String type;
@Embedded
private Status docStatus;

// CustomerAccount specific fields

Expand Down Expand Up @@ -123,11 +140,26 @@ public class CustomerAccountEntity extends IdentifiedObject {
private List<AccountNotification> notifications;

/**
* [extension] Customer contact information used to identify individual
* [extension] Customer contact information used to identify individual
* responsible for billing and payment of CustomerAccount.
*/
@Column(name = "contact_name", length = 256)
private String contactInfo;
@Embedded
@AttributeOverride(name = "organisationName", column = @Column(name = "organisation_name"))
@AttributeOverride(name = "streetAddress.streetDetail", column = @Column(name = "street_detail"))
@AttributeOverride(name = "streetAddress.townDetail", column = @Column(name = "town_detail"))
@AttributeOverride(name = "streetAddress.stateOrProvince", column = @Column(name = "state_or_province"))
@AttributeOverride(name = "streetAddress.postalCode", column = @Column(name = "postal_code"))
@AttributeOverride(name = "streetAddress.country", column = @Column(name = "country"))
@AttributeOverride(name = "postalAddress.streetDetail", column = @Column(name = "postal_street_detail"))
@AttributeOverride(name = "postalAddress.townDetail", column = @Column(name = "postal_town_detail"))
@AttributeOverride(name = "postalAddress.stateOrProvince", column = @Column(name = "postal_state_or_province"))
@AttributeOverride(name = "postalAddress.postalCode", column = @Column(name = "postal_postal_code"))
@AttributeOverride(name = "postalAddress.country", column = @Column(name = "postal_country"))
@AttributeOverride(name = "electronicAddress.email1", column = @Column(name = "contact_email1"))
@AttributeOverride(name = "electronicAddress.email2", column = @Column(name = "contact_email2"))
@AttributeOverride(name = "electronicAddress.web", column = @Column(name = "contact_web"))
@AttributeOverride(name = "electronicAddress.radio", column = @Column(name = "contact_radio"))
private Organisation contactInfo;

/**
* [extension] Customer account identifier
Expand All @@ -154,28 +186,31 @@ public class CustomerAccountEntity 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;
CustomerAccountEntity that = (CustomerAccountEntity) o;
return getId() != null && Objects.equals(getId(), that.getId());
}

@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 = " + getId() + ", " +
"type = " + getType() + ", " +
"authorName = " + getAuthorName() + ", " +
"createdDateTime = " + getCreatedDateTime() + ", " +
"lastModifiedDateTime = " + getLastModifiedDateTime() + ", " +
"revisionNumber = " + getRevisionNumber() + ", " +
"electronicAddress = " + getElectronicAddress() + ", " +
"subject = " + getSubject() + ", " +
"title = " + getTitle() + ", " +
"type = " + getType() + ", " +
"docStatus = " + getDocStatus() + ", " +
"billingCycle = " + getBillingCycle() + ", " +
"budgetBill = " + getBudgetBill() + ", " +
"lastBillAmount = " + getLastBillAmount() + ", " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import jakarta.persistence.Embedded;
import lombok.*;

import java.io.Serializable;

/**
* Embeddable class for Organisation information.
*
Expand All @@ -35,7 +37,7 @@
@Setter
@NoArgsConstructor
@ToString
public class Organisation {
public class Organisation implements Serializable {

/**
* Organisation name (replaces deprecated 'name' field)
Expand Down Expand Up @@ -70,7 +72,7 @@ public class Organisation {
@Embeddable
@Data
@NoArgsConstructor
public static class StreetAddress {
public static class StreetAddress implements Serializable {
@Column(name = "street_detail", length = 256)
private String streetDetail;

Expand All @@ -95,7 +97,7 @@ public static class StreetAddress {
@Embeddable
@Data
@NoArgsConstructor
public static class ElectronicAddress {
public static class ElectronicAddress implements Serializable {
@Column(name = "email1", length = 256)
private String email1;

Expand Down
Loading
Loading