From 146a0fb8b0e90f9196e569152f649baf60d6cc8f Mon Sep 17 00:00:00 2001
From: Joaquín Reñé <jrene@curisit.net>
Date: Tue, 07 Oct 2025 14:52:57 +0000
Subject: [PATCH] #4410 - Comments on classes

---
 securis/src/main/java/net/curisit/securis/services/ApiResource.java |  840 +++++++++++++++++++++++++++++++----------------------------
 1 files changed, 444 insertions(+), 396 deletions(-)

diff --git a/securis/src/main/java/net/curisit/securis/services/ApiResource.java b/securis/src/main/java/net/curisit/securis/services/ApiResource.java
index 2718e51..07da3d2 100644
--- a/securis/src/main/java/net/curisit/securis/services/ApiResource.java
+++ b/securis/src/main/java/net/curisit/securis/services/ApiResource.java
@@ -1,3 +1,6 @@
+/*
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
+*/
 package net.curisit.securis.services;
 
 import java.io.IOException;
@@ -49,446 +52,491 @@
 import net.curisit.securis.utils.TokenHelper;
 
 /**
- * External API to be accessed by third parties
- * 
- * @author roberto <roberto.sanchez@curisit.net>
- */
+* ApiResource
+* <p>
+* External API for license operations, intended for third-party clients.
+*
+* Endpoints:
+* - GET  /api/            -> Plain-text status with date (health check).
+* - GET  /api/ping        -> JSON status (message + date).
+* - POST /api/request     -> Create license from RequestBean (JSON).
+* - POST /api/request     -> Create license from request file (multipart).
+* - POST /api/renew       -> Renew from previous LicenseBean (JSON).
+* - POST /api/renew       -> Renew from previous license file (multipart).
+* - POST /api/validate    -> Server-side validation of a license.
+*
+* Security:
+* - Methods that mutate/inspect licenses require {@link Securable} with role {@link Rol#API_CLIENT}.
+* - {@link EnsureTransaction} ensures transaction handling at the filter/interceptor layer.
+*
+* Errors:
+* - Business errors are mapped to {@link SeCurisServiceException} with {@link ErrorCodes}.
+* 
+* @author JRA
+* Last reviewed by JRA on Oct 5, 2025.
+*/
 @Path("/api")
 public class ApiResource {
 
-	private static final Logger LOG = LogManager.getLogger(ApiResource.class);
+    private static final Logger LOG = LogManager.getLogger(ApiResource.class);
 
-	@Inject
-	TokenHelper tokenHelper;
+    @Inject TokenHelper tokenHelper;
+    @Inject private LicenseHelper licenseHelper;
+    @Context EntityManager em;
+    @Inject LicenseGenerator licenseGenerator;
 
-	@Inject
-	private LicenseHelper licenseHelper;
+    /** Fixed username representing API client actor for audit trails. */
+    public static final String API_CLIENT_USERNAME = "_client";
 
-	@Context
-	EntityManager em;
+    /** Default constructor (required by JAX-RS). */
+    public ApiResource() { }
 
-	@Inject
-	LicenseGenerator licenseGenerator;
+    // -------------------- Health checks --------------------
 
-	public static final String API_CLIENT_USERNAME = "_client";
+    /**
+    * index<p>
+    * Plain text endpoint to verify API is reachable.
+    *
+    * @return 200 OK with simple message
+    */
+    @GET
+    @Path("/")
+    @Produces({ MediaType.TEXT_PLAIN })
+    public Response index() {
+        return Response.ok("SeCuris API. Date: " + new Date()).build();
+    }
 
-	public ApiResource() {
-	}
+    /**
+    * ping<p>
+    * JSON endpoint for health checks.
+    *
+    * @return 200 OK with {@link StatusBean}
+    */
+    @GET
+    @Path("/ping")
+    @Produces({ MediaType.APPLICATION_JSON })
+    public Response ping() {
+        StatusBean status = new StatusBean();
+        status.setDate(new Date());
+        status.setMessage(LicenseManager.PING_MESSAGE);
+        return Response.ok(status).build();
+    }
 
-	/**
-	 * 
-	 * @return Simple text message to check API status
-	 */
-	@GET
-	@Path("/")
-	@Produces({ MediaType.TEXT_PLAIN })
-	public Response index() {
-		return Response.ok("SeCuris API. Date: " + new Date()).build();
-	}
+    // -------------------- License creation --------------------
 
-	/**
-	 * 
-	 * @return Simple text message to check API status
-	 */
-	@GET
-	@Path("/ping")
-	@Produces({ MediaType.APPLICATION_JSON })
-	public Response ping() {
-		StatusBean status = new StatusBean();
-		status.setDate(new Date());
-		status.setMessage(LicenseManager.PING_MESSAGE);
-		return Response.ok(status).build();
-	}
+    /**
+    * createFromRequest<p>
+    * Create a new license from JSON request data.
+    *
+    * @param request        RequestBean payload
+    * @param nameOrReference Holder name or external reference (header)
+    * @param userEmail      Email (header)
+    * @return {@link SignedLicenseBean} JSON
+    */
+    @POST
+    @Path("/request")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Securable(roles = Rol.API_CLIENT)
+    @Produces({ MediaType.APPLICATION_JSON })
+    @EnsureTransaction
+    public Response createFromRequest(RequestBean request,
+                                      @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference,
+                                      @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail)
+            throws IOException, SeCurisServiceException, SeCurisException {
+        LOG.info("Request to get license: {}", request);
+        SignedLicenseBean lic = createLicense(request, em, nameOrReference, userEmail);
+        return Response.ok(lic).build();
+    }
 
-	/**
-	 * Request a new license file based in a RequestBean object sent as
-	 * parameter
-	 * 
-	 * @param mpfdi
-	 * @param bsc
-	 * @return
-	 * @throws IOException
-	 * @throws SeCurisServiceException
-	 */
-	@POST
-	@Path("/request")
-	@Consumes(MediaType.APPLICATION_JSON)
-	@Securable(roles = Rol.API_CLIENT)
-	@Produces({ MediaType.APPLICATION_JSON })
-	@EnsureTransaction
-	public Response createFromRequest(RequestBean request, @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference,
-			@HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) throws IOException, SeCurisServiceException, SeCurisException {
-		LOG.info("Request to get license: {}", request);
-		SignedLicenseBean lic = createLicense(request, em, nameOrReference, userEmail);
+    /**
+    * createFromRequestFile<p>
+    * Create a new license from a multipart form (uploaded request fields).
+    *
+    * @param mpfdi          multipart input
+    * @param nameOrReference holder name/reference (header)
+    * @param userEmail      email (header)
+    * @return {@link SignedLicenseBean} JSON
+    */
+    @POST
+    @Path("/request")
+    @Consumes(MediaType.MULTIPART_FORM_DATA)
+    @Securable(roles = Rol.API_CLIENT)
+    @Produces({ MediaType.APPLICATION_JSON })
+    @EnsureTransaction
+    @SuppressWarnings("unchecked")
+    public Response createFromRequestFile(MultipartFormDataInput mpfdi,
+                                          @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference,
+                                          @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail)
+            throws IOException, SeCurisServiceException, SeCurisException {
+        RequestBean req = new RequestBean();
+        req.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null));
+        req.setActivationCode(mpfdi.getFormDataPart("activationCode", String.class, null));
+        req.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null));
+        req.setLicenseTypeCode(mpfdi.getFormDataPart("licenseTypeCode", String.class, null));
+        req.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null));
+        req.setArch(mpfdi.getFormDataPart("arch", String.class, null));
+        req.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null));
+        req.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null));
+        req.setOsName(mpfdi.getFormDataPart("osName", String.class, null));
 
