/* * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. */ package net.curisit.securis.services.helpers; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import net.curisit.integrity.exception.CurisRuntimeException; import net.curisit.securis.beans.LicenseBean; import net.curisit.securis.db.License; import net.curisit.securis.db.LicenseHistory; import net.curisit.securis.db.LicenseStatus; import net.curisit.securis.db.Pack; import net.curisit.securis.db.PackMetadata; import net.curisit.securis.db.User; import net.curisit.securis.security.BasicSecurityContext; import net.curisit.securis.services.exception.SeCurisServiceException; import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes; /** * LicenseHelper *
* Stateless utility component for license lifecycle operations and helpers: * - cancelation with history auditing * - license resolution and validity checks * - license file generation in a temp directory * - metadata extraction and expiration date computation from packs * - sequential code suffix allocation per pack * * Thread-safety: ApplicationScoped, stateless. * * @author JRA * Last reviewed by JRA on Oct 5, 2025. */ @ApplicationScoped public class LicenseHelper { @SuppressWarnings("unused") private static final Logger LOG = LogManager.getLogger(LicenseHelper.class); /** Milliseconds per day (used to derive expiration dates). */ private static final long MS_PER_DAY = 24L * 3600L * 1000L; @Inject private UserHelper userHelper; /** * cancelLicense *
* Transitions a license to CANCELLED, records who canceled it and why, * and appends a {@link LicenseHistory} entry. * * @param lic Target license (managed). * @param reason Human-readable cancellation reason (auditable). * @param bsc Current security context (used to identify the user). * @param em Entity manager to persist changes. * @throws SeCurisServiceException never thrown here, declared for symmetry with callers. */ public void cancelLicense(License lic, String reason, BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException { lic.setStatus(LicenseStatus.CANCELLED); lic.setCancelledById(bsc.getUserPrincipal().getName()); lic.setModificationTimestamp(new Date()); em.persist(lic); em.persist(createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.CANCEL, "Cancellation reason: " + reason)); } /** * getActiveLicenseFromDB *
* Resolve license by code and verify that it's ACTIVE or PRE_ACTIVE. * * @param licBean License bean containing the code to check. * @param em EntityManager for DB access. * @return The managed {@link License} instance. * @throws SeCurisServiceException if code not found or license not in an active-ish state. */ public License getActiveLicenseFromDB(LicenseBean licBean, EntityManager em) throws SeCurisServiceException { License lic = License.findLicenseByCode(licBean.getLicenseCode(), em); if (lic == null) { throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license code doesn't exist"); } if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) { throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license in not active"); } return lic; } /** * createLicenseHistoryAction *
* Helper to build a {@link LicenseHistory} entry. * * @param lic License affected. * @param user User performing the action. * @param action Action code (see {@link LicenseHistory.Actions}). * @param comments Optional comments, can be null. * @return transient {@link LicenseHistory} ready to persist. */ public LicenseHistory createLicenseHistoryAction(License lic, User user, String action, String comments) { LicenseHistory lh = new LicenseHistory(); lh.setLicense(lic); lh.setUser(user); lh.setCreationTimestamp(new Date()); lh.setAction(action); lh.setComments(comments); return lh; } /** * createLicenseHistoryAction *
* Overload without comments. * * @param lic License affected. * @param user User performing the action. * @param action Action code. * @return transient {@link LicenseHistory}. */ public LicenseHistory createLicenseHistoryAction(License lic, User user, String action) { return createLicenseHistoryAction(lic, user, action, null); } /** * createTemporaryLicenseFile *
* Materializes the license payload into a temporary file for emailing/download. * The file is created under a unique temporary directory. * * Caller is responsible for deleting the file and its parent directory. * * @param lic License whose JSON/XML/text payload is in {@code getLicenseData()}. * @param licFileName Desired file name (e.g. "license.lic"). * @return A {@link File} pointing to the newly created file. * @throws IOException If the temporary directory or file cannot be created/written. */ public File createTemporaryLicenseFile(License lic, String licFileName) throws IOException { File f = Files.createTempDirectory("securis-server").toFile(); f = new File(f, licFileName); FileUtils.writeStringToFile(f, lic.getLicenseData(), StandardCharsets.UTF_8); return f; } /** * extractPackMetadata *
* Converts pack metadata set to a map for license generation.
*
* @param packMetadata Set of {@link PackMetadata}.
* @return Map with keys/values copied from metadata entries.
*/
public Map
* Computes license expiration date depending on action type:
* - Pre-activation: {@code preactivationValidPeriod} days from now.
* - Renew/Activation: min(renewValidPeriod days, pack end date - now).
* Fails fast if pack end date is already in the past.
*
* @param pack Pack with policy data.
* @param isPreActivation Whether the operation is a pre-activation.
* @return Calculated expiration {@link Date}.
* @throws CurisRuntimeException if the pack's end date is in the past.
*/
public Date getExpirationDateFromPack(Pack pack, boolean isPreActivation) {
Long validPeriod;
if (pack.getEndValidDate().before(new Date())) {
throw new CurisRuntimeException("Pack end valid period is reached, no new licenses can be activated.");
}
if (isPreActivation) {
validPeriod = pack.getPreactivationValidPeriod() * MS_PER_DAY;
} else {
if (pack.getRenewValidPeriod() <= 0) {
return pack.getEndValidDate();
}
long renewPeriod = pack.getRenewValidPeriod() * MS_PER_DAY;
long expirationPeriod = pack.getEndValidDate().getTime() - new Date().getTime();
validPeriod = renewPeriod < expirationPeriod ? renewPeriod : expirationPeriod;
}
Date expirationDate = new Date(new Date().getTime() + validPeriod);
return expirationDate;
}
/**
* getNextCodeSuffix
*
* Retrieves the last used code suffix for a given pack and returns the next one.
* If none found, returns 1.
*
* @param packId Pack identifier.
* @param em EntityManager to query the DB.
* @return Next sequential suffix (>= 1).
*/
public int getNextCodeSuffix(int packId, EntityManager em) {
TypedQuery