Skip to content

Commit a17e725

Browse files
dfcoffinclaude
andauthored
feat: ESPI 4.0 Schema Compliance - Phase 13: RetailCustomer (#80)
* feat: ESPI 4.0 Schema Compliance - Phase 13: RetailCustomer (#80) Convert RetailCustomerEntity from IdentifiedObject to Object pattern since it is an application-specific correlation table, not part of ESPI standard. ## Changes - **Entity**: Changed RetailCustomerEntity from IdentifiedObject to Object - Removed IdentifiedObject inheritance (UUID ID, created, updated, published, links) - Changed ID from UUID to Long with auto-generated sequence - Removed description field (not needed for application table) - Removed merge(), updatePasswordDuringMerge(), getSelfHref(), getUpHref() methods - Updated toString() to remove IdentifiedObject fields - **DTO/Mapper**: Deleted RetailCustomerDto and RetailCustomerMapper - Not needed since RetailCustomer is not an ESPI resource (no XML marshalling) - **Repository**: Updated RetailCustomerRepository - Changed JpaRepository<RetailCustomerEntity, UUID> to JpaRepository<RetailCustomerEntity, Long> - Removed redundant @query annotations (let Spring Data generate queries) - Changed findLockedAccounts() to findByAccountLockedTrue() - Kept @query only for findAllIds() (performance optimization) - Changed return types from UUID to Long - **Service**: Simplified RetailCustomerService - Removed methods: findByHashedId, add, delete, importResource, associateByUUID - Changed findById parameter from UUID to Long - Kept only essential methods: findAll, findById, save, findByUsername - Removed dependencies on RetailCustomerMapper - **Database Migrations**: - Moved retail_customers table from V1 to vendor-specific V2 files (auto-increment syntax) - Added retail_customers with BIGINT AUTO_INCREMENT (H2/MySQL) and BIGSERIAL (PostgreSQL) - Kept only username index (authentication hot path) - Changed retail_customer_id columns from CHAR(36) to BIGINT in: - authorizations table (V1) - subscriptions table (V1) - usage_points table (V2) - Added FK constraints in V2 after retail_customers is created: - ALTER TABLE authorizations ADD CONSTRAINT fk_authorization_retail_customer - ALTER TABLE subscriptions ADD CONSTRAINT fk_subscription_retail_customer - ALTER TABLE usage_points ADD CONSTRAINT fk_usage_point_retail_customer - **Related Entity Updates**: - AuthorizationRepository: Changed findAllByRetailCustomerId(UUID) to (Long) - AuthorizationService: Updated all methods accepting retailCustomerId to use Long - UsagePointEntity: Changed getUpHref() to use retailCustomer.getId() instead of getHashedId() - UsagePointRepository: Changed findAllByRetailCustomerId(UUID) to (Long) - UsagePointService: Updated all methods accepting retailCustomerId to use Long - SubscriptionService: Changed findRetailCustomerId() return type from UUID to Long - SubscriptionRepository: Changed findByRetailCustomerId(UUID) to (Long) - ElectricPowerQualitySummaryRepository: Changed xpath methods to accept Long for o1Id - UsageSummaryRepository: Changed xpath methods to accept Long for o1Id - **Tests**: Updated all tests to use Long instead of UUID for retail_customer_id - RetailCustomerRepositoryTest: Updated for Long ID, removed IdentifiedObject tests - TestDataBuilders: Removed setDescription() for RetailCustomer - AuthorizationRepositoryTest: Changed UUID.randomUUID() to 999999L - UsagePointRepositoryTest: Changed UUID.randomUUID() to 999999L - SubscriptionRepositoryTest: Changed UUID.randomUUID() to 999999L - ElectricPowerQualitySummaryRepositoryTest: Updated for Long retailCustomerId - UsageSummaryRepositoryTest: Updated for Long retailCustomerId ## Test Results All 532 tests pass successfully including integration tests with H2, MySQL, and PostgreSQL. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: Update datacustodian controllers for RetailCustomer Long ID type Fix compilation errors in openespi-datacustodian module after Phase 13 RetailCustomer entity conversion from UUID to Long ID: - RetailCustomerController.java: * Change @PathVariable from UUID to Long in show() method * Remove unused UUID import - AssociateUsagePointController.java: * Change @PathVariable from UUID to Long in form() and create() methods * Comment out associateByUUID() call (method removed in Phase 13) * Update commented code to use associateById() with Long type * Keep UUID import (still used by validator) Resolves CI/CD build failures in PR #80. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: Update thirdparty RetailCustomerController for Long ID type Fix compilation error in openespi-thirdparty module after Phase 13 RetailCustomer entity conversion from UUID to Long ID. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: Update thirdparty UUID to Long for RetailCustomer Complete RetailCustomer ID type conversion from UUID to Long across thirdparty module repositories, services, controllers, and test factories. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: Update thirdparty test files for RetailCustomer Long ID type Update test files to use Long instead of UUID for retailCustomerId: - UsagePointRESTRepositoryIntegrationTest.java: * Line 85: Change mock from any(UUID.class) to any(Long.class) * Line 121: Change UUID.randomUUID() to 1000001L * Line 133: Change UUID.randomUUID() to 1000004L - MeterReadingRESTRepositoryImplTests.java: * Line 46: Change UUID retailCustomerId to Long (1000002L) - MeterReadingServiceImplTests.java: * Line 50: Change UUID retailCustomerId to Long (1000003L) All thirdparty tests pass: 47 tests run, 0 failures, 0 errors. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent f2361a9 commit a17e725

42 files changed

Lines changed: 280 additions & 620 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/RetailCustomerEntity.java

Lines changed: 26 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,41 @@
2828
import lombok.Getter;
2929
import lombok.NoArgsConstructor;
3030
import lombok.Setter;
31-
import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject;
3231
import org.greenbuttonalliance.espi.common.utils.security.PasswordPolicy;
3332
import org.hibernate.annotations.BatchSize;
3433
import org.hibernate.proxy.HibernateProxy;
3534

35+
import java.io.Serializable;
3636
import java.util.ArrayList;
3737
import java.util.List;
3838
import java.util.Objects;
39-
import java.util.UUID;
4039

4140
/**
4241
* Pure JPA/Hibernate entity for RetailCustomer without JAXB concerns.
4342
* <p>
4443
* Represents a retail energy customer for Green Button data access.
44+
* This is an application-specific correlation table (not part of ESPI standard).
45+
* Used to correlate UsagePoint energy data to individual users.
4546
* Authentication concerns are handled in DataCustodian/ThirdParty repositories.
4647
*/
4748
@Entity
4849
@Table(name = "retail_customers")
4950
@Getter
5051
@Setter
5152
@NoArgsConstructor
52-
public class RetailCustomerEntity extends IdentifiedObject {
53+
public class RetailCustomerEntity implements Serializable {
5354

5455
private static final long serialVersionUID = 1L;
5556

56-
// Password encoder removed - authentication moved to DataCustodian/ThirdParty
57-
// private static final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
58-
57+
/**
58+
* Primary key using database auto-increment.
59+
* RetailCustomer is an application table, not an ESPI resource.
60+
*/
61+
@Id
62+
@GeneratedValue(strategy = GenerationType.IDENTITY)
63+
@Column(name = "id")
64+
private Long id;
65+
5966
// Password policy validator
6067
private static final PasswordPolicy passwordPolicy = new PasswordPolicy();
6168

@@ -288,109 +295,13 @@ public void unlockAccount() {
288295
this.failedLoginAttempts = 0;
289296
}
290297

291-
/**
292-
* Generates the self href for this retail customer.
293-
*
294-
* @return self href string
295-
*/
296-
public String getSelfHref() {
297-
return "/espi/1_1/resource/RetailCustomer/" + getHashedId();
298-
}
299-
300-
/**
301-
* Generates the up href for this retail customer.
302-
*
303-
* @return up href string
304-
*/
305-
public String getUpHref() {
306-
return "/espi/1_1/resource/RetailCustomer";
307-
}
308-
309-
/**
310-
* Overrides the default self href generation to use retail customer specific logic.
311-
*
312-
* @return self href for this retail customer
313-
*/
314-
@Override
315-
protected String generateDefaultSelfHref() {
316-
return getSelfHref();
317-
}
318298

319-
/**
320-
* Overrides the default up href generation to use retail customer specific logic.
321-
*
322-
* @return up href for this retail customer
323-
*/
324-
@Override
325-
protected String generateDefaultUpHref() {
326-
return getUpHref();
327-
}
328-
329-
/**
330-
* Manual getter for ID field (Lombok issue workaround).
331-
*
332-
* @return the entity ID
333-
*/
334-
public UUID getId() {
335-
return this.id;
336-
}
337-
338-
/**
339-
* Custom implementation for retail customers to use ID instead of UUID.
340-
*
341-
* @return string representation of the ID
342-
*/
343-
@Override
344-
public String getHashedId() {
345-
return getId() != null ? getId().toString() : "";
346-
}
347-
348-
/**
349-
* Merges data from another RetailCustomerEntity.
350-
* Updates user information but preserves security-sensitive fields.
351-
*
352-
* @param other the other retail customer entity to merge from
353-
*/
354-
public void merge(RetailCustomerEntity other) {
355-
if (other != null) {
356-
super.merge(other);
357-
358-
// Update basic information
359-
this.firstName = other.firstName;
360-
this.lastName = other.lastName;
361-
this.email = other.email;
362-
this.phone = other.phone;
363-
364-
// Only update role if provided
365-
if (other.role != null && !other.role.trim().isEmpty()) {
366-
this.role = other.role;
367-
}
368-
369-
// SECURITY: Never merge password, enabled, accountLocked, or authentication fields
370-
// Password updates must use setPasswordSecurely() method
371-
// Note: Collections are not merged to preserve existing relationships
372-
}
373-
}
374-
375-
/**
376-
* Safely updates password during merge if a new password is provided.
377-
* This method should be called separately from merge() for security.
378-
*
379-
* @param newRawPassword the new plain text password, or null to skip update
380-
*/
381-
public void updatePasswordDuringMerge(String newRawPassword) {
382-
if (newRawPassword != null && !newRawPassword.trim().isEmpty()) {
383-
setPasswordSecurely(newRawPassword);
384-
}
385-
}
386299

387300
/**
388301
* Clears all relationships when unlinking the entity.
389-
* Simplified - applications handle relationship cleanup.
302+
* Applications handle relationship cleanup.
390303
*/
391304
public void unlink() {
392-
clearRelatedLinks();
393-
394305
// Simple collection clearing - applications handle bidirectional cleanup
395306
usagePoints.clear();
396307
authorizations.clear();
@@ -598,22 +509,17 @@ public final int hashCode() {
598509
@Override
599510
public String toString() {
600511
return getClass().getSimpleName() + "(" +
601-
"id = " + getId() + ", " +
602-
"username = " + getUsername() + ", " +
603-
"firstName = " + getFirstName() + ", " +
604-
"lastName = " + getLastName() + ", " +
605-
"password = " + getPassword() + ", " +
606-
"enabled = " + getEnabled() + ", " +
607-
"role = " + getRole() + ", " +
608-
"email = " + getEmail() + ", " +
609-
"phone = " + getPhone() + ", " +
610-
"accountCreated = " + getAccountCreated() + ", " +
611-
"lastLogin = " + getLastLogin() + ", " +
612-
"accountLocked = " + getAccountLocked() + ", " +
613-
"failedLoginAttempts = " + getFailedLoginAttempts() + ", " +
614-
"description = " + getDescription() + ", " +
615-
"created = " + getCreated() + ", " +
616-
"updated = " + getUpdated() + ", " +
617-
"published = " + getPublished() + ")";
512+
"id = " + id + ", " +
513+
"username = " + username + ", " +
514+
"firstName = " + firstName + ", " +
515+
"lastName = " + lastName + ", " +
516+
"enabled = " + enabled + ", " +
517+
"role = " + role + ", " +
518+
"email = " + email + ", " +
519+
"phone = " + phone + ", " +
520+
"accountCreated = " + accountCreated + ", " +
521+
"lastLogin = " + lastLogin + ", " +
522+
"accountLocked = " + accountLocked + ", " +
523+
"failedLoginAttempts = " + failedLoginAttempts + ")";
618524
}
619525
}

openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsagePointEntity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public String getSelfHref() {
242242
*/
243243
public String getUpHref() {
244244
if (retailCustomer != null) {
245-
return "RetailCustomer/" + retailCustomer.getHashedId() + "/UsagePoint";
245+
return "RetailCustomer/" + retailCustomer.getId() + "/UsagePoint";
246246
}
247247
return "/espi/1_1/resource/UsagePoint";
248248
}

openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/RetailCustomerDto.java

Lines changed: 0 additions & 118 deletions
This file was deleted.

0 commit comments

Comments
 (0)