-		return Response.ok(lic).build();
-	}
+        return createFromRequest(req, nameOrReference, userEmail);
+    }
 
-	/**
-	 * Returns a License file in JSON format from an uploaded Request file
-	 * 
-	 * @param mpfdi
-	 * @param bsc
-	 * @return
-	 * @throws IOException
-	 * @throws SeCurisServiceException
-	 * @throws SeCurisException
-	 */
-	@POST
-	@Path("/request")
-	@Consumes(MediaType.MULTIPART_FORM_DATA)
-	@Securable(roles = Rol.API_CLIENT)
-	@Produces({ MediaType.APPLICATION_JSON })
-	@EnsureTransaction
-	@SuppressWarnings("unchecked")
-	public Response createFromRequestFile(MultipartFormDataInput mpfdi, @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference,
-			@HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) throws IOException, SeCurisServiceException, SeCurisException {
-		RequestBean req = new RequestBean();
-		req.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null));
-		req.setActivationCode(mpfdi.getFormDataPart("activationCode", String.class, null));
-		req.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null));
-		req.setLicenseTypeCode(mpfdi.getFormDataPart("licenseTypeCode", String.class, null));
-		req.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null));
-		req.setArch(mpfdi.getFormDataPart("arch", String.class, null));
-		req.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null));
-		req.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null));
-		req.setOsName(mpfdi.getFormDataPart("osName", String.class, null));
+    // -------------------- License renew --------------------
 
