2424import lombok .Getter ;
2525import lombok .NoArgsConstructor ;
2626import lombok .Setter ;
27- import org .greenbuttonalliance . espi . common . domain . common . IdentifiedObject ;
27+ import org .hibernate . annotations . JdbcTypeCode ;
2828import org .hibernate .proxy .HibernateProxy ;
29+ import org .hibernate .type .SqlTypes ;
2930
30- import java .time .Instant ;
31- import java .time .LocalDateTime ;
32- import java .time .ZoneOffset ;
31+ import java .io .Serializable ;
3332import java .util .ArrayList ;
3433import java .util .List ;
3534import java .util .Objects ;
35+ import java .util .UUID ;
3636
3737/**
3838 * Pure JPA/Hibernate entity for Subscription without JAXB concerns.
39- *
39+ *
4040 * Defines the parameters of a subscription between Third Party and Data
4141 * Custodian. Represents a formal agreement allowing third-party applications
4242 * to access specific usage points and energy data for a retail customer.
43+ *
44+ * <p>Key characteristics:</p>
45+ * <ul>
46+ * <li>Application-specific entity (NOT an ESPI standard resource)</li>
47+ * <li>Uses UUID for primary key (indexed for API access)</li>
48+ * <li>Links OAuth2 Authorization to accessible UsagePoints</li>
49+ * <li>Does NOT extend IdentifiedObject (no selfLink/upLink in database)</li>
50+ * <li>Atom Feed output handled by DTO layer with dynamic links</li>
51+ * </ul>
4352 */
4453@ Entity
4554@ Table (name = "subscriptions" , indexes = {
4655 @ Index (name = "idx_subscription_retail_customer" , columnList = "retail_customer_id" ),
4756 @ Index (name = "idx_subscription_application" , columnList = "application_information_id" ),
48- @ Index (name = "idx_subscription_authorization" , columnList = "authorization_id" ),
49- @ Index (name = "idx_subscription_last_update" , columnList = "last_update" )
57+ @ Index (name = "idx_subscription_authorization" , columnList = "authorization_id" )
5058})
5159@ Getter
5260@ Setter
5361@ NoArgsConstructor
54- public class SubscriptionEntity extends IdentifiedObject {
62+ public class SubscriptionEntity implements Serializable {
5563
5664 private static final long serialVersionUID = 1L ;
5765
66+ /**
67+ * UUID primary key for API access.
68+ * Subscription is an application-specific entity, not an ESPI resource,
69+ * but uses UUID for consistent API patterns.
70+ */
71+ @ Id
72+ @ JdbcTypeCode (SqlTypes .CHAR )
73+ @ Column (length = 36 , columnDefinition = "char(36)" , updatable = false , nullable = false )
74+ private UUID id ;
75+
5876 /**
5977 * Optional hashed identifier for external references.
6078 * Used for privacy and security in external communications.
6179 */
6280 @ Column (name = "hashed_id" , length = 64 )
6381 private String hashedId ;
6482
65- /**
66- * Last update timestamp for this subscription.
67- * Tracks when the subscription configuration was last modified.
68- */
69- @ Column (name = "last_update" )
70- private LocalDateTime lastUpdate ;
71-
7283 /**
7384 * Retail customer who owns this subscription.
7485 * The customer whose data is being accessed through this subscription.
@@ -107,149 +118,42 @@ public class SubscriptionEntity extends IdentifiedObject {
107118 )
108119 private List <UsagePointEntity > usagePoints = new ArrayList <>();
109120
121+ /**
122+ * Constructor with UUID.
123+ * UUID5 should be generated by EspiIdGeneratorService.generateSubscriptionId().
124+ *
125+ * @param id the UUID5 identifier (must be provided, not generated here)
126+ */
127+ public SubscriptionEntity (UUID id ) {
128+ this .id = id ;
129+ }
130+
110131 /**
111132 * Constructor with basic subscription information.
112- *
133+ * Note: ID must be set separately using UUID5 from EspiIdGeneratorService.
134+ *
113135 * @param retailCustomer the customer who owns the subscription
114136 * @param applicationInformation the application accessing the data
115137 */
116138 public SubscriptionEntity (RetailCustomerEntity retailCustomer , ApplicationInformationEntity applicationInformation ) {
117139 this .retailCustomer = retailCustomer ;
118140 this .applicationInformation = applicationInformation ;
119- this .lastUpdate = LocalDateTime .now ();
120- }
121-
122- // Note: Simple setter for authorization is generated by Lombok @Data
123- // Complex bidirectional relationship management removed - handled by DataCustodian/ThirdParty applications
124-
125- // Note: Usage point collection accessors are generated by Lombok @Data
126- // Bidirectional relationship management methods removed - handled by DataCustodian/ThirdParty applications
127-
128- /**
129- * Updates the last update timestamp to current time.
130- */
131- public void updateLastUpdate () {
132- this .lastUpdate = LocalDateTime .now ();
133- }
134-
135- /**
136- * Gets the last update time as LocalDateTime.
137- *
138- * @return last update as LocalDateTime, or null if not set
139- */
140- public LocalDateTime getLastUpdateAsLocalDateTime () {
141- if (lastUpdate == null ) {
142- return null ;
143- }
144- return lastUpdate ;
145- }
146-
147- /**
148- * Sets the last update time from LocalDateTime.
149- *
150- * @param dateTime the LocalDateTime to set
151- */
152- public void setLastUpdateFromLocalDateTime (LocalDateTime dateTime ) {
153- this .lastUpdate = dateTime ;
154- }
155-
156- /**
157- * Gets the last update time as Instant.
158- *
159- * @return last update as Instant, or null if not set
160- */
161- public Instant getLastUpdateAsInstant () {
162- return lastUpdate != null ? lastUpdate .toInstant (ZoneOffset .UTC ): null ;
163- }
164-
165- /**
166- * Generates the self href for this subscription.
167- *
168- * @return self href string
169- */
170- public String getSelfHref () {
171- return "/espi/1_1/resource/Subscription/" + getHashedId ();
172141 }
173142
174143 /**
175- * Generates the up href for this subscription .
176- *
177- * @return up href string
144+ * Gets a string representation of the ID for href generation .
145+ *
146+ * @return string representation of the UUID
178147 */
179- public String getUpHref () {
180- return "/espi/1_1/resource/Subscription" ;
181- }
182-
183- /**
184- * Overrides the default self href generation to use subscription specific logic.
185- *
186- * @return self href for this subscription
187- */
188- @ Override
189- protected String generateDefaultSelfHref () {
190- return getSelfHref ();
191- }
192-
193- /**
194- * Overrides the default up href generation to use subscription specific logic.
195- *
196- * @return up href for this subscription
197- */
198- @ Override
199- protected String generateDefaultUpHref () {
200- return getUpHref ();
201- }
202-
203- /**
204- * Merges data from another SubscriptionEntity.
205- * Updates subscription parameters while preserving critical relationships.
206- *
207- * @param other the other subscription entity to merge from
208- */
209- public void merge (SubscriptionEntity other ) {
210- if (other != null ) {
211- super .merge (other );
212-
213- // Update basic fields
214- this .hashedId = other .hashedId ;
215- this .lastUpdate = other .lastUpdate ;
216-
217- // Update relationships if provided
218- if (other .applicationInformation != null ) {
219- this .applicationInformation = other .applicationInformation ;
220- }
221- if (other .authorization != null ) {
222- this .authorization = other .authorization ;
223- }
224- if (other .retailCustomer != null ) {
225- this .retailCustomer = other .retailCustomer ;
226- }
227- if (other .usagePoints != null ) {
228- this .usagePoints = new ArrayList <>(other .usagePoints );
229- }
230- }
231- }
232-
233- /**
234- * Clears all relationships when unlinking the entity.
235- * Simplified - applications handle relationship cleanup.
236- */
237- public void unlink () {
238- clearRelatedLinks ();
239-
240- // Simple collection clearing - applications handle bidirectional cleanup
241- usagePoints .clear ();
242-
243- // Clear authorization with simple field assignment
244- this .authorization = null ;
245-
246- // Note: We don't clear retailCustomer or applicationInformation as they might be referenced elsewhere
148+ public String getHashedId () {
149+ // Return the explicit hashedId if set, otherwise use UUID string
150+ return hashedId != null ? hashedId : (id != null ? id .toString () : null );
247151 }
248152
249153 /**
250154 * Checks if this subscription is active.
251155 * A subscription is active if it has an active authorization.
252- *
156+ *
253157 * @return true if subscription is active, false otherwise
254158 */
255159 public boolean isActive () {
@@ -259,7 +163,7 @@ public boolean isActive() {
259163 /**
260164 * Checks if this subscription has expired.
261165 * A subscription is expired if its authorization has expired.
262- *
166+ *
263167 * @return true if subscription is expired, false otherwise
264168 */
265169 public boolean isExpired () {
@@ -269,7 +173,7 @@ public boolean isExpired() {
269173 /**
270174 * Checks if this subscription is revoked.
271175 * A subscription is revoked if its authorization is revoked.
272- *
176+ *
273177 * @return true if subscription is revoked, false otherwise
274178 */
275179 public boolean isRevoked () {
@@ -278,7 +182,7 @@ public boolean isRevoked() {
278182
279183 /**
280184 * Gets the number of usage points in this subscription.
281- *
185+ *
282186 * @return count of usage points
283187 */
284188 public int getUsagePointCount () {
@@ -287,26 +191,18 @@ public int getUsagePointCount() {
287191
288192 /**
289193 * Checks if this subscription includes the specified usage point.
290- *
194+ *
291195 * @param usagePoint the usage point to check
292196 * @return true if included, false otherwise
293197 */
294198 public boolean includesUsagePoint (UsagePointEntity usagePoint ) {
295199 return usagePoints != null && usagePoints .contains (usagePoint );
296200 }
297201
298- /**
299- * Checks if this subscription includes a usage point with the specified ID.
300- *
301- * @param usagePointId the usage point ID to check
302- * @return true if included, false otherwise
303- */
304- // Note: includesUsagePointId() method removed - applications can implement custom lookup logic
305-
306202 /**
307203 * Gets the subscription ID from a resource URI.
308204 * Extracts the ID from URI patterns like "/espi/1_1/resource/Subscription/{id}".
309- *
205+ *
310206 * @param resourceURI the resource URI
311207 * @return subscription ID, or null if not found
312208 */
@@ -320,41 +216,29 @@ public static String getSubscriptionIdFromUri(String resourceURI) {
320216
321217 /**
322218 * Checks if this subscription belongs to the specified customer.
323- *
219+ *
324220 * @param customerId the customer ID to check
325221 * @return true if belongs to customer, false otherwise
326222 */
327223 public boolean belongsToCustomer (Long customerId ) {
328- return retailCustomer != null && customerId != null &&
224+ return retailCustomer != null && customerId != null &&
329225 customerId .equals (retailCustomer .getId ());
330226 }
331227
332228 /**
333- * Checks if this subscription is for the specified application.
334- *
335- * @param applicationId the application ID to check
336- * @return true if for the application, false otherwise
337- */
338- // Note: isForApplication() method removed - applications can implement custom lookup logic
339-
340- /**
341- * Pre-persist callback to set default values.
229+ * Pre-persist callback to validate required fields.
230+ * UUID5 must be set by the service layer before persisting.
231+ *
232+ * @throws IllegalStateException if ID is not set
342233 */
343234 @ PrePersist
344235 protected void onCreate () {
345- if (lastUpdate == null ) {
346- lastUpdate = LocalDateTime .now ();
236+ if (id == null ) {
237+ throw new IllegalStateException (
238+ "Subscription ID must be set using EspiIdGeneratorService.generateSubscriptionId() before persisting" );
347239 }
348240 }
349241
350- /**
351- * Pre-update callback to update the last update timestamp.
352- */
353- @ PreUpdate
354- protected void onUpdate () {
355- updateLastUpdate ();
356- }
357-
358242 @ Override
359243 public final boolean equals (Object o ) {
360244 if (this == o ) return true ;
@@ -375,11 +259,6 @@ public final int hashCode() {
375259 public String toString () {
376260 return getClass ().getSimpleName () + "(" +
377261 "id = " + getId () + ", " +
378- "hashedId = " + getHashedId () + ", " +
379- "lastUpdate = " + getLastUpdate () + ", " +
380- "description = " + getDescription () + ", " +
381- "created = " + getCreated () + ", " +
382- "updated = " + getUpdated () + ", " +
383- "published = " + getPublished () + ")" ;
262+ "hashedId = " + getHashedId () + ")" ;
384263 }
385- }
264+ }
0 commit comments