diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/RetailCustomerEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/RetailCustomerEntity.java index 57d9e235..73dccd0f 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/RetailCustomerEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/RetailCustomerEntity.java @@ -28,20 +28,21 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; import org.greenbuttonalliance.espi.common.utils.security.PasswordPolicy; import org.hibernate.annotations.BatchSize; import org.hibernate.proxy.HibernateProxy; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.UUID; /** * Pure JPA/Hibernate entity for RetailCustomer without JAXB concerns. *

* Represents a retail energy customer for Green Button data access. + * This is an application-specific correlation table (not part of ESPI standard). + * Used to correlate UsagePoint energy data to individual users. * Authentication concerns are handled in DataCustodian/ThirdParty repositories. */ @Entity @@ -49,13 +50,19 @@ @Getter @Setter @NoArgsConstructor -public class RetailCustomerEntity extends IdentifiedObject { +public class RetailCustomerEntity implements Serializable { private static final long serialVersionUID = 1L; - // Password encoder removed - authentication moved to DataCustodian/ThirdParty - // private static final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - + /** + * Primary key using database auto-increment. + * RetailCustomer is an application table, not an ESPI resource. + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + // Password policy validator private static final PasswordPolicy passwordPolicy = new PasswordPolicy(); @@ -288,109 +295,13 @@ public void unlockAccount() { this.failedLoginAttempts = 0; } - /** - * Generates the self href for this retail customer. - * - * @return self href string - */ - public String getSelfHref() { - return "/espi/1_1/resource/RetailCustomer/" + getHashedId(); - } - - /** - * Generates the up href for this retail customer. - * - * @return up href string - */ - public String getUpHref() { - return "/espi/1_1/resource/RetailCustomer"; - } - - /** - * Overrides the default self href generation to use retail customer specific logic. - * - * @return self href for this retail customer - */ - @Override - protected String generateDefaultSelfHref() { - return getSelfHref(); - } - /** - * Overrides the default up href generation to use retail customer specific logic. - * - * @return up href for this retail customer - */ - @Override - protected String generateDefaultUpHref() { - return getUpHref(); - } - - /** - * Manual getter for ID field (Lombok issue workaround). - * - * @return the entity ID - */ - public UUID getId() { - return this.id; - } - - /** - * Custom implementation for retail customers to use ID instead of UUID. - * - * @return string representation of the ID - */ - @Override - public String getHashedId() { - return getId() != null ? getId().toString() : ""; - } - - /** - * Merges data from another RetailCustomerEntity. - * Updates user information but preserves security-sensitive fields. - * - * @param other the other retail customer entity to merge from - */ - public void merge(RetailCustomerEntity other) { - if (other != null) { - super.merge(other); - - // Update basic information - this.firstName = other.firstName; - this.lastName = other.lastName; - this.email = other.email; - this.phone = other.phone; - - // Only update role if provided - if (other.role != null && !other.role.trim().isEmpty()) { - this.role = other.role; - } - - // SECURITY: Never merge password, enabled, accountLocked, or authentication fields - // Password updates must use setPasswordSecurely() method - // Note: Collections are not merged to preserve existing relationships - } - } - - /** - * Safely updates password during merge if a new password is provided. - * This method should be called separately from merge() for security. - * - * @param newRawPassword the new plain text password, or null to skip update - */ - public void updatePasswordDuringMerge(String newRawPassword) { - if (newRawPassword != null && !newRawPassword.trim().isEmpty()) { - setPasswordSecurely(newRawPassword); - } - } /** * Clears all relationships when unlinking the entity. - * Simplified - applications handle relationship cleanup. + * Applications handle relationship cleanup. */ public void unlink() { - clearRelatedLinks(); - // Simple collection clearing - applications handle bidirectional cleanup usagePoints.clear(); authorizations.clear(); @@ -598,22 +509,17 @@ public final int hashCode() { @Override public String toString() { return getClass().getSimpleName() + "(" + - "id = " + getId() + ", " + - "username = " + getUsername() + ", " + - "firstName = " + getFirstName() + ", " + - "lastName = " + getLastName() + ", " + - "password = " + getPassword() + ", " + - "enabled = " + getEnabled() + ", " + - "role = " + getRole() + ", " + - "email = " + getEmail() + ", " + - "phone = " + getPhone() + ", " + - "accountCreated = " + getAccountCreated() + ", " + - "lastLogin = " + getLastLogin() + ", " + - "accountLocked = " + getAccountLocked() + ", " + - "failedLoginAttempts = " + getFailedLoginAttempts() + ", " + - "description = " + getDescription() + ", " + - "created = " + getCreated() + ", " + - "updated = " + getUpdated() + ", " + - "published = " + getPublished() + ")"; + "id = " + id + ", " + + "username = " + username + ", " + + "firstName = " + firstName + ", " + + "lastName = " + lastName + ", " + + "enabled = " + enabled + ", " + + "role = " + role + ", " + + "email = " + email + ", " + + "phone = " + phone + ", " + + "accountCreated = " + accountCreated + ", " + + "lastLogin = " + lastLogin + ", " + + "accountLocked = " + accountLocked + ", " + + "failedLoginAttempts = " + failedLoginAttempts + ")"; } } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsagePointEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsagePointEntity.java index 127e04a4..55bb6beb 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsagePointEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsagePointEntity.java @@ -242,7 +242,7 @@ public String getSelfHref() { */ public String getUpHref() { if (retailCustomer != null) { - return "RetailCustomer/" + retailCustomer.getHashedId() + "/UsagePoint"; + return "RetailCustomer/" + retailCustomer.getId() + "/UsagePoint"; } return "/espi/1_1/resource/UsagePoint"; } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/RetailCustomerDto.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/RetailCustomerDto.java deleted file mode 100644 index a74a2e20..00000000 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/RetailCustomerDto.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * - * Copyright (c) 2025 Green Button Alliance, Inc. - * - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.greenbuttonalliance.espi.common.dto.usage; - -import jakarta.xml.bind.annotation.*; -import java.util.List; - -/** - * RetailCustomer DTO record for JAXB XML marshalling/unmarshalling. - * - * Represents a retail energy customer for Green Button data access. - * Security-sensitive fields like password are excluded from the DTO. - */ -@XmlRootElement(name = "RetailCustomer", namespace = "http://naesb.org/espi") -@XmlAccessorType(XmlAccessType.PROPERTY) -@XmlType(name = "RetailCustomer", namespace = "http://naesb.org/espi", propOrder = { - "username", "firstName", "lastName", "email", "phone", "role", - "enabled", "accountCreated", "lastLogin", "accountLocked", - "failedLoginAttempts", "usagePoints", "authorizations" -}) -public record RetailCustomerDto( - - String uuid, - String username, - String firstName, - String lastName, - String email, - String phone, - String role, - Boolean enabled, - Long accountCreated, - Long lastLogin, - Boolean accountLocked, - Integer failedLoginAttempts, - List usagePoints, - List authorizations - -) { - - /** - * Gets the full name of the customer. - * - * @return formatted full name - */ - public String getFullName() { - StringBuilder fullName = new StringBuilder(); - if (firstName != null && !firstName.trim().isEmpty()) { - fullName.append(firstName.trim()); - } - if (lastName != null && !lastName.trim().isEmpty()) { - if (fullName.length() > 0) fullName.append(" "); - fullName.append(lastName.trim()); - } - return fullName.toString(); - } - - /** - * Checks if the customer has admin privileges. - * - * @return true if role is ROLE_ADMIN, false otherwise - */ - public boolean isAdmin() { - return "ROLE_ADMIN".equals(role); - } - - /** - * Checks if the customer has custodian privileges. - * - * @return true if role is ROLE_CUSTODIAN, false otherwise - */ - public boolean isCustodian() { - return "ROLE_CUSTODIAN".equals(role); - } - - /** - * Checks if the customer is a regular user. - * - * @return true if role is ROLE_USER, false otherwise - */ - public boolean isRegularUser() { - return "ROLE_USER".equals(role); - } - - /** - * Gets the number of usage points for this customer. - * - * @return count of usage points - */ - public int getUsagePointCount() { - return usagePoints != null ? usagePoints.size() : 0; - } - - /** - * Gets the number of authorizations for this customer. - * - * @return count of authorizations - */ - public int getAuthorizationCount() { - return authorizations != null ? authorizations.size() : 0; - } -} \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/RetailCustomerMapper.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/RetailCustomerMapper.java deleted file mode 100644 index e6596d1d..00000000 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/RetailCustomerMapper.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * - * Copyright (c) 2025 Green Button Alliance, Inc. - * - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.greenbuttonalliance.espi.common.mapper.usage; - -import org.greenbuttonalliance.espi.common.domain.usage.RetailCustomerEntity; -import org.greenbuttonalliance.espi.common.dto.usage.RetailCustomerDto; -import org.greenbuttonalliance.espi.common.mapper.BaseMapperUtils; -import org.greenbuttonalliance.espi.common.mapper.DateTimeMapper; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; - -/** - * MapStruct mapper for converting between RetailCustomerEntity and RetailCustomerDto. - * - * Handles the conversion between the JPA entity used for persistence and the DTO - * used for JAXB XML marshalling in the Green Button API. - * Excludes security-sensitive fields like password from DTO mapping. - */ -@Mapper(componentModel = "spring", uses = { - DateTimeMapper.class, - BaseMapperUtils.class, - UsagePointMapper.class, - AuthorizationMapper.class -}) -public interface RetailCustomerMapper { - - /** - * Converts a RetailCustomerEntity to a RetailCustomerDto. - * Maps all customer fields except security-sensitive data like password. - * - * @param entity the retail customer entity - * @return the retail customer DTO - */ - @Mapping(target = "uuid", source = "id", qualifiedByName = "uuidToString") - @Mapping(target = "username", source = "username") - @Mapping(target = "firstName", source = "firstName") - @Mapping(target = "lastName", source = "lastName") - @Mapping(target = "email", source = "email") - @Mapping(target = "phone", source = "phone") - @Mapping(target = "role", source = "role") - @Mapping(target = "enabled", source = "enabled") - @Mapping(target = "accountCreated", source = "accountCreated") - @Mapping(target = "lastLogin", source = "lastLogin") - @Mapping(target = "accountLocked", source = "accountLocked") - @Mapping(target = "failedLoginAttempts", source = "failedLoginAttempts") - @Mapping(target = "usagePoints", source = "usagePoints") - @Mapping(target = "authorizations", source = "authorizations") - RetailCustomerDto toDto(RetailCustomerEntity entity); - - /** - * Converts a RetailCustomerDto to a RetailCustomerEntity. - * Maps customer fields but excludes collections to prevent cascading issues. - * Security fields like password are not set from DTO for security reasons. - * - * @param dto the retail customer DTO - * @return the retail customer entity - */ - @Mapping(target = "passwordSecurelyNoValidation", ignore = true) - @Mapping(target = "passwordSecurely", ignore = true) - @Mapping(target = "description", ignore = true) - @Mapping(target = "created", ignore = true) - @Mapping(target = "id", source = "uuid", qualifiedByName = "stringToUuid") - @Mapping(target = "username", source = "username") - @Mapping(target = "firstName", source = "firstName") - @Mapping(target = "lastName", source = "lastName") - @Mapping(target = "email", source = "email") - @Mapping(target = "phone", source = "phone") - @Mapping(target = "role", source = "role") - @Mapping(target = "enabled", source = "enabled") - @Mapping(target = "accountCreated", source = "accountCreated") - @Mapping(target = "lastLogin", source = "lastLogin") - @Mapping(target = "accountLocked", source = "accountLocked") - @Mapping(target = "failedLoginAttempts", source = "failedLoginAttempts") - @Mapping(target = "password", ignore = true) // Security: never set password from DTO - @Mapping(target = "usagePoints", ignore = true) // Managed separately - @Mapping(target = "authorizations", ignore = true) // Managed separately - @Mapping(target = "published", ignore = true) - @Mapping(target = "updated", ignore = true) - RetailCustomerEntity toEntity(RetailCustomerDto dto); - - /** - * Updates an existing RetailCustomerEntity with data from a RetailCustomerDto. - * Only updates non-security fields and preserves existing relationships. - * - * @param dto the source retail customer DTO - * @param entity the target retail customer entity to update - */ - @Mapping(target = "passwordSecurelyNoValidation", ignore = true) - @Mapping(target = "passwordSecurely", ignore = true) - @Mapping(target = "description", ignore = true) - @Mapping(target = "created", ignore = true) - @Mapping(target = "id", ignore = true) // Never change ID - @Mapping(target = "username", source = "username") - @Mapping(target = "firstName", source = "firstName") - @Mapping(target = "lastName", source = "lastName") - @Mapping(target = "email", source = "email") - @Mapping(target = "phone", source = "phone") - @Mapping(target = "role", source = "role") - @Mapping(target = "enabled", source = "enabled") - @Mapping(target = "accountCreated", ignore = true) // Preserve original creation time - @Mapping(target = "lastLogin", source = "lastLogin") - @Mapping(target = "accountLocked", source = "accountLocked") - @Mapping(target = "failedLoginAttempts", source = "failedLoginAttempts") - @Mapping(target = "password", ignore = true) // Security: never update password from DTO - @Mapping(target = "usagePoints", ignore = true) // Preserve existing relationships - @Mapping(target = "authorizations", ignore = true) // Preserve existing relationships - @Mapping(target = "published", ignore = true) - @Mapping(target = "updated", ignore = true) - void updateEntityFromDto(RetailCustomerDto dto, @MappingTarget RetailCustomerEntity entity); -} \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/AuthorizationRepository.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/AuthorizationRepository.java index ed060602..c5b89556 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/AuthorizationRepository.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/AuthorizationRepository.java @@ -36,7 +36,7 @@ public interface AuthorizationRepository extends JpaRepository findAllByRetailCustomerId(UUID retailCustomerId); + List findAllByRetailCustomerId(Long retailCustomerId); @Query("SELECT a.id FROM AuthorizationEntity a WHERE a.applicationInformation.id = :applicationInformationId") List findAllIdsByApplicationInformationId(@Param("applicationInformationId") UUID applicationInformationId); @@ -44,10 +44,10 @@ public interface AuthorizationRepository extends JpaRepository findByState(String state); @Query("SELECT a FROM AuthorizationEntity a WHERE a.scope = :scope AND a.retailCustomer.id = :retailCustomerId") - Optional findByScope(@Param("scope") String scope, @Param("retailCustomerId") UUID retailCustomerId); + Optional findByScope(@Param("scope") String scope, @Param("retailCustomerId") Long retailCustomerId); @Query("SELECT a.id FROM AuthorizationEntity a WHERE a.retailCustomer.id = :retailCustomerId") - List findAllIds(@Param("retailCustomerId") UUID retailCustomerId); + List findAllIds(@Param("retailCustomerId") Long retailCustomerId); // findById is already provided by JpaRepository // Optional findById(UUID id) is inherited @@ -77,6 +77,6 @@ public interface AuthorizationRepository extends JpaRepository findExpiredAuthorizations(@Param("currentTime") Long currentTime); @Query("SELECT a FROM AuthorizationEntity a WHERE a.retailCustomer.id = :customerId AND a.applicationInformation.id = :applicationId AND (a.expiresIn IS NULL OR a.expiresIn > :currentTime)") - List findActiveByCustomerAndApplication(@Param("customerId") UUID customerId, @Param("applicationId") UUID applicationId, @Param("currentTime") Long currentTime); + List findActiveByCustomerAndApplication(@Param("customerId") Long customerId, @Param("applicationId") UUID applicationId, @Param("currentTime") Long currentTime); } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/ElectricPowerQualitySummaryRepository.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/ElectricPowerQualitySummaryRepository.java index 223cdbb9..9af67fec 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/ElectricPowerQualitySummaryRepository.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/ElectricPowerQualitySummaryRepository.java @@ -59,9 +59,9 @@ public interface ElectricPowerQualitySummaryRepository extends JpaRepository findAllIdsByUsagePointId(@Param("usagePointId") UUID usagePointId); @Query("SELECT e.id FROM UsagePointEntity u, ElectricPowerQualitySummaryEntity e WHERE u.retailCustomer.id = :o1Id AND e.usagePoint.id = :o2Id") - List findAllIdsByXpath2(@Param("o1Id") UUID o1Id, @Param("o2Id") UUID o2Id); + List findAllIdsByXpath2(@Param("o1Id") Long o1Id, @Param("o2Id") UUID o2Id); @Query("SELECT e.id FROM UsagePointEntity u, ElectricPowerQualitySummaryEntity e WHERE u.retailCustomer.id = :o1Id AND e.usagePoint.id = :o2Id AND e.id = :o3Id") - Optional findIdByXpath(@Param("o1Id") UUID o1Id, @Param("o2Id") UUID o2Id, @Param("o3Id") UUID o3Id); + Optional findIdByXpath(@Param("o1Id") Long o1Id, @Param("o2Id") UUID o2Id, @Param("o3Id") UUID o3Id); } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/RetailCustomerRepository.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/RetailCustomerRepository.java index ea02e0c0..ba32cff7 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/RetailCustomerRepository.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/RetailCustomerRepository.java @@ -27,32 +27,30 @@ import java.util.List; import java.util.Optional; -import java.util.UUID; /** * Modern Spring Data JPA repository for RetailCustomer entities. + * RetailCustomer is an application-specific correlation table (not part of ESPI standard). * Replaces the legacy RetailCustomerRepositoryImpl with modern Spring Data patterns. */ @Repository -public interface RetailCustomerRepository extends JpaRepository { +public interface RetailCustomerRepository extends JpaRepository { // JpaRepository provides: save(), findById(), findAll(), deleteById(), etc. /** - * Find retail customer by username. + * Find retail customer by username (indexed). */ Optional findByUsername(String username); /** * Find retail customers by role. */ - @Query("SELECT rc FROM RetailCustomerEntity rc WHERE rc.role = :role") - List findByRole(@Param("role") String role); + List findByRole(String role); /** * Find enabled retail customers. */ - @Query("SELECT rc FROM RetailCustomerEntity rc WHERE rc.enabled = true") List findByEnabledTrue(); /** @@ -63,8 +61,7 @@ public interface RetailCustomerRepository extends JpaRepository findByFirstNameAndLastName(@Param("firstName") String firstName, @Param("lastName") String lastName); + List findByFirstNameAndLastName(String firstName, String lastName); /** * Check if username exists. @@ -77,10 +74,10 @@ public interface RetailCustomerRepository extends JpaRepository findAllIds(); + List findAllIds(); /** * Find retail customers created after timestamp. @@ -97,6 +94,5 @@ public interface RetailCustomerRepository extends JpaRepository findLockedAccounts(); + List findByAccountLockedTrue(); } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/SubscriptionRepository.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/SubscriptionRepository.java index d57de7de..e5d5448f 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/SubscriptionRepository.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/SubscriptionRepository.java @@ -55,7 +55,7 @@ public interface SubscriptionRepository extends JpaRepository findAllIds(); @Query("SELECT s FROM SubscriptionEntity s WHERE s.retailCustomer.id = :retailCustomerId") - List findByRetailCustomerId(@Param("retailCustomerId") UUID retailCustomerId); + List findByRetailCustomerId(@Param("retailCustomerId") Long retailCustomerId); @Query("SELECT s FROM SubscriptionEntity s WHERE s.applicationInformation.id = :applicationInformationId") List findByApplicationInformationId(@Param("applicationInformationId") UUID applicationInformationId); diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/UsagePointRepository.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/UsagePointRepository.java index c8dd51d5..043c7a3a 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/UsagePointRepository.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/UsagePointRepository.java @@ -43,7 +43,7 @@ public interface UsagePointRepository extends JpaRepository findAllByRetailCustomerId(@Param("retailCustomerId") UUID retailCustomerId); + List findAllByRetailCustomerId(@Param("retailCustomerId") Long retailCustomerId); /** * Find usage point by resource URI. @@ -67,7 +67,7 @@ public interface UsagePointRepository extends JpaRepository findAllIdsByRetailCustomerId(@Param("retailCustomerId") UUID retailCustomerId); + List findAllIdsByRetailCustomerId(@Param("retailCustomerId") Long retailCustomerId); /** * Find all usage point IDs. diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepository.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepository.java index 0280b1bc..c87c8966 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepository.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepository.java @@ -59,9 +59,9 @@ public interface UsageSummaryRepository extends JpaRepository findAllIdsByUsagePointId(@Param("usagePointId") UUID usagePointId); @Query("SELECT e.id FROM UsagePointEntity u, UsageSummaryEntity e WHERE u.retailCustomer.id = :o1Id AND e.usagePoint.id = :o2Id") - List findAllIdsByXpath2(@Param("o1Id") UUID o1Id, @Param("o2Id") UUID o2Id); + List findAllIdsByXpath2(@Param("o1Id") Long o1Id, @Param("o2Id") UUID o2Id); @Query("SELECT e.id FROM UsagePointEntity u, UsageSummaryEntity e WHERE u.retailCustomer.id = :o1Id AND e.usagePoint.id = :o2Id AND e.id = :o3Id") - Optional findIdByXpath(@Param("o1Id") UUID o1Id, @Param("o2Id") UUID o2Id, @Param("o3Id") UUID o3Id); + Optional findIdByXpath(@Param("o1Id") Long o1Id, @Param("o2Id") UUID o2Id, @Param("o3Id") UUID o3Id); } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/AuthorizationService.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/AuthorizationService.java index 23936c07..38408e7b 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/AuthorizationService.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/AuthorizationService.java @@ -28,7 +28,7 @@ public interface AuthorizationService { // residue from random stories - List findAllByRetailCustomerId(UUID retailCustomerId); + List findAllByRetailCustomerId(Long retailCustomerId); /** * @param applicationInformationId @@ -43,7 +43,7 @@ AuthorizationEntity createAuthorizationEntity(SubscriptionEntity subscription, AuthorizationEntity findByState(String state); - AuthorizationEntity findByScope(String scope, UUID retailCustomerId); + AuthorizationEntity findByScope(String scope, Long retailCustomerId); AuthorizationEntity findByAccessToken(String accessToken); @@ -68,7 +68,7 @@ AuthorizationEntity createAuthorizationEntity(SubscriptionEntity subscription, // import-exportResource services AuthorizationEntity importResource(InputStream stream); - AuthorizationEntity findById(UUID retailCustomerId, UUID authorizationId); + AuthorizationEntity findById(Long retailCustomerId, UUID authorizationId); AuthorizationEntity findByUUID(UUID uuid); diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/RetailCustomerService.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/RetailCustomerService.java index ff41884c..eabcf0bd 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/RetailCustomerService.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/RetailCustomerService.java @@ -20,31 +20,20 @@ package org.greenbuttonalliance.espi.common.service; import org.greenbuttonalliance.espi.common.domain.usage.RetailCustomerEntity; -import org.greenbuttonalliance.espi.common.domain.usage.SubscriptionEntity; -import java.io.InputStream; import java.util.List; -import java.util.UUID; +/** + * Service interface for RetailCustomer operations. + * RetailCustomer is an application-specific correlation table (not part of ESPI standard). + */ public interface RetailCustomerService { List findAll(); - RetailCustomerEntity findByHashedId(UUID retailCustomerId); + RetailCustomerEntity findById(Long retailCustomerId); RetailCustomerEntity save(RetailCustomerEntity customer); - RetailCustomerEntity findById(UUID retailCustomerId); - - RetailCustomerEntity findById(String retailCustomerId); - - void add(RetailCustomerEntity retailCustomer); - - void delete(RetailCustomerEntity retailCustomer); - - RetailCustomerEntity importResource(InputStream stream); - - SubscriptionEntity associateByUUID(UUID retailCustomerId, UUID uuId); - RetailCustomerEntity findByUsername(String username); } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/SubscriptionService.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/SubscriptionService.java index 63f9960d..201d5fb2 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/SubscriptionService.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/SubscriptionService.java @@ -45,6 +45,6 @@ public interface SubscriptionService { SubscriptionEntity addUsagePoint(SubscriptionEntity subscription, UsagePointEntity usagePoint); - UUID findRetailCustomerId(UUID subscriptionId, UUID usagePointId); + Long findRetailCustomerId(UUID subscriptionId, UUID usagePointId); } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/UsagePointService.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/UsagePointService.java index d99f6e74..c09b2d6e 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/UsagePointService.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/UsagePointService.java @@ -45,19 +45,19 @@ public interface UsagePointService { void deleteByHashedId(String usagePointHashedId); - List findAllIdsForRetailCustomer(UUID id); + List findAllIdsForRetailCustomer(Long id); String feedFor(List usagePoints) throws JAXBException; String entryFor(UsagePointEntity usagePoint); - List findAllByRetailCustomer(UUID retailCustomerId); + List findAllByRetailCustomer(Long retailCustomerId); UsagePointEntity save(UsagePointEntity usagePoint); UsagePointEntity findById(UUID usagePointId); - UsagePointEntity findById(UUID retailCustomerId, UUID usagePointId); + UsagePointEntity findById(Long retailCustomerId, UUID usagePointId); // Legacy EntryType methods removed - incompatible with Spring Boot 3.5 diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/AuthorizationServiceImpl.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/AuthorizationServiceImpl.java index 283004c4..f426b335 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/AuthorizationServiceImpl.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/AuthorizationServiceImpl.java @@ -50,7 +50,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { private final AuthorizationMapper authorizationMapper; @Override - public List findAllByRetailCustomerId(UUID retailCustomerId) { + public List findAllByRetailCustomerId(Long retailCustomerId) { return authorizationRepository .findAllByRetailCustomerId(retailCustomerId); } @@ -84,7 +84,7 @@ public AuthorizationEntity findByState(String state) { } @Override - public AuthorizationEntity findByScope(String scope, UUID retailCustomerId) { + public AuthorizationEntity findByScope(String scope, Long retailCustomerId) { return authorizationRepository.findByScope(scope, retailCustomerId).orElse(null); } @@ -173,7 +173,7 @@ public AuthorizationEntity importResource(InputStream stream) { } @Override - public AuthorizationEntity findById(UUID retailCustomerId, UUID authorizationId) { + public AuthorizationEntity findById(Long retailCustomerId, UUID authorizationId) { return this.authorizationRepository.findById(authorizationId).orElse(null); } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/RetailCustomerServiceImpl.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/RetailCustomerServiceImpl.java index f95cf783..6f3986eb 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/RetailCustomerServiceImpl.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/RetailCustomerServiceImpl.java @@ -22,20 +22,18 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.greenbuttonalliance.espi.common.domain.usage.RetailCustomerEntity; -import org.greenbuttonalliance.espi.common.domain.usage.SubscriptionEntity; -import org.greenbuttonalliance.espi.common.dto.usage.RetailCustomerDto; -import org.greenbuttonalliance.espi.common.mapper.usage.RetailCustomerMapper; import org.greenbuttonalliance.espi.common.repositories.usage.RetailCustomerRepository; import org.greenbuttonalliance.espi.common.service.RetailCustomerService; -import org.greenbuttonalliance.espi.common.service.UsagePointService; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.InputStream; import java.util.List; -import java.util.UUID; +/** + * Service implementation for RetailCustomer operations. + * RetailCustomer is an application-specific correlation table (not part of ESPI standard). + */ @Slf4j @Service @Transactional(rollbackFor = { jakarta.xml.bind.JAXBException.class }, noRollbackFor = { @@ -45,8 +43,6 @@ public class RetailCustomerServiceImpl implements RetailCustomerService { private final RetailCustomerRepository retailCustomerRepository; - private final RetailCustomerMapper retailCustomerMapper; - private final UsagePointService usagePointService; @Override public List findAll() { @@ -54,31 +50,13 @@ public List findAll() { } @Override - public RetailCustomerEntity save(RetailCustomerEntity customer) { - if (customer.getId() == null) { - customer.setId(UUID.randomUUID()); - } - return retailCustomerRepository.save(customer); - } - - @Override - public RetailCustomerEntity findById(UUID id) { + public RetailCustomerEntity findById(Long id) { return retailCustomerRepository.findById(id).orElse(null); } @Override - public RetailCustomerEntity findById(String retailCustomerId) { - try { - UUID id = UUID.fromString(retailCustomerId); - return retailCustomerRepository.findById(id).orElse(null); - } catch (IllegalArgumentException e) { - return null; - } - } - - @Override - public RetailCustomerEntity findByHashedId(UUID retailCustomerId) { - return findById(retailCustomerId); + public RetailCustomerEntity save(RetailCustomerEntity customer) { + return retailCustomerRepository.save(customer); } @Override @@ -91,52 +69,4 @@ public RetailCustomerEntity findByUsername(String username) { } } - @Override - public void add(RetailCustomerEntity retailCustomer) { - retailCustomerRepository.save(retailCustomer); - log.info("Added retail customer: " + retailCustomer.getId()); - } - - @Override - public void delete(RetailCustomerEntity retailCustomer) { - retailCustomerRepository.deleteById(retailCustomer.getId()); - log.info("Deleted retail customer: " + retailCustomer.getId()); - } - - @Override - public RetailCustomerEntity importResource(InputStream stream) { - try { - // Use JAXB to parse XML stream to DTO - jakarta.xml.bind.JAXBContext context = jakarta.xml.bind.JAXBContext.newInstance(RetailCustomerDto.class); - jakarta.xml.bind.Unmarshaller unmarshaller = context.createUnmarshaller(); - RetailCustomerDto dto = (RetailCustomerDto) unmarshaller.unmarshal(stream); - - // Convert DTO to Entity using mapper - RetailCustomerEntity entity = retailCustomerMapper.toEntity(dto); - - // Save and return entity - return retailCustomerRepository.save(entity); - - } catch (Exception e) { - // Security: Log error without exposing sensitive customer data - log.error("RetailCustomerService.importResource failed: " + e.getMessage()); - return null; - } - } - - @Override - public SubscriptionEntity associateByUUID(UUID retailCustomerId, UUID uuid) { - // TODO: Implement modern association logic using entity classes - log.info("Associating usage point UUID " + uuid + " with retail customer " + retailCustomerId); - - // Use the UsagePointService to handle the association - RetailCustomerEntity retailCustomer = findById(retailCustomerId); - if (retailCustomer != null) { - usagePointService.associateByUUID(retailCustomer, uuid); - } - - // TODO: Return appropriate subscription entity - return null; - } - } diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/SubscriptionServiceImpl.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/SubscriptionServiceImpl.java index a77acc61..88d57000 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/SubscriptionServiceImpl.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/SubscriptionServiceImpl.java @@ -147,8 +147,8 @@ public SubscriptionEntity addUsagePoint(SubscriptionEntity subscription, } @Override - public UUID findRetailCustomerId(UUID subscriptionId, UUID usagePointId) { - UUID result = null; + public Long findRetailCustomerId(UUID subscriptionId, UUID usagePointId) { + Long result = null; SubscriptionEntity subscription = findById(subscriptionId); if (subscription != null && subscription.getRetailCustomer() != null) { result = subscription.getRetailCustomer().getId(); diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/UsagePointServiceImpl.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/UsagePointServiceImpl.java index 6635c999..231b31f9 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/UsagePointServiceImpl.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/service/impl/UsagePointServiceImpl.java @@ -63,7 +63,7 @@ public UsagePointEntity findById(UUID usagePointId) { } @Override - public UsagePointEntity findById(UUID retailCustomerId, UUID usagePointId) { + public UsagePointEntity findById(Long retailCustomerId, UUID usagePointId) { // TODO: Implement scoped query for retailCustomer.usagePoint return usagePointRepository.findById(usagePointId).orElse(null); } @@ -119,7 +119,7 @@ public void deleteByHashedId(String usagePointHashedId) { } @Override - public List findAllIdsForRetailCustomer(UUID id) { + public List findAllIdsForRetailCustomer(Long id) { return usagePointRepository .findAllIdsByRetailCustomerId(id); } @@ -139,7 +139,7 @@ public String entryFor(UsagePointEntity usagePoint) { } @Override - public List findAllByRetailCustomer(UUID retailCustomerId) { + public List findAllByRetailCustomer(Long retailCustomerId) { return usagePointRepository.findAllByRetailCustomerId(retailCustomerId); } 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 9d1dbac4..11873a1d 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 @@ -137,39 +137,9 @@ CREATE TABLE application_information_scopes CREATE INDEX idx_app_info_scopes ON application_information_scopes (application_information_id); --- Retail Customer Table -CREATE TABLE retail_customers -( - id CHAR(36) PRIMARY KEY, - description VARCHAR(255), - created TIMESTAMP NOT NULL, - updated TIMESTAMP NOT NULL, - published TIMESTAMP, - up_link_rel VARCHAR(255), - up_link_href VARCHAR(1024), - up_link_type VARCHAR(255), - self_link_rel VARCHAR(255), - self_link_href VARCHAR(1024), - self_link_type VARCHAR(255), - - -- Retail customer specific fields - username VARCHAR(255) UNIQUE, - first_name VARCHAR(255), - last_name VARCHAR(255), - password VARCHAR(255), - enabled BOOLEAN DEFAULT TRUE, - role VARCHAR(50) DEFAULT 'ROLE_USER', - email VARCHAR(100), - phone VARCHAR(20), - account_created BIGINT, - last_login BIGINT, - account_locked BOOLEAN DEFAULT FALSE, - failed_login_attempts INTEGER DEFAULT 0 -); - -CREATE INDEX idx_retail_customer_username ON retail_customers (username); -CREATE INDEX idx_retail_customer_created ON retail_customers (created); -CREATE INDEX idx_retail_customer_updated ON retail_customers (updated); +-- Retail Customer Table - Moved to vendor-specific V2 migration files +-- RetailCustomer is an application-specific correlation table (not part of ESPI standard) +-- Table creation moved to V2 vendor files due to auto-increment syntax differences -- Service Delivery Point Table - Moved to vendor-specific V2 migration files -- ServiceDeliveryPoint extends Object (not IdentifiedObject) per ESPI 4.0 XSD (espi.xsd:1161) @@ -196,7 +166,7 @@ CREATE TABLE authorizations published_period_start BIGINT, published_period_duration BIGINT, application_information_id CHAR(36), - retail_customer_id CHAR(36), + retail_customer_id BIGINT, subscription_id CHAR(36), access_token VARCHAR(1024), refresh_token VARCHAR(1024), @@ -220,8 +190,8 @@ CREATE TABLE authorizations response_type VARCHAR(50), third_party VARCHAR(255), - FOREIGN KEY (application_information_id) REFERENCES application_information (id) ON DELETE CASCADE, - FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE + FOREIGN KEY (application_information_id) REFERENCES application_information (id) ON DELETE CASCADE + -- FK constraint for retail_customer_id added in V2 after retail_customers table is created ); CREATE INDEX idx_authorization_app_id ON authorizations (application_information_id); @@ -318,10 +288,10 @@ CREATE TABLE subscriptions -- Foreign key relationships application_information_id CHAR(36), authorization_id CHAR(36), - retail_customer_id CHAR(36), + retail_customer_id BIGINT, - FOREIGN KEY (application_information_id) REFERENCES application_information (id) ON DELETE CASCADE, - FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE + FOREIGN KEY (application_information_id) REFERENCES application_information (id) ON DELETE CASCADE + -- FK constraint for retail_customer_id added in V2 after retail_customers table is created ); CREATE INDEX idx_subscription_app_id ON subscriptions (application_information_id); diff --git a/openespi-common/src/main/resources/db/vendor/h2/V2__H2_Specific_Tables.sql b/openespi-common/src/main/resources/db/vendor/h2/V2__H2_Specific_Tables.sql index a57caec2..e6d334b1 100644 --- a/openespi-common/src/main/resources/db/vendor/h2/V2__H2_Specific_Tables.sql +++ b/openespi-common/src/main/resources/db/vendor/h2/V2__H2_Specific_Tables.sql @@ -135,12 +135,12 @@ CREATE TABLE usage_points rated_power_reading_type_ref VARCHAR(512), -- Foreign key relationships - retail_customer_id CHAR(36), + retail_customer_id BIGINT, service_delivery_point_id BIGINT, local_time_parameters_id CHAR(36), subscription_id CHAR(36), - FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE, + -- FK constraint for retail_customer_id added at end of V2 after retail_customers table is created FOREIGN KEY (service_delivery_point_id) REFERENCES service_delivery_points (id) ON DELETE SET NULL, FOREIGN KEY (local_time_parameters_id) REFERENCES time_configurations (id) ON DELETE SET NULL ); @@ -544,3 +544,43 @@ CREATE TABLE line_items CREATE INDEX idx_line_item_usage_summary ON line_items (usage_summary_id); CREATE INDEX idx_line_item_date_time ON line_items (date_time); CREATE INDEX idx_line_item_amount ON line_items (amount); + +-- Retail Customer Table (Application-specific correlation table) +-- RetailCustomer is an application-specific correlation table (not part of ESPI standard) +-- Used to correlate UsagePoint energy data to individual users +CREATE TABLE retail_customers +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + + -- Application correlation fields + username VARCHAR(255) UNIQUE, + first_name VARCHAR(255), + last_name VARCHAR(255), + password VARCHAR(255), + enabled BOOLEAN DEFAULT TRUE, + role VARCHAR(50) DEFAULT 'ROLE_USER', + email VARCHAR(100), + phone VARCHAR(20), + account_created BIGINT, + last_login BIGINT, + account_locked BOOLEAN DEFAULT FALSE, + failed_login_attempts INTEGER DEFAULT 0 +); + +-- Create index for authentication hot path +CREATE INDEX idx_retail_customer_username ON retail_customers (username); + +-- Add foreign key constraint for authorizations.retail_customer_id +-- (Column type changed to BIGINT in V1, FK constraint added here after retail_customers table exists) +ALTER TABLE authorizations ADD CONSTRAINT fk_authorization_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; + +-- Add foreign key constraint for subscriptions.retail_customer_id +-- (Column type changed to BIGINT in V1, FK constraint added here after retail_customers table exists) +ALTER TABLE subscriptions ADD CONSTRAINT fk_subscription_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; + +-- Add foreign key constraint for usage_points.retail_customer_id +-- (Column type changed to BIGINT, FK constraint added here after retail_customers table is created) +ALTER TABLE usage_points ADD CONSTRAINT fk_usage_point_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; diff --git a/openespi-common/src/main/resources/db/vendor/mysql/V2__MySQL_Specific_Tables.sql b/openespi-common/src/main/resources/db/vendor/mysql/V2__MySQL_Specific_Tables.sql index 2ec9948e..39d8383d 100644 --- a/openespi-common/src/main/resources/db/vendor/mysql/V2__MySQL_Specific_Tables.sql +++ b/openespi-common/src/main/resources/db/vendor/mysql/V2__MySQL_Specific_Tables.sql @@ -132,12 +132,12 @@ CREATE TABLE usage_points rated_power_reading_type_ref VARCHAR(512), -- Foreign key relationships - retail_customer_id CHAR(36), + retail_customer_id BIGINT, service_delivery_point_id BIGINT, local_time_parameters_id CHAR(36), subscription_id CHAR(36), - FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE, + -- FK constraint for retail_customer_id added at end of V2 after retail_customers table is created FOREIGN KEY (service_delivery_point_id) REFERENCES service_delivery_points (id) ON DELETE SET NULL, FOREIGN KEY (local_time_parameters_id) REFERENCES time_configurations (id) ON DELETE SET NULL, @@ -542,3 +542,43 @@ CREATE TABLE line_items INDEX idx_line_item_date_time (date_time), INDEX idx_line_item_amount (amount) ); + +-- Retail Customer Table (Application-specific correlation table) +-- RetailCustomer is an application-specific correlation table (not part of ESPI standard) +-- Used to correlate UsagePoint energy data to individual users +CREATE TABLE retail_customers +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + + -- Application correlation fields + username VARCHAR(255) UNIQUE, + first_name VARCHAR(255), + last_name VARCHAR(255), + password VARCHAR(255), + enabled BOOLEAN DEFAULT TRUE, + role VARCHAR(50) DEFAULT 'ROLE_USER', + email VARCHAR(100), + phone VARCHAR(20), + account_created BIGINT, + last_login BIGINT, + account_locked BOOLEAN DEFAULT FALSE, + failed_login_attempts INTEGER DEFAULT 0 +); + +-- Create index for authentication hot path +CREATE INDEX idx_retail_customer_username ON retail_customers (username); + +-- Add foreign key constraint for authorizations.retail_customer_id +-- (Column type changed to BIGINT in V1, FK constraint added here after retail_customers table exists) +ALTER TABLE authorizations ADD CONSTRAINT fk_authorization_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; + +-- Add foreign key constraint for subscriptions.retail_customer_id +-- (Column type changed to BIGINT in V1, FK constraint added here after retail_customers table exists) +ALTER TABLE subscriptions ADD CONSTRAINT fk_subscription_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; + +-- Add foreign key constraint for usage_points.retail_customer_id +-- (Column type changed to BIGINT, FK constraint added here after retail_customers table is created) +ALTER TABLE usage_points ADD CONSTRAINT fk_usage_point_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; diff --git a/openespi-common/src/main/resources/db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql b/openespi-common/src/main/resources/db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql index 3914b5a5..8649c391 100644 --- a/openespi-common/src/main/resources/db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql +++ b/openespi-common/src/main/resources/db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql @@ -133,12 +133,12 @@ CREATE TABLE usage_points rated_power_reading_type_ref VARCHAR(512), -- Foreign key relationships - retail_customer_id CHAR(36), + retail_customer_id BIGINT, service_delivery_point_id BIGINT, local_time_parameters_id CHAR(36), subscription_id CHAR(36), - FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE, + -- FK constraint for retail_customer_id added at end of V2 after retail_customers table is created FOREIGN KEY (service_delivery_point_id) REFERENCES service_delivery_points (id) ON DELETE SET NULL, FOREIGN KEY (local_time_parameters_id) REFERENCES time_configurations (id) ON DELETE SET NULL ); @@ -531,3 +531,43 @@ CREATE TABLE line_items CREATE INDEX idx_line_item_usage_summary ON line_items (usage_summary_id); CREATE INDEX idx_line_item_date_time ON line_items (date_time); CREATE INDEX idx_line_item_amount ON line_items (amount); + +-- Retail Customer Table (Application-specific correlation table) +-- RetailCustomer is an application-specific correlation table (not part of ESPI standard) +-- Used to correlate UsagePoint energy data to individual users +CREATE TABLE retail_customers +( + id BIGSERIAL PRIMARY KEY, + + -- Application correlation fields + username VARCHAR(255) UNIQUE, + first_name VARCHAR(255), + last_name VARCHAR(255), + password VARCHAR(255), + enabled BOOLEAN DEFAULT TRUE, + role VARCHAR(50) DEFAULT 'ROLE_USER', + email VARCHAR(100), + phone VARCHAR(20), + account_created BIGINT, + last_login BIGINT, + account_locked BOOLEAN DEFAULT FALSE, + failed_login_attempts INTEGER DEFAULT 0 +); + +-- Create index for authentication hot path +CREATE INDEX idx_retail_customer_username ON retail_customers (username); + +-- Add foreign key constraint for authorizations.retail_customer_id +-- (Column type changed to BIGINT in V1, FK constraint added here after retail_customers table exists) +ALTER TABLE authorizations ADD CONSTRAINT fk_authorization_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; + +-- Add foreign key constraint for subscriptions.retail_customer_id +-- (Column type changed to BIGINT in V1, FK constraint added here after retail_customers table exists) +ALTER TABLE subscriptions ADD CONSTRAINT fk_subscription_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; + +-- Add foreign key constraint for usage_points.retail_customer_id +-- (Column type changed to BIGINT, FK constraint added here after retail_customers table is created) +ALTER TABLE usage_points ADD CONSTRAINT fk_usage_point_retail_customer + FOREIGN KEY (retail_customer_id) REFERENCES retail_customers (id) ON DELETE CASCADE; diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/AuthorizationRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/AuthorizationRepositoryTest.java index 7a5ea7d3..b1a050a0 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/AuthorizationRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/AuthorizationRepositoryTest.java @@ -379,7 +379,7 @@ void shouldFindExpiredAuthorizations() { @DisplayName("Should handle empty results gracefully") void shouldHandleEmptyResultsGracefully() { // Act & Assert - assertThat(authorizationRepository.findAllByRetailCustomerId(UUID.randomUUID())).isEmpty(); + assertThat(authorizationRepository.findAllByRetailCustomerId(999999L)).isEmpty(); assertThat(authorizationRepository.findByState("nonexistent-state")).isEmpty(); assertThat(authorizationRepository.findByAccessToken("nonexistent-token")).isEmpty(); assertThat(authorizationRepository.findByRefreshToken("nonexistent-refresh-token")).isEmpty(); diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/RetailCustomerRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/RetailCustomerRepositoryTest.java index dd3b1ed7..20a82e41 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/RetailCustomerRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/RetailCustomerRepositoryTest.java @@ -32,15 +32,16 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.UUID; import static org.assertj.core.api.Assertions.*; /** * Comprehensive test suite for RetailCustomerRepository. - * - * Tests all CRUD operations, 11 custom query methods, relationships, + * + * Tests all CRUD operations, custom query methods, relationships, * and validation constraints for RetailCustomer entities. + * + * Note: RetailCustomer is an application-specific correlation table (not part of ESPI standard). */ @DisplayName("RetailCustomer Repository Tests") class RetailCustomerRepositoryTest extends BaseRepositoryTest { @@ -57,8 +58,9 @@ class CrudOperationsTest { void shouldSaveAndRetrieveRetailCustomerSuccessfully() { // Arrange RetailCustomerEntity retailCustomer = TestDataBuilders.createValidRetailCustomer(); - retailCustomer.setDescription("Test Retail Customer for CRUD"); retailCustomer.setUsername("testuser@example.com"); + retailCustomer.setFirstName("Test"); + retailCustomer.setLastName("User"); // Act RetailCustomerEntity saved = retailCustomerRepository.save(retailCustomer); @@ -69,8 +71,9 @@ void shouldSaveAndRetrieveRetailCustomerSuccessfully() { assertThat(saved).isNotNull(); assertThat(saved.getId()).isNotNull(); assertThat(retrieved).isPresent(); - assertThat(retrieved.get().getDescription()).isEqualTo("Test Retail Customer for CRUD"); assertThat(retrieved.get().getUsername()).isEqualTo("testuser@example.com"); + assertThat(retrieved.get().getFirstName()).isEqualTo("Test"); + assertThat(retrieved.get().getLastName()).isEqualTo("User"); assertThat(retrieved.get().getEnabled()).isTrue(); } @@ -99,7 +102,7 @@ void shouldDeleteRetailCustomerSuccessfully() { RetailCustomerEntity retailCustomer = TestDataBuilders.createValidRetailCustomer(); retailCustomer.setUsername("delete@example.com"); RetailCustomerEntity saved = retailCustomerRepository.save(retailCustomer); - UUID retailCustomerId = saved.getId(); + Long retailCustomerId = saved.getId(); flushAndClear(); // Act @@ -121,7 +124,7 @@ void shouldCheckIfRetailCustomerExists() { // Act & Assert assertThat(retailCustomerRepository.existsById(saved.getId())).isTrue(); - assertThat(retailCustomerRepository.existsById(UUID.randomUUID())).isFalse(); + assertThat(retailCustomerRepository.existsById(999999L)).isFalse(); } @Test @@ -324,7 +327,7 @@ void shouldFindAllRetailCustomerIds() { flushAndClear(); // Act - List allIds = retailCustomerRepository.findAllIds(); + List allIds = retailCustomerRepository.findAllIds(); // Assert assertThat(allIds).hasSizeGreaterThanOrEqualTo(3); @@ -413,7 +416,7 @@ void shouldFindLockedAccounts() { flushAndClear(); // Act - List lockedAccounts = retailCustomerRepository.findLockedAccounts(); + List lockedAccounts = retailCustomerRepository.findByAccountLockedTrue(); // Assert assertThat(lockedAccounts).hasSize(2); @@ -561,54 +564,45 @@ void shouldValidateRoleField() { } @Nested - @DisplayName("Base Class Functionality") - class BaseClassTest { + @DisplayName("Entity Functionality") + class EntityFunctionalityTest { @Test - @DisplayName("Should inherit IdentifiedObject functionality") - void shouldInheritIdentifiedObjectFunctionality() { - // Arrange & Act - RetailCustomerEntity retailCustomer = TestDataBuilders.createValidRetailCustomer(); - - // Assert - assertThat(retailCustomer.getId()).isNotNull(); - assertThat(retailCustomer.getId()).isInstanceOf(UUID.class); - // RetailCustomerEntity extends IdentifiedObject and inherits UUID functionality - } - - @Test - @DisplayName("Should set timestamps on persist") - void shouldSetTimestampsOnPersist() { + @DisplayName("Should auto-generate ID on persist") + void shouldAutoGenerateIdOnPersist() { // Arrange RetailCustomerEntity retailCustomer = TestDataBuilders.createValidRetailCustomer(); - retailCustomer.setUsername("timestamp@example.com"); + retailCustomer.setUsername("autoid@example.com"); + + // Verify ID is null before save + assertThat(retailCustomer.getId()).isNull(); // Act RetailCustomerEntity saved = retailCustomerRepository.save(retailCustomer); flushAndClear(); - // Assert - assertThat(saved.getCreated()).isNotNull(); - assertThat(saved.getUpdated()).isNotNull(); + // Assert - ID should be auto-generated by database + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getId()).isInstanceOf(Long.class); + assertThat(saved.getId()).isPositive(); } @Test - @DisplayName("Should test equals and hashCode") - void shouldTestEqualsAndHashCode() { + @DisplayName("Should test equals and hashCode with Long ID") + void shouldTestEqualsAndHashCodeWithLongId() { // Arrange - UUID sharedId = UUID.randomUUID(); - RetailCustomerEntity customer1 = TestDataBuilders.createValidRetailCustomer(); - customer1.setId(sharedId); customer1.setUsername("customer1@example.com"); - + RetailCustomerEntity savedCustomer1 = retailCustomerRepository.save(customer1); + flushAndClear(); + RetailCustomerEntity customer2 = TestDataBuilders.createValidRetailCustomer(); - customer2.setId(sharedId); + customer2.setId(savedCustomer1.getId()); customer2.setUsername("customer2@example.com"); - // Act & Assert - assertThat(customer1).isEqualTo(customer2); - assertThat(customer1.hashCode()).isEqualTo(customer2.hashCode()); + // Act & Assert - entities with same ID should be equal + assertThat(customer2).isEqualTo(savedCustomer1); + assertThat(customer2.hashCode()).isEqualTo(savedCustomer1.hashCode()); } @Test @@ -617,14 +611,16 @@ void shouldGenerateMeaningfulToStringRepresentation() { // Arrange RetailCustomerEntity retailCustomer = TestDataBuilders.createValidRetailCustomer(); retailCustomer.setUsername("tostring@example.com"); + RetailCustomerEntity saved = retailCustomerRepository.save(retailCustomer); + flushAndClear(); // Act - String toString = retailCustomer.toString(); + String toString = saved.toString(); // Assert assertThat(toString).isNotNull(); assertThat(toString).contains("RetailCustomerEntity"); - assertThat(toString).contains(retailCustomer.getId().toString()); + assertThat(toString).contains(saved.getId().toString()); } } } \ No newline at end of file diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/SubscriptionRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/SubscriptionRepositoryTest.java index 76933423..f4122e1b 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/SubscriptionRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/SubscriptionRepositoryTest.java @@ -551,7 +551,7 @@ void shouldHandleEmptyResultsGracefully() { // Act & Assert assertThat(subscriptionRepository.findByHashedId("nonexistent-hash")).isEmpty(); assertThat(subscriptionRepository.findByAuthorizationId(UUID.randomUUID())).isEmpty(); - assertThat(subscriptionRepository.findByRetailCustomerId(UUID.randomUUID())).isEmpty(); + assertThat(subscriptionRepository.findByRetailCustomerId(999999L)).isEmpty(); assertThat(subscriptionRepository.findByApplicationInformationId(UUID.randomUUID())).isEmpty(); assertThat(subscriptionRepository.findByUsagePointId(UUID.randomUUID())).isEmpty(); } diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsagePointRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsagePointRepositoryTest.java index 7daade4e..524869b2 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsagePointRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsagePointRepositoryTest.java @@ -370,10 +370,10 @@ void shouldDeleteUsagePointByUuid() { @DisplayName("Should handle empty results gracefully") void shouldHandleEmptyResultsGracefully() { // Act & Assert - assertThat(usagePointRepository.findAllByRetailCustomerId(UUID.randomUUID())).isEmpty(); + assertThat(usagePointRepository.findAllByRetailCustomerId(999999L)).isEmpty(); assertThat(usagePointRepository.findByResourceUri("nonexistent-uri")).isEmpty(); assertThat(usagePointRepository.findByRelatedHref("nonexistent-href")).isEmpty(); - assertThat(usagePointRepository.findAllIdsByRetailCustomerId(UUID.randomUUID())).isEmpty(); + assertThat(usagePointRepository.findAllIdsByRetailCustomerId(999999L)).isEmpty(); } } diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java index 18c375eb..552d330c 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java @@ -310,7 +310,7 @@ void shouldFindAllIdsByXpath2() { UsageSummaryEntity summary = createCompleteTestSetup(); UsageSummaryEntity saved = persistAndFlush(summary); - UUID retailCustomerId = summary.getUsagePoint().getRetailCustomer().getId(); + Long retailCustomerId = summary.getUsagePoint().getRetailCustomer().getId(); UUID usagePointId = summary.getUsagePoint().getId(); // Act @@ -327,7 +327,7 @@ void shouldFindIdByXpath() { UsageSummaryEntity summary = createCompleteTestSetup(); UsageSummaryEntity saved = persistAndFlush(summary); - UUID retailCustomerId = summary.getUsagePoint().getRetailCustomer().getId(); + Long retailCustomerId = summary.getUsagePoint().getRetailCustomer().getId(); UUID usagePointId = summary.getUsagePoint().getId(); UUID summaryId = saved.getId(); diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/test/TestDataBuilders.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/test/TestDataBuilders.java index 5a813793..c499ec23 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/test/TestDataBuilders.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/test/TestDataBuilders.java @@ -162,11 +162,11 @@ public static ReadingTypeEntity createValidReadingType() { /** * Creates a valid RetailCustomerEntity for testing. + * Note: RetailCustomer is an application-specific correlation table (not part of ESPI standard). */ public static RetailCustomerEntity createValidRetailCustomer() { RetailCustomerEntity retailCustomer = new RetailCustomerEntity(); - retailCustomer.setDescription(faker.lorem().sentence(4, 8)); - + // Ensure enabled is set properly (@NotNull constraint) retailCustomer.setEnabled(true); diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/custodian/AssociateUsagePointController.java b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/custodian/AssociateUsagePointController.java index 244d134e..54e2628c 100644 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/custodian/AssociateUsagePointController.java +++ b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/custodian/AssociateUsagePointController.java @@ -62,7 +62,7 @@ protected void initBinder(WebDataBinder binder) { } @GetMapping("/custodian/retailcustomers/{retailCustomerId}/usagepoints/form") - public String form(@PathVariable UUID retailCustomerId, ModelMap model) { + public String form(@PathVariable Long retailCustomerId, ModelMap model) { model.put("usagePointForm", new UsagePointEntityForm()); model.put("retailCustomerId", retailCustomerId); @@ -71,20 +71,21 @@ public String form(@PathVariable UUID retailCustomerId, ModelMap model) { @PostMapping("/custodian/retailcustomers/{retailCustomerId}/usagepoints/create") public String create( - @PathVariable UUID retailCustomerId, + @PathVariable Long retailCustomerId, @ModelAttribute("usagePointForm") @Valid UsagePointEntityForm usagePointForm, BindingResult result) { if (result.hasErrors()) return "/custodian/retailcustomers/usagepoints/form"; + // TODO: Implement usage point association - associateById method needs to be implemented // retailCustomerService returns legacy SubscriptionEntity, not SubscriptionEntityEntity - var subscription = retailCustomerService.associateByUUID( - retailCustomerId, UUID.fromString(usagePointForm.getUUID())); + // var subscription = retailCustomerService.associateById( + // retailCustomerId, UUID.fromString(usagePointForm.getUUID())); - if (subscription != null) { + // if (subscription != null) { // TODO: Implement NotificationService // notificationService.notify(subscription, null, null); - } + // } return "redirect:/custodian/retailcustomers"; } diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/custodian/RetailCustomerController.java b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/custodian/RetailCustomerController.java index f885484f..806dbeb1 100644 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/custodian/RetailCustomerController.java +++ b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/custodian/RetailCustomerController.java @@ -34,7 +34,6 @@ import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; -import java.util.UUID; @Controller @PreAuthorize("hasRole('ROLE_CUSTODIAN')") @@ -83,7 +82,7 @@ public String create( } @GetMapping("/custodian/retailcustomers/{retailCustomerId}/show") - public String show(@PathVariable UUID retailCustomerId, ModelMap model) { + public String show(@PathVariable Long retailCustomerId, ModelMap model) { RetailCustomerEntity retailCustomer = service.findById(retailCustomerId); model.put("retailCustomer", retailCustomer); return "/custodian/retailcustomers/show"; diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/MeterReadingRESTRepository.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/MeterReadingRESTRepository.java index 9a9b9a06..adab575e 100644 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/MeterReadingRESTRepository.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/MeterReadingRESTRepository.java @@ -25,6 +25,6 @@ import java.util.UUID; public interface MeterReadingRESTRepository { - MeterReadingEntity findByUUID(UUID retailCustomerId, UUID uuid) + MeterReadingEntity findByUUID(Long retailCustomerId, UUID uuid) throws JAXBException; } diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/UsagePointRESTRepository.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/UsagePointRESTRepository.java index 6e7dd54b..59cffaed 100755 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/UsagePointRESTRepository.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/UsagePointRESTRepository.java @@ -28,8 +28,8 @@ // TODO repository convergence with common // public interface UsagePointRESTRepository { - List findAllByRetailCustomerId(UUID id) throws JAXBException; + List findAllByRetailCustomerId(Long id) throws JAXBException; - UsagePointEntity findByHashedId(UUID retailCustomerId, String usagePointHashedId) + UsagePointEntity findByHashedId(Long retailCustomerId, String usagePointHashedId) throws JAXBException; } diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/MeterReadingRESTRepositoryImpl.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/MeterReadingRESTRepositoryImpl.java index 7db20705..5f87a934 100644 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/MeterReadingRESTRepositoryImpl.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/MeterReadingRESTRepositoryImpl.java @@ -48,7 +48,7 @@ public UsagePointRESTRepository getUsagePointRESTRepository( } @Override - public MeterReadingEntity findByUUID(UUID retailCustomerId, UUID uuid) + public MeterReadingEntity findByUUID(Long retailCustomerId, UUID uuid) throws JAXBException { List usagePointList = usagePointRESTRepository .findAllByRetailCustomerId(retailCustomerId); diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/UsagePointRESTRepositoryImpl.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/UsagePointRESTRepositoryImpl.java index 58817175..0c1043ff 100755 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/UsagePointRESTRepositoryImpl.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/UsagePointRESTRepositoryImpl.java @@ -100,7 +100,7 @@ public void setAuthorizationService( } @Override - public List findAllByRetailCustomerId(UUID retailCustomerId) + public List findAllByRetailCustomerId(Long retailCustomerId) throws JAXBException { AuthorizationEntity authorization = findAuthorization(retailCustomerId); @@ -129,7 +129,7 @@ public List findAllByRetailCustomerId(UUID retailCustomerId) } @Override - public UsagePointEntity findByHashedId(UUID retailCustomerId, + public UsagePointEntity findByHashedId(Long retailCustomerId, String usagePointHashedId) throws JAXBException { List usagePoints = findAllByRetailCustomerId(retailCustomerId); @@ -143,7 +143,7 @@ public UsagePointEntity findByHashedId(UUID retailCustomerId, } - private AuthorizationEntity findAuthorization(UUID retailCustomerId) { + private AuthorizationEntity findAuthorization(Long retailCustomerId) { List authorizations = authorizationService .findAllByRetailCustomerId(retailCustomerId); return authorizations.get(authorizations.size() - 1); diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/service/MeterReadingRESTService.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/service/MeterReadingRESTService.java index 8723b9d5..647c188d 100644 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/service/MeterReadingRESTService.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/service/MeterReadingRESTService.java @@ -26,6 +26,6 @@ public interface MeterReadingRESTService { - MeterReadingEntity findByUUID(UUID retailCustomerId, UUID uuid) + MeterReadingEntity findByUUID(Long retailCustomerId, UUID uuid) throws JAXBException; } diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/service/impl/MeterReadingRESTServiceImpl.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/service/impl/MeterReadingRESTServiceImpl.java index 283eddf8..076f10e9 100644 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/service/impl/MeterReadingRESTServiceImpl.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/service/impl/MeterReadingRESTServiceImpl.java @@ -34,7 +34,7 @@ public class MeterReadingRESTServiceImpl implements MeterReadingRESTService { protected MeterReadingRESTRepository repository; @Override - public MeterReadingEntity findByUUID(UUID retailCustomerId, UUID uuid) + public MeterReadingEntity findByUUID(Long retailCustomerId, UUID uuid) throws JAXBException { return repository.findByUUID(retailCustomerId, uuid); } diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/utils/factories/Factory.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/utils/factories/Factory.java index 624a19af..d5650263 100644 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/utils/factories/Factory.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/utils/factories/Factory.java @@ -36,7 +36,7 @@ public static UsagePointEntity newUsagePoint() { usagePoint.setServiceCategory(ServiceCategory.ELECTRICITY); RetailCustomerEntity retailCustomer = new RetailCustomerEntity(); - retailCustomer.setId(UUID.randomUUID()); + retailCustomer.setId(1000000L); usagePoint.setRetailCustomer(retailCustomer); usagePoint.getMeterReadings().add(newMeterReading()); diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/web/ModernAuthorizationController.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/web/ModernAuthorizationController.java index 345b4d3a..32ab20fa 100644 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/web/ModernAuthorizationController.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/web/ModernAuthorizationController.java @@ -128,7 +128,7 @@ public String authorizationCallback( @GetMapping("/authorizations") public String authorizationList(ModelMap model, Principal principal) { try { - UUID customerId = getCurrentCustomerId(principal); + Long customerId = getCurrentCustomerId(principal); var authorizations = authorizationService.findAllByRetailCustomerId(customerId); model.put("authorizationList", authorizations); return "/RetailCustomer/AuthorizationList/index"; @@ -246,7 +246,7 @@ private void markAuthorizationAsFailed(AuthorizationEntity authorization, String */ private void importInitialData(Principal principal) { try { - UUID customerId = getCurrentCustomerId(principal); + Long customerId = getCurrentCustomerId(principal); usagePointRESTRepository.findAllByRetailCustomerId(customerId); logger.debug("Successfully imported initial usage point data"); } catch (JAXBException e) { @@ -259,7 +259,7 @@ private void importInitialData(Principal principal) { /** * Gets the current customer ID from the authenticated principal. */ - private UUID getCurrentCustomerId(Principal principal) { + private Long getCurrentCustomerId(Principal principal) { if (principal instanceof Authentication auth) { var customer = retailCustomerService.findByUsername(auth.getName()); if (customer != null) { diff --git a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/web/custodian/RetailCustomerController.java b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/web/custodian/RetailCustomerController.java index 19032823..d9b0f3a3 100644 --- a/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/web/custodian/RetailCustomerController.java +++ b/openespi-thirdparty/src/main/java/org/greenbuttonalliance/espi/thirdparty/web/custodian/RetailCustomerController.java @@ -35,7 +35,6 @@ import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; -import java.util.UUID; @Controller @PreAuthorize("hasRole('ROLE_CUSTODIAN')") @@ -88,7 +87,7 @@ public String create( } @RequestMapping(value = "/custodian/retailcustomers/{retailCustomerId}", method = RequestMethod.GET) - public String show(@PathVariable UUID retailCustomerId, ModelMap model) { + public String show(@PathVariable Long retailCustomerId, ModelMap model) { RetailCustomerEntity retailCustomer = service.findById(retailCustomerId); model.put("retailCustomer", retailCustomer); return "/custodian/retailcustomers/show"; diff --git a/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/integration/repository/UsagePointRESTRepositoryIntegrationTest.java b/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/integration/repository/UsagePointRESTRepositoryIntegrationTest.java index a25700c9..8e484d5b 100644 --- a/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/integration/repository/UsagePointRESTRepositoryIntegrationTest.java +++ b/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/integration/repository/UsagePointRESTRepositoryIntegrationTest.java @@ -82,7 +82,7 @@ public void setUp() { mockAuthorization.setResourceURI("http://localhost:8080/DataCustodian/espi/1_1/resource/Batch/RetailCustomer/1/UsagePoint"); mockAuthorization.setAccessToken("test-access-token"); - when(authorizationService.findAllByRetailCustomerId(any(UUID.class))) + when(authorizationService.findAllByRetailCustomerId(any(Long.class))) .thenReturn(Collections.singletonList(mockAuthorization)); } @@ -118,7 +118,7 @@ public void testFindAllByRetailCustomerId_WithValidResponse() throws Exception { when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just(xmlResponse)); // Execute test - List result = usagePointRESTRepository.findAllByRetailCustomerId(UUID.randomUUID()); + List result = usagePointRESTRepository.findAllByRetailCustomerId(1000001L); // Verify results assertNotNull(result); @@ -130,7 +130,7 @@ public void testFindAllByRetailCustomerId_WithValidResponse() throws Exception { public void testFindByHashedId_WithExistingUsagePoint() throws Exception { // This test would require a more complete XML structure to work properly // For now, we'll test the method exists and handles null gracefully - UsagePointEntity result = usagePointRESTRepository.findByHashedId(UUID.randomUUID(), "test-hashed-id"); + UsagePointEntity result = usagePointRESTRepository.findByHashedId(1000004L, "test-hashed-id"); // Should return null when no matching usage points found assertNull(result); diff --git a/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/MeterReadingRESTRepositoryImplTests.java b/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/MeterReadingRESTRepositoryImplTests.java index a7e73b1a..8d81fe35 100644 --- a/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/MeterReadingRESTRepositoryImplTests.java +++ b/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/repository/impl/MeterReadingRESTRepositoryImplTests.java @@ -43,7 +43,7 @@ public void findByUUID() throws Exception { repository.setUsagePointRESTRepository(usagePointRESTRepository); // Create test data with UUID - UUID retailCustomerId = UUID.randomUUID(); + Long retailCustomerId = 1000002L; UUID meterReadingId = UUID.randomUUID(); // Create MeterReadingEntity diff --git a/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/service/impl/MeterReadingServiceImplTests.java b/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/service/impl/MeterReadingServiceImplTests.java index 9a3ae443..7a9daa50 100644 --- a/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/service/impl/MeterReadingServiceImplTests.java +++ b/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/service/impl/MeterReadingServiceImplTests.java @@ -47,7 +47,7 @@ public void before() { @Test public void findByUUID_returnsMeterReading() throws JAXBException { MeterReadingEntity meterReading = Factory.newMeterReading(); - UUID retailCustomerId = UUID.randomUUID(); + Long retailCustomerId = 1000003L; UUID meterReadingId = UUID.randomUUID(); when(repository.findByUUID(eq(retailCustomerId), eq(meterReadingId))).thenReturn(