-		return createFromRequest(req, nameOrReference, userEmail);
-	}
+    /**
+    * renewFromPreviousLicense<p>
+    * Renew a license from an existing {@link LicenseBean} JSON payload.
+    * Only <b>Active</b> licenses within one month of expiration are eligible.
+    *
+    * @param previousLic current license bean
+    * @param bsc         security context
+    * @return new {@link SignedLicenseBean}
+    */
+    @POST
+    @Path("/renew")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Securable(roles = Rol.API_CLIENT)
+    @Produces({ MediaType.APPLICATION_JSON })
+    @EnsureTransaction
+    public Response renewFromPreviousLicense(LicenseBean previousLic, @Context BasicSecurityContext bsc)
+            throws IOException, SeCurisServiceException, SeCurisException {
+        LOG.info("Renew license: {}", previousLic);
 
-	/**
-	 * Create a new License file based in a previous one
-	 * 
-	 * @param request
-	 * @param bsc
-	 * @return
-	 * @throws IOException
-	 * @throws SeCurisServiceException
-	 * @throws SeCurisException
-	 */
-	@POST
-	@Path("/renew")
-	@Consumes(MediaType.APPLICATION_JSON)
-	@Securable(roles = Rol.API_CLIENT)
-	@Produces({ MediaType.APPLICATION_JSON })
-	@EnsureTransaction
-	public Response renewFromPreviousLicense(LicenseBean previousLic, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException {
-		LOG.info("Renew license: {}", previousLic);
+        if (previousLic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) {
+            throw new SeCurisServiceException(ErrorCodes.UNNECESSARY_RENEW, "The license is still valid, not ready for renew");
+        }
 
-		if (previousLic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) {
-			throw new SeCurisServiceException(ErrorCodes.UNNECESSARY_RENEW, "The license is still valid, not ready for renew");
-		}
+        License lic = License.findLicenseByCode(previousLic.getLicenseCode(), em);
+        if (lic == null) {
+            throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license is missing in DB");
+        }
+        if (lic.getStatus() != LicenseStatus.ACTIVE) {
+            throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "Only licenses with status 'Active' can be renew");
+        }
 
-		// EntityManager em = emProvider.get();
-		License lic = License.findLicenseByCode(previousLic.getLicenseCode(), em);
-		if (lic == null) {
-			throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license is missing in DB");
-		}
+        SignedLicenseBean signedLic = renewLicense(previousLic, em);
+        LOG.info("Renewed license code: {}, until: {}", signedLic.getLicenseCode(), signedLic.getExpirationDate());
 
-		if (lic.getStatus() != LicenseStatus.ACTIVE) {
-			throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "Only licenses with status 'Active' can be renew");
-		}
+        return Response.ok(signedLic).build();
+    }
 
-		SignedLicenseBean signedLic = renewLicense(previousLic, em);
-		LOG.info("Renewed license code: {}, until: {}", signedLic.getLicenseCode(), signedLic.getExpirationDate());
+    /**
+    * renewFromLicenseFile<p>
+    * Renew a license from multipart (uploaded prior license fields).
+    *
+    * @param mpfdi multipart input
+    * @param bsc   security context
+    * @return new {@link SignedLicenseBean}
+    */
+    @POST
+    @Path("/renew")
+    @Consumes(MediaType.MULTIPART_FORM_DATA)
+    @Securable(roles = Rol.API_CLIENT)
+    @Produces({ MediaType.APPLICATION_JSON })
+    @EnsureTransaction
+    @SuppressWarnings("unchecked")
+    public Response renewFromLicenseFile(MultipartFormDataInput mpfdi, @Context BasicSecurityContext bsc)
+            throws IOException, SeCurisServiceException, SeCurisException {
+        LicenseBean lic = new LicenseBean();
 
-		return Response.ok(signedLic).build();
-	}
+        lic.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null));
+        lic.setActivationCode(mpfdi.getFormDataPart("activationName", String.class, null));
+        lic.setAppName(mpfdi.getFormDataPart("appName", String.class, null));
+        lic.setArch(mpfdi.getFormDataPart("arch", String.class, null));
+        lic.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null));
+        lic.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null));
+        lic.setLicenseTypeCode(mpfdi.getFormDataPart("licenseCode", String.class, null));
+        lic.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null));
+        lic.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null));
+        lic.setOsName(mpfdi.getFormDataPart("osName", String.class, null));
+        lic.setExpirationDate(mpfdi.getFormDataPart("expirationDate", Date.class, null));
 
