/* * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. */ package net.curisit.securis.db; import java.io.Serializable; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; import jakarta.persistence.EntityManager; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.NamedQueries; import jakarta.persistence.NamedQuery; import jakarta.persistence.NoResultException; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.TypedQuery; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hibernate.annotations.Type; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import net.curisit.integrity.commons.Utils; import net.curisit.securis.db.common.CreationTimestampEntity; import net.curisit.securis.db.common.ModificationTimestampEntity; import net.curisit.securis.db.listeners.CreationTimestampListener; import net.curisit.securis.db.listeners.ModificationTimestampListener; import net.curisit.securis.services.exception.SeCurisServiceException; /** * License *
* Main license entity. Contains identity, ownership, timestamps and payload fields.
* Includes convenience JSON properties for related IDs/names.
*
* Mapping details:
* - Table: license
* - Listeners: CreationTimestampListener, ModificationTimestampListener
* - Named queries: license-by-code, license-by-activation-code, last-code-suffix-used-in-pack, ...
* - Status column uses custom Hibernate type: net.curisit.securis.db.common.LicenseStatusType
*
* @author JRA
* Last reviewed by JRA on Oct 5, 2025.
*/
@JsonAutoDetect
@JsonInclude(Include.NON_NULL)
@Entity
@EntityListeners({ CreationTimestampListener.class, ModificationTimestampListener.class })
@Table(name = "license")
@JsonIgnoreProperties(ignoreUnknown = true)
@NamedQueries({
@NamedQuery(name = "license-by-code", query = "SELECT l FROM License l where l.code = :code"),
@NamedQuery(name = "license-by-activation-code", query = "SELECT l FROM License l where l.activationCode = :activationCode"),
@NamedQuery(name = "last-code-suffix-used-in-pack", query = "SELECT max(l.codeSuffix) FROM License l where l.pack.id = :packId"),
@NamedQuery(name = "list-licenses-by-pack", query = "SELECT l FROM License l where l.pack.id = :packId"),
@NamedQuery(name = "list-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash"),
@NamedQuery(name = "list-active-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('AC', 'PA')"),
@NamedQuery(name = "list-valid-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('RE', 'AC', 'PA')")
})
public class License implements CreationTimestampEntity, ModificationTimestampEntity, Serializable {
private static final long serialVersionUID = 2700310404904877227L;
private static final Logger LOG = LogManager.getLogger(License.class);
// ------------------------------------------------------------------
// Columns & relations
// ------------------------------------------------------------------
@Id
@GeneratedValue
private Integer id;
private String code;
@Column(name = "metadata_obsolete")
@JsonProperty("metadata_obsolete")
private Boolean metadataObsolete;
@Column(name = "activation_code")
@JsonProperty("activation_code")
private String activationCode;
@Column(name = "code_suffix")
@JsonProperty("code_suffix")
private Integer codeSuffix;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "pack_id")
private Pack pack;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "created_by")
private User createdBy;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "cancelled_by")
private User cancelledBy;
@Type(type = "net.curisit.securis.db.common.LicenseStatusType")
private LicenseStatus status;
@Column(name = "full_name")
@JsonProperty("full_name")
private String fullName;
private String email;
@Column(name = "request_data")
@JsonProperty("request_data")
private String requestData;
/**
* Request data hash (not serialized). Automatically updated by setRequestData().
*/
@Column(name = "request_data_hash")
@JsonIgnore
private String reqDataHash;
@Column(name = "license_data")
@JsonProperty("license_data")
@JsonIgnore
// License data is delivered separately (e.g., file download). Not sent in list views.
private String licenseData;
@Column(name = "creation_timestamp")
@JsonProperty("creation_timestamp")
private Date creationTimestamp;
@Column(name = "modification_timestamp")
@JsonProperty("modification_timestamp")
private Date modificationTimestamp;
@Column(name = "last_access_timestamp")
@JsonProperty("last_access_timestamp")
private Date lastAccessTimestamp;
@Column(name = "expiration_date")
@JsonProperty("expiration_date")
private Date expirationDate;
private String comments;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "license")
@JsonIgnore
private List
* Return primary key.
*
* @return id
*/
public Integer getId() { return id; }
/**
* getCode
* Return human-readable license code.
*
* @return code
*/
public String getCode() { return code; }
/**
* setCode
* Set human-readable license code.
*
* @param code
*/
public void setCode(String code) { this.code = code; }
/**
* getCreationTimestamp
* Required by CreationTimestampEntity.
*
* @return creationTimestamp
*/
@Override
public Date getCreationTimestamp() { return creationTimestamp; }
/**
* setCreationTimestamp
* Set creation timestamp.
*
* @param creationTimestamp
*/
@Override
public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; }
/**
* getCreatedBy
* Return creator user (entity).
*
* @return user
*/
public User getCreatedBy() { return createdBy; }
/**
* setCreatedBy
* Set creator user (entity).
*
* @param user
*/
public void setCreatedBy(User createdBy) { this.createdBy = createdBy; }
/**
* getPack
* Return owning pack.
*
* @return pack
*/
public Pack getPack() { return pack; }
/**
* setPack
* Set owning pack.
*
* @param pack
*/
public void setPack(Pack pack) { this.pack = pack; }
/**
* getCreatedById
* Expose creator username as JSON.
*
* @return username
*/
@JsonProperty("created_by_id")
public String getCreatedById() { return createdBy == null ? null : createdBy.getUsername(); }
/**
* setCreatedById
* Setter by username for JSON binding.
*
* @param username
*/
@JsonProperty("created_by_id")
public void setCreatedById(String username) {
if (username == null) {
createdBy = null;
} else {
createdBy = new User();
createdBy.setUsername(username);
}
}
/**
* getCancelledById
* Expose cancelling user username as JSON.
*
* @return username
*/
@JsonProperty("cancelled_by_id")
public String getCancelledById() { return cancelledBy == null ? null : cancelledBy.getUsername(); }
/**
* setCancelledById
* Setter by username for JSON binding.
*
* @param username
*/
@JsonProperty("cancelled_by_id")
public void setCancelledById(String username) {
if (username == null) {
cancelledBy = null;
} else {
cancelledBy = new User();
cancelledBy.setUsername(username);
}
}
/**
* getPackCode
* Expose pack code for convenience.
*
* @return packCode
*/
@JsonProperty("pack_code")
public String getPackCode() { return pack == null ? null : pack.getCode(); }
/**
* getPackId
* Expose pack id for convenience.
*
* @return packId
*/
@JsonProperty("pack_id")
public Integer getPackId() { return pack == null ? null : pack.getId(); }
/**
* setPackId
* Setter by id for JSON binding (creates a shallow Pack).
*
* @param packId
*/
@JsonProperty("pack_id")
public void setPackId(Integer idPack) {
if (idPack == null) {
pack = null;
} else {
pack = new Pack();
pack.setId(idPack);
}
}
/**
* getStatus
* Return license status.
*
* @return licenseStatus
*/
public LicenseStatus getStatus() { return status; }
/**
* setStatus
* Set license status.
*
* @param status
*/
public void setStatus(LicenseStatus status) { this.status = status; }
/**
* getModificationTimestamp
* Required by ModificationTimestampEntity.
*
* @return modificationTimestamp
*/
@Override
public Date getModificationTimestamp() { return modificationTimestamp; }
/**
* setModificationTimestamp
* Set modification timestamp.
*
* @param modificationTimestamp
*/
@Override
public void setModificationTimestamp(Date modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; }
/**
* getFullName
* Return license holder full name.
*
* @return name
*/
public String getFullName() { return fullName; }
/**
* setFullName
* Set license holder full name.
*
* @param name
*/
public void setFullName(String fullName) { this.fullName = fullName; }
/**
* getEmail
* Return email address.
*
* @return email
*/
public String getEmail() { return email; }
/**
* setEmail
* Set email address.
*
* @param email
*/
public void setEmail(String email) { this.email = email; }
/**
* setId
* Set primary key (rarely used).
*
* @param id
*/
public void setId(Integer id) { this.id = id; }
/**
* getCancelledBy
* Return cancelling user (entity).
*
* @param user
*/
public User getCancelledBy() { return cancelledBy; }
/**
* setCancelledBy
* Set cancelling user (entity).
*
* @param cancelledBy
*/
public void setCancelledBy(User cancelledBy) { this.cancelledBy = cancelledBy; }
/**
* getLastAccessTimestamp
* Return last access timestamp.
*
* @return lastAccessTimestamp
*/
public Date getLastAccessTimestamp() { return lastAccessTimestamp; }
/**
* setLastAccessTimestamp
* Set last access timestamp.
*
* @param lastAccessTimestamp
*/
public void setLastAccessTimestamp(Date lastAccessTimestamp) { this.lastAccessTimestamp = lastAccessTimestamp; }
/**
* getRequestData
* Return raw request data.
*
* @return requestData
*/
public String getRequestData() { return requestData; }
/**
* setRequestData
* Set raw request data and recompute {@link #reqDataHash} immediately using
* the same hashing strategy as BlockedRequest (SHA-256).
*
* @param requestData
*/
public void setRequestData(String requestData) {
this.requestData = requestData;
this.reqDataHash = BlockedRequest.generateHash(this.requestData);
}
/**
* getLicenseData
* Return opaque license data (not serialized in lists).
*
* @return licenseData
*/
public String getLicenseData() { return licenseData; }
/**
* setLicenseData
* Set opaque license data (large content kept server-side).
*
* @param licenseDate
*/
public void setLicenseData(String licenseData) { this.licenseData = licenseData; }
/**
* getComments
* Return optional comments.
*
* @return comments
*/
public String getComments() { return comments; }
/**
* setComments
* Set optional comments.
*
* @param comments
*/
public void setComments(String comments) { this.comments = comments; }
/**
* getHistory
* Return change history entries (lazy).
*
* @return history
*/
public List
* Set change history entries.
*
* @param history
*/
public void setHistory(List
* Return expiration date (nullable).
*
* @return expirationDate
*/
public Date getExpirationDate() { return expirationDate; }
/**
* setExpirationDate
* Set expiration date (nullable).
*
* @param expirationDate
*/
public void setExpirationDate(Date expirationDate) { this.expirationDate = expirationDate; }
/**
* getReqDataHash
* Return cached hash of request data (not exposed in JSON).
*
* @return reqDataHash
*/
public String getReqDataHash() { return reqDataHash; }
/**
* getCodeSuffix
* Return numeric suffix of the code.
*
* @return codeSuffix
*/
public Integer getCodeSuffix() { return codeSuffix; }
/**
* setCodeSuffix
* Set numeric suffix of the code.
*
* @param codeSuffix
*/
public void setCodeSuffix(Integer codeSuffix) { this.codeSuffix = codeSuffix; }
/**
* getActivationCode
* Return activation code.
*
* @return activationCode
*/
public String getActivationCode() { return activationCode; }
/**
* setActivationCode
* Set activation code.
*
* @param activationCode
*/
public void setActivationCode(String activationCode) { this.activationCode = activationCode; }
/**
* isMetadataObsolete
* Convenience Boolean → primitive with null-safe false.
*
* @return isMetadataObsolete
*/
public boolean isMetadataObsolete() { return metadataObsolete != null && metadataObsolete; }
/**
* setMetadataObsolete
* Set metadata obsolete flag (nullable wrapper).
*
* @param obsolete
*/
public void setMetadataObsolete(Boolean obsolete) { this.metadataObsolete = obsolete; }
// ------------------------------------------------------------------
// Status transitions helpers
// ------------------------------------------------------------------
/**
* Action
* Actions to take with the license
*/
public static class Action {
public static final int CREATE = 1;
public static final int REQUEST = 2;
public static final int ACTIVATION = 3;
public static final int SEND = 4;
public static final int DOWNLOAD = 5;
public static final int CANCEL = 6;
public static final int DELETE = 7;
public static final int BLOCK = 8;
public static final int UNBLOCK = 9;
}
/**
* Status
* Status of the requested license
*/
public static class Status {
private static final Map
* Check whether an action is valid given the current license status.
*
* @param action action constant from {@link Action}
* @param currentStatus current license status
* @return true if allowed
*/
public static boolean isActionValid(Integer action, LicenseStatus currentStatus) {
List
* Return the first license in statuses RE/AC/PA for the given request data hash.
*
* @param requestData raw request data
* @param em entity manager
* @return matching license or null
* @throws SeCurisServiceException
*/
public static License findValidLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException {
TypedQuery
* Retrieve a license by its activation code.
*
* @param activationCode
* @param entityManager
* @return license
* @throws SeCurisServiceException
*/
public static License findLicenseByActivationCode(String activationCode, EntityManager em) throws SeCurisServiceException {
TypedQuery
* Return the first AC/PA license for a given requestData.
*
* @param requestData
* @param entityManager
* @return license
* @throws SeCurisServiceException
*/
public static License findActiveLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException {
TypedQuery
* Retrieve a license by human-readable code.
*
* @param code
* @param entityManager
* @return license
* @throws SeCurisServiceException
*/
public static License findLicenseByCode(String code, EntityManager em) throws SeCurisServiceException {
TypedQuery