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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,51 +24,62 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.type.SqlTypes;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
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 Subscription without JAXB concerns.
*
*
* Defines the parameters of a subscription between Third Party and Data
* Custodian. Represents a formal agreement allowing third-party applications
* to access specific usage points and energy data for a retail customer.
*
* <p>Key characteristics:</p>
* <ul>
* <li>Application-specific entity (NOT an ESPI standard resource)</li>
* <li>Uses UUID for primary key (indexed for API access)</li>
* <li>Links OAuth2 Authorization to accessible UsagePoints</li>
* <li>Does NOT extend IdentifiedObject (no selfLink/upLink in database)</li>
* <li>Atom Feed output handled by DTO layer with dynamic links</li>
* </ul>
*/
@Entity
@Table(name = "subscriptions", indexes = {
@Index(name = "idx_subscription_retail_customer", columnList = "retail_customer_id"),
@Index(name = "idx_subscription_application", columnList = "application_information_id"),
@Index(name = "idx_subscription_authorization", columnList = "authorization_id"),
@Index(name = "idx_subscription_last_update", columnList = "last_update")
@Index(name = "idx_subscription_authorization", columnList = "authorization_id")
})
@Getter
@Setter
@NoArgsConstructor
public class SubscriptionEntity extends IdentifiedObject {
public class SubscriptionEntity implements Serializable {

private static final long serialVersionUID = 1L;

/**
* UUID primary key for API access.
* Subscription is an application-specific entity, not an ESPI resource,
* but uses UUID for consistent API patterns.
*/
@Id
@JdbcTypeCode(SqlTypes.CHAR)
@Column(length = 36, columnDefinition = "char(36)", updatable = false, nullable = false)
private UUID id;

/**
* Optional hashed identifier for external references.
* Used for privacy and security in external communications.
*/
@Column(name = "hashed_id", length = 64)
private String hashedId;

/**
* Last update timestamp for this subscription.
* Tracks when the subscription configuration was last modified.
*/
@Column(name = "last_update")
private LocalDateTime lastUpdate;

/**
* Retail customer who owns this subscription.
* The customer whose data is being accessed through this subscription.
Expand Down Expand Up @@ -107,149 +118,42 @@ public class SubscriptionEntity extends IdentifiedObject {
)
private List<UsagePointEntity> usagePoints = new ArrayList<>();

/**
* Constructor with UUID.
* UUID5 should be generated by EspiIdGeneratorService.generateSubscriptionId().
*
* @param id the UUID5 identifier (must be provided, not generated here)
*/
public SubscriptionEntity(UUID id) {
this.id = id;
}

/**
* Constructor with basic subscription information.
*
* Note: ID must be set separately using UUID5 from EspiIdGeneratorService.
*
* @param retailCustomer the customer who owns the subscription
* @param applicationInformation the application accessing the data
*/
public SubscriptionEntity(RetailCustomerEntity retailCustomer, ApplicationInformationEntity applicationInformation) {
this.retailCustomer = retailCustomer;
this.applicationInformation = applicationInformation;
this.lastUpdate = LocalDateTime.now();
}

// Note: Simple setter for authorization is generated by Lombok @Data
// Complex bidirectional relationship management removed - handled by DataCustodian/ThirdParty applications

// Note: Usage point collection accessors are generated by Lombok @Data
// Bidirectional relationship management methods removed - handled by DataCustodian/ThirdParty applications

/**
* Updates the last update timestamp to current time.
*/
public void updateLastUpdate() {
this.lastUpdate = LocalDateTime.now();
}

/**
* Gets the last update time as LocalDateTime.
*
* @return last update as LocalDateTime, or null if not set
*/
public LocalDateTime getLastUpdateAsLocalDateTime() {
if (lastUpdate == null) {
return null;
}
return lastUpdate;
}

/**
* Sets the last update time from LocalDateTime.
*
* @param dateTime the LocalDateTime to set
*/
public void setLastUpdateFromLocalDateTime(LocalDateTime dateTime) {
this.lastUpdate = dateTime;
}

/**
* Gets the last update time as Instant.
*
* @return last update as Instant, or null if not set
*/
public Instant getLastUpdateAsInstant() {
return lastUpdate != null ? lastUpdate.toInstant(ZoneOffset.UTC): null;
}

/**
* Generates the self href for this subscription.
*
* @return self href string
*/
public String getSelfHref() {
return "/espi/1_1/resource/Subscription/" + getHashedId();
}

/**
* Generates the up href for this subscription.
*
* @return up href string
* Gets a string representation of the ID for href generation.
*
* @return string representation of the UUID
*/
public String getUpHref() {
return "/espi/1_1/resource/Subscription";
}

/**
* Overrides the default self href generation to use subscription specific logic.
*
* @return self href for this subscription
*/
@Override
protected String generateDefaultSelfHref() {
return getSelfHref();
}

/**
* Overrides the default up href generation to use subscription specific logic.
*
* @return up href for this subscription
*/
@Override
protected String generateDefaultUpHref() {
return getUpHref();
}

/**
* Merges data from another SubscriptionEntity.
* Updates subscription parameters while preserving critical relationships.
*
* @param other the other subscription entity to merge from
*/
public void merge(SubscriptionEntity other) {
if (other != null) {
super.merge(other);

// Update basic fields
this.hashedId = other.hashedId;
this.lastUpdate = other.lastUpdate;

// Update relationships if provided
if (other.applicationInformation != null) {
this.applicationInformation = other.applicationInformation;
}
if (other.authorization != null) {
this.authorization = other.authorization;
}
if (other.retailCustomer != null) {
this.retailCustomer = other.retailCustomer;
}
if (other.usagePoints != null) {
this.usagePoints = new ArrayList<>(other.usagePoints);
}
}
}

/**
* Clears all relationships when unlinking the entity.
* Simplified - applications handle relationship cleanup.
*/
public void unlink() {
clearRelatedLinks();

// Simple collection clearing - applications handle bidirectional cleanup
usagePoints.clear();

// Clear authorization with simple field assignment
this.authorization = null;

// Note: We don't clear retailCustomer or applicationInformation as they might be referenced elsewhere
public String getHashedId() {
// Return the explicit hashedId if set, otherwise use UUID string
return hashedId != null ? hashedId : (id != null ? id.toString() : null);
}

/**
* Checks if this subscription is active.
* A subscription is active if it has an active authorization.
*
*
* @return true if subscription is active, false otherwise
*/
public boolean isActive() {
Expand All @@ -259,7 +163,7 @@ public boolean isActive() {
/**
* Checks if this subscription has expired.
* A subscription is expired if its authorization has expired.
*
*
* @return true if subscription is expired, false otherwise
*/
public boolean isExpired() {
Expand All @@ -269,7 +173,7 @@ public boolean isExpired() {
/**
* Checks if this subscription is revoked.
* A subscription is revoked if its authorization is revoked.
*
*
* @return true if subscription is revoked, false otherwise
*/
public boolean isRevoked() {
Expand All @@ -278,7 +182,7 @@ public boolean isRevoked() {

/**
* Gets the number of usage points in this subscription.
*
*
* @return count of usage points
*/
public int getUsagePointCount() {
Expand All @@ -287,26 +191,18 @@ public int getUsagePointCount() {

/**
* Checks if this subscription includes the specified usage point.
*
*
* @param usagePoint the usage point to check
* @return true if included, false otherwise
*/
public boolean includesUsagePoint(UsagePointEntity usagePoint) {
return usagePoints != null && usagePoints.contains(usagePoint);
}

/**
* Checks if this subscription includes a usage point with the specified ID.
*
* @param usagePointId the usage point ID to check
* @return true if included, false otherwise
*/
// Note: includesUsagePointId() method removed - applications can implement custom lookup logic

/**
* Gets the subscription ID from a resource URI.
* Extracts the ID from URI patterns like "/espi/1_1/resource/Subscription/{id}".
*
*
* @param resourceURI the resource URI
* @return subscription ID, or null if not found
*/
Expand All @@ -320,41 +216,29 @@ public static String getSubscriptionIdFromUri(String resourceURI) {

/**
* Checks if this subscription belongs to the specified customer.
*
*
* @param customerId the customer ID to check
* @return true if belongs to customer, false otherwise
*/
public boolean belongsToCustomer(Long customerId) {
return retailCustomer != null && customerId != null &&
return retailCustomer != null && customerId != null &&
customerId.equals(retailCustomer.getId());
}

/**
* Checks if this subscription is for the specified application.
*
* @param applicationId the application ID to check
* @return true if for the application, false otherwise
*/
// Note: isForApplication() method removed - applications can implement custom lookup logic

/**
* Pre-persist callback to set default values.
* Pre-persist callback to validate required fields.
* UUID5 must be set by the service layer before persisting.
*
* @throws IllegalStateException if ID is not set
*/
@PrePersist
protected void onCreate() {
if (lastUpdate == null) {
lastUpdate = LocalDateTime.now();
if (id == null) {
throw new IllegalStateException(
"Subscription ID must be set using EspiIdGeneratorService.generateSubscriptionId() before persisting");
}
}

/**
* Pre-update callback to update the last update timestamp.
*/
@PreUpdate
protected void onUpdate() {
updateLastUpdate();
}

@Override
public final boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -375,11 +259,6 @@ public final int hashCode() {
public String toString() {
return getClass().getSimpleName() + "(" +
"id = " + getId() + ", " +
"hashedId = " + getHashedId() + ", " +
"lastUpdate = " + getLastUpdate() + ", " +
"description = " + getDescription() + ", " +
"created = " + getCreated() + ", " +
"updated = " + getUpdated() + ", " +
"published = " + getPublished() + ")";
"hashedId = " + getHashedId() + ")";
}
}
}
Loading
Loading