-	/**
-	 * License validation on server side, in this case we validate that the
-	 * current licenses has not been cancelled and they are still in valid
-	 * period. If the pack has reached the end valid period, the license is no
-	 * longer valid.
-	 * 
-	 * @param currentLic
-	 * @param bsc
-	 * @return
-	 * @throws IOException
-	 * @throws SeCurisServiceException
-	 * @throws SeCurisException
-	 */
-	@POST
-	@Path("/validate")
-	@Consumes(MediaType.APPLICATION_JSON)
-	@Securable(roles = Rol.API_CLIENT)
-	@Produces({ MediaType.APPLICATION_JSON })
-	@EnsureTransaction
-	public Response validate(LicenseBean currentLic, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException {
-		LOG.info("Validate license: {}", currentLic);
+        LOG.info("Lic expires at: {}", lic.getExpirationDate());
+        if (lic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) {
+            throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "The license is still valid, not ready for renew");
+        }
 
-		if (currentLic.getExpirationDate().before(new Date())) {
-			throw new SeCurisServiceException(ErrorCodes.LICENSE_IS_EXPIRED, "The license is expired");
-		}
+        return renewFromPreviousLicense(lic, bsc);
+    }
 
-		License existingLic = licenseHelper.getActiveLicenseFromDB(currentLic, em);
+    // -------------------- Validation --------------------
 
-		Pack pack = existingLic.getPack();
-		if (pack.getEndValidDate().before(new Date())) {
-			throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack end valid date has been reached");
-		}
-		if (pack.getStatus() != PackStatus.ACTIVE) {
-			LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus());
-			throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack status is not Active");
-		}
+    /**
+    * validate<p>
+    * Server-side validation of a license:
+    * - Not expired
+    * - Pack still valid and active
+    * - Signature valid
+    *
+    * @param currentLic license to validate
+    * @param bsc        security context
+    * @return same license if valid
+    */
+    @POST
+    @Path("/validate")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Securable(roles = Rol.API_CLIENT)
+    @Produces({ MediaType.APPLICATION_JSON })
+    @EnsureTransaction
+    public Response validate(LicenseBean currentLic, @Context BasicSecurityContext bsc)
+            throws IOException, SeCurisServiceException, SeCurisException {
+        LOG.info("Validate license: {}", currentLic);
 
-		try {
-			SignatureHelper.getInstance().validateSignature(currentLic);
-		} catch (SeCurisException ex) {
-			throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The license signature is not valid");
-		}
+        if (currentLic.getExpirationDate().before(new Date())) {
+            throw new SeCurisServiceException(ErrorCodes.LICENSE_IS_EXPIRED, "The license is expired");
+        }
 
-		return Response.ok(currentLic).build();
-	}
+        License existingLic = licenseHelper.getActiveLicenseFromDB(currentLic, em);
 
