/* * 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 extractPackMetadata(Set packMetadata) { Map metadata = new HashMap<>(); for (PackMetadata md : packMetadata) { metadata.put(md.getKey(), md.getValue()); } return metadata; } /** * getExpirationDateFromPack *

* 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 query = em.createNamedQuery("last-code-suffix-used-in-pack", Integer.class); query.setParameter("packId", packId); Integer lastCodeSuffix = query.getSingleResult(); return lastCodeSuffix == null ? 1 : lastCodeSuffix + 1; } }