-	/**
-	 * Returns a new License file in JSON format based in a previous license
-	 * There is 2 /renew services with json input and with upload file
-	 * 
-	 * @param mpfdi
-	 * @param bsc
-	 * @return
-	 * @throws IOException
-	 * @throws SeCurisServiceException
-	 * @throws SeCurisException
-	 */
-	@POST
-	@Path("/renew")
-	@Consumes(MediaType.MULTIPART_FORM_DATA)
-	@Securable(roles = Rol.API_CLIENT)
-	@Produces({ MediaType.APPLICATION_JSON })
-	@EnsureTransaction
-	@SuppressWarnings("unchecked")
-	public Response renewFromLicenseFile(MultipartFormDataInput mpfdi, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException {
-		LicenseBean lic = new LicenseBean();
+        Pack pack = existingLic.getPack();
+        if (pack.getEndValidDate().before(new Date())) {
+            throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack end valid date has been reached");
+        }
+        if (pack.getStatus() != PackStatus.ACTIVE) {
+            LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus());
+            throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack status is not Active");
+        }
 
-		lic.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null));
-		lic.setActivationCode(mpfdi.getFormDataPart("activationName", String.class, null));
-		lic.setAppName(mpfdi.getFormDataPart("appName", String.class, null));
-		lic.setArch(mpfdi.getFormDataPart("arch", String.class, null));
-		lic.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null));
-		lic.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null));
-		lic.setLicenseTypeCode(mpfdi.getFormDataPart("licenseCode", String.class, null));
-		lic.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null));
-		lic.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null));
-		lic.setOsName(mpfdi.getFormDataPart("osName", String.class, null));
-		lic.setExpirationDate(mpfdi.getFormDataPart("expirationDate", Date.class, null));
-		LOG.info("Lic expires at: {}", lic.getExpirationDate());
-		if (lic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) {
-			throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "The license is still valid, not ready for renew");
-		}
+        try {
+            SignatureHelper.getInstance().validateSignature(currentLic);
+        } catch (SeCurisException ex) {
+            throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The license signature is not valid");
+        }
 
-		return renewFromPreviousLicense(lic, bsc);
-	}
+        return Response.ok(currentLic).build();
+    }
 
-	/**
-	 * Creates a new signed license from request data or from previous license
-	 * if It's a renew
-	 * 
-	 * @param req
-	 * @param em
-	 * @param renew
-	 * @return
-	 * @throws SeCurisServiceException
-	 */
-	private SignedLicenseBean createLicense(RequestBean req, EntityManager em, String nameOrReference, String email) throws SeCurisServiceException {
-		License lic = null;
+    // -------------------- Internal helpers --------------------
 
-		if (req.getActivationCode() != null) {
-			lic = License.findLicenseByActivationCode(req.getActivationCode(), em);
-			if (lic == null) {
-				throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code is invalid: " + req.getActivationCode());
-			}
-			if (lic.getStatus() == LicenseStatus.ACTIVE) {
-				RequestBean initialRequest;
-				try {
-					initialRequest = JsonUtils.json2object(lic.getRequestData(), RequestBean.class);
-					if (!req.match(initialRequest)) {
-						throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "There is already an active license for given activation code: " + req.getActivationCode());
-					} else {
-						return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class);
-					}
-				} catch (SeCurisException e) {
-					LOG.error("Error getting existing license", e);
-					throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Original request is wrong");
-				}
-			} else {
-				if (req.getAppCode() != null && !req.getAppCode().equals(lic.getPack().getLicenseType().getApplication().getCode())) {
-					LOG.error("Activation code {} belongs to app: {} but was sent by: {}", req.getActivationCode(), lic.getPack().getLicenseType().getApplication().getCode(),
-							req.getAppCode());
-					throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code belongs to a different application: " + req.getActivationCode());
-				}
-			}
-			// We validate if the HW is the same, otherwise an error is
-			// thrown
-		} else {
-			try {
-				lic = License.findValidLicenseByRequestData(JsonUtils.toJSON(req), em);
-			} catch (SeCurisException e1) {
-				throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Request sent is not valid");
-			}
-			if (lic != null) {
-				try {
-					if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) {
-						return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class);
-					}
-				} catch (SeCurisException e) {
-					throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error trying to get the license bean from license code: " + lic.getCode());
-				}
-			} else {
-				lic = new License();
-			}
-		}
+    /**
+    * createLicense<p>
+    * Creates a new signed license from request data or reuses an existing
+    * pre-active/active one when allowed by business rules.
+    *
+    * @param req request bean
+    * @param em  entity manager
+    * @param nameOrReference license holder name/reference (header)
+    * @param email email (header)
+    * @return signed license bean
+    */
+    private SignedLicenseBean createLicense(RequestBean req, EntityManager em, String nameOrReference, String email)
+            throws SeCurisServiceException {
 
-		Pack pack;
-		if (lic.getActivationCode() == null) {
-			try {
-				pack = em.createNamedQuery("pack-by-code", Pack.class).setParameter("code", req.getPackCode()).getSingleResult();
-			} catch (NoResultException e) {
-				throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "No pack found for code: " + req.getPackCode());
-			}
+        License lic = null;
 
-			if (pack.getNumAvailables() <= 0) {
-				throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "The current pack has no licenses availables");
-			}
-			if (lic.getStatus() == LicenseStatus.REQUESTED && !pack.isLicensePreactivation()) {
-				throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation");
-			}
+        // (1) Activation-code flow
+        if (req.getActivationCode() != null) {
+            lic = License.findLicenseByActivationCode(req.getActivationCode(), em);
+            if (lic == null) {
+                throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code is invalid: " + req.getActivationCode());
+            }
+            if (lic.getStatus() == LicenseStatus.ACTIVE) {
+                try {
+                    RequestBean initialRequest = JsonUtils.json2object(lic.getRequestData(), RequestBean.class);
+                    if (!req.match(initialRequest)) {
+                        throw new SeCurisServiceException(ErrorCodes.INVALID_DATA,
+                                "There is already an active license for given activation code: " + req.getActivationCode());
+                    } else {
+                        return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class);
+                    }
+                } catch (SeCurisException e) {
+                    LOG.error("Error getting existing license", e);
+                    throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Original request is wrong");
+                }
+            } else {
+                if (req.getAppCode() != null &&
+                    !req.getAppCode().equals(lic.getPack().getLicenseType().getApplication().getCode())) {
+                    LOG.error("Activation code {} belongs to app: {} but was sent by: {}",
+                              req.getActivationCode(), lic.getPack().getLicenseType().getApplication().getCode(), req.getAppCode());
+                    throw new SeCurisServiceException(ErrorCodes.INVALID_DATA,
+                            "The given activation code belongs to a different application: " + req.getActivationCode());
+                }
+            }
+        } else {
+            // (2) Request-data flow (idempotent check)
+            try {
+                lic = License.findValidLicenseByRequestData(JsonUtils.toJSON(req), em);
+            } catch (SeCurisException e1) {
+                throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Request sent is not valid");
+            }
+            if (lic != null) {
+                try {
+                    if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) {
+                        return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class);
+                    }
+                } catch (SeCurisException e) {
+                    throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT,
+                            "Error trying to get the license bean from license code: " + lic.getCode());
+                }
+            } else {
+                lic = new License();
+            }
+        }
 
-			if (!req.getCustomerCode().equals(pack.getOrganization().getCode())) {
-				throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Customer code is not valid: " + req.getCustomerCode());
-			}
+        // (3) Pack validation & constraints
+        Pack pack;
+        if (lic.getActivationCode() == null) {
+            try {
+                pack = em.createNamedQuery("pack-by-code", Pack.class)
+                         .setParameter("code", req.getPackCode())
+                         .getSingleResult();
+            } catch (NoResultException e) {
+                throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "No pack found for code: " + req.getPackCode());
+            }
 
-			if (!req.getLicenseTypeCode().equals(pack.getLicenseTypeCode())) {
-				throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "License type code is not valid: " + req.getLicenseTypeCode());
-			}
-		} else {
-			pack = lic.getPack();
-		}
-		if (pack.getStatus() != PackStatus.ACTIVE) {
-			LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus());
-			throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The pack status is not Active");
-		}
-		SignedLicenseBean signedLicense;
-		try {
-			String licCode;
-			if (lic.getCode() == null) {
-				licCode = LicUtils.getLicenseCode(pack.getCode(), licenseHelper.getNextCodeSuffix(pack.getId(), em));
-			} else {
-				licCode = lic.getCode();
-			}
-			Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, lic.getActivationCode() == null);
+            if (pack.getNumAvailables() <= 0) {
+                throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "The current pack has no licenses availables");
+            }
+            if (lic.getStatus() == LicenseStatus.REQUESTED && !pack.isLicensePreactivation()) {
+                throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation");
+            }
+            if (!req.getCustomerCode().equals(pack.getOrganization().getCode())) {
+                throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Customer code is not valid: " + req.getCustomerCode());
+            }
+            if (!req.getLicenseTypeCode().equals(pack.getLicenseTypeCode())) {
+                throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "License type code is not valid: " + req.getLicenseTypeCode());
+            }
+        } else {
+            pack = lic.getPack();
+        }
 
-			LicenseBean lb = licenseGenerator.generateLicense(req, licenseHelper.extractPackMetadata(pack.getMetadata()), expirationDate, licCode, pack.getAppName());
-			signedLicense = new SignedLicenseBean(lb);
-		} catch (SeCurisException e) {
-			throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString());
-		}
-		try {
-			lic.setRequestData(JsonUtils.toJSON(req));
-			if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
-				throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated");
-			}
-			lic.setLicenseData(JsonUtils.toJSON(signedLicense));
-		} catch (SeCurisException e) {
-			LOG.error("Error generating license JSON", e);
-			throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON");
-		}
+        if (pack.getStatus() != PackStatus.ACTIVE) {
+            LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus());
+            throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The pack status is not Active");
+        }
 
-		lic.setModificationTimestamp(new Date());
-		lic.setExpirationDate(signedLicense.getExpirationDate());
-		User user = em.find(User.class, API_CLIENT_USERNAME);
-		if (lic.getStatus() != LicenseStatus.REQUESTED) {
-			lic.setPack(pack);
-			lic.setCreatedBy(user);
-			lic.setCreationTimestamp(new Date());
-			if (lic.getActivationCode() != null) {
-				lic.setStatus(LicenseStatus.ACTIVE);
-			} else {
-				lic.setStatus(pack.isLicensePreactivation() ? LicenseStatus.PRE_ACTIVE : LicenseStatus.REQUESTED);
-			}
-			lic.setCode(signedLicense.getLicenseCode());
-			lic.setCodeSuffix(LicUtils.getLicenseCodeSuffix(signedLicense.getLicenseCode()));
-			if (lic.getEmail() == null || "".equals(lic.getEmail())) {
-				lic.setEmail(email);
-			}
-			if (lic.getFullName() == null || "".equals(lic.getFullName())) {
-				lic.setFullName(nameOrReference);
-			}
-			if (lic.getId() != null) {
-				em.merge(lic);
-			} else {
-				em.persist(lic);
-			}
-			em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.CREATE));
-			if (lic.getActivationCode() != null) {
-				em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.ACTIVATE, "Activated by code on creation"));
-			} else {
-				if (pack.isLicensePreactivation()) {
-					em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated on creation"));
-				} else {
-					LOG.warn("License ({}) created, but the pack doesn't allow preactivation", lic.getCode());
-					throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation");
-				}
-			}
-		} else {
-			lic.setStatus(LicenseStatus.PRE_ACTIVE);
-			em.merge(lic);
-			em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated after request"));
-		}
+        // (4) License generation
+        SignedLicenseBean signedLicense;
+        try {
+            String licCode = (lic.getCode() == null)
+                    ? LicUtils.getLicenseCode(pack.getCode(), licenseHelper.getNextCodeSuffix(pack.getId(), em))
+                    : lic.getCode();
 
-		return signedLicense;
-	}
+            Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, lic.getActivationCode() == null);
+            LicenseBean lb = licenseGenerator.generateLicense(
+                    req,
+                    licenseHelper.extractPackMetadata(pack.getMetadata()),
+                    expirationDate,
+                    licCode,
+                    pack.getAppName()
+            );
+            signedLicense = new SignedLicenseBean(lb);
+        } catch (SeCurisException e) {
+            throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString());
+        }
 
-	/**
-	 * Creates a new signed license from request data or from previous license
-	 * if It's a renew
-	 * 
-	 * @param req
-	 * @param em
-	 * @param renew
-	 * @return
-	 * @throws SeCurisServiceException
-	 */
-	private SignedLicenseBean renewLicense(LicenseBean previousLicenseBean, EntityManager em) throws SeCurisServiceException {
+        // (5) Persist/merge license + history
+        try {
+            lic.setRequestData(JsonUtils.toJSON(req));
+            if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
+                throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated");
+            }
+            lic.setLicenseData(JsonUtils.toJSON(signedLicense));
+        } catch (SeCurisException e) {
+            LOG.error("Error generating license JSON", e);
+            throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON");
+        }
 
-		License lic = License.findLicenseByCode(previousLicenseBean.getLicenseCode(), em);
-		if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) {
-			throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The current license has been cancelled");
-		}
+        lic.setModificationTimestamp(new Date());
+        lic.setExpirationDate(signedLicense.getExpirationDate());
+        User user = em.find(User.class, API_CLIENT_USERNAME);
 
-		Pack pack = lic.getPack();
-		SignedLicenseBean signedLicense;
-		try {
-			String licCode = lic.getCode();
-			Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, false);
+        if (lic.getStatus() != LicenseStatus.REQUESTED) {
+            lic.setPack(pack);
+            lic.setCreatedBy(user);
+            lic.setCreationTimestamp(new Date());
+            if (lic.getActivationCode() != null) {
+                lic.setStatus(LicenseStatus.ACTIVE);
+            } else {
+                lic.setStatus(pack.isLicensePreactivation() ? LicenseStatus.PRE_ACTIVE : LicenseStatus.REQUESTED);
+            }
+            lic.setCode(signedLicense.getLicenseCode());
+            lic.setCodeSuffix(LicUtils.getLicenseCodeSuffix(signedLicense.getLicenseCode()));
+            if (lic.getEmail() == null || "".equals(lic.getEmail())) {
+                lic.setEmail(email);
+            }
+            if (lic.getFullName() == null || "".equals(lic.getFullName())) {
+                lic.setFullName(nameOrReference);
+            }
+            if (lic.getId() != null) {
+                em.merge(lic);
+            } else {
+                em.persist(lic);
+            }
+            em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.CREATE));
+            if (lic.getActivationCode() != null) {
+                em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.ACTIVATE, "Activated by code on creation"));
+            } else {
+                if (pack.isLicensePreactivation()) {
+                    em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated on creation"));
+                } else {
+                    LOG.warn("License ({}) created, but the pack doesn't allow preactivation", lic.getCode());
+                    throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation");
+                }
+            }
+        } else {
+            lic.setStatus(LicenseStatus.PRE_ACTIVE);
+            em.merge(lic);
+            em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated after request"));
+        }
 
-			LicenseBean lb = licenseGenerator.generateLicense(previousLicenseBean, licenseHelper.extractPackMetadata(pack.getMetadata()), expirationDate, licCode,
-					pack.getAppName());
-			signedLicense = new SignedLicenseBean(lb);
-		} catch (SeCurisException e) {
-			throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString());
-		}
-		try {
-			lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class));
-			if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
-				throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated");
-			}
-			lic.setLicenseData(JsonUtils.toJSON(signedLicense));
-		} catch (SeCurisException e) {
-			LOG.error("Error generating license JSON", e);
-			throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON");
-		}
+        return signedLicense;
+    }
 
-		lic.setModificationTimestamp(new Date());
-		lic.setExpirationDate(signedLicense.getExpirationDate());
-		User user = em.find(User.class, API_CLIENT_USERNAME);
+    /**
+    * renewLicense<p>
+    * Internal renew logic used by JSON and multipart variants.
+    *
+    * @param previousLicenseBean previous license data
+    * @param em entity manager
+    * @return new signed license bean
+    */
+    private SignedLicenseBean renewLicense(LicenseBean previousLicenseBean, EntityManager em)
+            throws SeCurisServiceException {
 
-		lic.setStatus(LicenseStatus.ACTIVE);
-		em.merge(lic);
-		em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.RENEW));
+        License lic = License.findLicenseByCode(previousLicenseBean.getLicenseCode(), em);
+        if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) {
+            throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The current license has been cancelled");
+        }
 
-		return signedLicense;
-	}
+        Pack pack = lic.getPack();
+        SignedLicenseBean signedLicense;
+        try {
+            String licCode = lic.getCode();
+            Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, false);
+
+            LicenseBean lb = licenseGenerator.generateLicense(
+                    previousLicenseBean,
+                    licenseHelper.extractPackMetadata(pack.getMetadata()),
+                    expirationDate,
+                    licCode,
+                    pack.getAppName()
+            );
+            signedLicense = new SignedLicenseBean(lb);
+        } catch (SeCurisException e) {
+            throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString());
+        }
+        try {
+            lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class));
+            if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) {
+                throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated");
+            }
+            lic.setLicenseData(JsonUtils.toJSON(signedLicense));
+        } catch (SeCurisException e) {
+            LOG.error("Error generating license JSON", e);
+            throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON");
+        }
+
+        lic.setModificationTimestamp(new Date());
+        lic.setExpirationDate(signedLicense.getExpirationDate());
+        User user = em.find(User.class, API_CLIENT_USERNAME);
+
+        lic.setStatus(LicenseStatus.ACTIVE);
+        em.merge(lic);
+        em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.RENEW));
+
+        return signedLicense;
+    }
 }
+

--
Gitblit v1.3.2